resourcet-extra-0.0.1: lib/Control/Monad/Trans/Resource/Extra.hs
module Control.Monad.Trans.Resource.Extra
( -- * Acquire
mkAcquire1
, mkAcquireType1
, acquireReleaseSelf
-- * MonadResource
, acquireReleaseKey
, registerType
-- * MonadMask
, withAcquire
, withAcquireRelease
-- * Restore
, Restore (..)
, getRestoreIO
, withRestoreIO
-- * IO
, once
, onceK
) where
import Control.Concurrent.MVar
import Control.Exception.Safe qualified as Ex
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Trans.Resource.Internal qualified as R
import Data.Acquire.Internal qualified as A
import Data.IORef
import Data.IntMap.Strict qualified as IntMap
import Data.Kind
import System.IO.Unsafe
--------------------------------------------------------------------------------
-- | Like 'A.mkAcquire', but the release function will be run at most once.
mkAcquire1 :: IO a -> (a -> IO ()) -> A.Acquire a
mkAcquire1 m f = A.mkAcquire m (onceK f)
-- | Like 'A.mkAcquireType', but the release function will be run at most once.
mkAcquireType1 :: IO a -> (a -> A.ReleaseType -> IO ()) -> A.Acquire a
mkAcquireType1 m f = A.mkAcquireType m (curry (onceK (uncurry f)))
-- | Build an 'A.Acquire' having access to its own release function.
acquireReleaseSelf :: A.Acquire ((A.ReleaseType -> IO ()) -> a) -> A.Acquire a
acquireReleaseSelf (A.Acquire f) = A.Acquire \restore -> do
A.Allocated g rel0 <- f restore
let rel1 = onceK rel0
pure $ A.Allocated (g rel1) rel1
--------------------------------------------------------------------------------
-- | Like 'withAcquireRelease', but doesn't take the extra release function.
withAcquire :: (Ex.MonadMask m, MonadIO m) => A.Acquire a -> (a -> m b) -> m b
withAcquire (A.Acquire f) g = do
Restore restoreIO <- getRestoreIO
Ex.mask \restoreM -> do
A.Allocated x free <- liftIO $ f restoreIO
b <- Ex.withException (restoreM (g x)) \e ->
liftIO $ free $ A.ReleaseExceptionWith e
liftIO $ free A.ReleaseNormal
pure b
-- | @'withAcquireRelease' acq \\release a -> act@ acquires the @a@ and
-- automaticaly releases it when @mb@ returns or throws an exception.
-- If desired, @release@ can be used to release @a@ earlier.
withAcquireRelease
:: (Ex.MonadMask m, MonadIO m)
=> A.Acquire a
-> ((A.ReleaseType -> IO ()) -> a -> m b)
-> m b
withAcquireRelease (A.Acquire f) g = do
Restore restoreIO <- getRestoreIO
Ex.mask \restoreM -> do
A.Allocated x free <- liftIO $ f restoreIO
-- Wrapper so that we don't perform `free` again if `g` already did.
let free1 = onceK free
b <- Ex.withException (restoreM (g free1 x)) \e ->
liftIO $ free1 $ A.ReleaseExceptionWith e
liftIO $ free1 A.ReleaseNormal
pure b
--------------------------------------------------------------------------------
-- | Like 'R.register', but gives access to the 'A.ReleaseType' too.
registerType
:: (R.MonadResource m) => (A.ReleaseType -> IO ()) -> m R.ReleaseKey
registerType = R.liftResourceT . R.ResourceT . flip R.registerType
-- | 'acquireReleaseKey' will 'R.unprotect' the 'R.ReleaseKey',
-- and use 'A.Acquire' to manage the release action instead.
acquireReleaseKey :: R.ReleaseKey -> A.Acquire ()
acquireReleaseKey (R.ReleaseKey istate key) =
void $ A.mkAcquireType acq rel
where
acq :: IO (Maybe (A.ReleaseType -> IO ()))
acq =
-- The following code does pretty much the same as 'R.unprotect',
-- which we can't use directly because its result doesn't allow us
-- to specify the 'A.ReleaseType' during release.
atomicModifyIORef istate \case
R.ReleaseMap next rf im
| Just g <- IntMap.lookup key im ->
(R.ReleaseMap next rf (IntMap.delete key im), Just g)
rm -> (rm, Nothing)
rel :: Maybe (A.ReleaseType -> IO ()) -> A.ReleaseType -> IO ()
rel = maybe mempty id
--------------------------------------------------------------------------------
-- | Wrapper around a “restore” function like the one given
-- by @'mask' (\\restore -> ...)@, in a particular 'Monad' @m@.
type Restore :: (Type -> Type) -> Type
newtype Restore m = Restore (forall x. m x -> m x)
-- | Get the current 'Restore' action in 'IO', wrapped in 'Restore'.
getRestoreIO :: (MonadIO m) => m (Restore IO)
getRestoreIO =
-- Ugly, but safe. Check the implementation in base.
liftIO $ Ex.mask \f -> pure (Restore f)
-- | Get the current 'Restore' action in 'IO', without the 'Restore' wrapper.
withRestoreIO
:: (Ex.MonadMask m, MonadIO m) => ((forall x. IO x -> IO x) -> m a) -> m a
withRestoreIO f = getRestoreIO >>= \(Restore g) -> f g
--------------------------------------------------------------------------------
-- | @'once' ma@ wraps @ma@ so that @ma@ is executed at most once. Further
-- executions of the same @'once' ma@ are a no-op. It's safe to use the wrapper
-- concurrently; only one thread will get to execute the actual @ma@ at most.
once :: (MonadIO m, Ex.MonadMask m) => m () -> m ()
once ma = onceK (const ma) ()
-- | Kleisli version of 'once'.
onceK :: (MonadIO m, Ex.MonadMask m) => (a -> m ()) -> (a -> m ())
{-# NOINLINE onceK #-}
onceK kma = unsafePerformIO do
done <- newMVar False
pure \a ->
Ex.bracket
(liftIO $ takeMVar done)
(\_ -> liftIO $ putMVar done True)
(\d -> unless d (kma a))