diff --git a/Cargo.lock b/Cargo.lock index cea2d3d3..73672b0b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3,6 +3,8 @@ name = "CLAiR" version = "0.1.0" dependencies = [ "convey 0.2.0 (git+https://github.com/killercup/convey)", + "crossbeam-channel 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", + "ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "exitfailure 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)", "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -182,6 +184,18 @@ dependencies = [ "smallvec 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "crossbeam-channel" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "crossbeam-epoch 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "crossbeam-utils 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.5 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "crossbeam-epoch" version = "0.6.1" @@ -208,6 +222,15 @@ dependencies = [ "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "ctrlc" +version = "3.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "difference" version = "2.0.0" @@ -368,6 +391,18 @@ name = "memoffset" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "nix" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "cc 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.44 (registry+https://github.com/rust-lang/crates.io-index)", + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nodrop" version = "0.1.13" @@ -908,9 +943,11 @@ dependencies = [ "checksum console 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3949ee3155c602df08e8e9a557f209e196829174535d6a58b555ab77a9140573" "checksum convey 0.2.0 (git+https://github.com/killercup/convey)" = "" "checksum crossbeam-channel 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "7b85741761b7f160bc5e7e0c14986ef685b7f8bf9b7ad081c60c604bb4649827" +"checksum crossbeam-channel 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0ac88e108fa40799b39c08eb2a93bedf4cc99a9e5577f08ddf6dd6134ae65bf0" "checksum crossbeam-epoch 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2449aaa4ec7ef96e5fb24db16024b935df718e9ae1cec0a1e68feeca2efca7b8" "checksum crossbeam-utils 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "677d453a17e8bd2b913fa38e8b9cf04bcdbb5be790aa294f2389661d72036015" "checksum crossbeam-utils 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c55913cc2799171a550e307918c0a360e8c16004820291bf3b638969b4a01816" +"checksum ctrlc 3.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "630391922b1b893692c6334369ff528dcc3a9d8061ccf4c803aa8f83cb13db5e" "checksum difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" "checksum env_logger 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "afb070faf94c85d17d50ca44f6ad076bce18ae92f0037d350947240a36e9d42e" "checksum escargot 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "19db1f7e74438642a5018cdf263bb1325b2e792f02dd0a3ca6d6c0f0d7b1d5a5" @@ -931,6 +968,7 @@ dependencies = [ "checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6" "checksum memchr 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0a3eb002f0535929f1199681417029ebea04aadc0c7a4224b46be99c7f5d6a16" "checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3" +"checksum nix 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d37e713a259ff641624b6cb20e3b12b2952313ba36b6823c0f16e6cfd9e5de17" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum normalize-line-endings 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2e0a1a39eab95caf4f5556da9289b9e68f0aafac901b2ce80daaf020d3b733a8" "checksum num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "0b3a5d7cc97d6d30d8b9bc8fa19bf45349ffe46241e8816f50f62f6d6aaabee1" diff --git a/Cargo.toml b/Cargo.toml index 5df6cf59..ed6a3011 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,14 @@ path = "src/in-depth/machine-communication.rs" name = "machine-communication-convey" path = "src/in-depth/machine-communication-convey.rs" +[[bin]] +name = "signals-ctrlc" +path = "src/in-depth/signals-ctrlc.rs" + +[[bin]] +name = "signals-channels" +path = "src/in-depth/signals-channels.rs" + [workspace] members = [ "src/tutorial/testing", @@ -60,3 +68,5 @@ serde_json = "1" convey = { version = "0.2", git = "https://github.com/killercup/convey" } serde_derive = "1.0.80" serde = "1.0.80" +ctrlc = "3.1.1" +crossbeam-channel = "0.3.2" diff --git a/src/in-depth/signals-channels.rs b/src/in-depth/signals-channels.rs new file mode 100644 index 00000000..eb3daa3a --- /dev/null +++ b/src/in-depth/signals-channels.rs @@ -0,0 +1,31 @@ +use std::time::Duration; +use crossbeam_channel::{bounded, tick, Receiver, select}; + +fn ctrl_channel() -> Result, ctrlc::Error> { + let (sender, receiver) = bounded(100); + ctrlc::set_handler(move || { + let _ = sender.send(()); + })?; + + Ok(receiver) +} + +fn main() -> Result<(), exitfailure::ExitFailure> { + let ctrls = ctrl_channel()?; + let ticks = tick(Duration::from_secs(1)); + + loop { + select! { + recv(ticks) -> _ => { + println!("working!"); + } + recv(ctrls) -> _ => { + println!(); + println!("Goodbye!"); + break; + } + } + } + + Ok(()) +} diff --git a/src/in-depth/signals-ctrlc.rs b/src/in-depth/signals-ctrlc.rs new file mode 100644 index 00000000..017da83a --- /dev/null +++ b/src/in-depth/signals-ctrlc.rs @@ -0,0 +1,7 @@ +fn main() { + ctrlc::set_handler(move || { + println!("received Ctrl+C!"); + }).expect("Error setting Ctrl-C handler"); + + // ... +} diff --git a/src/in-depth/signals.md b/src/in-depth/signals.md index dc42837f..b7ac2f8b 100644 --- a/src/in-depth/signals.md +++ b/src/in-depth/signals.md @@ -1 +1,125 @@ # Signal handling + +Processes +like command line applications +need to react to signals sent by the operating system. +The most common example is probably Ctrl+C, +the signal that typically tells a process to terminate. +To handle signals in Rust programs +you need to consider how you can receive these signals +as well as how you can react to them. + + + +## Differences between operating systems + +On Unix systems +(like Linux, macOS, and FreeBSD) +a process can receive [signals] +It can either react to them +in a default (OS-provided) way, +catch the signal and handle them in a program-defined way, +or ignore the signal entirely. + +[signals]: https://manpages.ubuntu.com/manpages/bionic/en/man7/signal.7.html +[signal-safety man page]: http://manpages.ubuntu.com/manpages/bionic/man7/signal-safety.7.html + +Windows does not have signals. +You can use [Console Handlers] +to define callbacks that get executed when an event occurs. +There is also [structured exception handling] +which handles all the various types of system exceptions such as division by zero, invalid access exceptions, stack overflow, and so on + +[Console Handlers]: https://docs.microsoft.com/de-de/windows/console/console-control-handlers +[structured exception handling]: https://docs.microsoft.com/en-us/windows/desktop/debug/structured-exception-handling + +## First off: Handling Ctrl+C + +The [ctrlc] crate does just what the name suggests: +It allows you to react to the user pressing Ctrl+C, +in a cross-platform way. +The main way to use the crate is this: + +[ctrlc]: https://crates.io/crates/ctrlc + +```rust,ignore +{{#import signals-ctrlc.rs:1:7}} +``` + +This is, of course, not that helpful: +It only prints a message but otherwise doesn't stop the program. + +In a real-world program, +it's a good idea to instead set a variable in the signal handler +that you then check in various places in your program. +For example, +you can set an `Arc` +(a boolean that shareable between threads) +in your signal handler, +and in hot loops, +or when waiting for a thread, +you periodically check its value +and break when it becomes true. + +## Handling other types of signals + +The [ctrlc] crate only handels Ctrl+C, +or, what on Unix systems would be called SIGINT (the "interrupt" signal). +To react to more Unix signals, +you should have a look at [signal-hook]. +Its design is described in [this blog post][signal-hook-post], +and it is currently the library with the widest community support. + +[signal-hook-post]: https://vorner.github.io/2018/06/28/signal-hook.html + +## Using channels + +Another approach is to use channels: +You create a channel that the signal handler emits a value on +whenever the signal is received. +In you application code you use +this and other channels +as synchronization points between threads. +Using crossbeam-channels it would look something like this: + +```rust,ignore +{{#import signals-channels.rs:1:31}} +``` + +## Using futures and streams + +If you are using [tokio], +you are most likely already writing your application +with asynchronous patterns and an event-driven design. +Instead of using crossbeam's channels directly, +you can enable signal-hook's `tokio-support` feature. +This allows you to call [`.into_async()`] +on signal-hook's `Signals` types +to get a new type that implements `futures::Stream`. + +[signal-hook]: https://crates.io/crates/signal-hook +[tokio]: https://tokio.rs/ +[`.into_async()`]: https://docs.rs/signal-hook/0.1.6/signal_hook/iterator/struct.Signals.html#method.into_async + +## What to do when you receive another Ctrl+C while you're handling the first Ctrl+C + +Most user will press Ctrl+C, +and then give your program a few seconds to exit, +or tell them what's going on. +If that doesn't happen, +they will press Ctrl+C again. +The expected behavior is to have the application quit immediately.