How Haskell handles signals
Published on
How is it possible to write signal handlers in
GHC Haskell? After all, the set
of system calls allowed inside signal handlers is rather limited. In
particular, it is very hard to do memory allocation safely inside a
signal handler; one would have to modify global data (and thus not be
reentrant), call one of the banned syscalls (brk
,
sbrk
, or mmap
), or both.
On the other hand, we know that almost any Haskell code requires memory allocation. So what’s the trick?
The trick is that a Haskell handler is not installed as a true signal
handler. Instead, a signal is handled by a carefully crafted RTS
function generic_handler
(rts/posix/Signals.c
). All that function does (assuming the
threaded RTS) is write the signal number and the siginfo_t
structure describing the signal to a special pipe (called the control
pipe, see GHC.Event.Control
).
The other end of this pipe is being watched by the timer manager
thread (GHC.Event.TimerManager
). When awaken by a signal
message from the control pipe, it looks up the handler corresponding to
the signal number and, in case it’s an action, runs it in a new Haskell
thread.
The signal handlers are stored in a global array,
signal_handlers
(GHC.Conc.Signal
). When you
install a signal action in Haskell, you put a stable pointer to the
action’s code into the array cell corresponding to the signal number, so
that the timer thread could look it up later when an actual signal is
delivered.
See also https://ghc.haskell.org/trac/ghc/wiki/Commentary/Rts/Signals.