Skip to content

Commit 3d5cc30

Browse files
committed
feat: annotate 'waitXXX' function with the callsite for 'wait'
Previous commits extended the exception annotations so the default callstack represents the exception location of the original exception in the async process. This callstack also includes where the async process was started (e.g. in `withAsync`). This commits extends the exception context by adding a new `AsyncWaitLocation` exception annotation which contains the location of the `wait` call. Note the usage of `withFrozenCallStack` in order to not expose the internal of the async library in the callstack.
1 parent 16750e9 commit 3d5cc30

1 file changed

Lines changed: 36 additions & 6 deletions

File tree

Control/Concurrent/Async/Internal.hs

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,12 @@ import Data.IORef
5757
import GHC.Exts
5858
import GHC.IO hiding (finally, onException)
5959
import GHC.Conc (ThreadId(..))
60+
import GHC.Stack (CallStack, callStack, prettyCallStack, withFrozenCallStack)
61+
62+
#if MIN_VERSION_base(4,21,0)
63+
import Control.Exception.Annotation (ExceptionAnnotation (..))
64+
#endif
65+
import GHC.Stack.Types (HasCallStack)
6066

6167
#if defined(__MHS__)
6268
import Data.Traversable
@@ -259,15 +265,39 @@ rethrowIO' e =
259265
rethrowIO' = throwIO
260266
#endif
261267

268+
#if MIN_VERSION_base(4,21,0)
269+
-- | An exception annotation which stores the callstack of a 'wait',
270+
-- 'waitBoth', 'waitEither' call.
271+
data AsyncWaitLocation = AsyncWaitLocation CallStack
272+
deriving (Show)
273+
274+
instance ExceptionAnnotation AsyncWaitLocation where
275+
displayExceptionAnnotation (AsyncWaitLocation callstack) = "AsyncWaitLocation " <> prettyCallStack callstack
276+
277+
-- | Annotate an exception with the current callstack with GHC >= 9.12
278+
annotateWithCallSite :: HasCallStack => IO b -> IO b
279+
annotateWithCallSite action = do
280+
resM <- tryWithContext action
281+
case resM of
282+
Right res -> pure res
283+
Left (exc :: ExceptionWithContext SomeException) -> do
284+
annotateIO (AsyncWaitLocation callStack) $ rethrowIO exc
285+
#else
286+
-- | Do nothing with GHC < 9.12
287+
annotateWithCallSite :: HasCallStack => IO b -> IO b
288+
annotateWithCallSite action = action
289+
#endif
290+
291+
262292
-- | Wait for an asynchronous action to complete, and return its
263293
-- value. If the asynchronous action threw an exception, then the
264294
-- exception is re-thrown by 'wait'.
265295
--
266296
-- > wait = atomically . waitSTM
267297
--
268298
{-# INLINE wait #-}
269-
wait :: Async a -> IO a
270-
wait = tryAgain . atomically . waitSTM
299+
wait :: HasCallStack => Async a -> IO a
300+
wait = withFrozenCallStack $ annotateWithCallSite . tryAgain . atomically . waitSTM
271301
where
272302
-- See: https://github.com/simonmar/async/issues/14
273303
tryAgain f = f `catch` \BlockedIndefinitelyOnSTM -> f
@@ -485,8 +515,8 @@ waitEitherCatchCancel left right =
485515
-- re-thrown by 'waitEither'.
486516
--
487517
{-# INLINE waitEither #-}
488-
waitEither :: Async a -> Async b -> IO (Either a b)
489-
waitEither left right = atomically (waitEitherSTM left right)
518+
waitEither :: HasCallStack => Async a -> Async b -> IO (Either a b)
519+
waitEither left right = withFrozenCallStack $ annotateWithCallSite $ atomically (waitEitherSTM left right)
490520

491521
-- | A version of 'waitEither' that can be used inside an STM transaction.
492522
--
@@ -524,8 +554,8 @@ waitEitherCancel left right =
524554
-- re-thrown by 'waitBoth'.
525555
--
526556
{-# INLINE waitBoth #-}
527-
waitBoth :: Async a -> Async b -> IO (a,b)
528-
waitBoth left right = tryAgain $ atomically (waitBothSTM left right)
557+
waitBoth :: HasCallStack => Async a -> Async b -> IO (a,b)
558+
waitBoth left right = withFrozenCallStack $ annotateWithCallSite $ tryAgain $ atomically (waitBothSTM left right)
529559
where
530560
-- See: https://github.com/simonmar/async/issues/14
531561
tryAgain f = f `catch` \BlockedIndefinitelyOnSTM -> f

0 commit comments

Comments
 (0)