From 02f55fa0a0887057fa792ac1524ddf4525ba7053 Mon Sep 17 00:00:00 2001 From: smarm-dev Date: Fri, 5 Jun 2026 21:45:26 +0000 Subject: [PATCH] docs(roadmap): mark selective receive (#4) done Record what landed for recv_match/try_recv_match and update the no-select gotcha: selective receive stayed per-channel rather than introducing a cross-channel mailbox. --- task.md | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/task.md b/task.md index 54ac0a7..2ddd2e3 100644 --- a/task.md +++ b/task.md @@ -123,8 +123,30 @@ Shipped. What landed vs the plan: dead-pid link stops a non-trapper / messages a trapper; `unlink` prevents propagation. -### 4. Selective receive (independent track) -Channels are strict FIFO MPSC; there's no pattern-matched `receive`. +### 4. Selective receive (independent track) ✅ DONE (`03f3875`) +Shipped. What landed vs the plan: +- `Receiver::recv_match(pred) -> Result` scans the queued + `VecDeque` front-to-back, removes+returns the first match, leaves the rest in + arrival order; parks and re-scans when nothing matches. `try_recv_match` (the + non-blocking variant, mirroring `try_recv`) rolled in same commit. +- Wakeup turned out cheaper than feared: `Sender::send` *already* took + `parked_receiver` on every push, so "wake on ANY send" needed no send-side + change. The one real edit was relaxing `Sender::drop` to unpark the parked + receiver on the last-sender drop regardless of queue emptiness — a selective + receiver can park on a non-empty no-match queue and must wake to observe + closure. No-op for plain `recv` (only ever parks on an empty queue); stress + suite stays green. +- `pred` is `Fn(&T) -> bool` (not `FnMut`) on purpose: it's re-run from scratch + on every scan, so a stateful predicate would re-count surprisingly. It runs + under the channel lock — keep it cheap/pure, don't re-enter the channel. +- Close semantics: `recv_match` returns `Err(RecvError)` only when closed AND no + queued message matches; a match is still returned on a closed channel. + Non-matches are left for a later `recv`. +- Tests (`tests/selective_recv.rs`): out-of-order match pulled first; non-matches + remain in order; park-on-non-empty then wake on a match; closed-with-only- + non-matches → Err; closed-but-match-present → match; `try_recv_match` states. + + Original plan, for reference: - Add `Receiver::recv_match(pred) -> T`: scan the queued `VecDeque`, remove+return first match, leave the rest in order; park and re-scan on new arrivals. - This changes channel wakeup: a parked selective receiver must wake on ANY send @@ -166,7 +188,10 @@ Channels are strict FIFO MPSC; there's no pattern-matched `receive`. - **No `select`, no unified per-process mailbox.** Why the supervisor uses the single `supervisor_channel` funnel rather than N monitor channels. trap_exit resolved this by giving each trapping actor a dedicated `Receiver` - inbox (see #3); selective-receive delivery still needs its own decision. + inbox (see #3); selective receive (#4) stayed *per-channel* (`recv_match` + scans one channel's queue) rather than introducing a cross-channel mailbox — + if selective receive ever needs to span the monitor/trap inboxes too, that + cross-channel merge is the still-unmade decision. - **Cooperative-only**: preemption and (future) cancellation both depend on the actor reaching `check!()`/yield/alloc/blocking points. - `run()` is single-thread (`Config::exact(1)`); tests rely on deterministic