From d85044f6b201eae0a9e453b89c0433608e0778f0 Mon Sep 17 00:00:00 2001 From: Austin Seipp Date: Sun, 8 Sep 2013 02:08:45 -0500 Subject: Default to infinite stack size (#8189) 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 Signed-off-by: Austin Seipp --- docs/users_guide/runtime_control.xml | 11 +++++--- includes/Rts.h | 3 +- includes/rts/Constants.h | 2 +- includes/rts/storage/GC.h | 13 +++++++-- rts/RtsFlags.c | 4 +-- rts/RtsUtils.c | 6 ++-- rts/Schedule.c | 2 +- rts/Threads.c | 10 +++++-- rts/sm/Storage.c | 55 +++++++++++++++++++++++++----------- 9 files changed, 73 insertions(+), 33 deletions(-) diff --git a/docs/users_guide/runtime_control.xml b/docs/users_guide/runtime_control.xml index 3e3ae3f7d2..8044d9ccb3 100644 --- a/docs/users_guide/runtime_control.xml +++ b/docs/users_guide/runtime_control.xml @@ -676,10 +676,13 @@ $ ./prog -f +RTS -H32m -S -RTS -h foo bar stack, maximum size - [Default: 8M] Set the maximum stack size for - an individual thread to size - bytes. If the thread attempts to exceed this limit, it will - be send the StackOverflow exception. + + [Default: infinite] Set the maximum stack + size for an individual thread to + size bytes. A setting of zero + implies no maximum stack size limit. If the thread + attempts to exceed this limit, it will be sent the + StackOverflow exception. This option is there mainly to stop the program eating up diff --git a/includes/Rts.h b/includes/Rts.h index 122637c465..ee10a1044b 100644 --- a/includes/Rts.h +++ b/includes/Rts.h @@ -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 diff --git a/includes/rts/Constants.h b/includes/rts/Constants.h index 494abe2b22..deb550ee97 100644 --- a/includes/rts/Constants.h +++ b/includes/rts/Constants.h @@ -234,7 +234,7 @@ * 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 diff --git a/includes/rts/storage/GC.h b/includes/rts/storage/GC.h index fb5e21e832..07bc65128f 100644 --- a/includes/rts/storage/GC.h +++ b/includes/rts/storage/GC.h @@ -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 */ diff --git a/rts/RtsFlags.c b/rts/RtsFlags.c index 1e541a0201..46a8462dca 100644 --- a/rts/RtsFlags.c +++ b/rts/RtsFlags.c @@ -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 Sets the maximum stack size (default 8M) Egs: -K32k -K512k", +" -K Sets the maximum stack size (defaults to infinite) Egs: -K32k -K512k", " -ki Sets the initial thread stack size (default 1k) Egs: -ki4k -ki2m", " -kc Sets the stack chunk size (default 32k)", " -kb Sets the stack chunk buffer size (default 1k)", diff --git a/rts/RtsUtils.c b/rts/RtsUtils.c index cb9002c361..604c7eed61 100644 --- a/rts/RtsUtils.c +++ b/rts/RtsUtils.c @@ -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 } diff --git a/rts/Schedule.c b/rts/Schedule.c index 07ebec62b1..20077b1d53 100644 --- a/rts/Schedule.c +++ b/rts/Schedule.c @@ -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; diff --git a/rts/Threads.c b/rts/Threads.c index 14fb7e872c..90d3dfd115 100644 --- a/rts/Threads.c +++ b/rts/Threads.c @@ -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); diff --git a/rts/sm/Storage.c b/rts/sm/Storage.c index b575fc3e52..df088132e3 100644 --- a/rts/sm/Storage.c +++ b/rts/sm/Storage.c @@ -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. -- cgit v1.2.1