The signal handling facilities in ECL are constrained by two needs. First of all, we can not ignore the synchronous signals mentioned in Section 7.2.1. Second, all other signals should cause the least harm to the running threads. Third, when a signal is handled synchronously using a signal handler, the handler should do almost nothing unless we are completely sure that we are in an interruptible region, that is outside system calls, in code that ECL knows and controls.
The way in which this is solved is based on the existence of both synchronous and asynchronous signal handling code, as explained in the following two sections.
In systems in which this is possible, ECL creates a signal handling thread to detect and process asynchronous signals (See Section 7.2.2). This thread is a trivial one and does not process the signals itself: it communicates with, or launches new signal handling threads to act accordingly to the denoted events.
The use of a separate thread has some nice consequences. The first one is that those signals will not interrupt any sensitive code. The second one is that the signal handling thread will be able to execute arbitrary lisp or C code, since it is not being executed in a sensitive context. Most important, this style of signal handling is the recommended one by the POSIX standards, and it is the one that Windows uses.
The installation of the signal handling thread is dictated by a boot
time option, ECL_OPT_SIGNAL_HANDLING_THREAD
, and it will
only be possible in systems that support either POSIX or Windows
threads.
Systems which embed ECL as an extension language may wish to deactivate the signal handling thread using the previously mentioned option. If this is the case, then they should take appropriate measures to avoid interrupting the code in ECL when such signals are delivered.
Systems which embed ECL and do not mind having a separate signal
handling thread can control the set of asynchronous signals which is handled
by this thread. This is done again using the appropriate boot options such
as ECL_OPT_TRAP_SIGINT
,
ECL_OPT_TRAP_SIGTERM
, etc. Note that in order to detect
and handle those signals, ECL must block them from delivery to any other
thread. This means changing the sigprocmask()
in POSIX
systems or setting up a custom SetConsoleCtrlHandler()
in Windows.
We have already mentioned that certain synchronous signals and exceptions can not be ignored and yet the corresponding signal handlers are not able to execute arbitrary code. To solve this seemingly impossible contradiction, ECL uses a simple solution, which is to mark the sections of code which are interruptible, and in which it is safe for the handler to run arbitrary code. All other regions would be considered "unsafe" and would be protected from signals and exceptions.
In principle this "marking" of safe areas can be done using POSIX
functions such as pthread_sigmask()
or
sigprocmask()
. However in practice this is slow, as it
involves at least a function call, resolving thread-local variables, etc,
etc, and it will not work in Windows.
Furthermore, sometimes we want signals to be detected but not to be
immediately processed. For instance, when reading from the terminal we want
to be able to interrupt the process, but we can not execute the code from
the handler, since the C function which is used to read from the terminal,
read()
, may have left the input stream in an
inconsistent, or even locked state.
The approach in ECL is more lightweight: we install our own signal
handler and use a thread-local variable as a flag that determines whether
the thread is executing interrupt safe code or not. More precisely, if the
variable ecl_process_env()->disable_interrupts
is set, signals
and exceptions will be postponed and then the information about the signal
is queued. Otherwise the appropriate code is executed: for instance invoking
the debugger, jumping to a condition handler, quitting, etc.
Systems that embed ECL may wish to deactivate completely these
signal handlers. This is done using the boot options,
ECL_OPT_TRAP_SIGFPE
,
ECL_OPT_TRAP_SIGSEGV
,
ECL_OPT_TRAP_SIGBUS
,
ECL_OPT_TRAP_INTERRUPT_SIGNAL
.
Systems that embed ECL and want to allow handling of synchronous
signals should take care to also trap the associated lisp conditions that
may arise. This is automatically taken care of by functions such as
si_safe_eval()
, and in all other cases it can be solved
by enclosing the unsafe code in a CL_CATCH_ALL_BEGIN
frame (See CL_CATCH_ALL
).