Regarding Rust specifically, it would seem signal handling remains an open item[1] though some degree of support appears present.
I've considered designing some sort of complicated DSL in Rust to construct async-signal-safe handlers, but it didn't seem worth the complexity. (Although, now that I write this comment out, I think you can handle SIGSEGV safely from another thread as long as your first thread calls for help in an async-signal-safe way, so maybe that would be a library worth writing.)
For the rest, using sigwaitinfo or signalfd works just as well in Rust as in C/C++, but not really better or worse. There aren't any good libraries for it (which I keep meaning to do something about), but the underlying POSIX APIs are usable equally well in all three languages.
As for whether you should worry about it? Try not to use signals, regardless of language. If you're writing some sort of demand-paging system using SIGSEGV handlers, I hope you know what you're doing already. If you're thinking about SIGALRM, there are other ways to get a notification after a fixed time (and Rust's mio/tokio, for instance, gets you timers in your event loop). If you're trying to use SIGUSR1/SIGUSR2, please rethink your design - you'll be much happier with something like a UNIX socket listening for connections or a localhost-only URL for debugging or whatever. If you want to handle SIGINT (intercept Ctrl-C at a terminal), SIGQUIT (intercept Ctrl-\ at a terminal), SIGTSTP (intercept Ctrl-Z at a terminal), SIGTERM (intercept the "kill" command's default signal), SIGHUP (do something when the terminal exits), SIGWINCH (get notified when the terminal resizes), or SIGCHLD (get notified when a child process exits), you do have to use signals, but all of these can be run through sigwaitinfo or signalfd. And you don't have to actively worry about them if you don't want to customize their behavior, or if (for SIGINT, SIGHUP, etc.) you just want to ignore them and not do anything special.
The only odd case is SIGPIPE, and Rust's standard library blocks SIGPIPE for you and returns EPIPE from a system call instead, and unblocks it in child processes, so you don't need to worry about it. (The default SIGPIPE behavior is basically legacy compatibility with existing C code from the very early days of UNIX.)
There are a couple crates which should suffice for basic SIGTERM/SIGHUP/SIGINT handling:
https://github.com/BurntSushi/chan-signal creates a sigwait thread which sends signals over a channel.
https://github.com/alexcrichton/tokio-signal uses the self-pipe trick to integrate with a tokio event loop.
Yes, you could write a library for use inside signal handlers that shares no state with the actual libc. Then you could also safely printf, for instance, although you'd jump ahead of any printfs in the host program that are still buffered, etc.
But it's not just libc's globals that are a problem, it's your own globals and those of any other libraries, and if you intend to do nontrivial work in the signal handler (such that having a compiler check that you're async-signal-safe is worth doing), you probably are actually trying to share state with the main program. For instance, say you're installing SEGV handler to virtually map a large data file into memory, fetching it from some remote file storage API as needed. You want access to some data structures from the main program to figure out what to map, probably the ability to make HTTPS connections, etc. And you have to do that all from the signal handler, because whatever pointer segfaulted has to be working by the time you return from your signal handler.
You might be able to do this with Rust plus cleverness, by putting the signal handler in a separate statically-linked crate and using the ownership system to track the fact that the signal handler needs read-only access to some data. (Or with dlmopen, in C.) But it would still be very complex.
edit: I should have read ben0x539 comment which suggests the same thing.
How easy is it to fuck up the signalfd thing and end up not responding to SIGINT in a timely manner, because I'm being silly and doing some blocking work in the signalfd loop for some reason? Does that happen to people at all?
What kinda usecases are there for handling SIGSEGV? I'm not the kind of person to write on-demand-paging systems so I'm not really sure what that involves and when I need that sort of thing.
If you're building a runtime that includes go like goroutines, you could handle SIGSEGV when you run out of stack space and graceful kill the go routine.
You can use it for some kind of online migration of VMS.
You could build a moving garbage collector system for native code multi-threaded code where you don't need to stop the world... you can deal with race of not stoping / moving by making those pages in accessible and only stoping the thread / fixing up if happens to hit moved memory.
A runtime for a language that has multi-threading system that doesn't share VM mappings (which is possible using the clone sys call on linux) but can share other things.
All somewhat advanced uses. But uses never the less.
SIGBUS is simialr to SIGSEGV (It happens if a mmap is larger that the backing file and you try to read the unbacked memory). You basically have to handle it if you don't control the backing file, Eg: with shared memory when the other process resizes it after you've mmap'd it. libwayland-server can install one for you that replaces the map with 0'd memory.
Yes, but if you're doing nontrivial work, you probably don't just want memory for yourself, you want to share some actual program state with the main process. See the other comment I posted.
In practice, making any raw system call is async-signal-sae. POSIX doesn't specify this because it doesn't specify what is a system call at all - it's purely a C-language, compile-time interface, and a unikernel that makes everything a library call, a Windows subsystem, etc. are all valid ways to implement it. But on a system where mmap is in fact a system call (and the libc wrapper does nothing interesting) it follows from first principles: there's no userspace resource that can be left in an inconsistent state.
> How easy is it to fuck up the signalfd thing and end up not responding to SIGINT in a timely manner, because I'm being silly and doing some blocking work in the signalfd loop for some reason? Does that happen to people at all?
I mean, this is basically the same question as "How easy is it for a system that's only accessible via an HTTP API to get stuck processing an event and not process a POST /quit". If you're worried about messing up your event loop and getting stuck, then yes, SIGINT interrupting your process is useful -- but also, you've already admitted you don't know what the state of your process is. So an orderly shutdown is difficult. Any work you could do to make it easier turns out to be work that makes your original non-blocking design get implemented correctly.
You should probably just leave SIGINT at its default disposition (terminate the process immediately, same as SIGKILL) and save state to disk periodically or something. Or, at best, implement a signal handler that does very little interaction with process state, maybe just writing a log message (but note that syslog isn't async-signal-safe!) and exiting.
Another use case would be a massive one to one hash table. You allocate the address space and when you fault on it you load data from a database on demand and continue execution.
https://www.kernel.org/doc/Documentation/vm/userfaultfd.txt
There's also something in Mach for this on OS X, but I forget the details. And a handful of non-UNIX OSes have had support for an outside program being able to get notified efficiently on segfaults and map pages in.
Implementing a VM for Java or similar in which you need to trap null pointer accesses and turn them into exceptions is one use case.
another one: attempt a best-effort flush of your log queue/buffer.
If you care deeply about reliable logging in the presence of arbitrary segfaults, get it to a buffer outside of the program: either write(2) / send(2) it to a file/pipe/socket, or write it to a shared memory region / mmaped file and pay attention to the atomicity and ordering of memory writes on that region.
You might also conclude that arbitrary segfaults aren't in scope, and there's some specific high-risk code that is separate from your logging implementation. If you can prevent wild pointers in the high-risk code from reaching the pages used by the logging implementation (e.g., you're a language VM implementation or something), you can relax your requirements. In particular, if you can also prevent them from reaching the libc, and you know the high-risk code cannot induce a segfault in the libc, then you can probably start calling async-signal-unsafe functions. But this requires careful thought.
On the logging system I'm familiar with, the logging queue is lock-free, so any invariant is preserved at every instruction boundary. The log is actually written to disk from a background thread, and the signal handler is responsible of signaling the log thread to flush the logging queue(s).
Of course if the log thread itself has generated the sigsegv (because a log entry or the queue itself is corrupted by a bug) you are out of luck, but you are no worse than not attempting the flush.
Agree that making the log thread a separate process instead is significantly more robust (and not significantly harder in fact).