@@ -2124,7 +2124,20 @@ for more details.
21242124
21252125Consider wrapping all I/O calls with the `io!` macro to avoid nasty
21262126surprises if you accidentally end up calling such code in a
2127- transaction.
2127+ transaction. `io!` will throw an exception if it is called from within
2128+ a transaction, making accidental side-effects fail loudly instead of
2129+ silently executing (possibly more than once due to retries).
2130+
2131+ [source,clojure]
2132+ ----
2133+ ;; good - I/O is guarded against accidental use in transactions
2134+ (defn save-to-file [path content]
2135+ (io! (spit path content)))
2136+
2137+ ;; bad - could silently run multiple times if called inside dosync
2138+ (defn save-to-file [path content]
2139+ (spit path content))
2140+ ----
21282141
21292142==== Avoid `ref-set` [[refs-avoid-ref-set]]
21302143
@@ -2144,30 +2157,77 @@ Avoid the use of `ref-set` whenever possible.
21442157==== Small Transactions [[refs-small-transactions]]
21452158
21462159Try to keep the size of transactions (the amount of work encapsulated in them)
2147- as small as possible.
2160+ as small as possible. Long transactions increase the chance of conflicts
2161+ with other transactions, leading to retries and reduced throughput.
2162+
2163+ [source,clojure]
2164+ ----
2165+ ;; good - transaction only covers the coordinated update
2166+ (let [data (fetch-data source)]
2167+ (dosync (alter results conj data)))
2168+
2169+ ;; bad - slow I/O inside the transaction holds it open
2170+ (dosync
2171+ (let [data (fetch-data source)]
2172+ (alter results conj data)))
2173+ ----
21482174
21492175==== Avoid Short Long Transactions With Same Ref [[refs-avoid-short-long-transactions-with-same-ref]]
21502176
21512177Avoid having both short- and long-running transactions interacting
2152- with the same Ref.
2178+ with the same Ref. When a short transaction commits a change, any
2179+ long-running transaction touching the same Ref must retry from the
2180+ beginning, effectively starving it.
21532181
21542182=== Agents [[Agents]]
21552183
21562184==== Agents Send [[agents-send]]
21572185
21582186Use `send` only for actions that are CPU bound and don't block on I/O
2159- or other threads.
2187+ or other threads. `send` dispatches to a fixed-size thread pool, so a
2188+ blocking action would prevent other agents from making progress.
2189+
2190+ [source,clojure]
2191+ ----
2192+ ;; good - pure computation, no blocking
2193+ (send agent-a + 42)
2194+ ----
21602195
21612196==== Agents Send Off [[agents-send-off]]
21622197
21632198Use `send-off` for actions that might block, sleep, or otherwise tie
2164- up the thread.
2199+ up the thread. `send-off` uses an unbounded thread pool, so blocking
2200+ actions won't starve other agents.
2201+
2202+ [source,clojure]
2203+ ----
2204+ ;; good - potentially blocking I/O uses send-off
2205+ (send-off agent-b (fn [state] (assoc state :data (slurp url))))
2206+
2207+ ;; bad - blocking I/O via send can exhaust the fixed thread pool
2208+ (send agent-b (fn [state] (assoc state :data (slurp url))))
2209+ ----
21652210
21662211=== Atoms [[Atoms]]
21672212
21682213==== No Updates Within Transactions [[atoms-no-update-within-transactions]]
21692214
2170- Avoid atom updates inside STM transactions.
2215+ Avoid atom updates inside STM transactions. Atoms are not coordinated
2216+ by the STM, so an atom `swap!` inside a `dosync` will execute on every
2217+ retry — not just the final successful commit.
2218+
2219+ [source,clojure]
2220+ ----
2221+ ;; good - atom update happens after the transaction
2222+ (dosync (alter account-a - 100)
2223+ (alter account-b + 100))
2224+ (swap! transfer-log conj {:from :a :to :b :amount 100})
2225+
2226+ ;; bad - the log entry is appended on every retry
2227+ (dosync (alter account-a - 100)
2228+ (alter account-b + 100)
2229+ (swap! transfer-log conj {:from :a :to :b :amount 100}))
2230+ ----
21712231
21722232==== Prefer `swap!` over `reset!` [[atoms-prefer-swap-over-reset]]
21732233
0 commit comments