Parent: #1
Problem
Worker channel delivery previously allowed queue-allocation failure to look like a successful post, and unbounded inbox/outbox growth could exhaust process memory outside heap_limit_bytes.
Delivered
- Channel push is fallible and distinguishes closed-channel rejection, allocation failure, per-message rejection, queued-byte exhaustion, and message-count exhaustion.
- Zig
Worker.postMessage, worker-globalpostMessage, and the C Worker API propagate delivery failure. - Worker configuration exposes documented independent inbox/outbox per-message, queued-byte, and message-count limits; the C limit constructor applies the supplied limits in both directions.
- Queue byte/message accounting covers live FIFO entries only and remains exact across pop, compaction, close, drain, rejection, and deinit.
- Structured-clone frames and SAB manifest references transfer or release exactly once on every success/failure path.
- Complete frame construction is bounded from its first write, including header and SAB-manifest overhead.
- Rejection is nonblocking and cleanup occurs after releasing the channel lock.
- Focused tests cover closed push, injected metadata OOM, caps, recovery after pop, compaction, C/JS-visible errors, SAB rejection ownership, and lifecycle rejection oracles.
- Capacity arithmetic and Worker wait-time conversion are checked.
- Worker lifecycle, structured-clone, no-GIL, TSan, C-API, docs, and fuzz gates are green.
Contract
A post that returns successfully is queued exactly once. A rejected post is observable and releases its frame exactly once. Default limits are 64 MiB per message, 256 MiB queued bytes, and 1024 queued messages per channel; callers can configure stricter limits, including zero. Delivery never parks while holding a channel or JS/GC trace-sensitive lock.
Acceptance
Final combined gate: https://github.com/zig-utils/zig-js/actions/runs/29236051962 — 31 success, 1 expected nightly-only skip, 0 failures.