├── .gitignore ├── .travis.yml ├── COPYING ├── Cargo.toml ├── LICENSE-MIT ├── Makefile ├── README.md ├── UNLICENSE ├── examples ├── read_names.rs ├── select.rs ├── simple.rs ├── sleep.rs ├── test_block_specific.rs ├── test_many.rs ├── test_many_to_one.rs ├── test_one.rs ├── test_one_not_other.rs ├── test_sleep.rs └── test_usr1.rs ├── run-example-tests └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | doc 3 | tags 4 | examples/ss10pusa.csv 5 | build 6 | target 7 | Cargo.lock 8 | scratch* 9 | bench_large/huge 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - 1.16.0 4 | - stable 5 | - beta 6 | - nightly 7 | script: 8 | - cargo build --verbose 9 | - cargo doc 10 | - cargo test --verbose 11 | - ./run-example-tests 12 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | This project is dual-licensed under the Unlicense and MIT licenses. 2 | 3 | You may use this code under the terms of either license. 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chan-signal" 3 | version = "0.3.3" #:version 4 | authors = ["Andrew Gallant "] 5 | description = "DEPRECATED. Use crossbeam-channel and signal-hook instead." 6 | documentation = "https://docs.rs/chan-signal" 7 | homepage = "https://github.com/BurntSushi/chan-signal" 8 | repository = "https://github.com/BurntSushi/chan-signal" 9 | readme = "README.md" 10 | keywords = ["os", "signal", "channel", "select"] 11 | license = "Unlicense/MIT" 12 | 13 | [dependencies] 14 | bit-set = "0.4" 15 | chan = "0.1" 16 | lazy_static = "0.2" 17 | libc = "0.2" 18 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Andrew Gallant 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | echo Nothing to do... 3 | 4 | ctags: 5 | ctags --recurse --options=ctags.rust --languages=Rust 6 | 7 | docs: 8 | cargo doc 9 | in-dir ./target/doc fix-perms 10 | rscp ./target/doc/* gopher:~/www/burntsushi.net/rustdoc/ 11 | 12 | push: 13 | git push origin master 14 | git push github master 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## **This crate has reached its end-of-life and is now deprecated.** 2 | 3 | The intended successor of the `chan` crate is the 4 | [`crossbeam-channel`](https://github.com/crossbeam-rs/crossbeam/tree/master/crossbeam-channel) 5 | crate. Its API is strikingly similar, but comes with a much better `select!` 6 | macro, better performance, a better test suite and an all-around better 7 | implementation. 8 | 9 | If you were previously using this crate for signal handling, then it is 10 | simple to reproduce a similar API with `crossbeam-channel` and the 11 | [`signal-hook`](https://github.com/vorner/signal-hook) 12 | crate. For example, here's `chan-signal`'s `notify` function: 13 | 14 | ```rust 15 | extern crate crossbeam_channel as channel; 16 | extern crate signal_hook; 17 | 18 | fn notify(signals: &[c_int]) -> Result> { 19 | let (s, r) = channel::bounded(100); 20 | let signals = signal_hook::iterator::Signals::new(signals)?; 21 | thread::spawn(move || { 22 | for signal in signals.forever() { 23 | if s.send(signal).is_err() { 24 | break; 25 | } 26 | } 27 | }); 28 | Ok(r) 29 | } 30 | ``` 31 | 32 | This crate may continue to receive bug fixes, but should otherwise be 33 | considered dead. 34 | 35 | 36 | chan-signal 37 | =========== 38 | 39 | This crate provies experimental support for responding to OS signals using 40 | [channels](https://github.com/BurntSushi/chan). Currently, this only works on 41 | Unix based systems, but I'd appreciate help adding Windows support. 42 | 43 | [![Build status](https://api.travis-ci.org/BurntSushi/chan-signal.png)](https://travis-ci.org/BurntSushi/chan-signal) 44 | [![](http://meritbadge.herokuapp.com/chan-signal)](https://crates.io/crates/chan-signal) 45 | 46 | Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org). 47 | 48 | 49 | ### Documentation 50 | 51 | https://docs.rs/chan-signal 52 | 53 | 54 | ### Example 55 | 56 | Use is really simple. Just ask the `chan_signal` crate to create a channel 57 | subscribed to a set of signals. When a signal is sent to the process it will 58 | be delivered to the channel. 59 | 60 | ```rust 61 | use chan_signal::Signal; 62 | 63 | let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]); 64 | 65 | // Blocks until this process is sent an INT or TERM signal. 66 | // Since the channel is never closed, we can unwrap the received value. 67 | signal.recv().unwrap(); 68 | ``` 69 | 70 | ### A realer example 71 | 72 | When combined with `chan_select!` from the `chan` crate, one can easily 73 | integrate signals with the rest of your program. For example, consider a 74 | main function that waits for either normal completion of work (which is done 75 | in a separate thread) or for a signal to be delivered: 76 | 77 | ```rust 78 | #[macro_use] 79 | extern crate chan; 80 | extern crate chan_signal; 81 | 82 | use std::thread; 83 | use std::time::Duration; 84 | 85 | use chan_signal::Signal; 86 | 87 | fn main() { 88 | // Signal gets a value when the OS sent a INT or TERM signal. 89 | let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]); 90 | // When our work is complete, send a sentinel value on `sdone`. 91 | let (sdone, rdone) = chan::sync(0); 92 | // Run work. 93 | thread::spawn(move || run(sdone)); 94 | 95 | // Wait for a signal or for work to be done. 96 | chan_select! { 97 | signal.recv() -> signal => { 98 | println!("received signal: {:?}", signal) 99 | }, 100 | rdone.recv() => { 101 | println!("Program completed normally."); 102 | } 103 | } 104 | } 105 | 106 | fn run(_sdone: chan::Sender<()>) { 107 | println!("Running work for 5 seconds."); 108 | println!("Can you send a signal quickly enough?"); 109 | // Do some work. 110 | thread::sleep(Duration::from_secs(5)); 111 | 112 | // _sdone gets dropped which closes the channel and causes `rdone` 113 | // to unblock. 114 | } 115 | ``` 116 | 117 | This is much easier than registering a signal handler because: 118 | 119 | 1. Signal handlers run asynchronously. 120 | 2. The code you're permitted to execute in a signal handler is extremely 121 | constrained (e.g., no allocation), so it is difficult to integrate 122 | it with the rest of your program. 123 | 124 | Using channels, you can invent whatever flow you like and handle OS signals 125 | just like anything else. 126 | 127 | 128 | ### How it works 129 | 130 | TL;DR - Spawn a thread, block on `sigwait`, deliver signals, repeat. 131 | 132 | It's 133 | [explained a bit more in the docs](https://docs.rs/chan-signal/#how-it-works). 134 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /examples/read_names.rs: -------------------------------------------------------------------------------- 1 | // This example demonstrates how to do something almost useful with OS signals. 2 | // This program asks the user to inputs names. It then outputs these names 3 | // once the user inputs EOF. The catch is that it will *also* output these 4 | // names if the user sends the process SIGINT or SIGTERM. 5 | // 6 | // This is hard or impossible to do with regular asynchronous signal handlers. 7 | // But with a channel based API, it's easy to integrate with the rest of 8 | // your control flow. 9 | 10 | #![allow(deprecated)] // for connect=>join in 1.3 11 | 12 | #[macro_use] extern crate chan; 13 | extern crate chan_signal; 14 | 15 | use std::error::Error; 16 | use std::io::{self, BufRead, Write}; 17 | use std::process; 18 | use std::thread; 19 | 20 | use chan_signal::{Signal, notify}; 21 | 22 | fn main() { 23 | // It is imperative that we start listening for signals as soon as 24 | // possible. In particular, if `notify` is called after another thread 25 | // has spawned, then signal masking won't be applied to it and signal 26 | // handling won't work. 27 | // 28 | // See "Signal mask and pending signals" section of signal(7). 29 | let signal = notify(&[Signal::INT, Signal::TERM]); 30 | match run(signal) { 31 | Ok(mut names) => { 32 | names.sort(); 33 | println!("You entered {} names: {}", 34 | names.len(), names.connect(", ")); 35 | } 36 | Err(err) => { 37 | writeln!(&mut io::stderr(), "{}", err).unwrap(); 38 | process::exit(1); 39 | } 40 | } 41 | } 42 | 43 | type Result = ::std::result::Result>; 44 | 45 | fn run(signal: chan::Receiver) -> Result> { 46 | let lines = read_stdin_lines(); 47 | let mut names = vec![]; 48 | println!("Please enter some names, each on a new line:"); 49 | loop { 50 | chan_select! { 51 | lines.recv() -> line => { 52 | match line { 53 | // If the channel closed (i.e., reads EOF), then quit 54 | // the loop and print what we've got. 55 | // 56 | // The rightward drift is painful... 57 | None => break, 58 | Some(line) => names.push(try!(line).trim().to_owned()), 59 | } 60 | }, 61 | // If we get SIGINT or SIGTERM, just stop the loop and print 62 | // what we've got so far. 63 | signal.recv() => break, 64 | } 65 | } 66 | Ok(names) 67 | } 68 | 69 | // Spawns a new thread to read lines and sends the result on the returned 70 | // channel. 71 | fn read_stdin_lines() -> chan::Receiver> { 72 | let (s, r) = chan::sync(0); 73 | let stdin = io::stdin(); 74 | thread::spawn(move || { 75 | let stdin = stdin.lock(); 76 | for line in stdin.lines() { 77 | s.send(line); 78 | } 79 | }); 80 | r 81 | } 82 | -------------------------------------------------------------------------------- /examples/select.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate chan; 3 | extern crate chan_signal; 4 | 5 | use std::thread; 6 | use std::time::Duration; 7 | 8 | use chan_signal::Signal; 9 | 10 | fn main() { 11 | // Signal gets a value when the OS sent a INT or TERM signal. 12 | let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]); 13 | // When our work is complete, send a sentinel value on `sdone`. 14 | let (sdone, rdone) = chan::sync(0); 15 | // Run work. 16 | thread::spawn(move || run(sdone)); 17 | 18 | // Wait for a signal or for work to be done. 19 | chan_select! { 20 | signal.recv() -> signal => { 21 | println!("received signal: {:?}", signal) 22 | }, 23 | rdone.recv() => { 24 | println!("Program completed normally."); 25 | } 26 | } 27 | } 28 | 29 | fn run(_sdone: chan::Sender<()>) { 30 | println!("Running work for 5 seconds."); 31 | println!("Can you send a signal quickly enough?"); 32 | // Do some work. 33 | thread::sleep(Duration::from_secs(5)); 34 | } 35 | -------------------------------------------------------------------------------- /examples/simple.rs: -------------------------------------------------------------------------------- 1 | // This is a minimal example that demonstrates how to listen and respond to 2 | // OS signals. Namely, it requests to be notified about a SIGINT (usually 3 | // ^C in your terminal) and blocks until it gets one. 4 | 5 | #[macro_use] extern crate chan; 6 | extern crate chan_signal; 7 | 8 | use chan_signal::{Signal, notify}; 9 | 10 | fn main() { 11 | let signal = notify(&[Signal::INT]); 12 | println!("Send a INT signal my way!"); 13 | // block until we get a signal 14 | assert_eq!(signal.recv(), Some(Signal::INT)); 15 | println!("Thanks :]"); 16 | } 17 | -------------------------------------------------------------------------------- /examples/sleep.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] extern crate chan; 2 | extern crate chan_signal; 3 | 4 | use std::thread; 5 | use std::time::Duration; 6 | 7 | use chan_signal::{Signal, notify}; 8 | 9 | fn main() { 10 | let signal = notify(&[Signal::INT]); 11 | println!("Send a INT signal my way!"); 12 | thread::spawn(move || thread::sleep(Duration::from_secs(10))); 13 | // block until we get a signal 14 | assert_eq!(signal.recv(), Some(Signal::INT)); 15 | println!("Thanks :]"); 16 | } 17 | -------------------------------------------------------------------------------- /examples/test_block_specific.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate chan; 3 | extern crate chan_signal; 4 | 5 | use chan_signal::{Signal, kill_this}; 6 | 7 | fn main() { 8 | let r_usr1 = chan_signal::notify(&[Signal::USR1, Signal::ALRM]); 9 | kill_this(Signal::USR1); 10 | kill_this(Signal::ALRM); 11 | assert_eq!(r_usr1.recv(), Some(Signal::USR1)); 12 | assert_eq!(r_usr1.recv(), Some(Signal::ALRM)); 13 | 14 | let (s, r_usr2) = chan::sync(1); 15 | chan_signal::notify_on(&s, Signal::USR2); 16 | kill_this(Signal::USR2); 17 | assert_eq!(r_usr2.recv(), Some(Signal::USR2)); 18 | 19 | // The following will terminate the process, as it is NOT blocked 20 | // by the main thread. 21 | kill_this(Signal::TERM); 22 | unreachable!(); 23 | } 24 | -------------------------------------------------------------------------------- /examples/test_many.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate chan; 3 | extern crate chan_signal; 4 | 5 | use chan_signal::{Signal, kill_this}; 6 | 7 | fn main() { 8 | let (s1, r1) = chan::sync(1); 9 | let (s2, r2) = chan::sync(1); 10 | chan_signal::notify_on(&s1, Signal::HUP); 11 | chan_signal::notify_on(&s2, Signal::TERM); 12 | kill_this(Signal::HUP); 13 | assert_eq!(r1.recv(), Some(Signal::HUP)); 14 | kill_this(Signal::TERM); 15 | assert_eq!(r2.recv(), Some(Signal::TERM)); 16 | } 17 | -------------------------------------------------------------------------------- /examples/test_many_to_one.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate chan; 3 | extern crate chan_signal; 4 | 5 | use chan_signal::{Signal, kill_this}; 6 | 7 | fn main() { 8 | let (s1, r1) = chan::sync(1); 9 | let (s2, r2) = chan::sync(1); 10 | chan_signal::notify_on(&s1, Signal::HUP); 11 | chan_signal::notify_on(&s2, Signal::HUP); 12 | kill_this(Signal::HUP); 13 | assert_eq!(r1.recv(), Some(Signal::HUP)); 14 | assert_eq!(r2.recv(), Some(Signal::HUP)); 15 | } 16 | -------------------------------------------------------------------------------- /examples/test_one.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate chan; 3 | extern crate chan_signal; 4 | 5 | use chan_signal::{Signal, kill_this}; 6 | 7 | fn main() { 8 | let (s, r) = chan::sync(1); 9 | chan_signal::notify_on(&s, Signal::HUP); 10 | kill_this(Signal::HUP); 11 | assert_eq!(r.recv(), Some(Signal::HUP)); 12 | } 13 | -------------------------------------------------------------------------------- /examples/test_one_not_other.rs: -------------------------------------------------------------------------------- 1 | 2 | #[macro_use] 3 | extern crate chan; 4 | extern crate chan_signal; 5 | 6 | use chan_signal::{Signal, kill_this, block}; 7 | 8 | fn main() { 9 | block(&[Signal::TERM]); 10 | let (s, r) = chan::sync(1); 11 | chan_signal::notify_on(&s, Signal::HUP); 12 | kill_this(Signal::TERM); 13 | kill_this(Signal::HUP); 14 | assert_eq!(r.recv(), Some(Signal::HUP)); 15 | } 16 | -------------------------------------------------------------------------------- /examples/test_sleep.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate chan; 3 | extern crate chan_signal; 4 | 5 | use std::thread; 6 | use std::time::Duration; 7 | 8 | use chan_signal::{Signal, kill_this}; 9 | 10 | fn main() { 11 | let (s, r) = chan::sync(1); 12 | chan_signal::notify_on(&s, Signal::HUP); 13 | thread::spawn(move || thread::sleep(Duration::from_secs(10))); 14 | thread::sleep(Duration::from_millis(500)); 15 | kill_this(Signal::HUP); 16 | assert_eq!(r.recv(), Some(Signal::HUP)); 17 | } 18 | -------------------------------------------------------------------------------- /examples/test_usr1.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate chan; 3 | extern crate chan_signal; 4 | 5 | use chan_signal::{Signal, kill_this}; 6 | 7 | fn main() { 8 | let (s, r) = chan::sync(1); 9 | chan_signal::notify_on(&s, Signal::USR1); 10 | kill_this(Signal::USR1); 11 | assert_eq!(r.recv(), Some(Signal::USR1)); 12 | } 13 | -------------------------------------------------------------------------------- /run-example-tests: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | for t in ./examples/test_*.rs; do 4 | filename=$(basename "$t") 5 | cargo run --example ${filename%*.rs} 6 | done 7 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /*! 2 | This crate provides a simplistic interface to subscribe to operating system 3 | signals through a channel API. Use is extremely simple: 4 | 5 | ```no_run 6 | use chan_signal::Signal; 7 | 8 | let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]); 9 | 10 | // Blocks until this process is sent an INT or TERM signal. 11 | // Since the channel is never closed, we can unwrap the received value. 12 | signal.recv().unwrap(); 13 | ``` 14 | 15 | 16 | # Example 17 | 18 | When combined with `chan_select!` from the `chan` crate, one can easily 19 | integrate signals with the rest of your program. For example, consider a 20 | main function that waits for either normal completion of work (which is done 21 | in a separate thread) or for a signal to be delivered: 22 | 23 | ```no_run 24 | #[macro_use] 25 | extern crate chan; 26 | extern crate chan_signal; 27 | 28 | use chan_signal::Signal; 29 | 30 | fn main() { 31 | // Signal gets a value when the OS sent a INT or TERM signal. 32 | let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]); 33 | // When our work is complete, send a sentinel value on `sdone`. 34 | let (sdone, rdone) = chan::sync(0); 35 | // Run work. 36 | ::std::thread::spawn(move || run(sdone)); 37 | 38 | // Wait for a signal or for work to be done. 39 | chan_select! { 40 | signal.recv() -> signal => { 41 | println!("received signal: {:?}", signal) 42 | }, 43 | rdone.recv() => { 44 | println!("Program completed normally."); 45 | } 46 | } 47 | } 48 | 49 | fn run(_sdone: chan::Sender<()>) { 50 | // Do some work. 51 | ::std::thread::sleep_ms(1000); 52 | // Quit normally. 53 | // Note that we don't need to send any values. We just let the 54 | // sending channel drop, which closes the channel, which causes 55 | // the receiver to synchronize immediately and always. 56 | } 57 | ``` 58 | 59 | You can see this example in action by running `cargo run --example select` 60 | in the root directory of this crate's 61 | [repository](https://github.com/BurntSushi/chan-signal). 62 | 63 | # Platform support (no Windows support) 64 | 65 | This should work on Unix platforms supported by Rust itself. 66 | 67 | There is no Windows support at all. I welcome others to either help me add it 68 | or help educate me so that I may one day add it. 69 | 70 | 71 | # How it works 72 | 73 | Overview: uses the "spawn a thread and block on `sigwait`" approach. In 74 | particular, it avoids standard asynchronous signal handling because it is 75 | very difficult to do anything non-trivial inside a signal handler. 76 | 77 | After a call to `notify`/`notify_on` (or `block`), the given signals are set 78 | to *blocked*. This is necessary for synchronous signal handling using `sigwait`. 79 | 80 | After the first call to `notify` (or `notify_on`), a new thread is spawned and 81 | immediately blocks on a call to `sigwait`. It is only unblocked when one of the 82 | signals that were masked previously by calls to `notify` etc. arrives, which now 83 | cannot be delivered directly to any of the threads of the process, and therefore 84 | unblocks the waiting signal watcher thread. Once it's unblocked, it sends the 85 | signal on all subscribed channels via a non-blocking send. Once all channels 86 | have been visited, the thread blocks on `sigwait` again. 87 | 88 | This approach has some restrictions. Namely, your program must comply with the 89 | following: 90 | 91 | * Any and all threads spawned in your program **must** come after the first 92 | call to `notify` (or `notify_on`). This is so all spawned threads inherit 93 | the blocked status of signals. If a thread starts before `notify` is called, 94 | it will not have the correct signal mask. When a signal is delivered, the 95 | result is indeterminate. 96 | * No other threads may call `sigwait`. When a signal is delivered, only one 97 | `sigwait` is indeterminately unblocked. 98 | 99 | 100 | # Future work 101 | 102 | This crate exposes the simplest API I could think of. As a result, a few 103 | additions may be warranted: 104 | 105 | * Expand the set of signals. (Requires figuring out platform differences.) 106 | * Allow channel unsubscription. 107 | * Allow callers to reset the signal mask? (Seems hard.) 108 | * Support Windows. 109 | */ 110 | #![deny(missing_docs)] 111 | 112 | extern crate bit_set; 113 | #[macro_use] extern crate chan; 114 | #[macro_use] extern crate lazy_static; 115 | extern crate libc; 116 | 117 | use std::collections::HashMap; 118 | use std::io; 119 | use std::mem; 120 | use std::ptr; 121 | use std::sync::Mutex; 122 | use std::thread; 123 | 124 | use bit_set::BitSet; 125 | use chan::Sender; 126 | use libc::{ 127 | // POSIX.1-2008, minus SIGPOLL (not in some BSD, use SIGIO) 128 | SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, SIGFPE, SIGKILL, 129 | SIGSEGV, SIGPIPE, SIGALRM, SIGTERM, SIGUSR1, SIGUSR2, 130 | SIGCHLD, SIGCONT, SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU, 131 | SIGBUS, SIGPROF, SIGSYS, SIGTRAP, SIGURG, SIGVTALRM, 132 | SIGXCPU, SIGXFSZ, 133 | 134 | // Common Extensions (SIGINFO and SIGEMT not in libc) 135 | SIGIO, 136 | SIGWINCH, 137 | 138 | SIG_BLOCK, 139 | SIG_SETMASK, 140 | }; 141 | use libc::kill; 142 | use libc::getpid; 143 | 144 | lazy_static! { 145 | static ref HANDLERS: Mutex, BitSet>> = { 146 | init(); 147 | Mutex::new(HashMap::new()) 148 | }; 149 | } 150 | 151 | /// Create a new channel subscribed to the given signals. 152 | /// 153 | /// The channel returned is never closed. 154 | /// 155 | /// This is a convenience function for subscribing to multiple signals at once. 156 | /// See the documentation of `notify_on` for details. 157 | /// 158 | /// The channel returned has a small buffer to prevent signals from being 159 | /// dropped. 160 | /// 161 | /// **THIS MUST BE CALLED BEFORE ANY OTHER THREADS ARE SPAWNED IN YOUR 162 | /// PROCESS.** 163 | /// 164 | /// # Example 165 | /// 166 | /// ```no_run 167 | /// use chan_signal::Signal; 168 | /// 169 | /// let signal = chan_signal::notify(&[Signal::INT, Signal::TERM]); 170 | /// 171 | /// // Blocks until this process is sent an INT or TERM signal. 172 | /// // Since the channel is never closed, we can unwrap the received value. 173 | /// signal.recv().unwrap(); 174 | /// ``` 175 | pub fn notify(signals: &[Signal]) -> chan::Receiver { 176 | let (s, r) = chan::sync(100); 177 | for &sig in signals { 178 | notify_on(&s, sig); 179 | } 180 | // dropping `s` is OK because `notify_on` acquires one. 181 | r 182 | } 183 | 184 | /// Subscribe to a signal on a channel. 185 | /// 186 | /// When `signal` is delivered to this process, it will be sent on the channel 187 | /// given. 188 | /// 189 | /// Note that a signal is sent using a non-blocking send. Namely, if the 190 | /// channel's buffer is full (or it has no buffer) and it isn't ready to 191 | /// rendezvous, then the signal will be dropped. 192 | /// 193 | /// There is currently no way to unsubscribe. Moreover, the channel given 194 | /// here will be alive for the lifetime of the process. Therefore, the channel 195 | /// will never be closed. 196 | /// 197 | /// **THIS MUST BE CALLED BEFORE ANY OTHER THREADS ARE SPAWNED IN YOUR 198 | /// PROCESS.** 199 | pub fn notify_on(chan: &Sender, signal: Signal) { 200 | let mut subs = HANDLERS.lock().unwrap(); 201 | if subs.contains_key(chan) { 202 | subs.get_mut(chan).unwrap().insert(signal.as_sig() as usize); 203 | } else { 204 | let mut sigs = BitSet::new(); 205 | sigs.insert(signal.as_sig() as usize); 206 | subs.insert((*chan).clone(), sigs); 207 | } 208 | 209 | // Make sure that the signal that we want notifications on is blocked 210 | // It does not matter if we block the same signal twice. 211 | block(&[signal]); 212 | } 213 | 214 | /// Block all given signals without receiving notifications. 215 | /// 216 | /// If a signal has also been passed to `notify`/`notify_on` this function 217 | /// does not have any effect in terms of that signal. 218 | /// 219 | /// **THIS MUST BE CALLED BEFORE ANY OTHER THREADS ARE SPAWNED IN YOUR 220 | /// PROCESS.** 221 | pub fn block(signals: &[Signal]) { 222 | let mut block = SigSet::empty(); 223 | for signal in signals { 224 | block.add(signal.as_sig()).unwrap(); 225 | } 226 | block.thread_block_signals().unwrap(); 227 | } 228 | 229 | /// Block all subscribable signals. 230 | /// 231 | /// Calling this function effectively restores the default behavior of 232 | /// version <= 0.2.0 of this library. 233 | /// 234 | /// **THIS MUST BE CALLED BEFORE ANY OTHER THREADS ARE SPAWNED IN YOUR 235 | /// PROCESS.** 236 | pub fn block_all_subscribable() { 237 | SigSet::subscribable().thread_block_signals().unwrap(); 238 | } 239 | 240 | fn init() { 241 | // First: 242 | // Get the curren thread_mask. (We cannot just overwrite the threadmask with 243 | // an empty one because this function is executed lazily. 244 | let saved_mask = SigSet::current().unwrap(); 245 | 246 | // Then: 247 | // Block all signals in this thread. The signal mask will then be inherited 248 | // by the worker thread. 249 | SigSet::subscribable().thread_set_signal_mask().unwrap(); 250 | thread::spawn(move || { 251 | let mut listen = SigSet::subscribable(); 252 | 253 | loop { 254 | let sig = listen.wait().unwrap(); 255 | let subs = HANDLERS.lock().unwrap(); 256 | for (s, sigs) in subs.iter() { 257 | if !sigs.contains(sig as usize) { 258 | continue; 259 | } 260 | chan_select! { 261 | default => {}, 262 | s.send(Signal::new(sig)) => {}, 263 | } 264 | } 265 | } 266 | }); 267 | 268 | // Now: 269 | // Reset to the previously saved sigmask. 270 | // This whole procedure is necessary, as we cannot rely on the worker thread 271 | // starting fast enough to set its signal mask. Otherwise an early SIGTERM or 272 | // similar may take down the process even though the main thread has blocked 273 | // the signal. 274 | saved_mask.thread_set_signal_mask().unwrap(); 275 | } 276 | 277 | /// Kill the current process. (Only used in tests.) 278 | #[doc(hidden)] 279 | pub fn kill_this(sig: Signal) { 280 | unsafe { kill(getpid(), sig.as_sig()); } 281 | } 282 | 283 | type Sig = libc::c_int; 284 | 285 | /// The set of subscribable signals. 286 | /// 287 | /// After the first call to `notify_on` (or `notify`), precisely this set of 288 | /// signals are set to blocked status. 289 | #[allow(missing_docs)] 290 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 291 | pub enum Signal { 292 | HUP, 293 | INT, 294 | QUIT, 295 | ILL, 296 | ABRT, 297 | FPE, 298 | KILL, 299 | SEGV, 300 | PIPE, 301 | ALRM, 302 | TERM, 303 | USR1, 304 | USR2, 305 | CHLD, 306 | CONT, 307 | STOP, 308 | TSTP, 309 | TTIN, 310 | TTOU, 311 | BUS, 312 | PROF, 313 | SYS, 314 | TRAP, 315 | URG, 316 | VTALRM, 317 | XCPU, 318 | XFSZ, 319 | IO, 320 | WINCH, 321 | #[doc(hidden)] 322 | __NonExhaustiveMatch, 323 | } 324 | 325 | impl Signal { 326 | fn new(sig: Sig) -> Signal { 327 | match sig { 328 | SIGHUP => Signal::HUP, 329 | SIGINT => Signal::INT, 330 | SIGQUIT => Signal::QUIT, 331 | SIGILL => Signal::ILL, 332 | SIGABRT => Signal::ABRT, 333 | SIGFPE => Signal::FPE, 334 | SIGKILL => Signal::KILL, 335 | SIGSEGV => Signal::SEGV, 336 | SIGPIPE => Signal::PIPE, 337 | SIGALRM => Signal::ALRM, 338 | SIGTERM => Signal::TERM, 339 | SIGUSR1 => Signal::USR1, 340 | SIGUSR2 => Signal::USR2, 341 | SIGCHLD => Signal::CHLD, 342 | SIGCONT => Signal::CONT, 343 | SIGSTOP => Signal::STOP, 344 | SIGTSTP => Signal::TSTP, 345 | SIGTTIN => Signal::TTIN, 346 | SIGTTOU => Signal::TTOU, 347 | SIGBUS => Signal::BUS, 348 | SIGPROF => Signal::PROF, 349 | SIGSYS => Signal::SYS, 350 | SIGTRAP => Signal::TRAP, 351 | SIGURG => Signal::URG, 352 | SIGVTALRM => Signal::VTALRM, 353 | SIGXCPU => Signal::XCPU, 354 | SIGXFSZ => Signal::XFSZ, 355 | SIGIO => Signal::IO, 356 | SIGWINCH => Signal::WINCH, 357 | sig => panic!("unsupported signal number: {}", sig), 358 | } 359 | } 360 | 361 | fn as_sig(self) -> Sig { 362 | match self { 363 | Signal::HUP => SIGHUP, 364 | Signal::INT => SIGINT, 365 | Signal::QUIT => SIGQUIT, 366 | Signal::ILL => SIGILL, 367 | Signal::ABRT => SIGABRT, 368 | Signal::FPE => SIGFPE, 369 | Signal::KILL => SIGKILL, 370 | Signal::SEGV => SIGSEGV, 371 | Signal::PIPE => SIGPIPE, 372 | Signal::ALRM => SIGALRM, 373 | Signal::TERM => SIGTERM, 374 | Signal::USR1 => SIGUSR1, 375 | Signal::USR2 => SIGUSR2, 376 | Signal::CHLD => SIGCHLD, 377 | Signal::CONT => SIGCONT, 378 | Signal::STOP => SIGSTOP, 379 | Signal::TSTP => SIGTSTP, 380 | Signal::TTIN => SIGTTIN, 381 | Signal::TTOU => SIGTTOU, 382 | Signal::BUS => SIGBUS, 383 | Signal::PROF => SIGPROF, 384 | Signal::SYS => SIGSYS, 385 | Signal::TRAP => SIGTRAP, 386 | Signal::URG => SIGURG, 387 | Signal::VTALRM => SIGVTALRM, 388 | Signal::XCPU => SIGXCPU, 389 | Signal::XFSZ => SIGXFSZ, 390 | Signal::IO => SIGIO, 391 | Signal::WINCH => SIGWINCH, 392 | Signal::__NonExhaustiveMatch => unreachable!(), 393 | } 394 | } 395 | } 396 | 397 | /// Safe wrapper around `sigset_t`. 398 | struct SigSet(sigset_t); 399 | 400 | impl SigSet { 401 | fn empty() -> SigSet { 402 | let mut set = unsafe { mem::zeroed() }; 403 | unsafe { sigemptyset(&mut set) }; 404 | SigSet(set) 405 | } 406 | 407 | fn current() -> io::Result { 408 | let mut set = unsafe { mem::zeroed() }; 409 | let ecode = unsafe { 410 | pthread_sigmask(SIG_SETMASK, ptr::null_mut(), &mut set) 411 | }; 412 | ok_errno(SigSet(set), ecode) 413 | } 414 | 415 | /// Creates a new signal set with precisely the signals we're limited 416 | /// to subscribing to. 417 | fn subscribable() -> SigSet { 418 | let mut set = SigSet::empty(); 419 | set.add(SIGHUP).unwrap(); 420 | set.add(SIGINT).unwrap(); 421 | set.add(SIGQUIT).unwrap(); 422 | set.add(SIGILL).unwrap(); 423 | set.add(SIGABRT).unwrap(); 424 | set.add(SIGFPE).unwrap(); 425 | set.add(SIGKILL).unwrap(); 426 | set.add(SIGSEGV).unwrap(); 427 | set.add(SIGPIPE).unwrap(); 428 | set.add(SIGALRM).unwrap(); 429 | set.add(SIGTERM).unwrap(); 430 | set.add(SIGUSR1).unwrap(); 431 | set.add(SIGUSR2).unwrap(); 432 | set.add(SIGCHLD).unwrap(); 433 | set.add(SIGCONT).unwrap(); 434 | set.add(SIGSTOP).unwrap(); 435 | set.add(SIGTSTP).unwrap(); 436 | set.add(SIGTTIN).unwrap(); 437 | set.add(SIGTTOU).unwrap(); 438 | set.add(SIGBUS).unwrap(); 439 | set.add(SIGPROF).unwrap(); 440 | set.add(SIGSYS).unwrap(); 441 | set.add(SIGTRAP).unwrap(); 442 | set.add(SIGURG).unwrap(); 443 | set.add(SIGVTALRM,).unwrap(); 444 | set.add(SIGXCPU).unwrap(); 445 | set.add(SIGXFSZ).unwrap(); 446 | set.add(SIGIO).unwrap(); 447 | set.add(SIGWINCH).unwrap(); 448 | set 449 | } 450 | 451 | fn add(&mut self, sig: Sig) -> io::Result<()> { 452 | unsafe { ok_errno((), sigaddset(&mut self.0, sig)) } 453 | } 454 | 455 | fn wait(&mut self) -> io::Result { 456 | let mut sig: Sig = 0; 457 | let errno = unsafe { sigwait(&mut self.0, &mut sig) }; 458 | ok_errno(sig, errno) 459 | } 460 | 461 | fn thread_block_signals(&self) -> io::Result<()> { 462 | let ecode = unsafe { 463 | pthread_sigmask(SIG_BLOCK, &self.0, ptr::null_mut()) 464 | }; 465 | ok_errno((), ecode) 466 | } 467 | 468 | fn thread_set_signal_mask(&self) -> io::Result<()> { 469 | let ecode = unsafe { 470 | pthread_sigmask(SIG_SETMASK, &self.0, ptr::null_mut()) 471 | }; 472 | ok_errno((), ecode) 473 | } 474 | } 475 | 476 | fn ok_errno(ok: T, ecode: libc::c_int) -> io::Result { 477 | if ecode != 0 { Err(io::Error::from_raw_os_error(ecode)) } else { Ok(ok) } 478 | } 479 | 480 | extern { 481 | fn sigwait(set: *mut sigset_t, sig: *mut Sig) -> Sig; 482 | fn sigaddset(set: *mut sigset_t, sig: Sig) -> libc::c_int; 483 | fn sigemptyset(set: *mut sigset_t) -> libc::c_int; 484 | fn pthread_sigmask( 485 | how: libc::c_int, 486 | set: *const sigset_t, 487 | oldset: *mut sigset_t, 488 | ) -> libc::c_int; 489 | } 490 | 491 | // Most of this was lifted out of rust-lang:rust/src/libstd/sys/unix/c.rs. 492 | 493 | #[cfg(all(target_os = "linux", target_pointer_width = "32"))] 494 | #[repr(C)] 495 | struct sigset_t { 496 | __val: [libc::c_ulong; 32], 497 | } 498 | 499 | #[cfg(all(target_os = "linux", target_pointer_width = "64"))] 500 | #[repr(C)] 501 | struct sigset_t { 502 | __val: [libc::c_ulong; 16], 503 | } 504 | 505 | #[cfg(target_os = "android")] 506 | type sigset_t = libc::c_ulong; 507 | 508 | #[cfg(any(target_os = "macos", target_os = "ios"))] 509 | type sigset_t = u32; 510 | 511 | #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))] 512 | #[repr(C)] 513 | struct sigset_t { 514 | bits: [u32; 4], 515 | } 516 | 517 | #[cfg(any(target_os = "bitrig", target_os = "netbsd", target_os = "openbsd"))] 518 | type sigset_t = libc::c_uint; 519 | --------------------------------------------------------------------------------