Coder Social home page Coder Social logo

chan-signal's Introduction

This crate has reached its end-of-life and is now deprecated.

The intended successor of the chan crate is the crossbeam-channel crate. Its API is strikingly similar, but comes with a much better select! macro, better performance, a better test suite and an all-around better implementation.

If you were previously using this crate for signal handling, then it is simple to reproduce a similar API with crossbeam-channel and the signal-hook crate. For example, here's chan-signal's notify function:

extern crate crossbeam_channel as channel;
extern crate signal_hook;

fn notify(signals: &[c_int]) -> Result<channel::Receiver<c_int>> {
    let (s, r) = channel::bounded(100);
    let signals = signal_hook::iterator::Signals::new(signals)?;
    thread::spawn(move || {
        for signal in signals.forever() {
            if s.send(signal).is_err() {
                break;
            }
        }
    });
    Ok(r)
}

This crate may continue to receive bug fixes, but should otherwise be considered dead.

chan-signal

This crate provies experimental support for responding to OS signals using channels. Currently, this only works on Unix based systems, but I'd appreciate help adding Windows support.

Build status

Dual-licensed under MIT or the UNLICENSE.

Documentation

https://docs.rs/chan-signal

Example

Use is really simple. Just ask the chan_signal crate to create a channel subscribed to a set of signals. When a signal is sent to the process it will be delivered to the channel.

use chan_signal::Signal;

let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]);

// Blocks until this process is sent an INT or TERM signal.
// Since the channel is never closed, we can unwrap the received value.
signal.recv().unwrap();

A realer example

When combined with chan_select! from the chan crate, one can easily integrate signals with the rest of your program. For example, consider a main function that waits for either normal completion of work (which is done in a separate thread) or for a signal to be delivered:

#[macro_use]
extern crate chan;
extern crate chan_signal;

use std::thread;
use std::time::Duration;

use chan_signal::Signal;

fn main() {
    // Signal gets a value when the OS sent a INT or TERM signal.
    let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]);
    // When our work is complete, send a sentinel value on `sdone`.
    let (sdone, rdone) = chan::sync(0);
    // Run work.
    thread::spawn(move || run(sdone));

    // Wait for a signal or for work to be done.
    chan_select! {
        signal.recv() -> signal => {
            println!("received signal: {:?}", signal)
        },
        rdone.recv() => {
            println!("Program completed normally.");
        }
    }
}

fn run(_sdone: chan::Sender<()>) {
    println!("Running work for 5 seconds.");
    println!("Can you send a signal quickly enough?");
    // Do some work.
    thread::sleep(Duration::from_secs(5));

    // _sdone gets dropped which closes the channel and causes `rdone`
    // to unblock.
}

This is much easier than registering a signal handler because:

  1. Signal handlers run asynchronously.
  2. The code you're permitted to execute in a signal handler is extremely constrained (e.g., no allocation), so it is difficult to integrate it with the rest of your program.

Using channels, you can invent whatever flow you like and handle OS signals just like anything else.

How it works

TL;DR - Spawn a thread, block on sigwait, deliver signals, repeat.

It's explained a bit more in the docs.

chan-signal's People

Contributors

bnewbold avatar burntsushi avatar ftilde avatar jwilm avatar l0kod avatar little-dude avatar mbrubeck avatar milkey-mouse avatar oconnor663 avatar sector-f avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

chan-signal's Issues

Mac doesn't seem to close properly on any signal

Example code:

extern crate chan;
extern crate chan_signal;

use chan_signal::{Signal, notify};

fn main() {
    let signal = notify(&[Signal::INT]);
    println!("Signal: {:?}", signal.recv());
}

Here's what I've got from ps on 10.9.5:

