silk.system.signal_watch.
Receive process signals, such as an interrupt or a terminal resize, through a descriptor that an event loop can watch.
When to use#
Use a SignalWatch when a signal must become an event of your loop instead of ending the
process, as for process.on("SIGINT") in a JavaScript runtime. Add each signal with
SignalWatch.add, register SignalWatch.descriptor with a reactor, such as
silk.io.reactor, for reads, and call SignalWatch.read when it is readable.
Details#
While a signal is in the watch, its default action does not happen: the signal only makes the
descriptor readable. SignalWatch.remove and dropping the watch give each signal back its
earlier behavior. A signal that arrives more than one time before a read is reported one time.
The watch uses signalfd on Linux and a kqueue with EVFILT_SIGNAL filters on Darwin. The
descriptor is nonblocking and close-on-exec.
Errors#
Each operation fails with SignalError: the SignalOperation and the native error code.
Gotchas#
Keep one watch for each signal in a process. Two watches of one signal take its arrivals from each other.
On Linux, the watch blocks its signals in the thread that adds them, and threads that start later inherit the block. Open the watch and add its signals on the main thread before the program starts other threads: a thread that started earlier still gets the signal with its default action.
On Darwin, the watch sets a signal whose default action ends or stops the process to be ignored while it is in the watch. A child started with
posix_spawnorexecgets that ignored setting unless the starter resets it, assilk.system.subprocessdoes.SIGKILLandSIGSTOPcannot be watched.The module is empty on targets other than Darwin on x86_64 or aarch64, and Linux with GNU libc on x86_64 or aarch64.
Examples#
Receive a signal that the process sends to itself#
import silk.core.effect { Effect }
import silk.core.result { Result }
import silk.system.signal_watch { SignalError, SignalWatch }
unsafe extern "C" fn getpid() -> i32
unsafe extern "C" fn kill(pid: i32, signal: i32) -> i32
effect fn program() -> i32 ! SignalError {
let mut watch = run SignalWatch.open()
// SIGUSR1 ends the process by default; in the watch it only makes the descriptor readable.
let user = SignalWatch.userOne()
run watch.add(user)
if unsafe kill(unsafe getpid(), user) != 0 { return 1 }
let mut received: [i32; 4] = [0; 4]
let count = run watch.read(&mut received)
if count != 1 || received[0] != user { return 1 }
// Nothing more arrived, so a read does not block and returns 0.
let more = run watch.read(&mut received)
if more != 0 { return 1 }
run watch.remove(user)
return 0
}
pub effect fn main() -> i32 {
return match run Effect.result(program()) {
Result<i32, SignalError>.Success { value } => value
Result<i32, SignalError>.Failure { .. } => 1
}
}Import as SignalWatch with import silk.system.signal_watch { SignalWatch }.
Public declarations: 3.
SignalOperation#
pub enum SignalOperationThe operation of a SignalWatch that failed.
Open#
OpenOpening the descriptor, in SignalWatch.open.
Add#
AddAdding a signal, in SignalWatch.add.
Remove#
RemoveRemoving a signal, in SignalWatch.remove.
Read#
ReadReading the signals that arrived, in SignalWatch.read.
SignalError#
pub struct SignalErrorA failed SignalWatch operation: which one, and the native error code.
Details#
code is the errno value of the failed system call, such as 22 (EINVAL) for a signal number
that cannot be watched, or 24 (EMFILE) when the process has too many open descriptors.
Field operation#
pub operation: SignalOperationThe operation that failed.
Field code#
pub code: i32The native error code.
SignalWatch#
pub struct SignalWatchA descriptor that becomes readable when one of its signals arrives.
Details#
While a signal is in the watch, its default action does not happen. The descriptor is nonblocking and close-on-exec. Dropping the watch removes each of its signals, which gives each one back its earlier behavior, and closes the descriptor.
Gotchas#
Keep one watch for each signal in a process.
On Linux, add signals on the main thread before other threads start. A thread that started earlier still gets the signal with its default action.
On Darwin, a signal whose default action ends or stops the process is set to be ignored while it is in the watch, and a child started by
execalone inherits that.
Implementation Drop for SignalWatch#
impl Drop for SignalWatchOperation drop#
fn drop(self: &mut SignalWatch) -> ()Implementation SignalWatch#
impl SignalWatchAssociated function open#
pub effect fn open() -> SignalWatch ! SignalErrorOpens a watch with no signals.
Errors#
SignalError with operation Open if the descriptor cannot be opened, for example with
code 24 (EMFILE).
Method descriptor#
pub fn descriptor(self: &Self) -> i32Returns the descriptor to register for reads. The watch keeps owning it.
Method add#
pub effect fn add(self: &mut Self, signal: i32) -> () ! SignalErrorAdds signal: from now on it makes the descriptor readable instead of taking its default
action. Adding a signal that is in the watch does nothing.
Errors#
SignalError with operation Add, with code 22 (EINVAL) for SIGKILL, SIGSTOP, or a
number that is not a signal of the system: 1 to 64 on Linux, 1 to 31 on Darwin.
Method remove#
pub effect fn remove(self: &mut Self, signal: i32) -> () ! SignalErrorRemoves signal, and gives it back its behavior from before SignalWatch.add. Removing a
signal that is not in the watch does nothing.
Details#
An arrival of signal that was not read yet is discarded.
Errors#
SignalError with operation Remove if the system refuses the change, or with code 22
(EINVAL) for a number that is not a signal of the system.
Method contains#
pub fn contains(self: &Self, signal: i32) -> boolReturns whether signal is in the watch.
Method read#
pub effect fn read(self: &mut Self, signals: &mut [i32]) -> usize ! SignalErrorWrites the signals that arrived since the last read to the start of signals, and returns
their number. It never blocks: with no arrival, it returns 0.
Details#
A signal that arrived more than one time is written one time. At most signals.length
signals are written; the others stay for the next read.
Errors#
SignalError with operation Read if the system call fails.
Associated function hangup#
pub fn hangup() -> i32Returns the number of SIGHUP, sent when the terminal closes.
Associated function interrupt#
pub fn interrupt() -> i32Returns the number of SIGINT, sent by Control-C.
Associated function quit#
pub fn quit() -> i32Returns the number of SIGQUIT, sent by Control-Backslash.
Associated function terminate#
pub fn terminate() -> i32Returns the number of SIGTERM, the polite request to end.
Associated function userOne#
pub fn userOne() -> i32Returns the number of SIGUSR1.
Associated function userTwo#
pub fn userTwo() -> i32Returns the number of SIGUSR2.
Associated function child#
pub fn child() -> i32Returns the number of SIGCHLD, sent when a child process ends or stops.
Associated function windowChange#
pub fn windowChange() -> i32Returns the number of SIGWINCH, sent when the terminal changes its size.
Associated function brokenPipe#
pub fn brokenPipe() -> i32Returns the number of SIGPIPE, sent by a write to a pipe or socket with no reader.
Category: System.
Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, x86_64-unknown-linux-gnu.