You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Channel.ReceiveChannel[T] exposes only a blockingreceive() (suspends until an element is available, raising ChannelClosed when drained/closed) and cancel(). There is no non-blocking variant: a caller cannot ask "give me an element if one is immediately available, otherwise tell me the channel is empty" without parking the calling fiber.
This blocks consumers that need to poll a channel — drain-what's-there loops, select/poll-style multiplexing, and the kyo-compat binding's CChannel.poll, which requires a non-blocking take.
Agent Brief
Category: enhancement Summary: Add a non-blocking tryReceive() to ReceiveChannel, returning the next element if immediately available, None if empty-but-open, and raising ChannelClosed if drained/closed/cancelled.
Current behavior: ReceiveChannel[T] offers receive()(using Raise[ChannelClosed]): T, which blocks (via the channel's lock/condition) until an element is available or the channel is closed/cancelled, at which point it raises ChannelClosed. There is no way to attempt a receive without suspending.
Desired behavior:
A new method on ReceiveChannel[T]:
Behavior, by channel state, with no blocking and no waiting on conditions:
State
Result
An element is immediately available
Some(value) (element removed)
Empty, still open
None
Empty and closed (drained), or cancelled
Raise(ChannelClosed)
Naming is tryReceive (not poll): it is consistent with the existing receive() and the Kotlin-coroutines lineage this Channel API follows; poll is the underlying java.util.concurrent name the API deliberately wraps. The kyo-compatCChannel.poll maps onto tryReceive at the binding layer.
Must obey the project error-handling philosophy: no thrown exceptions / require; signal closed via Raise[ChannelClosed], exactly like receive().
Per channel-type semantics (all three implementations must be covered):
Unbounded — take the head if present; otherwise None (or ChannelClosed if empty+closed).
Bounded — same, and after taking an element it must signal any sender parked on a full buffer, just as receive() does (overflow/back-pressure behavior must remain correct).
Rendezvous — succeeds with Some only if a sender is already parked with an item ready at the moment of the call; otherwise None (no handshake is initiated). This is the expected rendezvous caveat — document it in the Scaladoc.
All concrete channel implementations (unbounded, bounded, rendezvous) — implement it, reusing the existing lock so it is atomic w.r.t. concurrent send/receive/close/cancel. It must not invoke the blocking await/condition path.
Acceptance criteria:
tryReceive() returns Some(v) when an element is immediately available and removes it, identical to what receive() would have returned.
tryReceive() returns None on an empty, open channel and does not block the caller.
tryReceive() raises ChannelClosed on an empty channel that is closed (fully drained) or cancelled.
On a closed channel that still has buffered elements, tryReceive() drains the remaining elements (Some) before it raises ChannelClosed — same drain-then-close ordering as receive().
Bounded: a successful tryReceive() unblocks a sender parked on a full buffer.
Rendezvous: tryReceive() returns Some only when a sender is already waiting with an item; returns None otherwise without initiating a handshake.
No exceptions thrown for invalid/closed states — closure is signalled only through Raise[ChannelClosed].
Tests added to the channel spec covering: available, empty-open, empty-closed, drain-then-close, bounded sender-unblock, rendezvous-no-partner — for the relevant channel types.
Scaladoc following project conventions (description, @return, the rendezvous caveat, a {{{ }}} usage example).
Out of scope:
A non-blocking trySend (symmetric counterpart) — separate issue if wanted.
Any change to the blocking receive() contract or to overflow strategies.
Timed/Async-aware receive variants.
The kyo-compat binding itself (this only provides the core primitive it will consume).
Further Notes
Motivated by the kyo-compat binding GAP analysis: CChannel.poll was the one open question in the otherwise-non-gap Channel mapping. This is an independent yaes-data primitive, decoupled from the Async.unsupervised epic (#302).
Problem Statement
Channel.ReceiveChannel[T]exposes only a blockingreceive()(suspends until an element is available, raisingChannelClosedwhen drained/closed) andcancel(). There is no non-blocking variant: a caller cannot ask "give me an element if one is immediately available, otherwise tell me the channel is empty" without parking the calling fiber.This blocks consumers that need to poll a channel — drain-what's-there loops, select/poll-style multiplexing, and the
kyo-compatbinding'sCChannel.poll, which requires a non-blocking take.Agent Brief
Category: enhancement
Summary: Add a non-blocking
tryReceive()toReceiveChannel, returning the next element if immediately available,Noneif empty-but-open, and raisingChannelClosedif drained/closed/cancelled.Current behavior:
ReceiveChannel[T]offersreceive()(using Raise[ChannelClosed]): T, which blocks (via the channel's lock/condition) until an element is available or the channel is closed/cancelled, at which point it raisesChannelClosed. There is no way to attempt a receive without suspending.Desired behavior:
A new method on
ReceiveChannel[T]:Behavior, by channel state, with no blocking and no waiting on conditions:
Some(value)(element removed)NoneRaise(ChannelClosed)Naming is
tryReceive(notpoll): it is consistent with the existingreceive()and the Kotlin-coroutines lineage this Channel API follows;pollis the underlyingjava.util.concurrentname the API deliberately wraps. Thekyo-compatCChannel.pollmaps ontotryReceiveat the binding layer.Must obey the project error-handling philosophy: no thrown exceptions /
require; signal closed viaRaise[ChannelClosed], exactly likereceive().Per channel-type semantics (all three implementations must be covered):
None(orChannelClosedif empty+closed).receive()does (overflow/back-pressure behavior must remain correct).Someonly if a sender is already parked with an item ready at the moment of the call; otherwiseNone(no handshake is initiated). This is the expected rendezvous caveat — document it in the Scaladoc.Key interfaces:
Channel.ReceiveChannel[T]— gainstryReceive()(using Raise[ChannelClosed]): Option[T].send/receive/close/cancel. It must not invoke the blocking await/condition path.Acceptance criteria:
tryReceive()returnsSome(v)when an element is immediately available and removes it, identical to whatreceive()would have returned.tryReceive()returnsNoneon an empty, open channel and does not block the caller.tryReceive()raisesChannelClosedon an empty channel that is closed (fully drained) or cancelled.tryReceive()drains the remaining elements (Some) before it raisesChannelClosed— same drain-then-close ordering asreceive().tryReceive()unblocks a sender parked on a full buffer.tryReceive()returnsSomeonly when a sender is already waiting with an item; returnsNoneotherwise without initiating a handshake.Raise[ChannelClosed].@return, the rendezvous caveat, a{{{ }}}usage example).Out of scope:
trySend(symmetric counterpart) — separate issue if wanted.receive()contract or to overflow strategies.Async-aware receive variants.kyo-compatbinding itself (this only provides the core primitive it will consume).Further Notes
Motivated by the
kyo-compatbinding GAP analysis:CChannel.pollwas the one open question in the otherwise-non-gap Channel mapping. This is an independentyaes-dataprimitive, decoupled from theAsync.unsupervisedepic (#302).