Ben Gamari pushed to branch wip/stm-mvar-deadlock-backtrace at Glasgow Haskell Compiler / GHC

Commits:

14 changed files:

Changes:

  • changelog.d/builtin-exception-backtraces
    1 1
     section: rts
    
    2
    -synopsis: Non-termination exceptions now have backtrace annotations
    
    2
    +synopsis: Non-termination and deadlock exceptions now have backtrace annotations
    
    3 3
     issues: #21878
    
    4
    -mrs: !16158
    
    4
    +mrs: !16158 !16221
    
    5 5
     description: {
    
    6
    -  The `NonTermination` exception (manifesting in printed exception output as
    
    7
    -  `<<loop>>`) now include `Backtrace` `ExceptionAnnotations`, like exceptions
    
    8
    -  thrown from user-written Haskell.
    
    6
    +  The `BlockedIndefinitelyOnMVar`, `BlockedIndefinitelyOnSTM`, and
    
    7
    +  `NonTermination` exceptions (the latter being the infamous `<<loop>>` error)
    
    8
    +  now include `Backtrace` `ExceptionAnnotations`, like exceptions thrown from
    
    9
    +  user-written Haskell.
    
    9 10
     }
    
    10 11
     

  • libraries/ghc-internal/include/RtsIfaceSymbols.h
    ... ... @@ -15,8 +15,8 @@ CLOSURE(GHCziInternalziWeakziFinalizze, runFinalizzerBatch_closure)
    15 15
     CLOSURE(GHCziInternalziIOziException, stackOverflow_closure)
    
    16 16
     CLOSURE(GHCziInternalziIOziException, heapOverflow_closure)
    
    17 17
     CLOSURE(GHCziInternalziIOziException, allocationLimitExceeded_closure)
    
    18
    -CLOSURE(GHCziInternalziIOziException, blockedIndefinitelyOnMVar_closure)
    
    19
    -CLOSURE(GHCziInternalziIOziException, blockedIndefinitelyOnSTM_closure)
    
    18
    +CLOSURE(GHCziInternalziIOziException, blockedIndefinitelyOnMVarError_closure)
    
    19
    +CLOSURE(GHCziInternalziIOziException, blockedIndefinitelyOnSTMError_closure)
    
    20 20
     CLOSURE(GHCziInternalziIOziException, cannotCompactFunction_closure)
    
    21 21
     CLOSURE(GHCziInternalziIOziException, cannotCompactPinned_closure)
    
    22 22
     CLOSURE(GHCziInternalziIOziException, cannotCompactMutable_closure)
    

  • libraries/ghc-internal/src/GHC/Internal/IO/Exception.hs
    ... ... @@ -24,8 +24,12 @@
    24 24
     -----------------------------------------------------------------------------
    
    25 25
     
    
    26 26
     module GHC.Internal.IO.Exception (
    
    27
    -  BlockedIndefinitelyOnMVar(..), blockedIndefinitelyOnMVar,
    
    28
    -  BlockedIndefinitelyOnSTM(..), blockedIndefinitelyOnSTM,
    
    27
    +  BlockedIndefinitelyOnMVar(..),
    
    28
    +  blockedIndefinitelyOnMVar,
    
    29
    +  blockedIndefinitelyOnMVarError,
    
    30
    +  BlockedIndefinitelyOnSTM(..),
    
    31
    +  blockedIndefinitelyOnSTM,
    
    32
    +  blockedIndefinitelyOnSTMError,
    
    29 33
       Deadlock(..),
    
    30 34
       AllocationLimitExceeded(..), allocationLimitExceeded,
    
    31 35
       AssertionFailed(..),
    
    ... ... @@ -84,6 +88,9 @@ instance Exception BlockedIndefinitelyOnMVar
    84 88
     instance Show BlockedIndefinitelyOnMVar where
    
    85 89
         showsPrec _ BlockedIndefinitelyOnMVar = showString "thread blocked indefinitely in an MVar operation"
    
    86 90
     
    
    91
    +blockedIndefinitelyOnMVarError :: IO () -- for the RTS
    
    92
    +blockedIndefinitelyOnMVarError = throwIO BlockedIndefinitelyOnMVar
    
    93
    +
    
    87 94
     blockedIndefinitelyOnMVar :: SomeException -- for the RTS
    
    88 95
     blockedIndefinitelyOnMVar = toException BlockedIndefinitelyOnMVar
    
    89 96
     
    
    ... ... @@ -100,6 +107,9 @@ instance Exception BlockedIndefinitelyOnSTM
    100 107
     instance Show BlockedIndefinitelyOnSTM where
    
    101 108
         showsPrec _ BlockedIndefinitelyOnSTM = showString "thread blocked indefinitely in an STM transaction"
    
    102 109
     
    
    110
    +blockedIndefinitelyOnSTMError :: IO () -- for the RTS
    
    111
    +blockedIndefinitelyOnSTMError = throwIO BlockedIndefinitelyOnSTM
    
    112
    +
    
    103 113
     blockedIndefinitelyOnSTM :: SomeException -- for the RTS
    
    104 114
     blockedIndefinitelyOnSTM = toException BlockedIndefinitelyOnSTM
    
    105 115
     
    

  • rts/Prelude.h
    ... ... @@ -53,8 +53,8 @@ extern StgClosure ZCMain_main_closure;
    53 53
     #define stackOverflow_closure     ghc_hs_iface->stackOverflow_closure
    
    54 54
     #define heapOverflow_closure      ghc_hs_iface->heapOverflow_closure
    
    55 55
     #define allocationLimitExceeded_closure ghc_hs_iface->allocationLimitExceeded_closure
    
    56
    -#define blockedIndefinitelyOnMVar_closure ghc_hs_iface->blockedIndefinitelyOnMVar_closure
    
    57
    -#define blockedIndefinitelyOnSTM_closure ghc_hs_iface->blockedIndefinitelyOnSTM_closure
    
    56
    +#define blockedIndefinitelyOnMVarError_closure ghc_hs_iface->blockedIndefinitelyOnMVarError_closure
    
    57
    +#define blockedIndefinitelyOnSTMError_closure ghc_hs_iface->blockedIndefinitelyOnSTMError_closure
    
    58 58
     #define cannotCompactFunction_closure ghc_hs_iface->cannotCompactFunction_closure
    
    59 59
     #define cannotCompactPinned_closure ghc_hs_iface->cannotCompactPinned_closure
    
    60 60
     #define cannotCompactMutable_closure ghc_hs_iface->cannotCompactMutable_closure
    

  • rts/RaiseAsync.c
    ... ... @@ -87,6 +87,55 @@ suspendComputation (Capability *cap, StgTSO *tso, StgUpdateFrame *stop_here)
    87 87
         throwToSingleThreaded__ (cap, tso, NULL, false, stop_here);
    
    88 88
     }
    
    89 89
     
    
    90
    +/* -----------------------------------------------------------------------------
    
    91
    +   scheduleRaiseViaIO
    
    92
    +
    
    93
    +   Schedule `tso` to raise an exception by running `io_action`, an IO () that
    
    94
    +   performs `throwIO`.  Unlike throwToSingleThreaded (which injects an exception
    
    95
    +   *value* via raiseAsync), the exception is raised by throwIO *within* the
    
    96
    +   thread, so it acquires a backtrace of the thread's stack.  This is used by
    
    97
    +   resurrectThreads to deliver the "blocked indefinitely" exceptions
    
    98
    +   (BlockedIndefinitelyOnMVar, BlockedIndefinitelyOnSTM, NonTermination).
    
    99
    +
    
    100
    +   We push a "run this IO action" frame on top of the thread's existing
    
    101
    +   (suspended) stack and make it runnable; when the thread runs, throwIO raises
    
    102
    +   the exception and its own stack unwinding handles any CATCH_FRAME /
    
    103
    +   ATOMICALLY_FRAME (e.g. aborting a blocked STM transaction).
    
    104
    +
    
    105
    +   removeFromQueues takes care of unlinking the thread from any blocking queue
    
    106
    +   (notably the MVar blocked queue) and appends it to the run queue.  As with
    
    107
    +   throwToSingleThreaded, the caller must own the TSO (e.g. hold all
    
    108
    +   capabilities during GC); in particular this relies on the thread not being
    
    109
    +   scheduled between removeFromQueues' enqueue and our stack push.
    
    110
    +   -------------------------------------------------------------------------- */
    
    111
    +
    
    112
    +void
    
    113
    +scheduleRaiseViaIO (Capability *cap, StgTSO *tso, StgClosure *io_action)
    
    114
    +{
    
    115
    +    // Thread already dead?
    
    116
    +    if (tso->what_next == ThreadComplete || tso->what_next == ThreadKilled) {
    
    117
    +        return;
    
    118
    +    }
    
    119
    +
    
    120
    +    // Unlink from any blocking queues; sets why_blocked = NotBlocked and
    
    121
    +    // appends the thread to the run queue.
    
    122
    +    removeFromQueues(cap, tso);
    
    123
    +
    
    124
    +    StgStack *stack = tso->stackobj;
    
    125
    +
    
    126
    +    // We are about to mutate the stack, so dirty it for the GC write barrier
    
    127
    +    // (resurrectThreads runs right after GC).
    
    128
    +    dirty_TSO(cap, tso);
    
    129
    +    dirty_STACK(cap, stack);
    
    130
    +
    
    131
    +    // Push a frame that enters `io_action` and applies the resulting IO
    
    132
    +    // action to RealWorld.
    
    133
    +    stack->sp -= 3;
    
    134
    +    stack->sp[0] = (W_)&stg_enter_info;
    
    135
    +    stack->sp[1] = (W_)io_action;
    
    136
    +    stack->sp[2] = (W_)&stg_ap_v_info;
    
    137
    +}
    
    138
    +
    
    90 139
     /* -----------------------------------------------------------------------------
    
    91 140
        throwToSelf
    
    92 141
     
    

  • rts/RaiseAsync.h
    ... ... @@ -38,6 +38,10 @@ void suspendComputation (Capability *cap,
    38 38
                              StgTSO *tso,
    
    39 39
                              StgUpdateFrame *stop_here);
    
    40 40
     
    
    41
    +void scheduleRaiseViaIO (Capability *cap,
    
    42
    +                         StgTSO *tso,
    
    43
    +                         StgClosure *io_action);
    
    44
    +
    
    41 45
     MessageThrowTo *throwTo (Capability *cap,      // the Capability we hold
    
    42 46
                              StgTSO *source,
    
    43 47
                              StgTSO *target,
    

  • rts/RtsStartup.c
    ... ... @@ -192,9 +192,9 @@ static void initBuiltinGcRoots(void)
    192 192
         getStablePtr((StgPtr)stackOverflow_closure);
    
    193 193
         getStablePtr((StgPtr)heapOverflow_closure);
    
    194 194
         getStablePtr((StgPtr)unpackCString_closure);
    
    195
    -    getStablePtr((StgPtr)blockedIndefinitelyOnMVar_closure);
    
    195
    +    getStablePtr((StgPtr)blockedIndefinitelyOnMVarError_closure);
    
    196 196
         getStablePtr((StgPtr)nonTerminationError_closure);
    
    197
    -    getStablePtr((StgPtr)blockedIndefinitelyOnSTM_closure);
    
    197
    +    getStablePtr((StgPtr)blockedIndefinitelyOnSTMError_closure);
    
    198 198
         getStablePtr((StgPtr)allocationLimitExceeded_closure);
    
    199 199
         getStablePtr((StgPtr)cannotCompactFunction_closure);
    
    200 200
         getStablePtr((StgPtr)cannotCompactPinned_closure);
    

  • rts/Schedule.c
    ... ... @@ -3276,17 +3276,6 @@ findAtomicallyFrameHelper (Capability *cap, StgTSO *tso)
    3276 3276
       }
    
    3277 3277
     }
    
    3278 3278
     
    
    3279
    -static void throwNontermination(Capability *cap, StgTSO *tso) {
    
    3280
    -  StgStack *stack = tso->stackobj;
    
    3281
    -  stack->sp -= 3;
    
    3282
    -  stack->sp[0] = (W_)&stg_enter_info;
    
    3283
    -  stack->sp[1] = (W_)nonTerminationError_closure;
    
    3284
    -  stack->sp[2] = (W_)&stg_ap_v_info;
    
    3285
    -  tso->why_blocked = NotBlocked;
    
    3286
    -  appendToRunQueue(cap,tso);
    
    3287
    -}
    
    3288
    -
    
    3289
    -
    
    3290 3279
     /* -----------------------------------------------------------------------------
    
    3291 3280
        resurrectThreads is called after garbage collection on the list of
    
    3292 3281
        threads found to be garbage.  Each of these threads will be woken
    
    ... ... @@ -3320,15 +3309,16 @@ resurrectThreads (StgTSO *threads)
    3320 3309
             case BlockedOnMVar:
    
    3321 3310
             case BlockedOnMVarRead:
    
    3322 3311
                 /* Called by GC - sched_mutex lock is currently held. */
    
    3323
    -            throwToSingleThreaded(cap, tso,
    
    3324
    -                                  (StgClosure *)blockedIndefinitelyOnMVar_closure);
    
    3312
    +            scheduleRaiseViaIO(cap, tso,
    
    3313
    +                               (StgClosure *)blockedIndefinitelyOnMVarError_closure);
    
    3325 3314
                 break;
    
    3326 3315
             case BlockedOnBlackHole:
    
    3327
    -            throwNontermination(cap, tso);
    
    3316
    +            scheduleRaiseViaIO(cap, tso,
    
    3317
    +                               (StgClosure *)nonTerminationError_closure);
    
    3328 3318
                 break;
    
    3329 3319
             case BlockedOnSTM:
    
    3330
    -            throwToSingleThreaded(cap, tso,
    
    3331
    -                                  (StgClosure *)blockedIndefinitelyOnSTM_closure);
    
    3320
    +            scheduleRaiseViaIO(cap, tso,
    
    3321
    +                               (StgClosure *)blockedIndefinitelyOnSTMError_closure);
    
    3332 3322
                 break;
    
    3333 3323
             case NotBlocked:
    
    3334 3324
                 /* This might happen if the thread was blocked on a black hole
    

  • rts/include/rts/RtsToHsIface.h
    ... ... @@ -20,8 +20,8 @@ typedef struct {
    20 20
         StgClosure *stackOverflow_closure;  // GHC.Internal.IO.Exception.stackOverflow_closure
    
    21 21
         StgClosure *heapOverflow_closure;  // GHC.Internal.IO.Exception.heapOverflow_closure
    
    22 22
         StgClosure *allocationLimitExceeded_closure;  // GHC.Internal.IO.Exception.allocationLimitExceeded_closure
    
    23
    -    StgClosure *blockedIndefinitelyOnMVar_closure;  // GHC.Internal.IO.Exception.blockedIndefinitelyOnMVar_closure
    
    24
    -    StgClosure *blockedIndefinitelyOnSTM_closure;  // GHC.Internal.IO.Exception.blockedIndefinitelyOnSTM_closure
    
    23
    +    StgClosure *blockedIndefinitelyOnMVarError_closure;  // GHC.Internal.IO.Exception.blockedIndefinitelyOnMVarError_closure
    
    24
    +    StgClosure *blockedIndefinitelyOnSTMError_closure;  // GHC.Internal.IO.Exception.blockedIndefinitelyOnSTMError_closure
    
    25 25
         StgClosure *cannotCompactFunction_closure;  // GHC.Internal.IO.Exception.cannotCompactFunction_closure
    
    26 26
         StgClosure *cannotCompactPinned_closure;  // GHC.Internal.IO.Exception.cannotCompactPinned_closure
    
    27 27
         StgClosure *cannotCompactMutable_closure;  // GHC.Internal.IO.Exception.cannotCompactMutable_closure
    

  • testsuite/tests/rts/MVarDeadlockBacktrace.hs
    1
    +{-# OPTIONS_GHC -finfo-table-map #-}
    
    2
    +
    
    3
    +-- | Check that a @BlockedIndefinitelyOnMVar@ deadlock exception carries a
    
    4
    +-- backtrace mentioning the blocking site in this module.
    
    5
    +import Control.Concurrent.MVar
    
    6
    +import GHC.Exception.Backtrace.Experimental
    
    7
    +
    
    8
    +main :: IO ()
    
    9
    +main = do
    
    10
    +  setBacktraceMechanismState IPEBacktrace True
    
    11
    +  mv <- newEmptyMVar :: IO (MVar ())
    
    12
    +  x <- takeMVar mv
    
    13
    +  print x

  • testsuite/tests/rts/MVarDeadlockBacktrace.stderr
    1
    +MVarDeadlockBacktrace: Uncaught exception ghc-internal:GHC.Internal.IO.Exception.BlockedIndefinitelyOnMVar:
    
    2
    +  Main.main (MVarDeadlockBacktrace.hs:16:3-36)

  • testsuite/tests/rts/STMDeadlockBacktrace.hs
    1
    +{-# OPTIONS_GHC -finfo-table-map #-}
    
    2
    +
    
    3
    +-- | Check that a @BlockedIndefinitelyOnSTM@ deadlock exception carries a
    
    4
    +-- backtrace mentioning the blocking site in this module.
    
    5
    +import GHC.Conc (atomically, retry)
    
    6
    +import GHC.Exception.Backtrace.Experimental
    
    7
    +
    
    8
    +main :: IO ()
    
    9
    +main = do
    
    10
    +  setBacktraceMechanismState IPEBacktrace True
    
    11
    +  x <- atomically retry :: IO ()
    
    12
    +  print x

  • testsuite/tests/rts/STMDeadlockBacktrace.stderr
    1
    +STMDeadlockBacktrace: Uncaught exception ghc-internal:GHC.Internal.IO.Exception.BlockedIndefinitelyOnSTM:
    
    2
    +  Main.main (STMDeadlockBacktrace.hs:16:3-32)

  • testsuite/tests/rts/all.T
    ... ... @@ -689,3 +689,9 @@ test('ClosureTable',
    689 689
     test('resizeMutableByteArrayInPlace', [req_cmm, extra_ways(['optasm', 'sanity']), only_ways(['optasm', 'sanity'])], compile_and_run, [''])
    
    690 690
     
    
    691 691
     test('LoopBacktrace', [exit_code(1)], compile_and_run, [''])
    
    692
    +
    
    693
    +deadlock_backtrace_norm = grep_errmsg(r'(Uncaught exception|Main\.)')
    
    694
    +test('MVarDeadlockBacktrace', [exit_code(1), only_ways(['normal']), deadlock_backtrace_norm],
    
    695
    +     compile_and_run, ['-O'])
    
    696
    +test('STMDeadlockBacktrace', [exit_code(1), only_ways(['normal']), deadlock_backtrace_norm],
    
    697
    +     compile_and_run, ['-O'])