Unix Signals in Rust

Hi,

I am working on a little program that I would like to perform cleanup logic if the program is interrupted or otherwise killed (e.g. with Control+C).

In a typical C program, I would use Unix signals to handle this. How would you do this in Rust? This is what I came up with, and it surprisingly works, but I get the feeling there might be pitfalls or a better way:

extern "C" {
  fn signal(sig: u32, cb: extern fn(u32)) -> fn(u32);
}

extern fn interrupt(_:u32) {
  println!("Interrupted!");
  fs::remove_file("/log.sock").unwrap();
}

fn main() {
  usafe {
    signal(2, interrupt);
  }
}

What surprises me about this is that the extern signal function works given that it has a function return type (the Unix signal function returns the previous handler as a function pointer). I tried this on a whim based on the chapter in the documentation on passing function pointers to C.

-Caleb

We don't have signal handling in Rust proper yet. We have implement signal handling · Issue #11203 · rust-lang/rust · GitHub as an issue (which I should probably move to the RFC repo)

Until then, using libc is the way to go.

Thanks!

I found the nix library has what I want with the sigaction function.

In case someone else stumbles on this, here is what I found that works:

use nix::sys::signal;

extern fn handle_sigint(_:i32) {
  println!("Interrupted!");
  panic!();
}

fn main() {
  let sig_action = signal::SigAction::new(handle_sigint,
                                          signal::SockFlag::empty(),
                                          signal::SigSet::empty());
  signal::sigaction(signal::SIGINT, &sig_action);
}

I doubt that you're allowed to call println! or even panic! from a signal handler.

@tbu

It compiles and appears to work, but whether it's a good idea or not is a different matter :smile:

Should I stick with C exit and printf statements?

I think the function should be unsafe because you're probably not allowed to do almost anything there. You're allowed to do system calls and probably also calling C's exit function – printf is probably disallowed as well.

I filed a bug report here:

For what it is worth, what I really would like is for mio to support signals using the best available strategy given the platform. I haven't personally had time to implement it though.

1 Like

A safe signal handling mechanism is hard to achieve but I posted a proposition about this important problem for a low-level language like Rust.

@caleb, unfortunately your signal handler is definitely not safe because of thread local variable use (cf. signal(7)) neither is the nix-rust implementation without a FnAsync-like function. If the thread is interrupted while using a global/shared thread memory (e.g. using println!()), the result would be undetermined (i.e. printing in the middle of another print, no print at all, replacing the thread print content…).
The panic!() should be replaced with an assignation to a global asynchronous atomic variable (which doesn't exist in Rust), then check this value and do the panic!() in the thread (but not the signal handler).

If a signal handler deal with the memory manager (i.e. allocate or free some memory on the heap), then chaos may happen :frowning:

Unless I misunderstand you, we have that today. I think it's called AtomicBool and is available in the standard library. Simply make it a global variable with static and then you can access it from multiple threads safely using its member functions.

AtomicBool can safely be shared between threads (cf. doc). Signals are asynchronous interruptions, they (temporary) replace the execution flow of an existing thread. So the ordering is crucial. I think AtomicBool with SeqCst ordering may be the same as volatile sig_atomic_t but a Rust/LLVM expert should validate this.

I believe ordering of atomic writes is also important between different threads, consider:

  1. Acquire lock.
  2. Do stuff.
  3. Release lock.

If this is done using atmoics (like e.g. in Arc), then it's also crucical it's not re-ordered in some ways.

If you want strict ordering, you can use SeqCst, but there's other stuff available. For just unconditionally setting a flag once in a signal handler, Relaxed is totally fine.

Was there a consensus on the possibility of combining the code outlined in Unix Signals in Rust - #3 by caleb with some sort of safe flag that can be set in the interrupt handler, and read from somewhere else to eg. break out of a loop or exit a thread?

I'm not aware of any specific progress on asynchronous signal handlers in Rust, but if you can live with synchronous signal handlers, then you can use chan-signal, which sends OS signals over a channel.