LEVIATHAN v962456e · 962456eee1

Standard Library

namespace signal

Operating-system signals delivered to the program as streams.

since 0.1.0-alpha.1linux

Overview

A signal never interrupts your code. Instead signal::on hands you an InStream<int> that receives the signal number each time the signal arrives, and you read it like any other stream: with for, subscribe or pullOrNone. Several subscribers to one signal each get every delivery. A signal that arrives while nobody is subscribed is dropped. Closing the stream ends the subscription, and when the last subscriber to a signal leaves, the signal goes back to its default behaviour. SIGKILL and SIGSTOP cannot be caught, so there are no constants for them and subscribing to them throws.

Description

A signal is the operating system's way of tapping a process on the shoulder (a terminal was resized, someone asked it to terminate). Leviathan never runs your code inside a signal handler. Instead a signal is a stream: you subscribe to it, and each delivery appears in the stream as the signal number.

signal::on(sig) returns an InStream<int> that receives one value each time the signal is delivered. The numbers are available as constants: signal::HUP (1), signal::INT (2), signal::QUIT (3), signal::USR1 (10), signal::TERM (15) and signal::WINCH (28, the terminal was resized). SIGKILL and SIGSTOP cannot be caught and are not offered.

The stream follows all the usual stream rules (see lang.streams): it can be pulled, subscribed or iterated, it has a single consumer, and it is IDisposable.

Because a subscription holds an operating-system resource, release it. Declaring it with using releases it on every way out of the block. When the last subscriber of a signal goes away, the signal returns to its default behavior and the event loop no longer waits for it.

Two subscribers, released together

console.writeln("HUP=${signal::HUP} INT=${signal::INT} QUIT=${signal::QUIT}");
console.writeln("USR1=${signal::USR1} TERM=${signal::TERM} WINCH=${signal::WINCH}");

void listen() {
    using InStream<int> first = signal::on(signal::TERM);
    using InStream<int> second = signal::on(signal::TERM);
    console.writeln("two subscribers to one signal");
}
listen();
console.writeln("both released");
HUP=1 INT=2 QUIT=3
USR1=10 TERM=15 WINCH=28
two subscribers to one signal
both released

Rules

  • Each subscription is its own stream. Two calls to signal::on for the same signal give two streams, and every delivery is pushed into both.
  • Nothing is buffered before you subscribe. A signal delivered before the first subscription is dropped.
  • Deliveries can coalesce. Several resizes in quick succession may arrive as fewer deliveries; the stream carries at least one value after a change.
  • Closing releases. close() (or leaving a using block) stops the subscription, is idempotent, and never throws. When it was the last subscriber, the signal is reset to its default action and the loop stops waiting for it.
  • A program that leaves a subscription open keeps running while the event loop waits for the signal, exactly like a pending timer. A program that subscribes and releases exits on its own.
  • In raw mode the interrupt key is not a signal. While term::enableRaw() is active, Ctrl-C does not produce signal::INT; only an external kill does.
  • None of the signal functions can run in comptime code.

Examples

A full-screen program reacts to terminal resizes. The loop runs for as long as the subscription is open:

using InStream<int> resizes = signal::on(signal::WINCH);
for (int s in resizes) {
    term::WinSize size = term::size();
    console.writeln("now ${size.cols} x ${size.rows}");
}
not run — needs a terminal

Notes

The constants are the Linux signal numbers.

Examples

void watch() {
    using InStream<int> usr1 = signal::on(signal::USR1);
    console.writeln(usr1.hasData());
}
watch();
console.writeln(signal::USR1);
console.writeln(signal::TERM);
false
10
15

Constants and globals

HUP

const int HUP = 1

The signal number of SIGHUP on Linux, 1: the controlling terminal was closed.

INT

const int INT = 2

The signal number of SIGINT on Linux, 2: an interrupt, usually from Ctrl-C.

QUIT

const int QUIT = 3

The signal number of SIGQUIT on Linux, 3: a quit request, usually from Ctrl-backslash.

TERM

const int TERM = 15

The signal number of SIGTERM on Linux, 15: a polite request to terminate.

USR1

const int USR1 = 10

The signal number of SIGUSR1 on Linux, 10: a signal reserved for the application's own use.

WINCH

const int WINCH = 28

The signal number of SIGWINCH on Linux, 28: the terminal window changed size.

Functions

on

on(int sig) -> InStream<int>

Subscribe to a signal and get a stream of its deliveries.

Each time the signal arrives, its number is pushed into the returned stream. A signal that arrived before the subscription is not replayed. Closing the stream, directly or with using, ends the subscription. While the terminal is in raw mode, signal::INT is delivered only when another process sends it, because Ctrl-C no longer generates the signal.

Parameters

sig
The signal number, such as signal::TERM.

Returns

A stream that receives sig once per delivery.

Throws

RuntimeException
when the signal cannot be watched, as for SIGKILL (9).

Examples

try {
    signal::on(9);
} catch (RuntimeException e) {
    console.writeln(e.message);
}
InStream<int> s = signal::on(signal::USR1);
console.writeln(s.pullOrNone() == None);
s.close();
signal: cannot watch signal 9
true

Shutting down cleanly on a termination request

InStream<int> requests = signal::on(signal::TERM);
requests.subscribe((n) => {
    console.writeln("received signal ${n}, shutting down");
    env::exit(0);
});
not run — waits for a signal sent from outside the program

See also: InStream

See also

  • InStream — The reading end of a stream: a queue of values of type T that something else produces.
  • IDisposable — The interface of an object that must be cleaned up when its work is finished.
  • term — Control of the terminal the program runs in: raw input mode and the window size.