primal-0.3.0.0: src/Control/Prim/Exception.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE UnboxedTuples #-}
#if !MIN_VERSION_base(4,9,0)
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ImplicitParams #-}
#endif
-- |
-- Module : Control.Prim.Exception
-- Copyright : (c) Alexey Kuleshevich 2020
-- License : BSD3
-- Maintainer : Alexey Kuleshevich <alexey@kuleshevi.ch>
-- Stability : experimental
-- Portability : non-portable
--
module Control.Prim.Exception
(
-- * Throwing
module Control.Prim.Monad.Throw
, throw
, throwTo
, impureThrow
-- * Catching
, catch
, catchAny
, catchAnySync
, catchAll
, catchAllSync
, try
, tryAny
, tryAnySync
, onException
-- TODO: Implement:
-- , onAsyncException
, withException
, withAnyException
-- TODO: Implement:
-- , withAsyncException
, finally
, bracket
, bracket_
, bracketOnError
, ufinally
, ubracket
, ubracket_
, ubracketOnError
, mask
, mask_
, maskPrimBase_
, uninterruptibleMask
, uninterruptibleMask_
, uninterruptibleMaskPrimBase_
, maskAsyncExceptions
, unmaskAsyncExceptions
, maskUninterruptible
, GHC.MaskingState(..)
, getMaskingState
-- * Exceptions
, GHC.Exception(..)
, GHC.SomeException
-- ** Async exceptions
, GHC.AsyncException(..)
, GHC.SomeAsyncException(..)
, isSyncException
, isAsyncException
, GHC.asyncExceptionToException
, GHC.asyncExceptionFromException
-- ** Standard exceptions
, GHC.ErrorCall(..)
, GHC.ArithException(..)
, GHC.ArrayException(..)
, GHC.AssertionFailed(..)
, GHC.IOException
, GHC.NonTermination(..)
, GHC.NestedAtomically(..)
, GHC.BlockedIndefinitelyOnMVar(..)
, GHC.BlockedIndefinitelyOnSTM(..)
, GHC.AllocationLimitExceeded(..)
, GHC.Deadlock(..)
-- * CallStack
, CallStack
, HasCallStack
, callStack
, getCallStack
, prettyCallStack
, SrcLoc(..)
, prettySrcLoc
, module Control.Prim.Monad
) where
import qualified Control.Exception as GHC
import Control.Prim.Monad
import Control.Prim.Monad.Throw
import Control.Prim.Monad.Unsafe
import qualified GHC.Conc as GHC
import GHC.Exts
import GHC.Stack
#if !MIN_VERSION_base(4,9,0)
import Data.List (intercalate)
import GHC.SrcLoc
#endif
--import GHC.IO (IO(..))
----- Exceptions
isSyncException :: GHC.Exception e => e -> Bool
isSyncException = not . isAsyncException
{-# INLINE isSyncException #-}
isAsyncException :: GHC.Exception e => e -> Bool
isAsyncException exc =
case GHC.fromException (GHC.toException exc) of
Just (GHC.SomeAsyncException _) -> True
Nothing -> False
{-# INLINE isAsyncException #-}
-- | This is the same as `throwIO`, but works with any `MonadPrim` without restriction on
-- `RealWorld`.
throw :: (GHC.Exception e, MonadPrim s m) => e -> m a
throw e = unsafePrim (raiseIO# (GHC.toException e))
-- {-# INLINEABLE throw #-}
-- | Raise an impure exception from pure code. Returns a thunk, which will result in a
-- supplied exceptionn being thrown when evaluated.
--
-- @since 0.3.0
impureThrow :: GHC.Exception e => e -> a
impureThrow e = raise# (GHC.toException e)
-- | Similar to `throwTo`, except that it wraps any known non-async exception with
-- `SomeAsyncException`. This is necessary, because receiving thread will get the exception in
-- an asynchronous manner and without proper wrapping it will not be able to distinguish it
-- from a regular synchronous exception
throwTo :: (MonadPrim s m, GHC.Exception e) => GHC.ThreadId -> e -> m ()
throwTo tid e =
unsafeIOToPrim $
GHC.throwTo tid $
if isAsyncException e
then GHC.toException e
else GHC.toException $ GHC.SomeAsyncException e
-- {-# INLINEABLE throwTo #-}
-- | Behaves exactly as `catch`, except that it works in any `MonadUnliftPrim`.
catch ::
forall e a m. (GHC.Exception e, MonadUnliftPrim RW m)
=> m a
-> (e -> m a)
-> m a
catch action handler =
runInPrimBase2 (const action) handler $ \action# handler# ->
let handler'# :: GHC.SomeException -> (State# RW -> (# State# RW, a #))
handler'# someExc =
case GHC.fromException someExc of
Just exc -> handler# exc
Nothing -> raiseIO# someExc
in catch# (action# ()) handler'#
-- {-# INLINEABLE catch #-}
--{-# SPECIALIZE catch :: GHC.Exception e => IO a -> (e -> IO a) -> IO a #-}
catchAny ::
forall a m. MonadUnliftPrim RW m
=> m a
-> (GHC.SomeException -> m a)
-> m a
catchAny action handler =
runInPrimBase2 (const action) handler $ \action# handler# ->
catch# (action# ()) handler#
-- {-# INLINEABLE catchAny #-}
--{-# SPECIALIZE catchAny :: IO a -> (GHC.SomeException -> IO a) -> IO a #-}
catchAnySync ::
forall a m. MonadUnliftPrim RW m
=> m a
-> (GHC.SomeException -> m a)
-> m a
catchAnySync action handler =
catchAny action $ \exc ->
when (isAsyncException exc) (throw exc) >> handler exc
-- {-# INLINEABLE catchAnySync #-}
catchAll ::
forall a m. MonadUnliftPrim RW m
=> m a
-> (forall e . GHC.Exception e => e -> m a)
-> m a
catchAll action handler =
runInPrimBase2
(const action)
(\(GHC.SomeException e) -> handler e)
(\action# handler# -> catch# (action# ()) handler#)
-- {-# INLINEABLE catchAll #-}
catchAllSync ::
forall a m. MonadUnliftPrim RW m
=> m a
-> (forall e . GHC.Exception e => e -> m a)
-> m a
catchAllSync action handler =
catchAll action $ \exc ->
when (isAsyncException exc) (throw exc) >> handler exc
-- {-# INLINEABLE catchAllSync #-}
try :: (GHC.Exception e, MonadUnliftPrim RW m) => m a -> m (Either e a)
try f = catch (fmap Right f) (pure . Left)
-- {-# INLINEABLE try #-}
--{-# SPECIALIZE try :: GHC.Exception e => IO a -> IO (Either e a) #-}
tryAny :: MonadUnliftPrim RW m => m a -> m (Either GHC.SomeException a)
tryAny f = catchAny (Right <$> f) (pure . Left)
-- {-# INLINEABLE tryAny #-}
tryAnySync :: MonadUnliftPrim RW m => m a -> m (Either GHC.SomeException a)
tryAnySync f = catchAnySync (Right <$> f) (pure . Left)
-- | Run an action, while invoking an exception handler if that action fails for some
-- reason. Exception handling function has async exceptions masked, but it is still
-- interruptible, which can be undesired in some scenarios. If you are sure that the
-- cleanup action does not deadlock and you do need hard guarantees that it gets executed
-- you can run it as uninterruptible:
--
-- > uninterruptibleMask $ \restore -> withException (restore action) handler
--
-- @since 0.3.0
withException ::
(MonadUnliftPrim RW m, GHC.Exception e) => m a -> (e -> m b) -> m a
withException action handler =
mask $ \restore -> do
catch
(restore action)
(\exc -> catchAnySync (void $ handler exc) (\_ -> pure ()) >> throw exc)
-- | Same as `withException`, but will invoke exception handling function on all
-- exceptions.
--
-- @since 0.3.0
withAnyException :: MonadUnliftPrim RW m => m a -> (GHC.SomeException -> m b) -> m a
withAnyException thing after =
mask $ \restore -> do
catchAny
(restore thing)
(\exc -> catchAnySync (void $ after exc) (\_ -> pure ()) >> throw exc)
-- | Async safe version of 'EUnsafe.onException'.
--
-- @since 0.1.0.0
onException :: MonadUnliftPrim RW m => m a -> m b -> m a
onException thing after = withAnyException thing (const after)
--
-- @since 0.3.0
bracket :: MonadUnliftPrim RW m => m a -> (a -> m b) -> (a -> m c) -> m c
bracket acquire cleanup action =
mask $ \restore -> do
resource <- acquire
result <-
catchAny (restore (action resource)) $ \exc -> do
catchAnySync (void $ cleanup resource) $ \_ -> pure ()
throw exc
result <$ cleanup resource
{-# INLINEABLE bracket #-}
bracketOnError :: MonadUnliftPrim RW m => m a -> (a -> m b) -> (a -> m c) -> m c
bracketOnError acquire cleanup action =
mask $ \restore -> do
resource <- acquire
catchAny (restore (action resource)) $ \exc -> do
catchAnySync (void $ cleanup resource) $ \_ -> pure ()
throw exc
finally :: MonadUnliftPrim RW m => m a -> m b -> m a
finally action cleanup =
mask $ \restore -> do
result <-
catchAny (restore action) $ \exc -> do
catchAnySync (void cleanup) $ \_ -> pure ()
throw exc
result <$ cleanup
--
-- @since 0.3.0
bracket_ :: MonadUnliftPrim RW m => m a -> m b -> m c -> m c
bracket_ acquire cleanup action = bracket acquire (const cleanup) (const action)
ubracket :: MonadUnliftPrim RW m => m a -> (a -> m b) -> (a -> m c) -> m c
ubracket acquire cleanup action =
uninterruptibleMask $ \restore ->
bracket (restore acquire) cleanup (restore . action)
--
-- @since 0.3.0
ubracket_ :: MonadUnliftPrim RW m => m a -> m b -> m c -> m c
ubracket_ acquire cleanup action = ubracket acquire (const cleanup) (const action)
ubracketOnError :: MonadUnliftPrim RW m => m a -> (a -> m b) -> (a -> m c) -> m c
ubracketOnError acquire cleanup action =
uninterruptibleMask $ \restore ->
bracketOnError (restore acquire) cleanup (restore . action)
ufinally :: MonadUnliftPrim RW m => m a -> m b -> m a
ufinally action cleanup =
uninterruptibleMask $ \restore -> finally (restore action) cleanup
-- | Mask all asychronous exceptions, but keep it interruptible, unless the inherited state
-- was uninterruptible already, in which case this action has no affect. Same as
-- `Control.Exception.mask_`, except that it is polymorphic in state token. Inside a state
-- thread it cannot affect the result of computation, therefore it is safe to use it within
-- `ST` monad.
--
-- @since 0.3.0
mask_ :: forall a m s. MonadUnliftPrim s m => m a -> m a
mask_ action =
unsafeIOToPrim getMaskingState >>= \case
GHC.Unmasked -> runInPrimBase action maskAsyncExceptionsInternal#
_ -> action
{-# INLINEABLE mask_ #-}
maskPrimBase_ :: forall a n m s. (MonadPrim s m, MonadPrimBase s n) => n a -> m a
maskPrimBase_ action =
unsafeIOToPrim getMaskingState >>= \case
GHC.Unmasked -> prim (maskAsyncExceptionsInternal# (primBase action))
_ -> liftPrimBase action
{-# INLINEABLE maskPrimBase_ #-}
-- | Mask all asychronous exceptions, but keep it interruptible, unless the inherited state
-- was uninterruptible already, in which case this action has no affect. Same as
-- `Control.Exception.mask`, except that it is polymorphic in state token. Inside a state
-- thread it cannot affect the result of computation, therefore it is safe to use it within
-- `ST` monad.
--
-- @since 0.3.0
mask ::
forall a m s. MonadUnliftPrim s m
=> ((forall b. m b -> m b) -> m a)
-> m a
mask action = do
unsafeIOToPrim getMaskingState >>= \case
GHC.Unmasked ->
runInPrimBase
(action (`runInPrimBase` unmaskAsyncExceptionsInternal#))
maskAsyncExceptionsInternal#
GHC.MaskedInterruptible ->
action (`runInPrimBase` maskAsyncExceptionsInternal#)
GHC.MaskedUninterruptible -> action uninterruptibleMask_
{-# INLINEABLE mask #-}
--{-# SPECIALIZE mask :: ((forall a. IO a -> IO a) -> IO b) -> IO b #-}
-- | Mask all asychronous exceptions and mark it uninterruptible. Same as
-- `Control.Exception.uninterruptibleMask`, except that it is polymorphic in state
-- token. Inside a state thread it cannot affect the result of computation, therefore it
-- is safe to use it within `ST` monad.
--
-- @since 0.3.0
uninterruptibleMask ::
forall a m s. MonadUnliftPrim s m
=> ((forall b. m b -> m b) -> m a)
-> m a
uninterruptibleMask action = do
unsafeIOToPrim getMaskingState >>= \case
GHC.Unmasked ->
runInPrimBase
(action (`runInPrimBase` unmaskAsyncExceptionsInternal#))
maskAsyncExceptionsInternal#
GHC.MaskedInterruptible ->
action (`runInPrimBase` maskAsyncExceptionsInternal#)
GHC.MaskedUninterruptible -> action uninterruptibleMask_
{-# INLINEABLE uninterruptibleMask #-}
-- | Mask all async exceptions and make sure evaluation cannot be interrupted. It is
-- polymorphic in the state token because it is perfectly safe to use with `ST` actions that
-- don't perform any allocations. It doesn't have to be restricted to `RealWorld` because it
-- has no impact on other threads and can't affect the result of computation, moreover pure
-- functions that implement tight loops are already non-interruptible. In fact using this
-- function is more dangerous in `IO` than it is in `ST`, because misuse can lead to deadlocks
-- in a concurrent setting.
--
-- @since 0.3.0
uninterruptibleMask_ :: forall a m s. MonadUnliftPrim s m => m a -> m a
uninterruptibleMask_ action = runInPrimBase action maskUninterruptibleInternal#
{-# INLINEABLE uninterruptibleMask_ #-}
uninterruptibleMaskPrimBase_ :: forall a n m s. (MonadPrimBase s n, MonadPrim s m) => n a -> m a
uninterruptibleMaskPrimBase_ action = prim (maskUninterruptibleInternal# (primBase action))
{-# INLINEABLE uninterruptibleMaskPrimBase_ #-}
-- | A direct wrapper around `maskAsyncExceptions#` primop. This is different and more
-- dangerous than `mask_` because it can turn uninterrubtable state into interruptable.
maskAsyncExceptions :: forall a m. MonadUnliftPrim RW m => m a -> m a
maskAsyncExceptions action = runInPrimBase action maskAsyncExceptions#
{-# INLINEABLE maskAsyncExceptions #-}
-- | A direct wrapper around `unmaskAsyncExceptions#` primop.
unmaskAsyncExceptions :: forall a m. MonadUnliftPrim RW m => m a -> m a
unmaskAsyncExceptions action = runInPrimBase action unmaskAsyncExceptions#
{-# INLINEABLE unmaskAsyncExceptions #-}
-- | A direct wrapper around `maskUninterruptible#` primop.
maskUninterruptible :: forall a m. MonadUnliftPrim RW m => m a -> m a
maskUninterruptible action = runInPrimBase action maskUninterruptible#
{-# INLINEABLE maskUninterruptible #-}
maskAsyncExceptionsInternal# :: (State# s -> (# State# s, a #)) -> State# s -> (# State# s, a #)
maskAsyncExceptionsInternal# = unsafeCoerce# maskAsyncExceptions#
{-# INLINEABLE maskAsyncExceptionsInternal# #-}
maskUninterruptibleInternal# :: (State# s -> (# State# s, a #)) -> State# s -> (# State# s, a #)
maskUninterruptibleInternal# = unsafeCoerce# maskUninterruptible#
{-# INLINEABLE maskUninterruptibleInternal# #-}
unmaskAsyncExceptionsInternal# :: (State# s -> (# State# s, b #)) -> State# s -> (# State# s, b #)
unmaskAsyncExceptionsInternal# = unsafeCoerce# unmaskAsyncExceptions#
{-# INLINEABLE unmaskAsyncExceptionsInternal# #-}
-- | Same as `GHC.getMaskingState`, but generalized to `MonadPrim`
--
-- @since 0.3.0
getMaskingState :: MonadPrim RW m => m GHC.MaskingState
getMaskingState = liftIO GHC.getMaskingState
{-# INLINEABLE getMaskingState #-}
#if !MIN_VERSION_base(4,9,0)
-- | (Implemented for compatibility with GHC-7.10.2)
type HasCallStack = (?callStack :: CallStack)
callStack :: HasCallStack => CallStack
callStack = ?callStack
-- | Pretty print a 'SrcLoc'. (Implemented for compatibility with GHC-7.10.2)
--
-- @since 3.0.0
prettySrcLoc :: SrcLoc -> String
prettySrcLoc = showSrcLoc
-- | Pretty print a 'CallStack'. (Implemented for compatibility with GHC-7.10.2)
--
-- @since 3.0.0
prettyCallStack :: CallStack -> String
prettyCallStack = intercalate "\n" . prettyCallStackLines
prettyCallStackLines :: CallStack -> [String]
prettyCallStackLines cs = case getCallStack cs of
[] -> []
stk -> "CallStack (from HasCallStack):"
: map ((" " ++) . prettyCallSite) stk
where
prettyCallSite (f, loc) = f ++ ", called at " ++ prettySrcLoc loc
#endif