Skip to content

Commit 2e692b1

Browse files
committed
Record Spike A findings in exploration plan
Spike A passed on OTP 28.3. Key results: enif_send does not transfer msg_env ownership (caller must enif_free_env); enif_self must be called from the scheduler thread; plain {ref, {:error, binary}} term transports errors cleanly; 160k round-trip stress shows no MLX or BEAM memory growth; dead-sender case does not leak. Phase 1 can proceed on the substrate validated by the spike. Reference: branch spike-a-enif-send, commit c119819.
1 parent bc56446 commit 2e692b1

1 file changed

Lines changed: 34 additions & 0 deletions

File tree

docs/planning/async-worker-exploration.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,40 @@ the BEAM VM with matching flags, which is not practical here. Drive
110110
as a plain term. Spike confirms which atoms the Elixir-side
111111
`raise_error_with_message` equivalent translates to.
112112

113+
**Findings (run on branch `spike-a-enif-send`, commit `c119819`,
114+
OTP 28.3):**
115+
116+
- **Passes.** All four sub-tests green: single call, 50 sequential
117+
calls from one process (mailbox stays empty — ref pattern-match
118+
pulls exactly one reply per call), 16-process × 10k stress
119+
(160k round-trips in ~500 ms, no MLX or BEAM growth), and
120+
dead-sender (100 fire-and-forget from a process that exits
121+
before receiving — worker drains, no MLX leak).
122+
- **`enif_send` env ownership:** it does NOT transfer ownership.
123+
`enif_free_env(msg_env)` is required after the send call (both
124+
on success and when the target PID is dead). Skipping it leaks
125+
the env. Confirmed on OTP 28.3.
126+
- **`enif_self` placement:** called in the NIF on the scheduler
127+
thread, `ErlNifPid` captured by value into the worker lambda.
128+
Worked as documented.
129+
- **Error term encoding:** a plain tuple `{ref, {:error, binary}}`
130+
built via `enif_make_new_binary` + `enif_make_tuple2` transports
131+
fine. No need for `enif_raise_exception` on the async path — the
132+
receive site re-raises in Elixir.
133+
- **Memory invariant holds:** `Tensor` ResourcePtr built on the
134+
worker thread, encoded with `enif_make_resource(msg_env, ptr)`,
135+
sent via `enif_send`. Receiver's term holds one ref; worker's
136+
ResourcePtr releases as the lambda unwinds; refcount nets to 1
137+
on the receiver and 0 after the receiver GCs the term.
138+
- **Worker `run_async` shape:** `template<typename F> void run_async(F&& f)`
139+
added to `WorkerThread` (c_src/emily/worker.hpp). Catches
140+
exceptions inside the queued task — the task owns error
141+
propagation. Non-blocking enqueue; `cv_.notify_one()` after
142+
releasing the mutex.
143+
144+
No changes to the plan required; Phase 1 can proceed using exactly
145+
this substrate.
146+
113147
### Spike B — Resource binaries across env boundaries
114148

115149
**What to prove:** `enif_make_resource_binary` can be called from a

0 commit comments

Comments
 (0)