Skip to content

Commit f4df2e4

Browse files
committed
Add examples and rationale to the Mutation/Concurrency section
The refs, agents, and atoms rules were mostly bare statements. Add code examples and brief explanations to: refs-io-macro, refs-small-transactions, refs-avoid-short-long-transactions-with- same-ref, agents-send, agents-send-off, and atoms-no-update-within-transactions.
1 parent 0a72ead commit f4df2e4

File tree

1 file changed

+66
-6
lines changed

1 file changed

+66
-6
lines changed

README.adoc

Lines changed: 66 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2124,7 +2124,20 @@ for more details.
21242124

21252125
Consider wrapping all I/O calls with the `io!` macro to avoid nasty
21262126
surprises 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

21462159
Try 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

21512177
Avoid 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

21582186
Use `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

21632198
Use `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

Comments
 (0)