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_spawn or exec gets that ignored setting unless the starter resets it, as silk.system.subprocess does.

  • SIGKILL and SIGSTOP cannot 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#

silk
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#

silk
pub enum SignalOperation

The operation of a SignalWatch that failed.

Open#

silk
Open

Opening the descriptor, in SignalWatch.open.

Add#

silk
Add

Adding a signal, in SignalWatch.add.

Remove#

silk
Remove

Removing a signal, in SignalWatch.remove.

Read#

silk
Read

Reading the signals that arrived, in SignalWatch.read.

SignalError#

silk
pub struct SignalError

A 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#

silk
pub operation: SignalOperation

The operation that failed.

Field code#

silk
pub code: i32

The native error code.

SignalWatch#

silk
pub struct SignalWatch

A 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 exec alone inherits that.

Implementation Drop for SignalWatch#

silk
impl Drop for SignalWatch

Operation drop#

silk
fn drop(self: &mut SignalWatch) -> ()

Implementation SignalWatch#

silk
impl SignalWatch

Associated function open#

silk
pub effect fn open() -> SignalWatch ! SignalError

Opens a watch with no signals.

Errors#

SignalError with operation Open if the descriptor cannot be opened, for example with code 24 (EMFILE).

Method descriptor#

silk
pub fn descriptor(self: &Self) -> i32

Returns the descriptor to register for reads. The watch keeps owning it.

Method add#

silk
pub effect fn add(self: &mut Self, signal: i32) -> () ! SignalError

Adds 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#

silk
pub effect fn remove(self: &mut Self, signal: i32) -> () ! SignalError

Removes 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#

silk
pub fn contains(self: &Self, signal: i32) -> bool

Returns whether signal is in the watch.

Method read#

silk
pub effect fn read(self: &mut Self, signals: &mut [i32]) -> usize ! SignalError

Writes 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#

silk
pub fn hangup() -> i32

Returns the number of SIGHUP, sent when the terminal closes.

Associated function interrupt#

silk
pub fn interrupt() -> i32

Returns the number of SIGINT, sent by Control-C.

Associated function quit#

silk
pub fn quit() -> i32

Returns the number of SIGQUIT, sent by Control-Backslash.

Associated function terminate#

silk
pub fn terminate() -> i32

Returns the number of SIGTERM, the polite request to end.

Associated function userOne#

silk
pub fn userOne() -> i32

Returns the number of SIGUSR1.

Associated function userTwo#

silk
pub fn userTwo() -> i32

Returns the number of SIGUSR2.

Associated function child#

silk
pub fn child() -> i32

Returns the number of SIGCHLD, sent when a child process ends or stops.

Associated function windowChange#

silk
pub fn windowChange() -> i32

Returns the number of SIGWINCH, sent when the terminal changes its size.

Associated function brokenPipe#

silk
pub fn brokenPipe() -> i32

Returns 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.

View the source of this page