A user recently ran Sidekiq with Ruby 2.0 and found that the signal handling did not work well at all. Ctrl-C and other signals resulted in some ominous stack traces.
It turns out that Ruby 2.0 locks down what you can do in a signal handler in order to prevent unsafe or possibly non-deterministic behavior. You can’t take a Mutex within a signal handler anymore as this could result in a thread context switch or even deadlock. In fact you can’t even write to a Logger because it tries to use a Mutex internally.
I rewrote the signal handling to conform with the new restrictions: all handlers now just push the name of the signal onto a global array and the main thread polls once per second for unhandled signals. This isn’t perfect, polling is something to be avoided where possible, but I don’t know of a better solution and it’s a lot better than the previous “fat” handlers that did a lot of work.
Hi.
Certainly when writing C programs which employ signal handling, it’s generally recommended that you do as little as possible in a signal handler, so I can see the logic here. I think that the chief problem that’s being referred to here is that signals can be re-entrant–that is, a signal handler can be interrupted by another signal (or sometimes the same signal).
With regard to threads, then , I’d suspect that the problem is that normally, the mutex locking functions (
pthread_mutex_lock&c) depend on being able to run to completion. If you catch a signal halfway through calling a mutex function, then there’s a good chance that the internal state of the mutex won’t be consistent, and hence fail to behave properly (ie: possibly deadlock the calling thread mysteriously).A compounding problem is that signal handlers need to be re-entrant-safe, so if another signal gets delivered whilst in a signal handler, the first signal handler will be interrupted, thus increasing the probability of poking a mutex whilst in an invalid state.
So I can understand the decision taken by the ruby core team, even if it is surprising. Mixing posix threads and signal handling usually is a bit of a nightmare.
Hope that’s useful.
Back in the days I used a pipe(2) with the read side in the main loop and a global write side to push a single character (possibly the signal number) from the signal handler to the main loop. This way you can use your current multiplexer, avoid polling and still have a nice list of all the triggered signals.