{-# 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 . -- -- @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 -- . -- -- It's usually a better idea to use the "UnliftIO.Async" module, see -- . -- -- @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 . -- -- @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)