Skip to content

Commit

Permalink
Book: First draft of signals chapter
Browse files Browse the repository at this point in the history
  • Loading branch information
killercup committed Dec 7, 2018
1 parent 682a8d0 commit 71cf1c9
Show file tree
Hide file tree
Showing 5 changed files with 210 additions and 0 deletions.
38 changes: 38 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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"
31 changes: 31 additions & 0 deletions src/in-depth/signals-channels.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
use std::time::Duration;
use crossbeam_channel::{bounded, tick, Receiver, select};

fn ctrl_channel() -> Result<Receiver<()>, 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(())
}
7 changes: 7 additions & 0 deletions src/in-depth/signals-ctrlc.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
fn main() {
ctrlc::set_handler(move || {
println!("received Ctrl+C!");
}).expect("Error setting Ctrl-C handler");

// ...
}
124 changes: 124 additions & 0 deletions src/in-depth/signals.md
Original file line number Diff line number Diff line change
@@ -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 <kbd>Ctrl</kbd>+<kbd>C</kbd>,
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.

<aside>

**Note:**
If your applications does not require special cleanup,
the default handling is fine
(i.e. exit immediately and let the OS cleanup).
In that case:
No need to do what this chapter tells you!
For applications that need to clean up after themselves,
for example to correct close network connections,
remove temporary files,
or reset system settings,
this chapter is however very relevant!

</aside>

## 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 <kbd>Ctrl</kbd>+<kbd>C</kbd>,
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<AtomicBool>`
(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 <kbd>Ctrl</kbd>+<kbd>C</kbd>,
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 <kbd>Ctrl</kbd>+<kbd>C</kbd>,
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 <kbd>Ctrl</kbd>+<kbd>C</kbd> again.
The expected behavior is to have the application quit immediately.

0 comments on commit 71cf1c9

Please sign in to comment.