fauna:~ edwinamsler$ ps
  PID TTY           TIME CMD
  301 ttys000    0:00.19 -bash
 1560 ttys000    0:00.00 target/debug/signals
 1575 ttys000    0:00.00 target/debug/signals
 1601 ttys000    0:00.00 target/debug/signals
 1629 ttys000    0:00.00 target/debug/signals
 1645 ttys000    0:00.00 target/debug/signals
 1650 ttys000    0:00.00 target/debug/signals
 1678 ttys000    0:00.00 target/debug/signals
 1691 ttys000    0:00.00 target/debug/signals
 1696 ttys000    0:00.00 target/debug/signals

API for waiting on any signal

It would be nice if there was an API for waiting on any signal (or just some straight forward way of getting a complete slice of the signal enum without manually typing them all out).

I've got a small application that's already got a signal mask set in libc land, and just need to receive any of the signals it can get.

Windows support

Windows does not have the signal handling mechanism of *nix. Instead it has two things:

  1. SetConsoleCtrlHandler which catches things like ctrl + C in the command line.
  2. Registering an SEH exception handler to catch certain kinds of SEH exceptions such as segfaults, illegal instructions, arithmetic interrupts, stuff like that. Could probably be done with AddVectoredExceptionHandler.

Can't catch Ignored signals

sigwait can't catch ignored signals at least in macOS (e.g., SIGIO or SIGWINCH).
man page of sigwait notes:

Processes which call sigwait() on ignored signals will wait indefinitely. Ignored signals are dropped immediately by the system, before delivery to a waiting process.

chan-signal mechanism is spawning watcher thread, wait with sigwait, and send Signal to chan.
However, sigwait ignore SIGWINCH, and it will never be sent to chan.

I'm using macOS, and I'm not sure this is also problem in linux.

The easiest way to resolve this is set the empty signal handler for ignored signals.
But this might cause other problems.
Is there any other way to resolve this?

Strange (?) interaction with SIGINT, cargo run, and stdin

Not sure this is a chan-signal issue or a cargo/std::process issue.

Given the following code:

extern crate chan_signal;
use std::io::{stdin, stdout};
use std::io::Write;
use std::thread;
use std::sync::atomic::*;

pub static INTR: AtomicBool = ATOMIC_BOOL_INIT;

fn main() {
    let hchan = chan_signal::notify(&[chan_signal::Signal::INT]);
    thread::spawn(move || {
        if let Some(_) = hchan.recv() {
            INTR.store(true, Ordering::Relaxed);
        }
    });
    loop {
        if INTR.load(Ordering::Relaxed) {
            break;
        }
    }
    write!(stdout(), ">>> ");
    stdout().flush();
    let mut line = String::new();
    let input = stdin().read_line(&mut line);
    println!("Input: {:?} Result: {:?}", line, input);
}

it works fine when running the compiled binary on its own: loop happens, press Ctrl-C, prompt appears, line is read from stdin. When run with cargo run, reading the line fails with EIO.

Alternative APIs

The chan-signal crate is currently tied to the chan crate. If I want to deliver signals to, for example, a mio event loop, I need to write a proxy that first receives on the chan receiver and then forwards to the event loop. This is just one case, I can imagine some folks might want to use mpsc channels or some other notification mechanism.

I propose that the notify API accept a generic Notify (name probably needs bikeshedding) parameter. That might look something like this:

/// Thing that can receive signal notifications
pub trait Notify {
    /// Called when a signal arrives
    fn notify(Signal);
}

and the notify function might look like this:

pub fn notify<N: Notify>(notifier: N, signals: &[Signal]) { /* ... */ }

A Notify based on chan::Sender could be added trivially with no overhead compared with the current implementation.

pub struct ChanNotify(chan::Sender<Signal>);

impl Notify for ChanNotify {
    #[inline]
    fn notify(signal: Signal) {
        self.0.send(signal);
    }
}

An implementation based on mpsc::Sender, mio::Sender, or whatever else a consumer might want could be added easily in their application.

I realize that this crate is chan-signal, so it's kind of tied to the chan crate. If these changes make sense, maybe it would be worth refactoring the core signal functionality into a generalized crate like signal-base or something with a much better name ;).

Using chan_signal::notify masks all signals

Consider this program:

