Default to infinite stack size (#8189)
authorAustin Seipp <aseipp@pobox.com>
Sun, 8 Sep 2013 07:08:45 +0000 (02:08 -0500)
committerAustin Seipp <aseipp@pobox.com>
Sun, 8 Sep 2013 08:55:06 +0000 (03:55 -0500)
When servicing a stack overflows, only throw an exception to the given
thread if the user explicitly set a max stack size, using +RTS -K.
Otherwise just service it normally and grow the stack.

In case we actually run out of *heap* (stack chuncks are allocated on
the heap), then we need to bail by calling the stackOverflow() hook and
exit immediately.

Authored-by: Ben Gamari <bgamari.foss@gmail.com>
Signed-off-by: Austin Seipp <aseipp@pobox.com>
docs/users_guide/runtime_control.xml
includes/Rts.h
includes/rts/Constants.h
includes/rts/storage/GC.h
rts/RtsFlags.c
rts/RtsUtils.c
rts/Schedule.c
rts/Threads.c
rts/sm/Storage.c

index 3e3ae3f..8044d9c 100644 (file)
@@ -676,10 +676,13 @@ $ ./prog -f +RTS -H32m -S -RTS -h foo bar
           <indexterm><primary>stack, maximum size</primary></indexterm>
         </term>
        <listitem>
-         <para>&lsqb;Default: 8M&rsqb; Set the maximum stack size for
-          an individual thread to <replaceable>size</replaceable>
-          bytes.  If the thread attempts to exceed this limit, it will
-            be send the <literal>StackOverflow</literal> exception.
+         <para>
+            &lsqb;Default: infinite&rsqb; Set the maximum stack
+            size for an individual thread to
+            <replaceable>size</replaceable> bytes. A setting of zero
+            implies no maximum stack size limit. If the thread
+            attempts to exceed this limit, it will be sent the
+            <literal>StackOverflow</literal> exception.
           </para>
           <para>
             This option is there mainly to stop the program eating up
index 122637c..ee10a10 100644 (file)
@@ -250,7 +250,7 @@ void getWin32ProgArgv(int *argc, wchar_t **argv[]);
 void setWin32ProgArgv(int argc, wchar_t *argv[]);
 #endif
 
-void stackOverflow(void);
+void stackOverflow(StgTSO* tso);
 
 void stg_exit(int n) GNU_ATTRIBUTE(__noreturn__);
 
@@ -268,6 +268,7 @@ int stg_sig_install (int, int, void *);
 #define EXIT_INTERRUPTED    252
 #define EXIT_HEAPOVERFLOW   251
 #define EXIT_KILLED         250
+#define EXIT_STACKOVERFLOW  249
 
 /* -----------------------------------------------------------------------------
    Miscellaneous garbage
index 494abe2..deb550e 100644 (file)
  * stopped for one reason or another.  See typedef StgThreadReturnCode
  * in TSO.h.
  */
-#define HeapOverflow   1                /* might also be StackOverflow */
+#define HeapOverflow   1
 #define StackOverflow  2
 #define ThreadYielding 3
 #define ThreadBlocked  4
index fb5e21e..07bc651 100644 (file)
@@ -130,9 +130,15 @@ extern generation * oldest_gen;
 
    StgPtr allocate(Capability *cap, W_ n)
                                 Allocates memory from the nursery in
-                               the current Capability.  This can be
-                               done without taking a global lock,
-                                unlike allocate().
+                                the current Capability. This can be
+                                done without taking a global lock,
+                                unlike allocate(). In the event of a
+                                heap overflow the program will be
+                                terminated.
+
+   StgPtr allocateFail(Capability *cap, W_ n)
+                                Similar to allocate() but returns NULL
+                                in the event of a heap overflow.
 
    StgPtr allocatePinned(Capability *cap, W_ n)
                                 Allocates a chunk of contiguous store
@@ -154,6 +160,7 @@ extern generation * oldest_gen;
    -------------------------------------------------------------------------- */
 
 StgPtr  allocate        ( Capability *cap, W_ n );
+StgPtr  allocateFail    ( Capability *cap, W_ n );
 StgPtr  allocatePinned  ( Capability *cap, W_ n );
 
 /* memory allocator for executable memory */
index 1e541a0..46a8462 100644 (file)
@@ -94,7 +94,7 @@ void initRtsFlagsDefaults(void)
     RtsFlags.GcFlags.statsFile         = NULL;
     RtsFlags.GcFlags.giveStats         = NO_GC_STATS;
 
-    RtsFlags.GcFlags.maxStkSize                = (8 * 1024 * 1024) / sizeof(W_);
+    RtsFlags.GcFlags.maxStkSize                = 0;    /* off by default */
     RtsFlags.GcFlags.initialStkSize    = 1024 / sizeof(W_);
     RtsFlags.GcFlags.stkChunkSize       = (32 * 1024) / sizeof(W_);
     RtsFlags.GcFlags.stkChunkBufferSize = (1 * 1024) / sizeof(W_);
@@ -233,7 +233,7 @@ usage_text[] = {
 "  -?       Prints this message and exits; the program is not executed",
 "  --info   Print information about the RTS used by this program",
 "",
-"  -K<size> Sets the maximum stack size (default 8M)  Egs: -K32k   -K512k",
+"  -K<size> Sets the maximum stack size (defaults to infinite)  Egs: -K32k   -K512k",
 "  -ki<size> Sets the initial thread stack size (default 1k)  Egs: -ki4k -ki2m",
 "  -kc<size> Sets the stack chunk size (default 32k)",
 "  -kb<size> Sets the stack chunk buffer size (default 1k)",
index cb9002c..604c7ee 100644 (file)
@@ -114,12 +114,12 @@ stgFree(void* p)
    -------------------------------------------------------------------------- */
 
 void
-stackOverflow(void)
+stackOverflow(StgTSO* tso)
 {
-  StackOverflowHook(RtsFlags.GcFlags.maxStkSize * sizeof(W_));
+    StackOverflowHook(tso->tot_stack_size * sizeof(W_));
 
 #if defined(TICKY_TICKY)
-  if (RtsFlags.TickyFlags.showTickyStats) PrintTickyInfo();
+    if (RtsFlags.TickyFlags.showTickyStats) PrintTickyInfo();
 #endif
 }
 
index 07ebec6..20077b1 100644 (file)
@@ -393,7 +393,7 @@ schedule (Capability *initialCapability, Task *task)
         
 run_thread:
 
-    // CurrentTSO is the thread to run.  t might be different if we
+    // CurrentTSO is the thread to run. It might be different if we
     // loop back to run_thread, so make sure to set CurrentTSO after
     // that.
     cap->r.rCurrentTSO = t;
index 14fb7e8..90d3dfd 100644 (file)
@@ -499,7 +499,8 @@ threadStackOverflow (Capability *cap, StgTSO *tso)
 
     IF_DEBUG(sanity,checkTSO(tso));
 
-    if (tso->tot_stack_size >= RtsFlags.GcFlags.maxStkSize
+    if (   (RtsFlags.GcFlags.maxStkSize > 0) // don't throw if we have infinite stack
+        && (tso->tot_stack_size >= RtsFlags.GcFlags.maxStkSize)
         && !(tso->flags & TSO_BLOCKEX)) {
         // NB. never raise a StackOverflow exception if the thread is
         // inside Control.Exceptino.block.  It is impractical to protect
@@ -575,7 +576,12 @@ threadStackOverflow (Capability *cap, StgTSO *tso)
                   "allocating new stack chunk of size %d bytes",
                   chunk_size * sizeof(W_));
 
-    new_stack = (StgStack*) allocate(cap, chunk_size);
+    new_stack = (StgStack*) allocateFail(cap, chunk_size);
+    if (new_stack == NULL) {
+        // We've really run out of memory in the heap, so die.
+        stackOverflow(tso);
+        stg_exit(EXIT_STACKOVERFLOW);
+    }
     SET_HDR(new_stack, &stg_STACK_info, old_stack->header.prof.ccs);
     TICK_ALLOC_STACK(chunk_size);
 
index b575fc3..df08813 100644 (file)
@@ -624,27 +624,27 @@ move_STACK (StgStack *src, StgStack *dest)
 }
 
 /* -----------------------------------------------------------------------------
-   allocate()
+   allocateFail()
 
    This allocates memory in the current thread - it is intended for
-   use primarily from STG-land where we have a Capability.  It is
-   better than allocate() because it doesn't require taking the
-   sm_mutex lock in the common case.
+   use primarily from STG-land where we have a Capability. 
 
    Memory is allocated directly from the nursery if possible (but not
    from the current nursery block, so as not to interfere with
    Hp/HpLim).
+
+   We return NULL in the event of a heap overflow.
    -------------------------------------------------------------------------- */
 
 StgPtr
-allocate (Capability *cap, W_ n)
+allocateFail (Capability *cap, W_ n)
 {
     bdescr *bd;
     StgPtr p;
 
     TICK_ALLOC_HEAP_NOCTR(WDS(n));
     CCS_ALLOC(cap->r.rCCCS,n);
-    
+
     if (n >= LARGE_OBJECT_THRESHOLD/sizeof(W_)) {
         W_ req_blocks =  (W_)BLOCK_ROUND_UP(n*sizeof(W_)) / BLOCK_SIZE;
 
@@ -655,14 +655,7 @@ allocate (Capability *cap, W_ n)
             req_blocks >= HS_INT32_MAX)   // avoid overflow when
                                           // calling allocGroup() below
         {
-            heapOverflow();
-            // heapOverflow() doesn't exit (see #2592), but we aren't
-            // in a position to do a clean shutdown here: we
-            // either have to allocate the memory or exit now.
-            // Allocating the memory would be bad, because the user
-            // has requested that we not exceed maxHeapSize, so we
-            // just exit.
-            stg_exit(EXIT_HEAPOVERFLOW);
+            return NULL; // heap overflow
         }
 
         ACQUIRE_SM_LOCK
@@ -682,12 +675,12 @@ allocate (Capability *cap, W_ n)
 
     bd = cap->r.rCurrentAlloc;
     if (bd == NULL || bd->free + n > bd->start + BLOCK_SIZE_W) {
-        
+
         // The CurrentAlloc block is full, we need to find another
         // one.  First, we try taking the next block from the
         // nursery:
         bd = cap->r.rCurrentNursery->link;
-        
+
         if (bd == NULL || bd->free + n > bd->start + BLOCK_SIZE_W) {
             // The nursery is empty, or the next block is already
             // full: allocate a fresh block (we can't fail here).
@@ -720,6 +713,36 @@ allocate (Capability *cap, W_ n)
     return p;
 }
 
+/* -----------------------------------------------------------------------------
+   allocate()
+
+   This allocates memory in the current thread - it is intended for
+   use primarily from STG-land where we have a Capability.
+
+   Memory is allocated directly from the nursery if possible (but not
+   from the current nursery block, so as not to interfere with
+   Hp/HpLim).
+
+   We crash with a HeapOverflow when the allocation fails.
+   -------------------------------------------------------------------------- */
+
+StgPtr
+allocate (Capability *cap, W_ n)
+{
+    StgPtr p = allocateFail(cap, n);
+    if (p == NULL) {
+        heapOverflow();
+        // heapOverflow() doesn't exit (see #2592), but we aren't
+        // in a position to do a clean shutdown here: we
+        // either have to allocate the memory or exit now.
+        // Allocating the memory would be bad, because the user
+        // has requested that we not exceed maxHeapSize, so we
+        // just exit.
+        stg_exit(EXIT_HEAPOVERFLOW);
+    }
+    return p;
+}
+
 /* ---------------------------------------------------------------------------
    Allocate a fixed/pinned object.