unliftio-0.2.23.0: src/UnliftIO/Exception.hs
{-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE ImplicitParams #-}
-- | Unlifted "Control.Exception", with extra async exception safety
-- and more helper functions.
--
-- This module works best when your cleanup functions adhere to certain
-- expectations around exception safety and interruptible actions.
-- For more details, see [this exception safety tutorial](https://www.fpcomplete.com/haskell/tutorial/exceptions/).
module UnliftIO.Exception
( -- * Throwing
throwIO
, throwString
, StringException (..)
, stringException
, throwTo
, impureThrow
, fromEither
, fromEitherIO
, fromEitherM
, mapExceptionM
-- * Catching (with recovery)
, catch
, catchIO
, catchAny
, catchDeep
, catchAnyDeep
, catchJust
, handle
, handleIO
, handleAny
, handleDeep
, handleAnyDeep
, handleJust
, try
, tryIO
, tryAny
, tryDeep
, tryAnyDeep
, tryJust
, pureTry
, pureTryDeep
, ESafe.Handler (..)
, catches
, catchesDeep
-- * Catching async exceptions (with recovery)
, catchSyncOrAsync
, handleSyncOrAsync
, trySyncOrAsync
-- * Cleanup (no recovery)
, onException
, bracket
, bracket_
, finally
, withException
, bracketOnError
, bracketOnError_
-- * Coercion to sync and async
-- | In version /0.2.23.0/, these were changed with aliases to the values
-- from "Control.Exception.Safe" in the @safe-exceptions@ package.
, ESafe.SyncExceptionWrapper(..)
, toSyncException
, ESafe.AsyncExceptionWrapper(..)
, toAsyncException
, fromExceptionUnwrap
-- * Check exception type
, isSyncException
, isAsyncException
-- * Masking
, mask
, uninterruptibleMask
, mask_
, uninterruptibleMask_
-- * Evaluation
, evaluate
, evaluateDeep
-- * Reexports
, Exception (..)
, Typeable
, SomeException (..)
, SomeAsyncException (..)
, IOException
, EUnsafe.assert
, EUnsafe.asyncExceptionToException
, EUnsafe.asyncExceptionFromException
#if !MIN_VERSION_base(4,8,0)
, displayException
#endif
) where
import Control.Concurrent (ThreadId)
import Control.Monad (liftM)
import Control.Monad.IO.Unlift
import Control.Exception (Exception (..), SomeException (..), IOException, SomeAsyncException (..))
import qualified Control.Exception as EUnsafe
import Control.DeepSeq (NFData (..), ($!!))
import Data.Typeable (Typeable, cast)
import System.IO.Unsafe (unsafePerformIO)
import qualified Control.Exception.Safe as ESafe
import Control.Exception.Safe (Handler(..))
#if MIN_VERSION_base(4,9,0)
import GHC.Stack (prettySrcLoc)
import GHC.Stack.Types (HasCallStack, CallStack, getCallStack)
#endif
-- | Catch a synchronous (but not asynchronous) exception and recover from it.
--
-- This is parameterized on the exception type. To catch all synchronous exceptions,
-- use 'catchAny'.
--
-- @since 0.1.0.0
catch
:: (MonadUnliftIO m, Exception e)
=> m a -- ^ action
-> (e -> m a) -- ^ handler
-> m a
catch f g = withRunInIO $ \run -> run f `EUnsafe.catch` \e ->
if isSyncException e
then run (g e)
-- intentionally rethrowing an async exception synchronously,
-- since we want to preserve async behavior
else EUnsafe.throwIO e
-- | 'catch' specialized to only catching 'IOException's.
--
-- @since 0.1.0.0
catchIO :: MonadUnliftIO m => m a -> (IOException -> m a) -> m a
catchIO = catch
-- | 'catch' specialized to catch all synchronous exceptions.
--
-- @since 0.1.0.0
catchAny :: MonadUnliftIO m => m a -> (SomeException -> m a) -> m a
catchAny = catch
-- | Same as 'catch', but fully force evaluation of the result value
-- to find all impure exceptions.
--
-- @since 0.1.0.0
catchDeep :: (MonadUnliftIO m, Exception e, NFData a)
=> m a -> (e -> m a) -> m a
catchDeep m = catch (m >>= evaluateDeep)
-- | 'catchDeep' specialized to catch all synchronous exception.
--
-- @since 0.1.0.0
catchAnyDeep :: (NFData a, MonadUnliftIO m) => m a -> (SomeException -> m a) -> m a
catchAnyDeep = catchDeep
-- | 'catchJust' is like 'catch' but it takes an extra argument which
-- is an exception predicate, a function which selects which type of
-- exceptions we're interested in.
--
-- @since 0.1.0.0
catchJust :: (MonadUnliftIO m, Exception e) => (e -> Maybe b) -> m a -> (b -> m a) -> m a
catchJust f a b = a `catch` \e -> maybe (liftIO (throwIO e)) b $ f e
-- | A variant of 'catch' that catches both synchronous and asynchronous exceptions.
--
-- WARNING: This function (and other @*SyncOrAsync@ functions) is for advanced users. Most of the
-- time, you probably want to use the non-@SyncOrAsync@ versions.
--
-- Before attempting to use this function, be familiar with the "Rules for async safe handling"
-- section in
-- [this blog post](https://www.fpcomplete.com/blog/2018/04/async-exception-handling-haskell/).
--
-- @since 0.2.17
catchSyncOrAsync :: (MonadUnliftIO m, Exception e) => m a -> (e -> m a) -> m a
catchSyncOrAsync f g = withRunInIO $ \run -> run f `EUnsafe.catch` \e -> run (g e)
-- | Flipped version of 'catch'.
--
-- @since 0.1.0.0
handle :: (MonadUnliftIO m, Exception e) => (e -> m a) -> m a -> m a
handle = flip catch
-- | 'handle' specialized to only catching 'IOException's.
--
-- @since 0.1.0.0
handleIO :: MonadUnliftIO m => (IOException -> m a) -> m a -> m a
handleIO = handle
-- | Flipped version of 'catchAny'.
--
-- @since 0.1.0.0
handleAny :: MonadUnliftIO m => (SomeException -> m a) -> m a -> m a
handleAny = handle
-- | Flipped version of 'catchDeep'.
--
-- @since 0.1.0.0
handleDeep :: (MonadUnliftIO m, Exception e, NFData a) => (e -> m a) -> m a -> m a
handleDeep = flip catchDeep
-- | Flipped version of 'catchAnyDeep'.
--
-- @since 0.1.0.0
handleAnyDeep :: (MonadUnliftIO m, NFData a) => (SomeException -> m a) -> m a -> m a
handleAnyDeep = flip catchAnyDeep
-- | Flipped 'catchJust'.
--
-- @since 0.1.0.0
handleJust :: (MonadUnliftIO m, Exception e) => (e -> Maybe b) -> (b -> m a) -> m a -> m a
handleJust f = flip (catchJust f)
-- | A variant of 'handle' that catches both synchronous and asynchronous exceptions.
--
-- See 'catchSyncOrAsync'.
--
-- @since 0.2.17
handleSyncOrAsync :: (MonadUnliftIO m, Exception e) => (e -> m a) -> m a -> m a
handleSyncOrAsync = flip catchSyncOrAsync
-- | Run the given action and catch any synchronous exceptions as a 'Left' value.
--
-- This is parameterized on the exception type. To catch all synchronous exceptions,
-- use 'tryAny'.
--
-- @since 0.1.0.0
try :: (MonadUnliftIO m, Exception e) => m a -> m (Either e a)
try f = catch (liftM Right f) (return . Left)
-- | 'try' specialized to only catching 'IOException's.
--
-- @since 0.1.0.0
tryIO :: MonadUnliftIO m => m a -> m (Either IOException a)
tryIO = try
-- | 'try' specialized to catch all synchronous exceptions.
--
-- @since 0.1.0.0
tryAny :: MonadUnliftIO m => m a -> m (Either SomeException a)
tryAny = try
-- | Same as 'try', but fully force evaluation of the result value
-- to find all impure exceptions.
--
-- @since 0.1.0.0
tryDeep :: (MonadUnliftIO m, Exception e, NFData a) => m a -> m (Either e a)
tryDeep f = catch (liftM Right (f >>= evaluateDeep)) (return . Left)
-- | 'tryDeep' specialized to catch all synchronous exceptions.
--
-- @since 0.1.0.0
tryAnyDeep :: (MonadUnliftIO m, NFData a) => m a -> m (Either SomeException a)
tryAnyDeep = tryDeep
-- | A variant of 'try' that takes an exception predicate to select
-- which exceptions are caught.
--
-- @since 0.1.0.0
tryJust :: (MonadUnliftIO m, Exception e) => (e -> Maybe b) -> m a -> m (Either b a)
tryJust f a = catch (Right `liftM` a) (\e -> maybe (throwIO e) (return . Left) (f e))
-- | A variant of 'try' that catches both synchronous and asynchronous exceptions.
--
-- See 'catchSyncOrAsync'.
--
-- @since 0.2.17
trySyncOrAsync :: (MonadUnliftIO m, Exception e) => m a -> m (Either e a)
trySyncOrAsync f = catchSyncOrAsync (liftM Right f) (return . Left)
-- | Evaluate the value to WHNF and catch any synchronous exceptions.
--
-- The expression may still have bottom values within it; you may
-- instead want to use 'pureTryDeep'.
--
-- @since 0.2.2.0
pureTry :: a -> Either SomeException a
pureTry a = unsafePerformIO $ (return $! Right $! a) `catchAny` (return . Left)
-- | Evaluate the value to NF and catch any synchronous exceptions.
--
-- @since 0.2.2.0
pureTryDeep :: NFData a => a -> Either SomeException a
pureTryDeep = unsafePerformIO . tryAnyDeep . return
-- | Internal.
catchesHandler :: MonadIO m => [Handler m a] -> SomeException -> m a
catchesHandler handlers e = foldr tryHandler (liftIO (EUnsafe.throwIO e)) handlers
where tryHandler (ESafe.Handler handler) res
= case fromException e of
Just e' -> handler e'
Nothing -> res
-- | Similar to 'catch', but provides multiple different handler functions.
--
-- For more information on motivation, see @base@'s 'EUnsafe.catches'. Note that,
-- unlike that function, this function will not catch asynchronous exceptions.
--
-- @since 0.1.0.0
catches :: MonadUnliftIO m => m a -> [Handler m a] -> m a
catches io handlers = io `catch` catchesHandler handlers
-- | Same as 'catches', but fully force evaluation of the result value
-- to find all impure exceptions.
--
-- @since 0.1.0.0
catchesDeep :: (MonadUnliftIO m, NFData a) => m a -> [Handler m a] -> m a
catchesDeep io handlers = (io >>= evaluateDeep) `catch` catchesHandler handlers
-- | Lifted version of 'EUnsafe.evaluate'.
--
-- @since 0.1.0.0
evaluate :: MonadIO m => a -> m a
evaluate = liftIO . EUnsafe.evaluate
-- | Deeply evaluate a value using 'evaluate' and 'NFData'.
--
-- @since 0.1.0.0
evaluateDeep :: (MonadIO m, NFData a) => a -> m a
evaluateDeep = (evaluate $!!)
-- | Allocate and clean up a resource safely.
--
-- For more information on motivation and usage of this function, see @base@'s
-- 'EUnsafe.bracket'. This function has two differences from the one in @base@.
-- The first, and more obvious, is that it works on any @MonadUnliftIO@
-- instance, not just @IO@.
--
-- The more subtle difference is that this function will use uninterruptible
-- masking for its cleanup handler. This is a subtle distinction, but at a
-- high level, means that resource cleanup has more guarantees to complete.
-- This comes at the cost that an incorrectly written cleanup function
-- cannot be interrupted.
--
-- For more information, please see <https://github.com/fpco/safe-exceptions/issues/3>.
--
-- @since 0.1.0.0
bracket :: MonadUnliftIO m => m a -> (a -> m b) -> (a -> m c) -> m c
bracket before after thing = withRunInIO $ \run -> EUnsafe.mask $ \restore -> do
x <- run before
res1 <- EUnsafe.try $ restore $ run $ thing x
case res1 of
Left (e1 :: SomeException) -> do
-- explicitly ignore exceptions from after. We know that
-- no async exceptions were thrown there, so therefore
-- the stronger exception must come from thing
--
-- https://github.com/fpco/safe-exceptions/issues/2
_ :: Either SomeException b <-
EUnsafe.try $ EUnsafe.uninterruptibleMask_ $ run $ after x
EUnsafe.throwIO e1
Right y -> do
_ <- EUnsafe.uninterruptibleMask_ $ run $ after x
return y
-- | Same as 'bracket', but does not pass the acquired resource to cleanup and use functions.
--
-- For more information, see @base@'s 'EUnsafe.bracket_'.
--
-- @since 0.1.0.0
bracket_ :: MonadUnliftIO m => m a -> m b -> m c -> m c
bracket_ before after thing = bracket before (const after) (const thing)
-- | Same as 'bracket', but only perform the cleanup if an exception is thrown.
--
-- @since 0.1.0.0
bracketOnError :: MonadUnliftIO m => m a -> (a -> m b) -> (a -> m c) -> m c
bracketOnError before after thing = withRunInIO $ \run -> EUnsafe.mask $ \restore -> do
x <- run before
res1 <- EUnsafe.try $ restore $ run $ thing x
case res1 of
Left (e1 :: SomeException) -> do
-- ignore the exception, see bracket for explanation
_ :: Either SomeException b <-
EUnsafe.try $ EUnsafe.uninterruptibleMask_ $ run $ after x
EUnsafe.throwIO e1
Right y -> return y
-- | A variant of 'bracketOnError' where the return value from the first
-- computation is not required.
--
-- @since 0.1.0.0
bracketOnError_ :: MonadUnliftIO m => m a -> m b -> m c -> m c
bracketOnError_ before after thing = bracketOnError before (const after) (const thing)
-- | Perform @thing@, guaranteeing that @after@ will run after, even if an exception occurs.
--
-- Same interruptible vs uninterrupible points apply as with 'bracket'. See @base@'s
-- 'EUnsafe.finally' for more information.
--
-- @since 0.1.0.0
finally
:: MonadUnliftIO m
=> m a -- ^ thing
-> m b -- ^ after
-> m a
finally thing after = withRunInIO $ \run -> EUnsafe.uninterruptibleMask $ \restore -> do
res1 <- EUnsafe.try $ restore $ run thing
case res1 of
Left (e1 :: SomeException) -> do
-- see bracket for explanation
_ :: Either SomeException b <- EUnsafe.try $ run after
EUnsafe.throwIO e1
Right x -> do
_ <- run after
return x
-- | Like 'onException', but provides the handler the thrown
-- exception.
--
-- @since 0.1.0.0
withException :: (MonadUnliftIO m, Exception e)
=> m a -> (e -> m b) -> m a
withException thing after = withRunInIO $ \run -> EUnsafe.uninterruptibleMask $ \restore -> do
res1 <- EUnsafe.try $ restore $ run thing
case res1 of
Left e1 -> do
-- see explanation in bracket
_ :: Either SomeException b <- EUnsafe.try $ run $ after e1
EUnsafe.throwIO e1
Right x -> return x
-- | Like 'finally', but only call @after@ if an exception occurs.
--
-- @since 0.1.0.0
onException :: MonadUnliftIO m => m a -> m b -> m a
onException thing after = withException thing (\(_ :: SomeException) -> after)
-- | Synchronously throw the given exception.
--
-- Note that, if you provide an exception value which is of an asynchronous
-- type, it will be wrapped up in 'SyncExceptionWrapper'. See 'toSyncException'.
--
-- @since 0.1.0.0
throwIO :: (MonadIO m, Exception e) => e -> m a
throwIO = liftIO . EUnsafe.throwIO . toSyncException
-- | Convert an exception into a synchronous exception.
--
-- For synchronous exceptions, this is the same as 'toException'.
-- For asynchronous exceptions, this will wrap up the exception with
-- 'SyncExceptionWrapper'.
--
-- @since 0.1.0.0
toSyncException :: Exception e => e -> SomeException
toSyncException =
ESafe.toSyncException
-- | Convert an exception into an asynchronous exception.
--
-- For asynchronous exceptions, this is the same as 'toException'.
-- For synchronous exceptions, this will wrap up the exception with
-- 'AsyncExceptionWrapper'.
--
-- @since 0.1.0.0
toAsyncException :: Exception e => e -> SomeException
toAsyncException =
ESafe.toAsyncException
-- | Convert from a possibly wrapped exception.
--
-- The inverse of 'toAsyncException' and 'toSyncException'. When using those
-- functions (or functions that use them, like 'throwTo' or 'throwIO'),
-- 'fromException' might not be sufficient because the exception might be
-- wrapped within 'SyncExceptionWrapper' or 'AsyncExceptionWrapper'.
--
-- @since 0.2.17
fromExceptionUnwrap :: Exception e => SomeException -> Maybe e
fromExceptionUnwrap se
| Just (ESafe.AsyncExceptionWrapper e) <- fromException se = cast e
| Just (ESafe.SyncExceptionWrapper e) <- fromException se = cast e
| otherwise = fromException se
-- | Check if the given exception is synchronous.
--
-- @since 0.1.0.0
isSyncException :: Exception e => e -> Bool
isSyncException =
ESafe.isSyncException
-- | Check if the given exception is asynchronous.
--
-- @since 0.1.0.0
isAsyncException :: Exception e => e -> Bool
isAsyncException = not . isSyncException
{-# INLINE isAsyncException #-}
#if !MIN_VERSION_base(4,8,0)
-- | A synonym for 'show', specialized to 'Exception' instances.
--
-- Starting with base 4.8, the 'Exception' typeclass has a method
-- @displayException@, used for user-friendly display of exceptions.
-- This function provides backwards compatibility for users on base 4.7 and earlier,
-- so that anyone importing this module can simply use @displayException@.
--
-- @since 0.1.0.0
displayException :: Exception e => e -> String
displayException = show
#endif
-- | Unlifted version of 'EUnsafe.mask'.
--
-- @since 0.1.0.0
mask :: MonadUnliftIO m => ((forall a. m a -> m a) -> m b) -> m b
mask f = withRunInIO $ \run -> EUnsafe.mask $ \unmask ->
run $ f $ liftIO . unmask . run
-- | Unlifted version of 'EUnsafe.uninterruptibleMask'.
--
-- @since 0.1.0.0
uninterruptibleMask :: MonadUnliftIO m => ((forall a. m a -> m a) -> m b) -> m b
uninterruptibleMask f = withRunInIO $ \run -> EUnsafe.uninterruptibleMask $ \unmask ->
run $ f $ liftIO . unmask . run
-- | Unlifted version of 'EUnsafe.mask_'.
--
-- @since 0.1.0.0
mask_ :: MonadUnliftIO m => m a -> m a
mask_ f = withRunInIO $ \run -> EUnsafe.mask_ (run f)
-- | Unlifted version of 'EUnsafe.uninterruptibleMask_'.
--
-- @since 0.1.0.0
uninterruptibleMask_ :: MonadUnliftIO m => m a -> m a
uninterruptibleMask_ f = withRunInIO $ \run -> EUnsafe.uninterruptibleMask_ (run f)
-- | A convenience function for throwing a user error. This is useful
-- for cases where it would be too high a burden to define your own
-- exception type.
--
-- This throws an exception of type 'StringException'. When GHC
-- supports it (base 4.9 and GHC 8.0 and onward), it includes a call
-- stack.
--
-- @since 0.1.0.0
#if MIN_VERSION_base(4,9,0)
throwString :: (MonadIO m, HasCallStack) => String -> m a
throwString s = throwIO (StringException s ?callStack)
#else
throwString :: MonadIO m => String -> m a
throwString s = throwIO (StringException s ())
#endif
-- | Smart constructor for a 'StringException' that deals with the
-- call stack.
--
-- @since 0.1.0.0
#if MIN_VERSION_base(4,9,0)
stringException :: HasCallStack => String -> StringException
stringException s = StringException s ?callStack
#else
stringException :: String -> StringException
stringException s = StringException s ()
#endif
-- | Exception type thrown by 'throwString'.
--
-- Note that the second field of the data constructor depends on
-- GHC/base version. For base 4.9 and GHC 8.0 and later, the second
-- field is a call stack. Previous versions of GHC and base do not
-- support call stacks, and the field is simply unit (provided to make
-- pattern matching across GHC versions easier).
--
-- @since 0.1.0.0
#if MIN_VERSION_base(4,9,0)
data StringException = StringException String CallStack
deriving Typeable
-- | @since 0.1.0.0
instance Show StringException where
show (StringException s cs) = concat
$ "UnliftIO.Exception.throwString called with:\n\n"
: s
: "\nCalled from:\n"
: map go (getCallStack cs)
where
go (x, y) = concat
[ " "
, x
, " ("
, prettySrcLoc y
, ")\n"
]
#else
data StringException = StringException String ()
deriving Typeable
-- | @since 0.1.0.0
instance Show StringException where
show (StringException s _) = "UnliftIO.Exception.throwString called with:\n\n" ++ s
#endif
-- | @since 0.2.19
instance Eq StringException where
StringException msg1 _ == StringException msg2 _ = msg1 == msg2
-- | @since 0.1.0.0
instance Exception StringException
-- | Throw an asynchronous exception to another thread.
--
-- Synchronously typed exceptions will be wrapped into an
-- `AsyncExceptionWrapper`, see
-- <https://github.com/fpco/safe-exceptions#determining-sync-vs-async>.
--
-- It's usually a better idea to use the "UnliftIO.Async" module, see
-- <https://github.com/fpco/safe-exceptions#quickstart>.
--
-- @since 0.1.0.0
throwTo :: (Exception e, MonadIO m) => ThreadId -> e -> m ()
throwTo tid = liftIO . EUnsafe.throwTo tid . toAsyncException
-- | Generate a pure value which, when forced, will synchronously
-- throw the given exception.
--
-- Generally it's better to avoid using this function and instead use 'throwIO',
-- see <https://github.com/fpco/safe-exceptions#quickstart>.
--
-- @since 0.1.0.0
impureThrow :: Exception e => e -> a
impureThrow = EUnsafe.throw . toSyncException
-- | Unwrap an 'Either' value, throwing its 'Left' value as a runtime
-- exception via 'throwIO' if present.
--
-- @since 0.1.0.0
fromEither :: (Exception e, MonadIO m) => Either e a -> m a
fromEither = either throwIO return
-- | Same as 'fromEither', but works on an 'IO'-wrapped 'Either'.
--
-- @since 0.1.0.0
fromEitherIO :: (Exception e, MonadIO m) => IO (Either e a) -> m a
fromEitherIO = fromEitherM . liftIO
-- | Same as 'fromEither', but works on an 'm'-wrapped 'Either'.
--
-- @since 0.1.0.0
fromEitherM :: (Exception e, MonadIO m) => m (Either e a) -> m a
fromEitherM = (>>= fromEither)
-- | Same as 'Control.Exception.mapException', except works in
-- a monadic context.
--
-- @since 0.2.15
mapExceptionM :: (Exception e1, Exception e2, MonadUnliftIO m) => (e1 -> e2) -> m a -> m a
mapExceptionM f = handle (throwIO . f)