iyi

Prelude, 0.18.0

Channel

Channel(T).new is a rendezvous, as Crystal's is: a send completes when a receive takes it, and either side parks until the other arrives. A parked sender carries its value in its node's box, and a parked receiver has a slot a sender fills, so the handoff needs no third party; new(capacity) buffers that many sends first. Closing drains what was delivered, then refuses: a parked sender whose box is still full failed, and its value dies with the node.

In the prelude: every iyi program has it, with no import.

T : Share is not enforced yet for the reason the file header gives: one thread cannot race, and a marker that gates nothing cannot be tested for gating. select is answered by this protocol — a node per arm, one nonce, losers stale by arithmetic until the woken select takes them out — at the bottom of the class.

Channel(T)class

Channeliyi doc Channel
class Channel(T)

  # Everyone parked is woken; what each wake means is read on the woken
  # side — a receiver finds the buffer, then `ChannelClosed`; a sender
  # finds its box still full.
  def close : Nil

  def closed? : Bool

  def new(capacity : Int32)

  def new

  # `select`'s arms park through these with one shared nonce; a plain
  # call parks with a fresh nonce of its own. *slot* is where a rendezvous
  # sender puts its value (`IyiWaiter#slot`).
  def park_receive_node(fiber : IyiFiber, nonce : UInt64, slot : UInt64) : IyiWaiter

  def park_send_node(fiber : IyiFiber, nonce : UInt64, value : T) : IyiSendNode(T)

  # Assumes `send_ready?`. A rendezvous puts the value in the parked
  # receiver's own slot (`hand`). It went into the buffer, and the send
  # answered delivered on the word of the wake: a select woken that way
  # took another arm ready by then, a receiver cancelled before it ran
  # answered `Cancelled`, and either way the value stayed in a channel of
  # no capacity after its sender left. A buffered send completes when the
  # buffer takes it, so there the woken receiver finds it in the buffer.
  def put_now(value : T) : Nil | ChannelClosed

  # A closed channel drains what was delivered before it refuses. A
  # rendezvous sender that finds this receive parked writes its value into
  # `got` and marks the node handed, and that is the answer even when the
  # task was cancelled before it ran: the sender was told it was taken.
  # A woken park that was handed nothing takes its node out of the queue,
  # so a cancelled one leaves nothing behind.
  def receive : T | ChannelClosed | Cancelled

  def receive_ready? : Bool

  # Nothing happens on the arm `take_now` answers: `ChannelClosed` is
  # the refusal here, where a closed channel's buffer is spent.
  def receive_refused? : Bool

  def receive_select_action : IyiReceiveSelect(T)

  def send(value : T) : Nil | ChannelClosed | Cancelled

  def send_ready? : Bool

  def send_select_action(value : T) : IyiSendSelect(T)

  # Assumes `receive_ready?`: the buffer, then a parked sender's box,
  # then the refusal.
  def take_now : T | ChannelClosed

  # Out of the receive queue from wherever it stands, in constant time,
  # and no longer linked anywhere; a node no queue holds is left as it
  # is. The queue is linked both ways because a select's losing arms
  # leave from the middle: they stayed until something walked them off
  # the head, so a loop selecting over a busy channel and an idle one
  # left a node in the idle one's queue for every park - 30 MiB kept
  # after two million rounds, freed only when the idle channel was used.
  def unlink_receiver(node : IyiWaiter) : Nil

  # The send queue's `unlink_receiver`.
  def unlink_sender(node : IyiSendNode(T)) : Nil

  # No arm ready answers -1, which the expansion's case meets with the
  # program's own `else`.
  def self.non_blocking_select(actions : U, bound : Int32) forall U

  # ── select — III.4.6, the library half of the expansion ────────────
  #
  # A `select` compiles to `%i, %v, %h = ::Channel.select({arm, arm}, b)`
  # and a case over the index; `non_blocking_select` when an `else` arm
  # exists. One def serves every arity, because a tuple's size is a
  # compile-time fact a macro walks. `%h` says the arm's operation
  # happened, and an arm nobody binds runs its body only then: it ran
  # unless its answer was an `Error`, so a `Channel(Boom)`'s receive took
  # the `Boom` and skipped the body. *bound* is the first arm that binds,
  # -1 for none, and a cancelled task's `Cancelled` goes to it, as that
  # arm's own primitive would have answered it.
  def self.select(actions : U, bound : Int32) forall U
end