~ chicken-core (chicken-5) 3f43b7c808269ff9eba0702800018ed327d7758e
commit 3f43b7c808269ff9eba0702800018ed327d7758e
Author: Peter Bex <peter.bex@xs4all.nl>
AuthorDate: Mon Nov 4 21:07:59 2013 +0100
Commit: Mario Domenech Goulart <mario.goulart@gmail.com>
CommitDate: Mon Nov 4 19:23:36 2013 -0200
Git rid of endless GC loop while handling signals. Fixes #989 (and #877?)
Signal handling is intertwined with the GC for performance reasons.
When a signal is received, the real C signal handler will put the
signal on a pending stack for Scheme to pick up. In order to get the
signal handler invoked ASAP, it will also force the stack to appear
"full" to the GC.
If a signal is received during a Scheme "signal handler", the stack
will be set to "full" in the real signal handler, and control will
then continue in the regular code, as is usual in POSIX. The next
signal is fetched by ##sys#signal-hook through C_i_pending_interrupt,
which clears interrupt_reason. Then it calls the inner LOOP in
##sys#signal-hook, which performs a stack_probe in the generated C
code which will cause a minor GC.
Thus, the stack is "full" but handle_interrupt() is not reached (as
interrupt_reason was already cleared). So the GC will clear the stack
but it cannot because the stack is mostly empty already. This means
it will still seem "full" after the GC has finished. Then, the
trampoline calls proc, which again does a stack_probe which triggers a
minor GC, ad infinitum.
To make things worse, this only happens if the user-mode signal
handler is uninterruptible (ie, native code or code with a
disable-interrupts declaration). If the user code contains its own
stack_probe it will get to it before ##sys#interrupt-hook has a chance
to clear interrupt_reason, thus it will cause the regular handler to
be invoked through save_and_reclaim(). In order to normalise all this
behaviour, the route into handle_interrupts() through the GC is now
blocked while ##sys#interrupt-hook is running. This block also
applies to forcing the stack to appear full while handling
interrupt-hook as that's not required because we're still looping and
fetching interrupts from the queue.
Signed-off-by: Mario Domenech Goulart <mario.goulart@gmail.com>
diff --git a/runtime.c b/runtime.c
index 58f8e078..11e4ee88 100644
--- a/runtime.c
+++ b/runtime.c
@@ -435,6 +435,7 @@ static C_TLS int
gc_count_2,
weak_table_randomization,
interrupt_reason,
+ handling_interrupts=0,
stack_size_changed,
dlopen_flags,
heap_size_changed,
@@ -2789,7 +2790,9 @@ C_regparm void C_fcall C_reclaim(void *trampoline, void *proc)
/* assert(C_timer_interrupt_counter >= 0); */
- if(interrupt_reason && C_interrupts_enabled)
+ /* trampoline + proc already represent ##sys#interrupt-hook when
+ handling_interrupts, so don't reinvoke (which allocates state) */
+ if(interrupt_reason && !handling_interrupts && C_interrupts_enabled)
handle_interrupt(trampoline, proc);
/* Note: the mode argument will always be GC_MINOR or GC_REALLOC. */
@@ -3704,6 +3707,7 @@ void handle_interrupt(void *trampoline, void *proc)
C_temporary_stack = C_temporary_stack_bottom;
i = interrupt_reason;
interrupt_reason = 0;
+ handling_interrupts = 1;
C_stack_limit = saved_stack_limit;
/* Invoke high-level interrupt handler: */
@@ -4466,13 +4470,23 @@ C_regparm void C_fcall C_raise_interrupt(int reason)
}
}
else {
- saved_stack_limit = C_stack_limit;
+ /*
+ * Force the next stack check to fail by faking a "full" stack.
+ * That causes save_and_reclaim() to be called, which will
+ * invoke handle_interrupt() (which restores the stack limit).
+ *
+ * Only do this when we're not already inside the interrupt
+ * handler, to avoid an endless GC loop.
+ */
+ if (!handling_interrupts) {
+ saved_stack_limit = C_stack_limit;
#if C_STACK_GROWS_DOWNWARD
- C_stack_limit = C_stack_pointer + 1000;
+ C_stack_limit = C_stack_pointer + 1000;
#else
- C_stack_limit = C_stack_pointer - 1000;
+ C_stack_limit = C_stack_pointer - 1000;
#endif
+ }
interrupt_reason = reason;
interrupt_time = C_cpu_milliseconds();
@@ -8119,6 +8133,7 @@ void C_ccall C_context_switch(C_word c, C_word closure, C_word k, C_word state)
adrs = C_block_item(state, 0);
TRAMPOLINE trampoline;
+ handling_interrupts = 0; /* context_switch happens after interrupt handling */
C_temporary_stack = C_temporary_stack_bottom - n;
C_memcpy(C_temporary_stack, (C_word *)state + 2, n * sizeof(C_word));
trampoline = (TRAMPOLINE)C_block_item(adrs,0);
Trap