Rustでシグナルを手軽に扱う


[2018.08.14 追記] chan-signalはdeprecatedになって、代わりにcrossbeam-channelsignal-hookを使うことが推奨されています。

概要

なるほどUnixプロセスRustで実装してみるということをやっていて、そこでシグナルを扱う章があったのですがあまり情報がない感じだったのと、今後Rustでもシグナルを扱いたい場面がでてきそうなので、試したことのメモを共有します。

Rustはstableで1.16前提です。

chan-signalを使う

Goのchannel/selectライクに非同期処理を書くことができるchanクレイトと、chanと合わせてシグナルを容易に扱うことができるchan-signalクレイトを使ってシグナルを扱ってみます。

2017年4月現在の最新バージョンはchan:0.1.19chan-signal:0.2.0です。

最初はchan_signalだけを使った例です。

Cargo.toml
[dependencies]
chan = "*"
chan-signal = "*"
chan-signal.rs
extern crate chan_signal;

use chan_signal::Signal;

fn main() {
    let s = chan_signal::notify(&[Signal::TERM, Signal::USR1]);
    match s.recv() {
        Some(s) => println!("signal={:?}", s),
        _ => {}
    }
}

SIGTERMとSIGUSR1を受信設定して1度だけ受信を待ち受けて、シグナル受信時にデバッグ出力します。(Ctrl-Cで停止できないので、kill -TERM PID等で停止する必要があります。)

chanとchan-signalを組み合わせて使う

chanクレイトを使って、10秒後にメッセージを受信またはシグナルSIGINTの受信を同時に待ち受ける処理の例です。

chan-signal-with-chan.rs
#[macro_use]
extern crate chan;
extern crate chan_signal;

use chan_signal::Signal;

fn main() {
    let s = chan_signal::notify(&[Signal::INT]);
    let do_something = chan::after_ms(10_000);

    chan_select! {
        s.recv() -> signal => {
            println!("signal={:?}", signal);
        },
        do_something.recv() => {
            println!("do something");
        }
    }
}

実行して何もしなければ10秒後にdo somethingを出力して終了し、途中でCtrl-C等でシグナルSIGINTを受信すればデバッグ出力して終了します。

手軽です。

chanクレイトはそれ単体で面白い機構なので、もう少し中身見てみたり実際に使ってみたりしたいですね。

nixを使う

nixクレイトのsigactionあたりを使えば、いわゆる生のxNIX APIを使う感覚でシグナルを扱う処理を実装できます。

2017年4月現在のnixの最新バージョンは0.8.1です。

Cargo.toml
[dependencies]
nix = "*"

以下は先ほどのchanchan-signalを組み合わせた例をnixで実装してみたものです。

nix-sigaction.rs
#[macro_use]
extern crate chan;
extern crate nix;

use nix::sys::signal;
use nix::sys::signal::{sigaction, SigAction, SigHandler, SA_RESETHAND, SigSet};

extern "C" fn handle_signal(signum: i32) {
    println!("handler. signal={}", signum);
}

fn main() {
    let sa = SigAction::new(SigHandler::Handler(handle_signal), SA_RESETHAND, SigSet::empty());
    unsafe { sigaction(signal::SIGINT, &sa) }.unwrap();

    let do_something = chan::after_ms(10_000);

    chan_select! {
        do_something.recv() => {
            println!("do something");
        }
    }
}

SA_RESETHANDで1度だけ定義したハンドラ設定が有効になるので、実行後にCtrl-Cでhandler. signal=2と表示され、もう一度Ctrl-Cでプログラムが終了します。(実行後何もしなければ10秒後にdo somethingを出力して終了するのは変わりません。)

おわりに

以上、chan chan-signal nixを使ったRustでのシグナルの扱いについてのメモでした。