🪶
ctrlc よりも簡単に Ctrl-C を検知する、小さな crate を作った
前提
CLI ツールを書いていると「Ctrl-C を検知したい」という場面がよくあります。
その度にこんな実装を書くわけですが、どうにも気持ち悪いとずっと思っていました。
main.rs
let flag = Arc::new(AtomicBool::new(false));
let f = flag.clone();
ctrlc::set_handler(move || {
f.store(true, Ordering::SeqCst);
})?;
loop {
if flag.load(Ordering::SeqCst) {
println!("Ctrl-C detected");
break;
}
// work...
}
- 目的のわりに実装が Too Much
- 内部実装のオーバーヘッドが気になる
-
Arc<AtomicBool>が嫌い
そこで ctrlc-tiny という「Ctrl-C が押されたかを知りたい」だけに特化した crate を作りました。
使い方
Cargo.toml
ctrlc-tiny = "0.1"
main.rs
ctrlc_tiny::init_ctrlc()?;
loop {
if ctrlc_tiny::is_ctrlc_received() {
println!("Ctrl-C detected");
break;
}
// work...
}
-
init_ctrlc()でハンドラを登録 - あとは
is_ctrlc_received()をポーリングするだけ - AtomicBool や Arc といった、スレッドセーフな共有状態の管理を意識する必要がない
Ctrl-C を複数回検知したい時は
reset_ctrlc_received() という関数を用意しています。
こんな場合でも、共有状態の管理を意識する必要のないシンプルな実装が可能です。
main.rs
ctrlc_tiny::init_ctrlc()?;
let mut count = 0;
loop {
if ctrlc_tiny::is_ctrlc_received() {
ctrlc_tiny::reset_ctrlc_received();
count += 1;
println!("Detected Ctrl-C: {count}");
if count == 10 {
break;
}
}
}
実装の面白かったところ
今回の実装は、ほぼすべて C 言語で完結しており、Rust 側では FFI 経由で C 関数を呼び出しているだけです。
これまで低レイヤーでのシグナルハンドリングは触ってきませんでしたが、想像以上にクセがあり、なかなか楽しかったです。
特に、シグナルハンドラの中で使える変数やシステムコールはかなり限られており、async-signal-safeな実装にするためには、かなり気を使う必要があります。
今回は is_sigint_received に代入するだけなので、わかりやすく async-signal-safe なシグナルハンドラを実現できています。
sigint.c
volatile sig_atomic_t is_sigint_received = 0;
void handle_sigint(int signo)
{
(void)signo;
is_sigint_received = 1;
}
int init_sigint_handler()
{
struct sigaction sa = {0};
sa.sa_handler = handle_sigint;
sigemptyset(&sa.sa_mask);
sa.sa_flags = SA_RESTART;
return sigaction(SIGINT, &sa, NULL);
}
sig_atomic_t get_is_sigint_received()
{
return is_sigint_received;
}
lib.rs
static INIT: Once = Once::new();
pub fn init_ctrlc() -> io::Result<()> {
let mut result = Ok(());
INIT.call_once(|| unsafe {
if bindings::init_sigint_handler() != 0 {
result = Err(io::Error::last_os_error());
}
});
result
}
pub fn is_ctrlc_received() -> bool {
unsafe { bindings::get_is_sigint_received() != 0 }
}
終わりに
正直なところ、Arc や AtomicBool、あるいは ctrlc クレート内部の実装について、パフォーマンス的に現実的な問題があるわけではありません。
個人的、主観的な気持ち悪さと、実装のちょっとした煩雑さの解消が今回の目的で、その点では満足できる仕上がりになったと思っています。
ご意見・改善案などあれば PR お待ちしています!
- Crates.io: https://crates.io/crates/ctrlc-tiny
- GitHub: https://github.com/malt03/ctrlc-tiny
- Docs: https://docs.rs/ctrlc-tiny
Discussion