ki-1.0.1.0: src/Ki/Internal/IO.hs
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
-- | Miscellaneous IO utilities
module Ki.Internal.IO
( -- * Unexceptional IO
UnexceptionalIO (..),
IOResult (..),
unexceptionalTry,
unexceptionalTryEither,
-- * Exception utils
isAsyncException,
interruptiblyMasked,
uninterruptiblyMasked,
tryEitherSTM,
-- * Fork utils
forkIO,
forkOn,
)
where
import Control.Exception
import Control.Monad (join)
import Data.Coerce (coerce)
import GHC.Base (maskAsyncExceptions#, maskUninterruptible#)
import GHC.Conc (STM, ThreadId (ThreadId), catchSTM)
import GHC.Exts (Int (I#), fork#, forkOn#)
import GHC.IO (IO (IO))
import Prelude
-- A little promise that this IO action cannot throw an exception.
--
-- Yeah it's verbose, and maybe not that necessary, but the code that bothers to use it really does require
-- un-exceptiony IO actions for correctness, so here we are.
newtype UnexceptionalIO a = UnexceptionalIO
{runUnexceptionalIO :: IO a}
deriving newtype (Applicative, Functor, Monad)
data IOResult a
= Failure !SomeException -- sync or async exception
| Success a
unexceptionalTry :: forall a. IO a -> UnexceptionalIO (IOResult a)
unexceptionalTry action =
UnexceptionalIO do
(Success <$> action) `catch` \exception ->
pure (Failure exception)
-- Like try, but with continuations. Also, catches all exceptions, because that's the only flavor we need.
unexceptionalTryEither ::
forall a b.
(SomeException -> UnexceptionalIO b) ->
(a -> UnexceptionalIO b) ->
IO a ->
UnexceptionalIO b
unexceptionalTryEither onFailure onSuccess action =
UnexceptionalIO do
join do
catch
(coerce @_ @(a -> IO b) onSuccess <$> action)
(pure . coerce @_ @(SomeException -> IO b) onFailure)
isAsyncException :: SomeException -> Bool
isAsyncException exception =
case fromException @SomeAsyncException exception of
Nothing -> False
Just _ -> True
-- | Call an action with asynchronous exceptions interruptibly masked.
interruptiblyMasked :: IO a -> IO a
interruptiblyMasked (IO io) =
IO (maskAsyncExceptions# io)
-- | Call an action with asynchronous exceptions uninterruptibly masked.
uninterruptiblyMasked :: IO a -> IO a
uninterruptiblyMasked (IO io) =
IO (maskUninterruptible# io)
-- Like try, but with continuations
tryEitherSTM :: Exception e => (e -> STM b) -> (a -> STM b) -> STM a -> STM b
tryEitherSTM onFailure onSuccess action =
join (catchSTM (onSuccess <$> action) (pure . onFailure))
-- Control.Concurrent.forkIO without the exception handler
forkIO :: IO () -> IO ThreadId
forkIO (IO action) =
IO \s0 ->
case fork# action s0 of
(# s1, tid #) -> (# s1, ThreadId tid #)
-- Control.Concurrent.forkOn without the exception handler
forkOn :: Int -> IO () -> IO ThreadId
forkOn (I# cap) (IO action) =
IO \s0 ->
case forkOn# cap action s0 of
(# s1, tid #) -> (# s1, ThreadId tid #)