I got E08251 SHARED_STATE_MUTATION_OUTSIDE_LOCK — why can't my async worker write shared state?
← Concurrency · Ref: Q1350
E08251 fires when code mutates shared state that crosses a thread boundary without doing so inside a MutexLock.enter(...) body. The most common trigger is capturing a shared object into a `| async` worker and writing one of its fields there — the worker runs on a different thread, so the write races with anyone else touching that object.
WHAT THE ERROR MEANS
A `| async` stage dispatches each element to a thread-pool worker. If the worker captures a shared record and writes its field, that write happens off the caller's thread with no synchronisation. EK9 requires every mutation of shared state to happen inside a MutexKey body guarded by a MutexLock.
HOW TO FIX
Either keep the mutation on the same thread (read-only in the worker, aggregate afterwards), or route the mutation through a MutexLock so the write is serialised. Reading shared state in a worker is fine — only mutation is rejected.
//worker only READS; the write happens under a lock elsewhere worker <- (shared) is asyncWorkerFn as function rtn: shared.total > 0
WHY EK9 DETECTS THIS
Data races on shared mutable state are among the hardest bugs to reproduce. EK9 makes the thread boundary visible (`| async`) and tracks which state the worker captures, refusing any unsynchronised mutation.
Example
defines module qa.concurrency.shared.state.outside.lock defines record Tally total <- 0 default operator ? defines function asyncWorkerFn() as abstract <- rtn as Boolean? toLine() as pure -> ok as Boolean <- rtn as String: $ok runPipeline() stdout <- Stdout() shared <- Tally() worker <- (shared) is asyncWorkerFn as function rtn: shared.total > 0 workers <- [ worker ] cat workers | async | map with toLine > stdout
Common mistakes
E08251 — The async worker captures the shared `Tally` and writes `shared.total` on a thread-pool thread with no lock, racing any other access. The safe form only reads in the worker; any mutation must go through a MutexLock body. See ek9 -h E08251.
Incorrect:
shared.total: shared.total + 1 rtn: true
Correct:
rtn: shared.total > 0
Other ways to ask this
- Why must I write shared state inside a lock in EK9?
- async worker mutates shared field error E08251
- How to mutate shared data safely across an async boundary
- captured write outside MutexLock EK9
Coming from another language?
Java/Go: nothing stops a worker/goroutine from mutating captured shared state; races are found (if ever) via stress testing or the race detector at runtime. EK9: mutation of thread-crossing shared state outside a MutexLock is a compile-time error.
Keywords: worker, state, concurrent, captured, MutexLock, mutation, lock, race, async, E08251, shared