~ 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