PR #32's stated known limit, filed so it lives in the tracker rather than a merged PR body: "the runtime registers receivers but not senders (sender wakeups ride the family broadcast, per the documented design) — a mixed family with a plain-substrate taker would need the sender registration the substrate already provides."
What holds today, and where the edge is
Within a family whose processes all run this runtime, capacity-0 shared channels are complete (#14 / PR #32): receivers register with the substrate while parked, senders park on their ticket and are woken by the family broadcast when the take completes, both sides bounded (measured 9 and 14 wakeups for 8 handoffs against a bound of 64).
The edge: a process that talks to the ring through the substrate's own API rather than this runtime — a worker embedding the extension directly, or a non-PHP-coroutines sibling. Its take does not go through this runtime's notify path, so a sender parked here waits on a broadcast that never comes. The substrate already provides sender-side registration; this runtime deliberately does not use it (registration derived from the waiter list was the receivers' anti-drift design — the same derivation would need to exist for senders).
Secondary limit from the same PR, same fate
The select-send refusal on a capacity-0 shared channel fires when the select actually parks on the case, not at declaration. Declaring it earlier (at ->send() case construction) would fail sooner and closer to the mistake; today a select that never parks on that arm never learns.
Suggested shape
- Sender registration derived from the parked-ticket list, mirroring the receivers' derived-not-counted design so there is no count to drift.
- Withdraw on take, cancellation and select-loss, probed from another process like
testASelectLoserLeavesNoRendezvousRegistrationForALaterSend.phpt does for receivers.
- Move the select-send refusal to case-construction time.
Enhancement-shaped, not a defect: no current test or documented workload mixes API surfaces within one family. Worth doing when one does.
PR #32's stated known limit, filed so it lives in the tracker rather than a merged PR body: "the runtime registers receivers but not senders (sender wakeups ride the family broadcast, per the documented design) — a mixed family with a plain-substrate taker would need the sender registration the substrate already provides."
What holds today, and where the edge is
Within a family whose processes all run this runtime, capacity-0 shared channels are complete (#14 / PR #32): receivers register with the substrate while parked, senders park on their ticket and are woken by the family broadcast when the take completes, both sides bounded (measured 9 and 14 wakeups for 8 handoffs against a bound of 64).
The edge: a process that talks to the ring through the substrate's own API rather than this runtime — a worker embedding the extension directly, or a non-PHP-coroutines sibling. Its take does not go through this runtime's notify path, so a sender parked here waits on a broadcast that never comes. The substrate already provides sender-side registration; this runtime deliberately does not use it (registration derived from the waiter list was the receivers' anti-drift design — the same derivation would need to exist for senders).
Secondary limit from the same PR, same fate
The select-send refusal on a capacity-0 shared channel fires when the select actually parks on the case, not at declaration. Declaring it earlier (at
->send()case construction) would fail sooner and closer to the mistake; today a select that never parks on that arm never learns.Suggested shape
testASelectLoserLeavesNoRendezvousRegistrationForALaterSend.phptdoes for receivers.Enhancement-shaped, not a defect: no current test or documented workload mixes API surfaces within one family. Worth doing when one does.