Teo Camarasu pushed to branch wip/T22859 at Glasgow Haskell Compiler / GHC
Commits:
-
d3d7e58c
by Teo Camarasu at 2025-06-10T16:09:47+01:00
24 changed files:
- compiler/GHC/Builtin/primops.txt.pp
- compiler/GHC/StgToCmm/Prim.hs
- compiler/GHC/StgToJS/Prim.hs
- libraries/ghc-experimental/ghc-experimental.cabal.in
- + libraries/ghc-experimental/src/System/Mem/Experimental.hs
- libraries/ghc-internal/ghc-internal.cabal.in
- + libraries/ghc-internal/src/GHC/Internal/AllocationLimitHandler.hs
- rts/Prelude.h
- rts/PrimOps.cmm
- rts/RtsStartup.c
- rts/RtsSymbols.c
- rts/Schedule.c
- rts/external-symbols.list.in
- rts/include/rts/storage/GC.h
- rts/include/rts/storage/TSO.h
- rts/include/stg/MiscClosures.h
- testsuite/tests/interface-stability/base-exports.stdout
- testsuite/tests/interface-stability/base-exports.stdout-javascript-unknown-ghcjs
- testsuite/tests/interface-stability/base-exports.stdout-mingw32
- testsuite/tests/interface-stability/base-exports.stdout-ws-32
- testsuite/tests/interface-stability/ghc-experimental-exports.stdout
- + testsuite/tests/rts/T22859.hs
- + testsuite/tests/rts/T22859.stderr
- testsuite/tests/rts/all.T
Changes:
| ... | ... | @@ -4065,6 +4065,15 @@ primop SetThreadAllocationCounter "setThreadAllocationCounter#" GenPrimOp |
| 4065 | 4065 | effect = ReadWriteEffect
|
| 4066 | 4066 | out_of_line = True
|
| 4067 | 4067 | |
| 4068 | +primop SetOtherThreadAllocationCounter "setOtherThreadAllocationCounter#" GenPrimOp
|
|
| 4069 | + Int64# -> ThreadId# -> State# RealWorld -> State# RealWorld
|
|
| 4070 | + { Sets the allocation counter for the another thread to the given value.
|
|
| 4071 | + This doesn't take allocations into the current nursery chunk into account.
|
|
| 4072 | + Therefore it is only accurate if the other thread is not currently running. }
|
|
| 4073 | + with
|
|
| 4074 | + effect = ReadWriteEffect
|
|
| 4075 | + out_of_line = True
|
|
| 4076 | + |
|
| 4068 | 4077 | primtype StackSnapshot#
|
| 4069 | 4078 | { Haskell representation of a @StgStack*@ that was created (cloned)
|
| 4070 | 4079 | with a function in "GHC.Stack.CloneStack". Please check the
|
| ... | ... | @@ -1775,6 +1775,7 @@ emitPrimOp cfg primop = |
| 1775 | 1775 | TraceEventBinaryOp -> alwaysExternal
|
| 1776 | 1776 | TraceMarkerOp -> alwaysExternal
|
| 1777 | 1777 | SetThreadAllocationCounter -> alwaysExternal
|
| 1778 | + SetOtherThreadAllocationCounter -> alwaysExternal
|
|
| 1778 | 1779 | KeepAliveOp -> alwaysExternal
|
| 1779 | 1780 | |
| 1780 | 1781 | where
|
| ... | ... | @@ -1173,6 +1173,7 @@ genPrim prof bound ty op = case op of |
| 1173 | 1173 | WhereFromOp -> unhandledPrimop op -- should be easily implementable with o.f.n
|
| 1174 | 1174 | |
| 1175 | 1175 | SetThreadAllocationCounter -> unhandledPrimop op
|
| 1176 | + SetOtherThreadAllocationCounter -> unhandledPrimop op
|
|
| 1176 | 1177 | |
| 1177 | 1178 | ------------------------------- Vector -----------------------------------------
|
| 1178 | 1179 | -- For now, vectors are unsupported on the JS backend. Simply put, they do not
|
| ... | ... | @@ -38,6 +38,7 @@ library |
| 38 | 38 | GHC.RTS.Flags.Experimental
|
| 39 | 39 | GHC.Stats.Experimental
|
| 40 | 40 | Prelude.Experimental
|
| 41 | + System.Mem.Experimental
|
|
| 41 | 42 | if arch(wasm32)
|
| 42 | 43 | exposed-modules: GHC.Wasm.Prim
|
| 43 | 44 | other-extensions:
|
| 1 | +module System.Mem.Experimental
|
|
| 2 | + ( setGlobalAllocationLimitHandler
|
|
| 3 | + , AllocationLimitKillBehaviour(..)
|
|
| 4 | + , getAllocationCounterFor
|
|
| 5 | + , setAllocationCounterFor
|
|
| 6 | + , enableAllocationLimitFor
|
|
| 7 | + , disableAllocationLimitFor
|
|
| 8 | + )
|
|
| 9 | + where
|
|
| 10 | +import GHC.Internal.AllocationLimitHandler |
| ... | ... | @@ -122,6 +122,7 @@ Library |
| 122 | 122 | rts == 1.0.*
|
| 123 | 123 | |
| 124 | 124 | exposed-modules:
|
| 125 | + GHC.Internal.AllocationLimitHandler
|
|
| 125 | 126 | GHC.Internal.ClosureTypes
|
| 126 | 127 | GHC.Internal.Control.Arrow
|
| 127 | 128 | GHC.Internal.Control.Category
|
| 1 | +{-# LANGUAGE MagicHash #-}
|
|
| 2 | +{-# LANGUAGE UnboxedTuples #-}
|
|
| 3 | +{-# LANGUAGE UnliftedFFITypes #-}
|
|
| 4 | +{-# LANGUAGE GHCForeignImportPrim #-}
|
|
| 5 | +{-# OPTIONS_HADDOCK not-home #-}
|
|
| 6 | +module GHC.Internal.AllocationLimitHandler
|
|
| 7 | + ( runAllocationLimitHandler
|
|
| 8 | + , setGlobalAllocationLimitHandler
|
|
| 9 | + , AllocationLimitKillBehaviour(..)
|
|
| 10 | + , getAllocationCounterFor
|
|
| 11 | + , setAllocationCounterFor
|
|
| 12 | + , enableAllocationLimitFor
|
|
| 13 | + , disableAllocationLimitFor
|
|
| 14 | + )
|
|
| 15 | + where
|
|
| 16 | +import GHC.Internal.Base
|
|
| 17 | +import GHC.Internal.Conc.Sync (ThreadId(..))
|
|
| 18 | +import GHC.Internal.Data.IORef (IORef, readIORef, writeIORef, newIORef)
|
|
| 19 | +import GHC.Internal.Foreign.C.Types
|
|
| 20 | +import GHC.Internal.IO (unsafePerformIO)
|
|
| 21 | +import GHC.Internal.Int (Int64(..))
|
|
| 22 | + |
|
| 23 | + |
|
| 24 | +{-# NOINLINE allocationLimitHandler #-}
|
|
| 25 | +allocationLimitHandler :: IORef (ThreadId -> IO ())
|
|
| 26 | +allocationLimitHandler = unsafePerformIO (newIORef defaultHandler)
|
|
| 27 | + |
|
| 28 | +defaultHandler :: ThreadId -> IO ()
|
|
| 29 | +defaultHandler _ = pure ()
|
|
| 30 | + |
|
| 31 | +foreign import ccall "setAllocLimitKill" setAllocLimitKill :: CBool -> CBool -> IO ()
|
|
| 32 | + |
|
| 33 | +runAllocationLimitHandler :: ThreadId# -> IO ()
|
|
| 34 | +runAllocationLimitHandler tid = do
|
|
| 35 | + hook <- getAllocationLimitHandler
|
|
| 36 | + hook $ ThreadId tid
|
|
| 37 | + |
|
| 38 | +getAllocationLimitHandler :: IO (ThreadId -> IO ())
|
|
| 39 | +getAllocationLimitHandler = readIORef allocationLimitHandler
|
|
| 40 | + |
|
| 41 | +data AllocationLimitKillBehaviour =
|
|
| 42 | + KillOnAllocationLimit
|
|
| 43 | + -- ^ Throw a @AllocationLimitExceeded@ async exception to the thread when the
|
|
| 44 | + -- allocation limit is exceeded.
|
|
| 45 | + | DontKillOnAllocationLimit
|
|
| 46 | + -- ^ Do not throw an exception when the allocation limit is exceeded.
|
|
| 47 | + |
|
| 48 | +-- | Define the behaviour for handling allocation limits.
|
|
| 49 | +-- By default we throw a @AllocationLimitExceeded@ async exception to the thread.
|
|
| 50 | +-- This can be controlled using @AllocationLimitKillBehaviour@.
|
|
| 51 | +--
|
|
| 52 | +-- We can also run a user-specified handler, which can be done in addition to
|
|
| 53 | +-- or in place of the exception.
|
|
| 54 | +-- This allows for instance logging on the allocation limit being exceeded,
|
|
| 55 | +-- or dynamically determining whether to terminate the thread.
|
|
| 56 | +-- The handler is not guaranteed to run before the thread is terminated or restarted.
|
|
| 57 | +--
|
|
| 58 | +-- Note: that if you don't terminate the thread, then the allocation limit gets
|
|
| 59 | +-- removed.
|
|
| 60 | +-- If you wish to keep the allocation limit you will have to reset it using
|
|
| 61 | +-- @setAllocationCounter@ and @enableAllocationLimit@.
|
|
| 62 | +setGlobalAllocationLimitHandler :: AllocationLimitKillBehaviour -> Maybe (ThreadId -> IO ()) -> IO ()
|
|
| 63 | +setGlobalAllocationLimitHandler killBehaviour mHandler = do
|
|
| 64 | + shouldRunHandler <- case mHandler of
|
|
| 65 | + Just hook -> do
|
|
| 66 | + writeIORef allocationLimitHandler hook
|
|
| 67 | + pure 1
|
|
| 68 | + Nothing -> do
|
|
| 69 | + writeIORef allocationLimitHandler defaultHandler
|
|
| 70 | + pure 0
|
|
| 71 | + let shouldKill =
|
|
| 72 | + case killBehaviour of
|
|
| 73 | + KillOnAllocationLimit -> 1
|
|
| 74 | + DontKillOnAllocationLimit -> 0
|
|
| 75 | + setAllocLimitKill shouldKill shouldRunHandler
|
|
| 76 | + |
|
| 77 | +-- | Retrieves the allocation counter for the another thread.
|
|
| 78 | +foreign import prim "stg_getOtherThreadAllocationCounterzh" getOtherThreadAllocationCounter#
|
|
| 79 | + :: ThreadId#
|
|
| 80 | + -> State# RealWorld
|
|
| 81 | + -> (# State# RealWorld, Int64# #)
|
|
| 82 | + |
|
| 83 | +-- | Get the allocation counter for a different thread.
|
|
| 84 | +-- Note this doesn't take the current nursery chunk into account.
|
|
| 85 | +-- If the thread is running then it may underestimate allocations by the size of a nursery thread.
|
|
| 86 | +getAllocationCounterFor :: ThreadId -> IO Int64
|
|
| 87 | +getAllocationCounterFor (ThreadId t#) = IO $ \s ->
|
|
| 88 | + case getOtherThreadAllocationCounter# t# s of (# s', i# #) -> (# s', I64# i# #)
|
|
| 89 | + |
|
| 90 | +-- | Set the allocation counter for a different thread.
|
|
| 91 | +-- This can be combined with 'enableAllocationLimitFor' to enable allocation limits for another thread.
|
|
| 92 | +-- Note this doesn't take the current nursery chunk into account.
|
|
| 93 | +-- If the thread is running then it may overestimate allocations by the size of a nursery thread,
|
|
| 94 | +-- and trigger the limit sooner than expected.
|
|
| 95 | +setAllocationCounterFor :: Int64 -> ThreadId -> IO ()
|
|
| 96 | +setAllocationCounterFor (I64# i#) (ThreadId t#) = IO $ \s ->
|
|
| 97 | + case setOtherThreadAllocationCounter# i# t# s of s' -> (# s', () #)
|
|
| 98 | + |
|
| 99 | + |
|
| 100 | +-- | Enable allocation limit processing the thread @t@.
|
|
| 101 | +enableAllocationLimitFor :: ThreadId -> IO ()
|
|
| 102 | +enableAllocationLimitFor (ThreadId t) = do
|
|
| 103 | + rts_enableThreadAllocationLimit t
|
|
| 104 | + |
|
| 105 | +-- | Disable allocation limit processing the thread @t@.
|
|
| 106 | +disableAllocationLimitFor :: ThreadId -> IO ()
|
|
| 107 | +disableAllocationLimitFor (ThreadId t) = do
|
|
| 108 | + rts_disableThreadAllocationLimit t
|
|
| 109 | + |
|
| 110 | +foreign import ccall unsafe "rts_enableThreadAllocationLimit"
|
|
| 111 | + rts_enableThreadAllocationLimit :: ThreadId# -> IO ()
|
|
| 112 | + |
|
| 113 | +foreign import ccall unsafe "rts_disableThreadAllocationLimit"
|
|
| 114 | + rts_disableThreadAllocationLimit :: ThreadId# -> IO () |
| ... | ... | @@ -67,6 +67,7 @@ PRELUDE_CLOSURE(ghczminternal_GHCziInternalziEventziWindows_processRemoteComplet |
| 67 | 67 | |
| 68 | 68 | PRELUDE_CLOSURE(ghczminternal_GHCziInternalziTopHandler_flushStdHandles_closure);
|
| 69 | 69 | PRELUDE_CLOSURE(ghczminternal_GHCziInternalziTopHandler_runMainIO_closure);
|
| 70 | +PRELUDE_CLOSURE(ghczminternal_GHCziInternalziAllocationLimitHandler_runAllocationLimitHandler_closure);
|
|
| 70 | 71 | |
| 71 | 72 | PRELUDE_INFO(ghczminternal_GHCziInternalziCString_unpackCStringzh_info);
|
| 72 | 73 | PRELUDE_INFO(ghczminternal_GHCziInternalziTypes_Czh_con_info);
|
| ... | ... | @@ -102,6 +103,7 @@ PRELUDE_INFO(ghczminternal_GHCziInternalziStable_StablePtr_con_info); |
| 102 | 103 | #if defined(mingw32_HOST_OS)
|
| 103 | 104 | #define processRemoteCompletion_closure DLL_IMPORT_DATA_REF(ghczminternal_GHCziInternalziEventziWindows_processRemoteCompletion_closure)
|
| 104 | 105 | #endif
|
| 106 | +#define runAllocationLimitHandler_closure DLL_IMPORT_DATA_REF(ghczminternal_GHCziInternalziAllocationLimitHandler_runAllocationLimitHandler_closure)
|
|
| 105 | 107 | |
| 106 | 108 | #define flushStdHandles_closure DLL_IMPORT_DATA_REF(ghczminternal_GHCziInternalziTopHandler_flushStdHandles_closure)
|
| 107 | 109 | #define runMainIO_closure DLL_IMPORT_DATA_REF(ghczminternal_GHCziInternalziTopHandler_runMainIO_closure)
|
| ... | ... | @@ -2889,6 +2889,11 @@ stg_getThreadAllocationCounterzh () |
| 2889 | 2889 | return (StgTSO_alloc_limit(CurrentTSO) - TO_I64(offset));
|
| 2890 | 2890 | }
|
| 2891 | 2891 | |
| 2892 | +stg_getOtherThreadAllocationCounterzh ( gcptr t )
|
|
| 2893 | +{
|
|
| 2894 | + return (StgTSO_alloc_limit(t));
|
|
| 2895 | +}
|
|
| 2896 | + |
|
| 2892 | 2897 | stg_setThreadAllocationCounterzh ( I64 counter )
|
| 2893 | 2898 | {
|
| 2894 | 2899 | // Allocation in the current block will be subtracted by
|
| ... | ... | @@ -2901,6 +2906,12 @@ stg_setThreadAllocationCounterzh ( I64 counter ) |
| 2901 | 2906 | return ();
|
| 2902 | 2907 | }
|
| 2903 | 2908 | |
| 2909 | +stg_setOtherThreadAllocationCounterzh ( I64 counter, gcptr t )
|
|
| 2910 | +{
|
|
| 2911 | + StgTSO_alloc_limit(t) = counter;
|
|
| 2912 | + return ();
|
|
| 2913 | +}
|
|
| 2914 | + |
|
| 2904 | 2915 | |
| 2905 | 2916 | #define KEEP_ALIVE_FRAME_FIELDS(w_,p_,info_ptr,p1,p2,c) \
|
| 2906 | 2917 | w_ info_ptr, \
|
| ... | ... | @@ -224,6 +224,7 @@ static void initBuiltinGcRoots(void) |
| 224 | 224 | * GHC.Core.Make.mkExceptionId.
|
| 225 | 225 | */
|
| 226 | 226 | getStablePtr((StgPtr)absentSumFieldError_closure);
|
| 227 | + getStablePtr((StgPtr)runAllocationLimitHandler_closure);
|
|
| 227 | 228 | }
|
| 228 | 229 | |
| 229 | 230 | void
|
| ... | ... | @@ -916,7 +916,9 @@ extern char **environ; |
| 916 | 916 | SymI_HasDataProto(stg_traceMarkerzh) \
|
| 917 | 917 | SymI_HasDataProto(stg_traceBinaryEventzh) \
|
| 918 | 918 | SymI_HasDataProto(stg_getThreadAllocationCounterzh) \
|
| 919 | + SymI_HasDataProto(stg_getOtherThreadAllocationCounterzh) \
|
|
| 919 | 920 | SymI_HasDataProto(stg_setThreadAllocationCounterzh) \
|
| 921 | + SymI_HasDataProto(stg_setOtherThreadAllocationCounterzh) \
|
|
| 920 | 922 | SymI_HasProto(getMonotonicNSec) \
|
| 921 | 923 | SymI_HasProto(lockFile) \
|
| 922 | 924 | SymI_HasProto(unlockFile) \
|
| ... | ... | @@ -41,6 +41,7 @@ |
| 41 | 41 | #include "Threads.h"
|
| 42 | 42 | #include "Timer.h"
|
| 43 | 43 | #include "ThreadPaused.h"
|
| 44 | +#include "ThreadLabels.h"
|
|
| 44 | 45 | #include "Messages.h"
|
| 45 | 46 | #include "StablePtr.h"
|
| 46 | 47 | #include "StableName.h"
|
| ... | ... | @@ -94,6 +95,10 @@ StgWord recent_activity = ACTIVITY_YES; |
| 94 | 95 | */
|
| 95 | 96 | StgWord sched_state = SCHED_RUNNING;
|
| 96 | 97 | |
| 98 | + |
|
| 99 | +bool allocLimitKill = true;
|
|
| 100 | +bool allocLimitRunHook = false;
|
|
| 101 | + |
|
| 97 | 102 | /*
|
| 98 | 103 | * This mutex protects most of the global scheduler data in
|
| 99 | 104 | * the THREADED_RTS runtime.
|
| ... | ... | @@ -1125,19 +1130,36 @@ schedulePostRunThread (Capability *cap, StgTSO *t) |
| 1125 | 1130 | }
|
| 1126 | 1131 | }
|
| 1127 | 1132 | |
| 1128 | - //
|
|
| 1129 | - // If the current thread's allocation limit has run out, send it
|
|
| 1130 | - // the AllocationLimitExceeded exception.
|
|
| 1133 | + // Handle the current thread's allocation limit running out,
|
|
| 1131 | 1134 | |
| 1132 | 1135 | if (PK_Int64((W_*)&(t->alloc_limit)) < 0 && (t->flags & TSO_ALLOC_LIMIT)) {
|
| 1133 | - // Use a throwToSelf rather than a throwToSingleThreaded, because
|
|
| 1134 | - // it correctly handles the case where the thread is currently
|
|
| 1135 | - // inside mask. Also the thread might be blocked (e.g. on an
|
|
| 1136 | - // MVar), and throwToSingleThreaded doesn't unblock it
|
|
| 1137 | - // correctly in that case.
|
|
| 1138 | - throwToSelf(cap, t, allocationLimitExceeded_closure);
|
|
| 1139 | - ASSIGN_Int64((W_*)&(t->alloc_limit),
|
|
| 1140 | - (StgInt64)RtsFlags.GcFlags.allocLimitGrace * BLOCK_SIZE);
|
|
| 1136 | + if(allocLimitKill) {
|
|
| 1137 | + // Throw the AllocationLimitExceeded exception.
|
|
| 1138 | + // Use a throwToSelf rather than a throwToSingleThreaded, because
|
|
| 1139 | + // it correctly handles the case where the thread is currently
|
|
| 1140 | + // inside mask. Also the thread might be blocked (e.g. on an
|
|
| 1141 | + // MVar), and throwToSingleThreaded doesn't unblock it
|
|
| 1142 | + // correctly in that case.
|
|
| 1143 | + throwToSelf(cap, t, allocationLimitExceeded_closure);
|
|
| 1144 | + ASSIGN_Int64((W_*)&(t->alloc_limit),
|
|
| 1145 | + (StgInt64)RtsFlags.GcFlags.allocLimitGrace * BLOCK_SIZE);
|
|
| 1146 | + } else {
|
|
| 1147 | + // If we aren't killing the thread, we must disable the limit
|
|
| 1148 | + // otherwise we will immediatelly retrigger it.
|
|
| 1149 | + // User defined handlers should re-enable it if wanted.
|
|
| 1150 | + t->flags = t->flags & ~TSO_ALLOC_LIMIT;
|
|
| 1151 | + }
|
|
| 1152 | + |
|
| 1153 | + if(allocLimitRunHook)
|
|
| 1154 | + {
|
|
| 1155 | + // Create a thread to run the allocation limit handler.
|
|
| 1156 | + StgClosure* c = rts_apply(cap, runAllocationLimitHandler_closure, (StgClosure*)t);
|
|
| 1157 | + StgTSO* hookThread = createIOThread(cap, RtsFlags.GcFlags.initialStkSize, c);
|
|
| 1158 | + setThreadLabel(cap, hookThread, "allocation limit handler thread");
|
|
| 1159 | + // Schedule the handler to be run immediatelly.
|
|
| 1160 | + pushOnRunQueue(cap, hookThread);
|
|
| 1161 | + }
|
|
| 1162 | + |
|
| 1141 | 1163 | }
|
| 1142 | 1164 | |
| 1143 | 1165 | /* some statistics gathering in the parallel case */
|
| ... | ... | @@ -3342,3 +3364,9 @@ resurrectThreads (StgTSO *threads) |
| 3342 | 3364 | }
|
| 3343 | 3365 | }
|
| 3344 | 3366 | }
|
| 3367 | + |
|
| 3368 | +void setAllocLimitKill(bool shouldKill, bool shouldHook)
|
|
| 3369 | +{
|
|
| 3370 | + allocLimitKill = shouldKill;
|
|
| 3371 | + allocLimitRunHook = shouldHook;
|
|
| 3372 | +} |
| ... | ... | @@ -43,6 +43,7 @@ ghczminternal_GHCziInternalziTypes_Izh_con_info |
| 43 | 43 | ghczminternal_GHCziInternalziTypes_Fzh_con_info
|
| 44 | 44 | ghczminternal_GHCziInternalziTypes_Dzh_con_info
|
| 45 | 45 | ghczminternal_GHCziInternalziTypes_Wzh_con_info
|
| 46 | +ghczminternal_GHCziInternalziAllocationLimitHandler_runAllocationLimitHandler_closure
|
|
| 46 | 47 | ghczminternal_GHCziInternalziPtr_Ptr_con_info
|
| 47 | 48 | ghczminternal_GHCziInternalziPtr_FunPtr_con_info
|
| 48 | 49 | ghczminternal_GHCziInternalziInt_I8zh_con_info
|
| ... | ... | @@ -209,6 +209,10 @@ void flushExec(W_ len, AdjustorExecutable exec_addr); |
| 209 | 209 | // Used by GC checks in external .cmm code:
|
| 210 | 210 | extern W_ large_alloc_lim;
|
| 211 | 211 | |
| 212 | +// Should triggering an allocation limit kill the thread
|
|
| 213 | +// and should we run a user-defined hook when it is triggered.
|
|
| 214 | +void setAllocLimitKill(bool, bool);
|
|
| 215 | + |
|
| 212 | 216 | /* -----------------------------------------------------------------------------
|
| 213 | 217 | Performing Garbage Collection
|
| 214 | 218 | -------------------------------------------------------------------------- */
|
| ... | ... | @@ -157,9 +157,10 @@ typedef struct StgTSO_ { |
| 157 | 157 | /*
|
| 158 | 158 | * The allocation limit for this thread, which is updated as the
|
| 159 | 159 | * thread allocates. If the value drops below zero, and
|
| 160 | - * TSO_ALLOC_LIMIT is set in flags, we raise an exception in the
|
|
| 161 | - * thread, and give the thread a little more space to handle the
|
|
| 162 | - * exception before we raise the exception again.
|
|
| 160 | + * TSO_ALLOC_LIMIT is set in flags, then a handler is triggerd.
|
|
| 161 | + * Either we raise an exception in the thread, and give the thread
|
|
| 162 | + * a little more space to handle the exception before we raise the
|
|
| 163 | + * exception again; or we run a user defined handler.
|
|
| 163 | 164 | *
|
| 164 | 165 | * This is an integer, because we might update it in a place where
|
| 165 | 166 | * it isn't convenient to raise the exception, so we want it to
|
| ... | ... | @@ -604,7 +604,9 @@ RTS_FUN_DECL(stg_traceEventzh); |
| 604 | 604 | RTS_FUN_DECL(stg_traceBinaryEventzh);
|
| 605 | 605 | RTS_FUN_DECL(stg_traceMarkerzh);
|
| 606 | 606 | RTS_FUN_DECL(stg_getThreadAllocationCounterzh);
|
| 607 | +RTS_FUN_DECL(stg_getOtherThreadAllocationCounterzh);
|
|
| 607 | 608 | RTS_FUN_DECL(stg_setThreadAllocationCounterzh);
|
| 609 | +RTS_FUN_DECL(stg_setOtherThreadAllocationCounterzh);
|
|
| 608 | 610 | |
| 609 | 611 | RTS_FUN_DECL(stg_castWord64ToDoublezh);
|
| 610 | 612 | RTS_FUN_DECL(stg_castDoubleToWord64zh);
|
| ... | ... | @@ -4607,6 +4607,7 @@ module GHC.Base where |
| 4607 | 4607 | sequence :: forall (m :: * -> *) a. Monad m => [m a] -> m [a]
|
| 4608 | 4608 | setAddrRange# :: Addr# -> Int# -> Int# -> State# RealWorld -> State# RealWorld
|
| 4609 | 4609 | setByteArray# :: forall d. MutableByteArray# d -> Int# -> Int# -> Int# -> State# d -> State# d
|
| 4610 | + setOtherThreadAllocationCounter# :: Int64# -> ThreadId# -> State# RealWorld -> State# RealWorld
|
|
| 4610 | 4611 | setThreadAllocationCounter# :: Int64# -> State# RealWorld -> State# RealWorld
|
| 4611 | 4612 | shiftL# :: Word# -> Int# -> Word#
|
| 4612 | 4613 | shiftRL# :: Word# -> Int# -> Word#
|
| ... | ... | @@ -6693,6 +6694,7 @@ module GHC.Exts where |
| 6693 | 6694 | seq# :: forall a s. a -> State# s -> (# State# s, a #)
|
| 6694 | 6695 | setAddrRange# :: Addr# -> Int# -> Int# -> State# RealWorld -> State# RealWorld
|
| 6695 | 6696 | setByteArray# :: forall d. MutableByteArray# d -> Int# -> Int# -> Int# -> State# d -> State# d
|
| 6697 | + setOtherThreadAllocationCounter# :: Int64# -> ThreadId# -> State# RealWorld -> State# RealWorld
|
|
| 6696 | 6698 | setThreadAllocationCounter# :: Int64# -> State# RealWorld -> State# RealWorld
|
| 6697 | 6699 | shiftL# :: Word# -> Int# -> Word#
|
| 6698 | 6700 | shiftRL# :: Word# -> Int# -> Word#
|
| ... | ... | @@ -4607,6 +4607,7 @@ module GHC.Base where |
| 4607 | 4607 | sequence :: forall (m :: * -> *) a. Monad m => [m a] -> m [a]
|
| 4608 | 4608 | setAddrRange# :: Addr# -> Int# -> Int# -> State# RealWorld -> State# RealWorld
|
| 4609 | 4609 | setByteArray# :: forall d. MutableByteArray# d -> Int# -> Int# -> Int# -> State# d -> State# d
|
| 4610 | + setOtherThreadAllocationCounter# :: Int64# -> ThreadId# -> State# RealWorld -> State# RealWorld
|
|
| 4610 | 4611 | setThreadAllocationCounter# :: Int64# -> State# RealWorld -> State# RealWorld
|
| 4611 | 4612 | shiftL# :: Word# -> Int# -> Word#
|
| 4612 | 4613 | shiftRL# :: Word# -> Int# -> Word#
|
| ... | ... | @@ -6665,6 +6666,7 @@ module GHC.Exts where |
| 6665 | 6666 | seq# :: forall a s. a -> State# s -> (# State# s, a #)
|
| 6666 | 6667 | setAddrRange# :: Addr# -> Int# -> Int# -> State# RealWorld -> State# RealWorld
|
| 6667 | 6668 | setByteArray# :: forall d. MutableByteArray# d -> Int# -> Int# -> Int# -> State# d -> State# d
|
| 6669 | + setOtherThreadAllocationCounter# :: Int64# -> ThreadId# -> State# RealWorld -> State# RealWorld
|
|
| 6668 | 6670 | setThreadAllocationCounter# :: Int64# -> State# RealWorld -> State# RealWorld
|
| 6669 | 6671 | shiftL# :: Word# -> Int# -> Word#
|
| 6670 | 6672 | shiftRL# :: Word# -> Int# -> Word#
|
| ... | ... | @@ -4610,6 +4610,7 @@ module GHC.Base where |
| 4610 | 4610 | sequence :: forall (m :: * -> *) a. Monad m => [m a] -> m [a]
|
| 4611 | 4611 | setAddrRange# :: Addr# -> Int# -> Int# -> State# RealWorld -> State# RealWorld
|
| 4612 | 4612 | setByteArray# :: forall d. MutableByteArray# d -> Int# -> Int# -> Int# -> State# d -> State# d
|
| 4613 | + setOtherThreadAllocationCounter# :: Int64# -> ThreadId# -> State# RealWorld -> State# RealWorld
|
|
| 4613 | 4614 | setThreadAllocationCounter# :: Int64# -> State# RealWorld -> State# RealWorld
|
| 4614 | 4615 | shiftL# :: Word# -> Int# -> Word#
|
| 4615 | 4616 | shiftRL# :: Word# -> Int# -> Word#
|
| ... | ... | @@ -6836,6 +6837,7 @@ module GHC.Exts where |
| 6836 | 6837 | seq# :: forall a s. a -> State# s -> (# State# s, a #)
|
| 6837 | 6838 | setAddrRange# :: Addr# -> Int# -> Int# -> State# RealWorld -> State# RealWorld
|
| 6838 | 6839 | setByteArray# :: forall d. MutableByteArray# d -> Int# -> Int# -> Int# -> State# d -> State# d
|
| 6840 | + setOtherThreadAllocationCounter# :: Int64# -> ThreadId# -> State# RealWorld -> State# RealWorld
|
|
| 6839 | 6841 | setThreadAllocationCounter# :: Int64# -> State# RealWorld -> State# RealWorld
|
| 6840 | 6842 | shiftL# :: Word# -> Int# -> Word#
|
| 6841 | 6843 | shiftRL# :: Word# -> Int# -> Word#
|
| ... | ... | @@ -4607,6 +4607,7 @@ module GHC.Base where |
| 4607 | 4607 | sequence :: forall (m :: * -> *) a. Monad m => [m a] -> m [a]
|
| 4608 | 4608 | setAddrRange# :: Addr# -> Int# -> Int# -> State# RealWorld -> State# RealWorld
|
| 4609 | 4609 | setByteArray# :: forall d. MutableByteArray# d -> Int# -> Int# -> Int# -> State# d -> State# d
|
| 4610 | + setOtherThreadAllocationCounter# :: Int64# -> ThreadId# -> State# RealWorld -> State# RealWorld
|
|
| 4610 | 4611 | setThreadAllocationCounter# :: Int64# -> State# RealWorld -> State# RealWorld
|
| 4611 | 4612 | shiftL# :: Word# -> Int# -> Word#
|
| 4612 | 4613 | shiftRL# :: Word# -> Int# -> Word#
|
| ... | ... | @@ -6693,6 +6694,7 @@ module GHC.Exts where |
| 6693 | 6694 | seq# :: forall a s. a -> State# s -> (# State# s, a #)
|
| 6694 | 6695 | setAddrRange# :: Addr# -> Int# -> Int# -> State# RealWorld -> State# RealWorld
|
| 6695 | 6696 | setByteArray# :: forall d. MutableByteArray# d -> Int# -> Int# -> Int# -> State# d -> State# d
|
| 6697 | + setOtherThreadAllocationCounter# :: Int64# -> ThreadId# -> State# RealWorld -> State# RealWorld
|
|
| 6696 | 6698 | setThreadAllocationCounter# :: Int64# -> State# RealWorld -> State# RealWorld
|
| 6697 | 6699 | shiftL# :: Word# -> Int# -> Word#
|
| 6698 | 6700 | shiftRL# :: Word# -> Int# -> Word#
|
| ... | ... | @@ -10916,6 +10916,16 @@ module Prelude.Experimental where |
| 10916 | 10916 | data Unit# = ...
|
| 10917 | 10917 | getSolo :: forall a. Solo a -> a
|
| 10918 | 10918 | |
| 10919 | +module System.Mem.Experimental where
|
|
| 10920 | + -- Safety: None
|
|
| 10921 | + type AllocationLimitKillBehaviour :: *
|
|
| 10922 | + data AllocationLimitKillBehaviour = KillOnAllocationLimit | DontKillOnAllocationLimit
|
|
| 10923 | + disableAllocationLimitFor :: GHC.Internal.Conc.Sync.ThreadId -> GHC.Types.IO ()
|
|
| 10924 | + enableAllocationLimitFor :: GHC.Internal.Conc.Sync.ThreadId -> GHC.Types.IO ()
|
|
| 10925 | + getAllocationCounterFor :: GHC.Internal.Conc.Sync.ThreadId -> GHC.Types.IO GHC.Internal.Int.Int64
|
|
| 10926 | + setAllocationCounterFor :: GHC.Internal.Int.Int64 -> GHC.Internal.Conc.Sync.ThreadId -> GHC.Types.IO ()
|
|
| 10927 | + setGlobalAllocationLimitHandler :: AllocationLimitKillBehaviour -> GHC.Internal.Maybe.Maybe (GHC.Internal.Conc.Sync.ThreadId -> GHC.Types.IO ()) -> GHC.Types.IO ()
|
|
| 10928 | + |
|
| 10919 | 10929 | |
| 10920 | 10930 | -- Instances:
|
| 10921 | 10931 | instance GHC.Internal.Base.Alternative GHC.Internal.Types.IO -- Defined in ‘GHC.Internal.Base’
|
| 1 | +{-# LANGUAGE NumericUnderscores #-}
|
|
| 2 | +{-# LANGUAGE MagicHash #-}
|
|
| 3 | +{-# LANGUAGE UnboxedTuples #-}
|
|
| 4 | + |
|
| 5 | +import Control.Exception
|
|
| 6 | +import Control.Exception.Backtrace
|
|
| 7 | +import Control.Concurrent
|
|
| 8 | +import Control.Concurrent.MVar
|
|
| 9 | +import System.Mem
|
|
| 10 | +import System.Mem.Experimental
|
|
| 11 | +import GHC.IO (IO (..))
|
|
| 12 | +import GHC.Exts
|
|
| 13 | +import System.IO
|
|
| 14 | + |
|
| 15 | +-- | Just do some work and hPutStrLn to stderr to indicate that we are making progress
|
|
| 16 | +worker :: IO ()
|
|
| 17 | +worker = loop [] 2
|
|
| 18 | + where
|
|
| 19 | + loop !m !n
|
|
| 20 | + | n > 30 = hPutStrLn stderr . show $ length m
|
|
| 21 | + | otherwise = do
|
|
| 22 | + let x = show n
|
|
| 23 | + hPutStrLn stderr x
|
|
| 24 | + -- just to bulk out the allocations
|
|
| 25 | + IO (\s -> case newByteArray# 900000# s of (# s', arr# #) -> (# s', () #))
|
|
| 26 | + yield
|
|
| 27 | + loop (x:m) (n + 1)
|
|
| 28 | + |
|
| 29 | +main :: IO ()
|
|
| 30 | +main = do
|
|
| 31 | + done <- newMVar () -- we use this lock to wait for the worker to finish
|
|
| 32 | + started <- newEmptyMVar
|
|
| 33 | + let runWorker = do
|
|
| 34 | + forkIO . withMVar done $ \_ -> flip onException (hPutStrLn stderr "worker died") $ do
|
|
| 35 | + hPutStrLn stderr "worker starting"
|
|
| 36 | + putMVar started ()
|
|
| 37 | + setAllocationCounter 1_000_000
|
|
| 38 | + enableAllocationLimit
|
|
| 39 | + worker
|
|
| 40 | + hPutStrLn stderr "worker done"
|
|
| 41 | + takeMVar started
|
|
| 42 | + readMVar done
|
|
| 43 | + hFlush stderr
|
|
| 44 | + threadDelay 1000
|
|
| 45 | + -- default behaviour:
|
|
| 46 | + -- kill it after the limit is exceeded
|
|
| 47 | + hPutStrLn stderr "default behaviour"
|
|
| 48 | + runWorker
|
|
| 49 | + hPutStrLn stderr "just log once on the hook being triggered"
|
|
| 50 | + setGlobalAllocationLimitHandler DontKillOnAllocationLimit (Just $ \_ -> hPutStrLn stderr "allocation limit triggered 1")
|
|
| 51 | + runWorker
|
|
| 52 | + hPutStrLn stderr "just log on the hook being triggered"
|
|
| 53 | + setGlobalAllocationLimitHandler DontKillOnAllocationLimit . Just $ \tid -> do
|
|
| 54 | + hPutStrLn stderr "allocation limit triggered 2"
|
|
| 55 | + -- re-enable the hook
|
|
| 56 | + setAllocationCounterFor 1_000_000 tid
|
|
| 57 | + enableAllocationLimitFor tid
|
|
| 58 | + runWorker
|
|
| 59 | + hPutStrLn stderr "kill from the hook"
|
|
| 60 | + setGlobalAllocationLimitHandler DontKillOnAllocationLimit . Just $ \tId -> throwTo tId AllocationLimitExceeded
|
|
| 61 | + runWorker
|
|
| 62 | + -- not super helpful, but let's test it anyway
|
|
| 63 | + hPutStrLn stderr "do nothing"
|
|
| 64 | + setGlobalAllocationLimitHandler DontKillOnAllocationLimit Nothing
|
|
| 65 | + runWorker
|
|
| 66 | + -- this is possible to handle using an exception handler instead.
|
|
| 67 | + hPutStrLn stderr "kill and log"
|
|
| 68 | + setGlobalAllocationLimitHandler KillOnAllocationLimit (Just $ \_ -> hPutStrLn stderr "allocation limit triggered 3")
|
|
| 69 | + runWorker
|
|
| 70 | + threadDelay 1000
|
|
| 71 | + hPutStrLn stderr "done" |
| 1 | +default behaviour
|
|
| 2 | +worker starting
|
|
| 3 | +2
|
|
| 4 | +3
|
|
| 5 | +worker died
|
|
| 6 | +T22859: allocation limit exceeded
|
|
| 7 | +HasCallStack backtrace:
|
|
| 8 | + collectBacktraces, called at libraries/ghc-internal/src/GHC/Internal/Exception.hs:92:13 in ghc-internal:GHC.Internal.Exception
|
|
| 9 | + toExceptionWithBacktrace, called at libraries/ghc-internal/src/GHC/Internal/IO.hs:260:11 in ghc-internal:GHC.Internal.IO
|
|
| 10 | + throwIO, called at libraries/ghc-internal/src/GHC/Internal/Control/Exception/Base.hs:195:43 in ghc-internal:GHC.Internal.Control.Exception.Base
|
|
| 11 | + |
|
| 12 | + |
|
| 13 | +just log once on the hook being triggered
|
|
| 14 | +worker starting
|
|
| 15 | +2
|
|
| 16 | +3
|
|
| 17 | +allocation limit triggered 1
|
|
| 18 | +4
|
|
| 19 | +5
|
|
| 20 | +6
|
|
| 21 | +7
|
|
| 22 | +8
|
|
| 23 | +9
|
|
| 24 | +10
|
|
| 25 | +11
|
|
| 26 | +12
|
|
| 27 | +13
|
|
| 28 | +14
|
|
| 29 | +15
|
|
| 30 | +16
|
|
| 31 | +17
|
|
| 32 | +18
|
|
| 33 | +19
|
|
| 34 | +20
|
|
| 35 | +21
|
|
| 36 | +22
|
|
| 37 | +23
|
|
| 38 | +24
|
|
| 39 | +25
|
|
| 40 | +26
|
|
| 41 | +27
|
|
| 42 | +28
|
|
| 43 | +29
|
|
| 44 | +30
|
|
| 45 | +29
|
|
| 46 | +worker done
|
|
| 47 | +just log on the hook being triggered
|
|
| 48 | +worker starting
|
|
| 49 | +2
|
|
| 50 | +3
|
|
| 51 | +allocation limit triggered 2
|
|
| 52 | +4
|
|
| 53 | +5
|
|
| 54 | +allocation limit triggered 2
|
|
| 55 | +6
|
|
| 56 | +7
|
|
| 57 | +allocation limit triggered 2
|
|
| 58 | +8
|
|
| 59 | +9
|
|
| 60 | +allocation limit triggered 2
|
|
| 61 | +10
|
|
| 62 | +11
|
|
| 63 | +allocation limit triggered 2
|
|
| 64 | +12
|
|
| 65 | +13
|
|
| 66 | +allocation limit triggered 2
|
|
| 67 | +14
|
|
| 68 | +15
|
|
| 69 | +allocation limit triggered 2
|
|
| 70 | +16
|
|
| 71 | +17
|
|
| 72 | +allocation limit triggered 2
|
|
| 73 | +18
|
|
| 74 | +19
|
|
| 75 | +allocation limit triggered 2
|
|
| 76 | +20
|
|
| 77 | +21
|
|
| 78 | +allocation limit triggered 2
|
|
| 79 | +22
|
|
| 80 | +23
|
|
| 81 | +allocation limit triggered 2
|
|
| 82 | +24
|
|
| 83 | +25
|
|
| 84 | +allocation limit triggered 2
|
|
| 85 | +26
|
|
| 86 | +27
|
|
| 87 | +allocation limit triggered 2
|
|
| 88 | +28
|
|
| 89 | +29
|
|
| 90 | +allocation limit triggered 2
|
|
| 91 | +30
|
|
| 92 | +29
|
|
| 93 | +worker done
|
|
| 94 | +kill from the hook
|
|
| 95 | +worker starting
|
|
| 96 | +2
|
|
| 97 | +3
|
|
| 98 | +worker died
|
|
| 99 | +T22859: allocation limit exceeded
|
|
| 100 | +HasCallStack backtrace:
|
|
| 101 | + collectBacktraces, called at libraries/ghc-internal/src/GHC/Internal/Exception.hs:92:13 in ghc-internal:GHC.Internal.Exception
|
|
| 102 | + toExceptionWithBacktrace, called at libraries/ghc-internal/src/GHC/Internal/IO.hs:260:11 in ghc-internal:GHC.Internal.IO
|
|
| 103 | + throwIO, called at libraries/ghc-internal/src/GHC/Internal/Control/Exception/Base.hs:195:43 in ghc-internal:GHC.Internal.Control.Exception.Base
|
|
| 104 | + |
|
| 105 | + |
|
| 106 | +do nothing
|
|
| 107 | +worker starting
|
|
| 108 | +2
|
|
| 109 | +3
|
|
| 110 | +4
|
|
| 111 | +5
|
|
| 112 | +6
|
|
| 113 | +7
|
|
| 114 | +8
|
|
| 115 | +9
|
|
| 116 | +10
|
|
| 117 | +11
|
|
| 118 | +12
|
|
| 119 | +13
|
|
| 120 | +14
|
|
| 121 | +15
|
|
| 122 | +16
|
|
| 123 | +17
|
|
| 124 | +18
|
|
| 125 | +19
|
|
| 126 | +20
|
|
| 127 | +21
|
|
| 128 | +22
|
|
| 129 | +23
|
|
| 130 | +24
|
|
| 131 | +25
|
|
| 132 | +26
|
|
| 133 | +27
|
|
| 134 | +28
|
|
| 135 | +29
|
|
| 136 | +30
|
|
| 137 | +29
|
|
| 138 | +worker done
|
|
| 139 | +kill and log
|
|
| 140 | +worker starting
|
|
| 141 | +2
|
|
| 142 | +3
|
|
| 143 | +allocation limit triggered 3
|
|
| 144 | +worker died
|
|
| 145 | +T22859: allocation limit exceeded
|
|
| 146 | +HasCallStack backtrace:
|
|
| 147 | + collectBacktraces, called at libraries/ghc-internal/src/GHC/Internal/Exception.hs:92:13 in ghc-internal:GHC.Internal.Exception
|
|
| 148 | + toExceptionWithBacktrace, called at libraries/ghc-internal/src/GHC/Internal/IO.hs:260:11 in ghc-internal:GHC.Internal.IO
|
|
| 149 | + throwIO, called at libraries/ghc-internal/src/GHC/Internal/Control/Exception/Base.hs:195:43 in ghc-internal:GHC.Internal.Control.Exception.Base
|
|
| 150 | + |
|
| 151 | + |
|
| 152 | +done |
| ... | ... | @@ -643,3 +643,4 @@ test('T25280', [unless(opsys('linux'),skip),req_process,js_skip], compile_and_ru |
| 643 | 643 | test('T25560', [req_c_rts, ignore_stderr], compile_and_run, [''])
|
| 644 | 644 | |
| 645 | 645 | test('TestProddableBlockSet', [req_c_rts], multimod_compile_and_run, ['TestProddableBlockSet.c', '-no-hs-main'])
|
| 646 | +test('T22859', [js_skip], compile_and_run, ['-with-rtsopts -A8K']) |