What are the best practices for Unix signal handling?

Hello everyone!

Some operating systems implement a concept of signals, that allow an application to be notified of various system events and intervene the control flow by invoking a signal handling. The standard way is to install a signal handler, however, this is extremely unsafe (because in this case, signal delivery means interrupting a thread and redirecting the control flow), so I think it should be limited to special cases, such as:

  • Handling emergency signals (SIGBUS, SIGSEGV, SIGILL, SIGFPE, SIGABRT) -- they can only be caught with a signal handler (if not manually invoked)
  • Invoking emergency action (that has to be done right now and cannot wait to return to the event loop)
  • Forcing a time-out on system calls -- a signal handler shouldn't do anything in that case (however, please note that some libraries can restart the system call if they see it failed with EINTR).

For other cases, I recommend synchronous signal handling -- that is, blocking them and handling them and specific points (by unblocking-and-reblocking, using sigtimedwait() or OS-specific facilities such as signalfd() in Linux). It can happen inside the event loop of an application. However, if an application creates child processes, this can be a problem, because signal mask is inherited to child processes and is preserved across execve() -- strictly speaking, the initial signal mask is undefined for the new process after execution, and if it wants to use ordinary signal handlers, it should explicitly unblock signals. However, many apps don't do that and simply assume all signals are unblocked from the beginning. To avoid breaking signal handling in these apps, the process can:

  • Use the pre_exec() method from std::os::unix::process::CommandExt trait (the solution I prefer)
  • Install an ordinary signal handler and use the self-pipe trick -- open a non-blocking pipe, then, after the delivery of a signal, drop the contents of siginfo_t structure into the write end in the handler
  • Use the global variable. In C, volatile sig_atomic_t variables are used (as far as I know, sig_atomic_t is a typedef for a type that can be re-assigned in one instruction). I'd like to ask, are types in std::sync::atomic module considered safe to change in signal handlers?

You might be looking for this crate. As you can see, the flag approach does use Rust atomics.