Marge Bot pushed to branch master at Glasgow Haskell Compiler / GHC
Commits:
-
fd22f71e
by Zubin Duggal at 2026-08-26T15:10:20-04:00
7 changed files:
- + changelog.d/T27657
- libraries/ghc-internal/src/GHC/Internal/STM.hs
- + testsuite/tests/concurrent/should_run/T27657a.hs
- + testsuite/tests/concurrent/should_run/T27657a.stdout
- + testsuite/tests/concurrent/should_run/T27657b.hs
- + testsuite/tests/concurrent/should_run/T27657b.stdout
- testsuite/tests/concurrent/should_run/all.T
Changes:
| 1 | +section: base
|
|
| 2 | +issues: #27657
|
|
| 3 | +mrs: !16508
|
|
| 4 | +synopsis:
|
|
| 5 | + Fix ``retry`` and async exception delivery inside a ``catchSTM`` handler
|
|
| 6 | +description:
|
|
| 7 | + ``catchSTM``\'s ``WhileHandling`` annotation used ``catch#``, leaving an IO
|
|
| 8 | + ``CATCH_FRAME`` inside the transaction. Use ``catchSTM#``, which is the
|
|
| 9 | + correct way to catch exceptions inside STM. |
| ... | ... | @@ -31,7 +31,7 @@ import GHC.Internal.Exception.Context (ExceptionAnnotation) |
| 31 | 31 | import GHC.Internal.Exception.Type (WhileHandling(..))
|
| 32 | 32 | import GHC.Internal.Maybe (Maybe(..))
|
| 33 | 33 | import GHC.Internal.Prim (
|
| 34 | - RealWorld, State#, TVar#, atomically#, catch#, catchRetry#, catchSTM#,
|
|
| 34 | + RealWorld, State#, TVar#, atomically#, catchRetry#, catchSTM#,
|
|
| 35 | 35 | newTVar#, raiseIO#, readTVar#, readTVarIO#, retry#, writeTVar#,
|
| 36 | 36 | )
|
| 37 | 37 | import GHC.Internal.Prim.PtrEq (sameTVar#)
|
| ... | ... | @@ -213,7 +213,7 @@ catchSTM (STM m) handler = STM $ catchSTM# m handler' |
| 213 | 213 | -- | Execute an 'STM' action, adding the given 'ExceptionContext'
|
| 214 | 214 | -- to any thrown synchronous exceptions.
|
| 215 | 215 | annotateSTM :: forall e a. ExceptionAnnotation e => e -> STM a -> STM a
|
| 216 | -annotateSTM ann (STM io) = STM (catch# io handler)
|
|
| 216 | +annotateSTM ann (STM io) = STM (catchSTM# io handler) -- not catch#, see #27657
|
|
| 217 | 217 | where
|
| 218 | 218 | handler se = raiseIO# (addExceptionContext ann se)
|
| 219 | 219 |
| 1 | +{-# LANGUAGE ScopedTypeVariables #-}
|
|
| 2 | + |
|
| 3 | +-- A retry escaping a catchSTM handler must reach the enclosing orElse. An IO
|
|
| 4 | +-- CATCH_FRAME in the way trips an assertion in findRetryFrameHelper.
|
|
| 5 | + |
|
| 6 | +import Control.Exception
|
|
| 7 | +import GHC.Conc
|
|
| 8 | + |
|
| 9 | +main :: IO ()
|
|
| 10 | +main = do
|
|
| 11 | + r <- atomically $
|
|
| 12 | + catchSTM (throwSTM (ErrorCall "boom"))
|
|
| 13 | + (\(_ :: SomeException) -> retry)
|
|
| 14 | + `orElse` pure "T27657a: completed"
|
|
| 15 | + putStrLn r |
| 1 | +T27657a: completed |
| 1 | +{-# LANGUAGE ScopedTypeVariables #-}
|
|
| 2 | + |
|
| 3 | +-- An async exception delivered while a catchSTM handler runs must abort the
|
|
| 4 | +-- transaction, not be swallowed by a restart of the invalidated one.
|
|
| 5 | + |
|
| 6 | +import Control.Concurrent.MVar
|
|
| 7 | +import Control.Exception
|
|
| 8 | +import GHC.Conc
|
|
| 9 | + |
|
| 10 | +waitParked :: ThreadId -> IO ()
|
|
| 11 | +waitParked t = do
|
|
| 12 | + s <- threadStatus t
|
|
| 13 | + case s of
|
|
| 14 | + ThreadBlocked BlockedOnMVar -> pure ()
|
|
| 15 | + _ -> threadDelay 1000 >> waitParked t
|
|
| 16 | + |
|
| 17 | +main :: IO ()
|
|
| 18 | +main = do
|
|
| 19 | + tv <- newTVarIO (0 :: Int)
|
|
| 20 | + park <- newEmptyMVar
|
|
| 21 | + result <- newEmptyMVar
|
|
| 22 | + t <- forkIO $ do
|
|
| 23 | + r <- try $ atomically $ do
|
|
| 24 | + v <- readTVar tv
|
|
| 25 | + catchSTM (throwSTM (ErrorCall "boom"))
|
|
| 26 | + (\(_ :: SomeException) ->
|
|
| 27 | + if v == 0
|
|
| 28 | + then do unsafeIOToSTM (takeMVar park)
|
|
| 29 | + pure "handler resumed"
|
|
| 30 | + else pure "transaction restarted, exception dropped")
|
|
| 31 | + putMVar result (r :: Either SomeException String)
|
|
| 32 | + -- parked in the handler, so t cannot revalidate its trec before delivery
|
|
| 33 | + waitParked t
|
|
| 34 | + atomically (writeTVar tv 1)
|
|
| 35 | + killThread t
|
|
| 36 | + r <- takeMVar result
|
|
| 37 | + putStrLn $ case r of
|
|
| 38 | + Left e | Just ThreadKilled <- fromException e -> "T27657b: killThread delivered"
|
|
| 39 | + | otherwise -> "T27657b: unexpected exception: " ++ displayException e
|
|
| 40 | + Right s -> "T27657b: FAILED, " ++ s |
| 1 | +T27657b: killThread delivered |
| ... | ... | @@ -340,3 +340,6 @@ test('T27105_fail', |
| 340 | 340 | extra_run_opts('+RTS -C0.2 -RTS'), expect_fail,
|
| 341 | 341 | run_timeout_multiplier(0.05)],
|
| 342 | 342 | multimod_compile_and_run, ['T27105.hs', ''])
|
| 343 | + |
|
| 344 | +test('T27657a', normal, compile_and_run, [''])
|
|
| 345 | +test('T27657b', normal, compile_and_run, ['']) |