4 ms·
Signals are one of the most dangerous features of POSIX, but this guide is awesome. I used this resource while writing the Redis 2.6 "Software Watchdog" feature
by antirez 15y ago
Signals are one of the most dangerous features of POSIX, but this guide is awesome. I used this resource while writing the Redis 2.6 "Software Watchdog" feature (documented at the end of this page: http://redis.io/topics/latency http://redis.io/topics/latency). Cool that it was posted here just a few days later.
- javert 15y agoWhat exactly makes you say they are dangerous? Genuinely curious. Certain kinds of programmer error? (I haven't finished reading the guide yet... apologies if it's in there... but I'd say I'm pretty familiar with signals.)
- antirez 15y agoreentrancy of everything called inside a signal handler, interrupted system calls to handle, complexity in masking/unmasking, reinstalling of signal handler, and so forth. It's a very complex system.
- javert 15y agoThat's what I figured was being referred to. Those things aren't so bad if you really know what you're doing.
- calloc 15y agoThere are certain race conditions if you rely on signals to interrupt system calls and the interrupt happens before the syscall is called (after setting the signal, but before the syscall happens). Since your code could have been anywhere in the execution path you can only do very limited amount of things within the signal handler that won't cause catastrophic issues otherwise.
- javert 15y agocertain race conditions if you rely on signals to interrupt system calls and the interrupt happens before the syscall is called I'm pretty familiar with signals, but I don't really know what the problem is here. Why would you rely on signals to interrupt system calls? Maybe if I know why you would do that, I will see why it's a problem if the signal comes before the syscall happens.
- spc476 15y agoAt work, we have a time-critical program. In that program, we need to lock a file in order to modify it safely. The files themselves are stored on NFS (don't ask---this was a design decision from long ago). fcntl() (used to lock files) does not include a timeout. We need a timeout because the program is time-critical. NFS is not a local filesystem, but across the network. If the network is flaky, we don't want to hang on locking the file. So we have to rely upon SIGALRM (via setitimer()) to interrupt the fcntl() call so we don't hang indefinitely.
- javert 15y agoInteresting, thanks.
- javert 15y agoWow, I cannot fathom how I could be downvoted on an honest and purely technical question.
- X-Istence 15y agoThe examples given in the article linked explain this pretty well actually... For example, lets say you want Ctrl + C to quit the program (bare with me, very simple example), and you have a select() call. So you set up your signal handlers, and then start filling the required structures for the select() call, before select() is called though you receive a SIGINT, your signal handler simply sets a global flag that is checked when select() returns (with errno == EINTR). So now the flag is set, the handler has done its job, and select() gets called and your program now starts waiting on file descriptors. What you really wanted to happen is that the program would see the flag, clean up nicely and quit. Now the user has to send a second Ctrl + C to interrupt the select() syscall and to have the code executed that checks for errno == EINTR and the global flag that was set in the signal handler. This is but a simple example of a race condition that can exist. The SIGALRM example given by the other HN user is also an excellent example of when things can go awry when not intentioned, and unless you program your signal handlers with that in mind you may get results you weren't expecting.
- gchpaco 15y agoSignals do not interact gracefully with the rest of libc or the kernel, let alone concurrency. Signals can come in and interrupt kernel system calls--this is what EINTR is there for. When in the signal handler itself, the list of libc functions it is safe to call is small; the Single UNIX specification only guarantees less than 120 functions. V7 signals are not reliable and so it is dangerous to be in the signal handler for an extended period of time; but even BSD and SysV reliable signals have a list of caveats as long as my arm. The synchronous (think SIGFPE or SIGSEGV)/asynchronous (think SIGINT) distinction is an additional complication that the interface needed like it needed an additional head. And I haven't even gotten into the interactions between signals and process groups. Basically signals are a primitive, crude and dangerous form of IPC that one is nonetheless obligated to pay attention to. As a rule of thumb I generally prefer to use signal handlers to set a small amount of global data and nothing else, and have the main interrupt loop notice and deal with the condition. It's possible to use weird siglongjmp things to get to the main loop if you are not there already, but (like longjmp in general) it is kind of weird and bizarre.
- caf 15y agoThe best thing to do in a signal handler is often to write() a single byte to a pipe. Your main loop can then notice that the other end of the pipe is readable (the main loop is normally watching file descriptors for activity anyway).
- gchpaco 15y agoThat's a good point, and what I actually did last time this came up. signalfd on Linux just codifies this convention, but it is (particularly in evented code) an excellent idea.
- emmelaich 15y agoI believe this is referred to as the 'self pipe' trick. Dan Bernstein says he came up with it in 1990. http://cr.yp.to/docs/selfpipe.html http://cr.yp.to/docs/selfpipe.html
- javert 15y ago