Signal handling
***************

Groups of signals
=================

There are two distinct groups of signals.

Semi-synchronous signals
------------------------

The first group, tentatively named ``semi-synchronous'', consists of signals that are raised on illegal instruction, hitting a protected page, or on a trap. Examples from this group are: SIGBUS/SIGSEGV, SIGTRAP, SIGILL and SIGEMT. The exact meaning and function of these signals varies by platform and OS. Understandably, because these signals are raised in a controllable manner they are never blocked or deferred.

Blockable signals
-----------------

The other group is of blockable signals. Typically, signal handlers block them to protect against being interrupted at all. For example SIGHUP, SIGINT, SIGQUIT belong to this group.

With the exception of SIG_STOP_FOR_GC all blockable signals are deferrable.

The deferral mechanism
======================

Pseudo atomic sections
----------------------

Some operations, such as allocation, consist of several steps and temporarily break for instance gc invariants. Interrupting said operations is therefore dangerous to one's health. Blocking the signals for each allocation is out of question as the overhead of the two sigsetmask system calls would be enormous. Instead, pseudo atomic sections are implemented with a simple flag.

When a deferrable signal is delivered to a thread within a pseudo atomic section the pseudo-atomic-interrupted flag is set, the signal and its context are stored, and all deferrable signals blocked. This is to guarantee that there is at most one pending handler in SBCL. While the signals are blocked, the responsibilty of keeping track of other pending signals lies with the OS.

On leaving the pseudo atomic section, the pending handler is run and the signals are unblocked.

WITHOUT-INTERRUPTS
------------------

Similar to pseudo atomic, WITHOUT-INTERRUPTS defers deferrable signals in its thread until the end of its body, provided it is not nested in another WITHOUT-INTERRUPTS.

Not so frequently used as pseudo atomic, WITHOUT-INTERRUPTS benefits less from the deferral mechanism.

Stop the world
--------------

Something of a special case, a signal that is blockable but not deferrable by WITHOUT-INTERRUPTS is SIG_STOP_FOR_GC. It is deferred by pseudo atomic and WITHOUT-GCING.

Implementation warts
====================

Miscellaneous issues
--------------------

Signal handlers should automatically restore errno and fp state. Currently, this is not the case.

POSIX -- Letter and Spirit
--------------------------

POSIX restricts signal handlers to a use only a narrow subset of POSIX functions, and declares anything else to have undefined semantics.

Apparently the real reason is that a signal handler is potentially interrupting a POSIX call: so the signal safety requirement is really a re-entrancy requirement. We can work around the letter of the standard by arranging to handle the interrupt when the signal handler returns (see: arrange_return_to_lisp_function.) This does, however, in no way protect us from the real issue of re-entrancy: even though we would no longer be in a signal handler, we might still be in the middle of an interrupted POSIX call.

For some signals this appears to be a non-issue: SIGSEGV and other semi-synchronous signals are raised by our code for our code, and so we can be sure that we are not interrupting a POSIX call with any of them.

For asynchronous signals like SIGALARM and SIGINT this is a real issue.

The right thing to do in multithreaded builds would probably be to use POSIX semaphores (which are signal safe) to inform a separate handler thread about such asynchronous events. In single-threaded builds there does not seem to be any other option aside from generally blocking asynch signals and listening for them every once and a while at safe points. Neither of these is implemented as of SBCL 1.0.4.

Currently all our handlers invoke unsafe functions without hesitation.

Programming with signal handling in mind
========================================

On reentrancy
-------------

Since they might be invoked in the middle of just about anything, signal handlers must invoke only reentrant functions or async signal safe functions to be more precise. Functions passed to INTERRUPT-THREAD have the same restrictions and considerations as signal handlers.

Destructive modification, and holding mutexes to protect desctructive modifications from interfering with each other are often the cause of non-reentrancy. Recursive locks are not likely to help, and while WITHOUT-INTERRUPTS is, it is considered untrendy to litter the code with it.

Some basic functionality, such as streams and the debugger are intended to be reentrant, but not much effort has been spent on verifying it.

More deadlocks
--------------

If functions A and B directly or indirectly lock mutexes M and N, they should do so in the same order to avoid deadlocks.

A less trivial scenario is where there is only one lock involved but it is acquired in a WITHOUT-GCING in thread A, and outside of WITHOUT-GCING in thread B. If thread A has entered WITHOUT-GCING but thread B has the lock when the gc hits, then A cannot leave WITHOUT-GCING because it is waiting for the lock the already suspended thread B has. From this scenario one can easily derive the rule: in a WITHOUT-GCING form (or pseudo atomic for that matter) never wait for another thread that's not in WITHOUT-GCING.

Somewhat of a special case, it is enforced by the runtime that SIG_STOP_FOR_GC and SIG_RESUME_FROM_GC always unblocked when we might trigger a gc (i.e. on alloc or calling into Lisp).

Calling user code
-----------------

For the reasons above, calling user code, i.e. functions passed in, or in other words code that one cannot reason about, from non-reentrant code (holding locks), WITHOUT-INTERRUPTS, WITHOUT-GCING is dangerous and best avoided.

Debugging
=========

It is not easy to debug signal problems. The best bet probably is to enable QSHOW and QSHOW_SIGNALS in runtime.h and once SBCL runs into problems attach gdb. A simple thread apply all ba is already tremendously useful. Another possibility is to send a SIGABORT to SBCL to provoke landing in LDB, if it's compiled with it and it has not yet done so on its own.

Note, that fprintf used by QSHOW is not reentrant and at least on x86 linux it is known to cause deadlocks, so place SHOW and co carefully, ideally to places where blockable signals are blocked. Use QSHOW_SAFE if you like.