#[macro_use]
extern crate chan;
extern crate chan_signal;

use chan::Receiver;
use chan_signal::Signal;

fn signal_received(rec: &Receiver<Signal>) -> bool
{
  chan_select! {
    default         => { return false },
    rec.recv() -> _ => { return true  }
  }
}

fn main() {
    // Signal gets a value when the OS sent a INT or TERM signal.
    let signal = chan_signal::notify(&[Signal::ILL]);

    // Wait for a signal or for work to be done.
    loop {
      let ret = signal_received(&signal);
      if ret {
        println!("illegal instruction");
        break
      }
    }
}

When I run this it only responds to SIGILL and SIGKILL. It effectively ignores all other signals. This came as a surprise since only SIGILL is given to notify. I would have expected the other signals to work as normal.

Using longer constant names?

INT and TERM are ambiguous. They have their own meanings. So you may want to rename them to INTERRUPT and TERMINATE, because we do have the possibility to spell them in full in the 21th century. If you look to the Apple API or Java API, this is really common and helps newcomers to better understand the code.

Proceed with other constants the same way like SIGALRM.

Consider to do a breaking change in order to have just one right way.

Allow Setting signals to default handling as well as ignoring them

The way i imagine this is somewhat hard due to some design choices, and in most cases this would probably be a breaking change. My proposel would be the following

// Handle SIGTERM. channel is the writing end of the channel
chan_signal::handle([Signal::TERM], Handle(channel));
// We don't care about SIGINT
chan_signal::handle([Signal::INT], Ignore);
// Someone else has messed with SIGSEGV - lets teach them a lesson
chan_signal::handle([Signal::SEGV], Default);

Implementing this would also fix #2

Drop Trait blocked for variables given to the worker thread

I have a variable that I want to be properly destroyed after a SIGINT. Therefor I have implemented the Drop trait for the type of the variable, initialize it before starting the runner thread and give the thread a reference.
Unfortunately the drop() function will never be called.
Here is an example to illustrate the problem:

#[macro_use]
extern crate chan;
extern crate chan_signal;

use std::thread;
use chan_signal::Signal;

struct Thing{
    a_thing: u8
}

impl Drop for Thing {
    fn drop(&mut self){
        println!("Thing #{:?} dropped", self.a_thing);
    }
}
fn main() {
    let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]);
    let (sdone, rdone) = chan::sync(0);

    {
        let thing1 = Thing{a_thing: 1};
        let thing2 = Thing{a_thing: 2};
        thread::spawn(move || run( thing2, sdone));
        chan_select! {
            signal.recv() -> signal => {
                println!("received signal: {:?}", signal)
            },
            rdone.recv() => {
                println!("Program completed normally.");
            }
        }
    }
}

fn run(thing:  Thing, _sdone: chan::Sender<()>) {
    let thing3 = Thing{a_thing: 3};
    loop {
    }
}

running this example will output:

received signal: Some(INT)
Thing #1 dropped

Expected would be:

received signal: Some(INT)
Thing #1 dropped
Thing #2 dropped

I would expect thing3.drop() not to be called, since its thread is terminated violently.

My rustc is at 1.19.0 and I tested the code on Kubuntu 16.04

I assume rust can't call the destructor safely because the inner state of the object might be inconsistent (e.g. maybe the runner thread already started to call the destructor and didn't finished entirely). So I don't assume that this issue will be fixed anytime soon, but I thought it might be of interest.

doesn't seem to work with `fork()`

I'm trying to mix this chan module together with libc::fork() but somehow it doesn't work.
This is probably due to some difference between thread::spawn() and fork() but I just don't understand why it should not work.

Here's the example from the README adapted to fork(). Interestingly the INT signal works but the TERM not even though sdone is really moved into run

(this issue moved from here)

Please export Signal::as_sig

Enumerable values are nice, but hard to use when I come to kill a child process with the trapped signal. Is there any chance that Signal::as_sig to be exported or am I missing something? Great library, thanks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.