Package: groovy.concurrent

## [Java] Class ChannelSelect

- groovy.concurrent.ChannelSelect

-     public final class ChannelSelect
      extends Object

  Selects the first offer that can complete among multiple AsyncChannel operations.

  This is the channel equivalent of Awaitable.any — while `Awaitable.any` races futures, `ChannelSelect` races channel operations. Each call to select() returns an Awaitable that completes with a 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 Offer.when 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 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 classes
    Modifiers | Name | Description
    --- | --- | ---
    `static class` | `ChannelSelect.Offer` | One branch of a select: an input guard created by receive(AsyncChannel), an output guard created by send(AsyncChannel, Object), or a timeout created by after(long).
    `static class` | `ChannelSelect.Result` | The result of a select() operation, indicating which offer committed.

  - ### Methods Summary

    Methods
    Type Params | Return Type | Name and description
    --- | --- | ---
    `` | `public static ChannelSelect.Offer` | `after(long millis)` An offer that commits once `millis` has elapsed from the start of the select() call it takes part in: a timeout as a branch of the choice, rather than an exception thrown around it by Awaitable.orTimeout.
    `` | `public static ChannelSelect.Offer` | `after(Duration duration)` An offer that commits once `duration` has elapsed from the start of the select call it takes part in; see after(long).
    `` | `public ChannelSelect` | `fair()` Returns a select over the same offers that chooses fairly among offers that are ready at the same time.
    `` | `public static ChannelSelect` | `from(AsyncChannel<?> channels)` Creates a select over receives from the given channels.
    `` | `public static ChannelSelect` | `offers(ChannelSelect.Offer offers)` Creates a select over the given offers, which may mix sends and receives.
    `` | `public ChannelSelect` | `random()` Returns a select over the same offers that chooses uniformly at random among offers that are ready at the same time.
    `` | `public static ChannelSelect.Offer` | `receive(AsyncChannel<?> channel)` An offer to receive the next value from `channel`: an input guard, the branch a plain from(AsyncChannel...) select is made of.
    `` | `public Awaitable<Result>` | `select()` Waits for the first offer that can complete.
    `` | `public Awaitable<Result>` | `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.
    `<V>` | `public static ChannelSelect.Offer` | `send(AsyncChannel<V> channel, V value)` An offer to transfer `value` into `channel`: an output guard.

  - ### Inherited Methods Summary

    Inherited Methods
    Methods inherited from class | Name
    --- | ---
    `class Object` | `equals, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait`

- - ### Method Detail

    - #### public static ChannelSelect.Offer after(long millis)

      An offer that commits once `millis` has elapsed from the start of the select() call it takes part in: a timeout as a branch of the choice, rather than an exception thrown around it by Awaitable.orTimeout. It commits with the Instant at 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 its fair() rotation. The channel form is the fixed deadline shared by every round. Like any other offer it may be guarded with Offer.when; 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 the `default` clause of Go's `select`: 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
      See Also:
        after(Duration)
        Result.isTimeout
      Since:
        6.0.0

    - #### public static ChannelSelect.Offer after(Duration duration)

      An offer that commits once `duration` has elapsed from the start of the select call it takes part in; see after(long).

      throws:
        NullPointerException if duration is null
      Parameters:
        `duration` - how long to wait from the start of each select call
      Returns:
        the timer offer
      Since:
        6.0.0

    - #### public ChannelSelect 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 within `n` calls, where `n` is the number of offers. This is the rotating policy of JCSP's `fairSelect`. 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
      See Also:
        random()
      Since:
        6.0.0

    - #### @SafeVarargs
      public static ChannelSelect from(AsyncChannel<?> channels)

      Creates a select over receives from the given channels.

      Parameters:
        `channels` - the channels to select from
      Returns:
        a new ChannelSelect

    - #### public static ChannelSelect offers(ChannelSelect.Offer 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
      See Also:
        send(AsyncChannel, Object)
        receive(AsyncChannel)
      Since:
        6.0.0

    - #### public ChannelSelect 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 `select` and GPars' `Select`. Unlike fair() 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
      See Also:
        fair()
      Since:
        6.0.0

    - #### public static ChannelSelect.Offer receive(AsyncChannel<?> channel)

      An offer to receive the next value from `channel`: an input guard, the branch a plain from(AsyncChannel...) select is made of.

      Parameters:
        `channel` - the channel to receive from
      Returns:
        the receive offer
      Since:
        6.0.0

    - #### public Awaitable<Result> select()

      Waits for the first offer that can complete.

      Returns an Awaitable that completes with a Result naming 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 and random() for a random one). Cancelling the result (for example through Awaitable.orTimeout) 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 from after(long) or receive from a timer channel from AsyncChannel.after.

      If every offer's channel is closed, the result fails with ChannelClosedException. If every offer is guarded off (see Offer.when) there is nothing to wait for and the result fails with IllegalStateException.

      Only channels created by AsyncChannel.create take part in the claim protocol that makes this possible. Any other `AsyncChannel` implementation 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

    - #### public Awaitable<Result> 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.

      Flag `i` enables offer `i`, 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, so 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.

      throws:
        IllegalArgumentException if the number of flags is not the number of offers
      Parameters:
        `enabled` - one flag per offer, in offer order
      Returns:
        an awaitable result indicating which offer committed, or one that fails with IllegalStateException if every offer is disabled
      See Also:
        Offer.when
      Since:
        6.0.0

    - #### <V> public static ChannelSelect.Offer send(AsyncChannel<V> channel, V value)

      An offer to transfer `value` into `channel`: 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 like `channel.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.

      throws:
        IllegalArgumentException if the channel is not a built-in one
      Parameters:
        `channel` - the channel to send into
        `value` - the value to transfer
      Type Parameters:
        `V` - the payload type
      Returns:
        the send offer
      Since:
        6.0.0