>From 20cddd42d1ec122cc3268c9503284251dc5d6e08 Mon Sep 17 00:00:00 2001 From: Peter Bex Date: Mon, 4 Nov 2013 21:07:59 +0100 Subject: [PATCH] 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. --- runtime.c | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/runtime.c b/runtime.c index 58f8e07..11e4ee8 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); -- 1.8.3.4