core/io/signal
skip to the library navigationShared: available on every enabled target.
Signals as events: the concept a provider that takes turns supplies when it delivers the system's signals to a fibre that waits for them.
| Call | What it does |
|---|---|
signal.watch(host, which) | Take a signal from the system's default disposition and deliver it as an event from then on. |
signal.next_signal(host) | Wait, taking turns, for the next arrival of a watched signal: a signal.event. |
signal.stop_watching(host) | Give every watched signal back to its default disposition, and wake every waiting fibre, whose wait fails io_failed. |
A signal is named portably by a signal.kind, and an arrival says which one came and the number the system gave it:
| Kind | Signal |
|---|---|
signal.interrupt | SIGINT, the terminal's Ctrl-C |
signal.terminate | SIGTERM, a request to end |
signal.hangup | SIGHUP, the terminal gone, or a daemon's cue to reload |
signal.child | SIGCHLD, a child ended, stopped or went on |
signal.user1, signal.user2 | SIGUSR1 and SIGUSR2 |
- A signal never runs a handler of Landin code. The provider reads it as it reads any other event, through signalfd on Linux and kqueue's signal filter on Darwin and FreeBSD, and wakes the fibre that waits.
signalsrefinesfibre.turns: only a provider whose fibres take turns supplies it, so a fibre waiting for a signal lets the others run.hosted/eventssupplies it; overhosted/blockinga program cannot ask for it, and a signal there keeps the system's default disposition: SIGTERM ends the process.- Each arrival is read once, by one waiter. Two arrivals of one signal before it is read may be one event, as the systems keep them.
- A fibre waiting for a signal is stopped as any waiting fibre is, by failing its wait:
stop_watchingdoes that.
The fake
signals_over makes a fake.signals over the caller's signals_space and a fibre.turns it waits on, whose start, finished, wait and yield it passes on, so it is a provider of turns as the concept asks. A test raises a signal into it with fake.raise(host, which), and the fibre waiting in next_signal receives it after its next turn.
- A raised signal nobody watches is counted in
ignoredand dropped, where a system would act on it. - A signal raised again while it waits to be read is one event.
- The fake numbers each signal as Linux does on x86-64 and arm64.
raised,deliveredandturns, the turns its waits took, count the rest.
The fake runs on Cortex-M0, over a round of turns.
Items
Concepts 1
Functions 3
Also on this page
public signals: type = concept (provider: type) is fibre.turns
Deliver which as an event from now on.
watch: (self: provider, which: kind) -> none ! io.io_failed
Wait, taking turns, for the next arrival of a watched signal.
next_signal: (self: provider) -> (received: event) ! io.io_failed
Give every watched signal back to its default disposition, and wake every waiter, failing its call.
stop_watching: (self: provider) -> none ! io.io_failed
end signals
A provider that delivers signals to a fibre as events (D449). Only a provider of fibre.turns supplies it (D477): a fibre waiting for a signal lets the provider's other fibres run, where over a provider that runs each task to its end it would hold the program before it served anything. A program that waits for a signal asks for signals, and over the blocking host it cannot be written, so a signal there keeps the system's default disposition: SIGTERM ends the process.
watch takes a signal from the system's default disposition and delivers it as an event from then on. next_signal waits as a receive does, taking turns, for the next arrival of a watched signal; each arrival is read once, by one waiter, and two arrivals of one signal before it is read may be one event, as the systems' own queues keep them. stop_watching gives every watched signal back to its default disposition and wakes every fibre waiting in next_signal, whose call fails io_failed, which is how a program stops the fibre that waits (D449). next_signal is the only entry that may switch.
public event: type = struct
which: kind
number: u32
end event
One arrival of a watched signal: its portable name and the number the system gave it, as a log or kill would show it.
The signals a provider of signals delivers, by portable names: each system numbers them its own way, and a program that names one here means the same signal on every system.
public child: atom
A child process ended, stopped or went on, SIGCHLD.
public hangup: atom
The controlling terminal has gone, SIGHUP; a daemon, which has none, commonly takes it to read its configuration again.
public interrupt: atom
The terminal's interrupt, SIGINT: someone pressed Ctrl-C.
public terminate: atom
A request to end, SIGTERM: what a service manager sends to stop a server, and what kill sends when it is not told otherwise.
public user1: atom
The first signal left to programs, SIGUSR1.
public user2: atom
The second signal left to programs, SIGUSR2.
public next_signal: (provider: type is signals, host: provider)
-> (received: event) ! io.io_failed
Wait, taking turns, for the next arrival of a watched signal.
public stop_watching: (provider: type is signals, host: provider)
-> none ! io.io_failed
Stop delivering signals as events, and wake every fibre waiting for one, whose call fails io_failed.
public watch: (provider: type is signals, host: provider, which: kind)
-> none ! io.io_failed
Deliver which as an event through signals evidence.
Example program
This complete program is one of the compiler's runtime tests. View source.
The program, 110 lines
-- D449, D477: core/io/fake's signals over a round of turns, within a -- 32 KiB firmware image. A fibre written against `any signal.signals` -- watches SIGUSR1 and SIGTERM and waits for each in turn; another fibre -- takes a turn and then raises SIGUSR1, SIGHUP, which nobody watches and -- is dropped, and SIGTERM twice, the second while the first waits to be -- read and so one event with it. The waiter hears SIGUSR1 and then -- SIGTERM, each with the number the fake gives it. Last, a fibre -- waiting for a signal is woken by `stop_watching`, its wait failing -- `io_failed`. import core/failure import core/fibre import core/fibre/stacks import core/io import core/io/fake import core/io/signal -- What the waiter heard: each signal's number, in order, and `s` when a -- wait failed because watching stopped. mut heard: [8]u32 = zeroed mut count: usize = 0 note: (number: u32) -> none = heard[count] = number inc count end note -- Every task's state, one type so the group's `admit` is one routine in -- the image: the source as any provider of signals, and as the fake a -- test raises signals into. waiting: type = struct signals: any signal.signals source: fake.signals end waiting -- Hear signals until SIGTERM. Nothing in it knows the source is a fake. until_terminate: (inout state: waiting) -> none ! failure.reason = try state.signals.watch(signal.user1) try state.signals.watch(signal.terminate) loop do received: signal.event = try state.signals.next_signal() note(received.number) break when received.which == signal.terminate end loop end until_terminate -- Take a turn, so the waiter waits first, and raise four signals. raise_four: (inout state: waiting) -> none ! failure.reason = fibre.yield(state.source) fake.raise(state.source, signal.user1) fake.raise(state.source, signal.hangup) fake.raise(state.source, signal.terminate) fake.raise(state.source, signal.terminate) end raise_four -- Wait for a signal that never comes. wait_in_vain: (inout state: waiting) -> none ! failure.reason = try state.signals.watch(signal.hangup) received: signal.event = state.signals.next_signal() else (problem) if problem == io.io_failed then note(0) end if return end _ = received end wait_in_vain -- Module storage, so the stacks are no frame's: Cortex-M0's reset stack -- is 4 KiB, and its 12 KiB of RAM below that hold two stacks of 4 KiB. fixed if compiler.word_size == 32 then mut backing: [2 * 4096]u8 = zeroed else mut backing: [2 * 8192]u8 = zeroed end if run: (signals: fake.signals, counts: ptr fake.signals_space) -> none ! failure.reason = runs: any fibre.turns = any(addr signals) mut tasks := try fibre.turns_over(runs, backing, 2) defer fibre.join(tasks) else none parts: waiting = (signals: any(addr signals), source: signals) try fibre.admit(tasks, until_terminate, parts) try fibre.admit(tasks, raise_four, parts) try fibre.join(tasks) fail io.io_failed when counts.val.raised <> 4 or counts.val.ignored <> 1 or counts.val.delivered <> 2 or counts.val.turns == 0 try fibre.admit(tasks, wait_in_vain, parts) fibre.yield(signals) try signal.stop_watching(signals) try fibre.join(tasks) end run public main: () -> (code: i32) = code = 1 mut state: stacks.round_space = zeroed mut entries: [3]usize = zeroed idler: stacks.spin = zeroed round := stacks.round_over(state, entries, any(addr idler)) else (problem) _ = problem return end mut space: fake.signals_space = zeroed signals := fake.signals_over(space, any(addr round)) run(signals, addr space) else (problem) _ = problem return end code = 2 return when count <> 3 or heard[0] <> 10 or heard[1] <> 15 or heard[2] <> 0 code = 42 end main