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']) |