Skip to content

Commit 5f37246

Browse files
committed
Record Spikes C + D findings in exploration plan
Spike C (mailbox hygiene) passed: the receive-with-^ref pattern scales linearly when draining 5k-deep mailbox backlogs, at <100 µs per receive. The BEAM's reference-creation marker optimisation prevents O(N^2) scans. Spike D (PID liveness) passed: enif_send to dead PIDs silently drops, 500-process dead-sender race produces no MLX growth, and dropping a Stream mid-flight drains the worker cleanly. All four spikes (A, B, C, D) now complete; the phased implementation in section 2 can proceed unchanged. Reference: branch spike-cd-mailbox-liveness, commit 4c0f08e.
1 parent 178deb2 commit 5f37246

1 file changed

Lines changed: 47 additions & 0 deletions

File tree

docs/planning/async-worker-exploration.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,28 @@ If mailbox grows, we have an ordering bug — the receive's `^ref` pin
224224
should make this impossible, but if the worker ever sends out-of-order
225225
we'd see it.
226226

227+
**Findings (run on branch `spike-cd-mailbox-liveness`, commit `4c0f08e`,
228+
OTP 28.3):**
229+
230+
- **Passes.** Three sub-tests green: 1k sequential dispatch+receive
231+
(mailbox stays at 0), 1k batched then drained (peak mailbox
232+
observed >0, drains to 0 at end), 5k batched drain runs in
233+
~linear time (<100 µs per receive).
234+
- **Receive-with-`^ref` optimisation works as expected.** The
235+
batched-drain test fires 5k async requests, lets them pile up in
236+
the mailbox, then drains via `receive do {^ref, _} -> ... end`
237+
in the order the refs were generated. Total drain time is linear
238+
— a quadratic scan would take multiple seconds for 5k messages
239+
and blow the budget.
240+
- **Ordering in practice:** replies arrive in roughly FIFO order
241+
(worker processes its queue in order), but the test doesn't rely
242+
on it — each `^ref` pin pulls exactly the matching message
243+
regardless of mailbox position. Out-of-order worker replies would
244+
not manifest as a bug.
245+
246+
No changes to the plan required; the async model's mailbox
247+
semantics scale.
248+
227249
### Spike D — Queue depth and PID liveness
228250

229251
**What to prove:** `enif_send` to a dead PID is a silent no-op;
@@ -237,6 +259,31 @@ mid-flight. Confirm:
237259
goes out of scope (automatic via RAII).
238260
- No growth in `mx::get_active_memory()` after `:erlang.garbage_collect/1`.
239261

262+
**Findings (run on branch `spike-cd-mailbox-liveness`, commit `4c0f08e`,
263+
OTP 28.3):**
264+
265+
- **Passes.** Two sub-tests green: 500-process dead-sender race
266+
(each process enqueues one call and exits immediately — no MLX
267+
allocator growth after drain), stream-dropped-mid-flight
268+
(enqueue 200 items, receive one, let the stream go out of scope
269+
— WorkerThread destructor drains the queue and joins cleanly,
270+
no hang).
271+
- **`enif_send` to dead PID behaves as documented:** silent drop.
272+
No crash, no error return visible to C++. The worker continues
273+
processing subsequent items.
274+
- **Worker shutdown drains:** `WorkerThread::stop()` (current
275+
implementation) leaves `stop_ = true` and the condvar wakes the
276+
worker; the worker processes anything still in the queue before
277+
breaking out. In-flight items complete; `enif_send` to the
278+
(still-live) caller fires normally; lambdas release their
279+
captured `ResourcePtr<Tensor>` on return. No leaks observed.
280+
- **Spike A + B already covered the simplest dead-sender case**
281+
(sender grabs a tensor/binary then exits). Spike D adds the
282+
race and shutdown scenarios; all pass.
283+
284+
No changes to the plan required; the async model is robust to
285+
caller death and stream lifetime.
286+
240287
---
241288

242289
## 2. Phased implementation — four PRs

0 commit comments

Comments
 (0)