Phase 3: convert to_binary to async enif_send - #43
Merged
Conversation
Converts the last worker-bound NIF to the async model. `to_binary` was the hardest case because it returns a resource binary that aliases MLX-owned memory via `fine::make_resource_binary` — Spike B validated that building the resource binary on the worker-allocated msg_env correctly pins the Tensor resource across `enif_send`, so no memcpy fallback is needed. The worker thread now runs `mx::contiguous + mx::eval` and builds the resource binary directly into msg_env. The binary carries one resource ref into the caller's heap fragment, keeping the MLX buffer alive for the lifetime of the binary on the receiver. Drops the `ERL_NIF_DIRTY_JOB_CPU_BOUND` flag — `to_binary` was the last dirty-flagged NIF in the project. With Phase 3 merged, no NIF blocks any BEAM scheduler (regular or dirty) on MLX work. Full suite: 425 tests, 0 failures. Full heavy suite (including qwen3_full, vit_full, whisper_full, distilbert_full, training_full, qwen3_quant_full, fast_kernels_full, conformance): 453 tests, 0 failures.
This was referenced Apr 19, 2026
ausimian
added a commit
that referenced
this pull request
Apr 19, 2026
After the Phase 1-3 async-worker migration (#41-#43), NIFs no longer block a scheduler on a sync promise — they enqueue on the worker and the public wrapper awaits a reply via enif_send. Update the README Concurrency model section and the CHANGELOG worker-thread-dispatch bullet so they describe the shipping behaviour.
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Third PR of the async-worker migration. Phases 1 (#41) and 2 (#42) merged; this PR converts the last worker-bound NIF — `to_binary` — to the async pattern.
What changes
Pinning
The worker calls `fine::make_resource_binary(msg_env, std::move(pin), data, nbytes)`. ERTS bumps the Tensor resource refcount internally; the binary term carries that ref through `enif_send` into the receiver's heap fragment, where it survives until the binary is GC'd. Spike B exercised this with 250 distinct 4 MB tensors (1 GB of transient MLX memory) — all reclaimed cleanly after GC + `clear_cache`.
User-visible effect
None. `Emily.Native.to_binary(w, tensor)` still blocks until the tensor is materialised and returns a resource binary aliasing MLX memory.
Test plan