Class ChannelSelect
AsyncChannel operations.
This is the channel equivalent of Awaitable.any(Object...) —
while Awaitable.any races futures, ChannelSelect races
channel operations. Each call to select() returns an
Awaitable that completes with a ChannelSelect.Result indicating
which offer committed and, for a receive, what value arrived.
def prices = AsyncChannel.create(10)
def alerts = AsyncChannel.create(10)
def sel = ChannelSelect.from(prices, alerts)
def result = await sel.select()
println "Channel ${result.index}: ${result.value}"
A select may also mix input and output guards — the mixed choice
of the CSP literature: "I will send my opener, or take my peer's if it
sends first". Offers to send are created with
send(AsyncChannel, Object), offers to receive with
receive(AsyncChannel), and combined with offers(Offer...):
import static groovy.concurrent.ChannelSelect.*
def result = await offers(send(ping, 1), receive(pong)).select()
if (result.send) { ... my opener committed ... }
else { ... my peer opened first: result.value ... }
A send offer commits as soon as its channel can accept the value: when a receiver is waiting, or when buffer space holds it. Two peers racing a mixed choice are therefore coherent — exactly one of the two openers commits — only over rendezvous (capacity-0) channels, where a send cannot complete unilaterally; over buffered channels with space, each peer's send offer commits immediately under its own select, and the two proceed down different branches of the same session.
Any branch may also carry a precondition — the guarded choice of
CSP — either written onto the offer itself with
ChannelSelect.Offer.when(BooleanSupplier) or passed positionally to
select(boolean...). A disabled offer is left unregistered while
the enabled ones keep their positions, so a guard can be masked off
without renumbering the branches around it.
def result = await offers(receive(input).when { size < capacity },
receive(request).when { size > 0 }).select()
A deadline is just another branch. The timer offer after(long)
arms a fresh timer on every select() call, so a select held
across rounds waits for work, but not forever, and keeps its choice
policy state while it does:
def sel = offers(receive(work), after(100)).fair()
while (true) {
def result = await sel.select()
if (result.timeout) { ... nothing arrived this round ... }
}
For a fixed deadline across rounds, receive from a timer channel
instead: AsyncChannel.after(long) starts its clock when it is
created and delivers the instant it fired.
Inspired by GPars' Select and Go's select statement
(whose send cases these offers mirror).
- Since:
- 6.0.0
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final classOne branch of a select: an input guard created byreceive(AsyncChannel), an output guard created bysend(AsyncChannel, Object), or a timeout created byafter(long).static final classThe result of aselect()operation, indicating which offer committed. -
Method Summary
Modifier and TypeMethodDescriptionstatic ChannelSelect.Offerafter(long millis) An offer that commits oncemillishas elapsed from the start of theselect()call it takes part in: a timeout as a branch of the choice, rather than an exception thrown around it byAwaitable.orTimeout(long, TimeUnit).static ChannelSelect.OfferAn offer that commits oncedurationhas elapsed from the start of the select call it takes part in; seeafter(long).fair()Returns a select over the same offers that chooses fairly among offers that are ready at the same time.static ChannelSelectfrom(AsyncChannel<?>... channels) Creates a select over receives from the given channels.static ChannelSelectoffers(ChannelSelect.Offer... offers) Creates a select over the given offers, which may mix sends and receives.random()Returns a select over the same offers that chooses uniformly at random among offers that are ready at the same time.static ChannelSelect.Offerreceive(AsyncChannel<?> channel) An offer to receive the next value fromchannel: an input guard, the branch a plainfrom(AsyncChannel...)select is made of.select()Waits for the first offer that can complete.select(boolean... enabled) Waits for the first offer that can complete among those its precondition enables: the guarded choice of the CSP literature, where a branch is masked off for this call without disturbing the others.static <V> ChannelSelect.Offersend(AsyncChannel<V> channel, V value) An offer to transfervalueintochannel: an output guard.
-
Method Details
-
from
Creates a select over receives from the given channels.- Parameters:
channels- the channels to select from- Returns:
- a new ChannelSelect
-
offers
Creates a select over the given offers, which may mix sends and receives.- Parameters:
offers- the offers to select among- Returns:
- a new ChannelSelect
- Since:
- 6.0.0
- See Also:
-
send
An offer to transfervalueintochannel: an output guard. It commits when the channel can accept the value — a waiting receiver takes it, or buffer space holds it — and a committed offer behaves exactly likechannel.send(value). A retired offer has no effect on the channel.Only channels created by
AsyncChannel.create()can take part in the claim protocol that makes an effect-free retired send possible, so only they may carry send offers.- Type Parameters:
V- the payload type- Parameters:
channel- the channel to send intovalue- the value to transfer- Returns:
- the send offer
- Throws:
IllegalArgumentException- if the channel is not a built-in one- Since:
- 6.0.0
-
receive
An offer to receive the next value fromchannel: an input guard, the branch a plainfrom(AsyncChannel...)select is made of.- Parameters:
channel- the channel to receive from- Returns:
- the receive offer
- Since:
- 6.0.0
-
after
An offer that commits oncemillishas elapsed from the start of theselect()call it takes part in: a timeout as a branch of the choice, rather than an exception thrown around it byAwaitable.orTimeout(long, TimeUnit). It commits with theInstantat which it fired.Semantically it is a receive from a timer channel that the select creates and, if the offer loses, cancels on every call — the difference from
receive(AsyncChannel.after(millis)), whose clock starts once, when the channel is created. The timer offer is therefore the "wait for work, but not forever" branch of a select that is held and reused, where it re-arms each round while the instance keeps itsfair()rotation. The channel form is the fixed deadline shared by every round. Like any other offer it may be guarded withChannelSelect.Offer.when(java.util.function.BooleanSupplier); a guarded-off timer offer arms nothing.A timer offer with a positive delay is never ready at the moment of registration, so among offers that are ready at once the choice policy considers only the channel offers; the timer commits only when no channel offer could before it fired. A delay that is not positive has already elapsed, so that offer is ready at registration and takes part in the choice among ready offers like any other: under the default priority it wins if listed before every ready channel offer and loses to one listed before it, and
fair()rotates over it. Listed last,after(0)is therefore thedefaultclause of Go'sselect: take a transfer if one can complete at once, otherwise proceed without waiting.def result = await offers(receive(work), after(0)).select() if (result.timeout) { ... nothing was ready ... }- Parameters:
millis- how long to wait, in milliseconds, from the start of each select call- Returns:
- the timer offer
- Since:
- 6.0.0
- See Also:
-
after
An offer that commits oncedurationhas elapsed from the start of the select call it takes part in; seeafter(long).- Parameters:
duration- how long to wait from the start of each select call- Returns:
- the timer offer
- Throws:
NullPointerException- if duration is null- Since:
- 6.0.0
-
fair
Returns a select over the same offers that chooses fairly among offers that are ready at the same time.By default
select()prefers the offer listed first, so a channel that always has a value waiting starves the ones after it. A fair select instead starts each call at the offer after the one that last won, so every offer that is ready is taken withinncalls, wherenis the number of offers. This is the rotating policy of JCSP'sfairSelect. When no offer is ready, the first to become completable wins under either policy.The rotation state lives in the returned instance, so keep and reuse it across calls (typically in a loop); a shared instance may be used from several threads, in which case the rotation is best effort.
- Returns:
- a fair ChannelSelect over the same offers
- Since:
- 6.0.0
- See Also:
-
random
Returns a select over the same offers that chooses uniformly at random among offers that are ready at the same time.This is the policy of Go's
selectand GPars'Select. Unlikefair()it keeps no state between calls, so it is exactly as fair from any number of threads and cannot fall into lock-step with the producers, but it offers no bound on how long a ready offer may be passed over. When no offer is ready, the first to become completable wins under either policy.- Returns:
- a randomly choosing ChannelSelect over the same offers
- Since:
- 6.0.0
- See Also:
-
select
Waits for the first offer that can complete.Returns an
Awaitablethat completes with aChannelSelect.Resultnaming the committed offer: the received value for an input guard, the sent value for an output guard.Exactly one offer commits. The other channels are left untouched: their contents and order are preserved, and nothing remains registered on them once the result completes — a retired send offer in particular leaves no buffered residue and no waiting sender. When several offers are ready, the one listed first commits (see
fair()for a rotating choice andrandom()for a random one). Cancelling the result (for example throughAwaitable.orTimeout(long, java.util.concurrent.TimeUnit)) withdraws the pending offers, so a timed-out select consumes nothing and sends nothing. To take a timeout as a branch instead of an exception, add a timer offer fromafter(long)or receive from a timer channel fromAsyncChannel.after(long).If every offer's channel is closed, the result fails with
ChannelClosedException. If every offer is guarded off (seeChannelSelect.Offer.when(BooleanSupplier)) there is nothing to wait for and the result fails withIllegalStateException.Only channels created by
AsyncChannel.create()take part in the claim protocol that makes this possible. Any otherAsyncChannelimplementation consumes a value before the select can decide; if that value loses, it is re-sent to its channel, which preserves it but may reorder that channel. Send offers are limited to built-in channels for the same reason.- Returns:
- an awaitable result indicating which offer committed
-
select
Waits for the first offer that can complete among those its precondition enables: the guarded choice of the CSP literature, where a branch is masked off for this call without disturbing the others.Flag
ienables offeri, and is conjoined with any guard the offer carries of its own: an offer is registered only if both hold. A disabled offer is not registered on its channel — nothing is consumed from it and nothing is sent to it — but it keeps its position, soChannelSelect.Result.getIndex()still denotes the same branch whatever the mask. That positional stability is the point: dropping an offer from the argument list instead would silently renumber the branches after it.// the classic bounded buffer: take input only while there is room, // answer requests only while there is something to hand over def sel = offers(receive(input), receive(request)) while (true) { def result = await sel.select(size < capacity, size > 0) if (result.index == 0) { ... buffer result.value ... } else { ... reply with the oldest value ... } }In every other respect this behaves as
select(): the enabled offers are scanned in the order the choice policy dictates, and exactly one commits.- Parameters:
enabled- one flag per offer, in offer order- Returns:
- an awaitable result indicating which offer committed, or one
that fails with
IllegalStateExceptionif every offer is disabled - Throws:
IllegalArgumentException- if the number of flags is not the number of offers- Since:
- 6.0.0
- See Also:
-