iyi

Standard library, 0.18.0

std/signal

Signals: a program told to stop, as a value it waits for.

import std/signal to use it. Source: src/std/signal.iyi at v0.18.0.

  group do |g|
    g.spawn { Server.serve(listener) { |request| answer(request) }; 0 }
    g.spawn do
      Signal.wait(Signal::INT, Signal::TERM)
      listener.close   # the server stops taking connections
      0
    end
  end
  puts "bye"           # the group's end waited for the requests in flight

wait parks the fiber until one of the signals it names arrives and answers which, or Cancelled if its task is. From the first wait that names a signal, that signal no longer has its default action - SIGINT and SIGTERM no longer end the process - and every arrival is kept until a wait takes it, so one that comes before the wait is not lost; a signal nobody has named keeps its default. A graceful stop is then ordinary structured concurrency: close what takes new work, let the group join what is in flight.

Underneath, on Linux and darwin, the handler does the one thing a signal handler may: it writes the signal's number to a pipe. The waiting fiber parks on the pipe's read end like any socket read, so the program's own code never runs inside a handler. On Windows the console's control handler runs on a thread of its own and posts a completion to the waiting fiber's overlapped slot: Ctrl-C and Ctrl-Break are INT, closing the console, logging off and shutting down are TERM; the other signals have no Windows event, and are refused there by name.

One fiber waits at a time - two at once are refused by the poller's own rule, one reader per descriptor.

Signal.each is a task's handler: its block runs for each arrival, in the task, until the task is cancelled - the other library's trap, owned by a group like every other task:

  g.spawn { Signal.each(Signal::HUP) { |s| reload } }

Signal.ignore sets a signal's action to nothing - a SIGPIPE ignored is a write that answers EPIPE - and Signal.reset gives it its default back. A wait or each that names it afterwards takes it again.

Signalenum

std/signaliyi doc src/std/signal.iyi
pub enum Signal : Int32
  HUP = 1
  INT = 2
  QUIT = 3
  PIPE = 13
  ALRM = 14
  TERM = 15
  WINCH = 28
  USR1 = 10
  USR2 = 12
  CHLD = 17
  # Runs the block for each arrival of one of *signals*, in this task,
  # until the task is cancelled, and answers `Cancelled` then. Arrivals
  # while the block runs are kept, each signal once.
  def self.each(*signals : Signal, &block : (Signal -> _)) : Cancelled
  # Sets each of *signals* to be ignored by the process: its arrivals are
  # dropped, and one that was pending is forgotten. Windows can ignore
  # Ctrl-C (`INT`) alone; anything else is refused there.
  def self.ignore(*signals : Signal) : Nil
  # Gives each of *signals* its default action back: INT and TERM end the
  # process again, WINCH is ignored again. Windows: `INT` alone.
  def self.reset(*signals : Signal) : Nil
  # Parks until one of *signals* arrives; answers it.
  def self.wait(*signals : Signal) : Signal | Cancelled
end