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.
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
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),
is dangerous and best avoided.