resource-simple-0.1: src/Control/Monad/Resource.hs
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE UndecidableInstances #-}
{-|
Allocate resources which are guaranteed to be released.
One point to note: all register cleanup actions live in IO, not the main
monad. This allows both more efficient code, and for monads to be transformed.
-}
module Control.Monad.Resource
( -- * Data types
ResourceT
, ReleaseKey
-- * Run
, runResourceT
-- * Resource allocation
, with
, register
, release
-- * Monad transformation
, transResourceT
)
where
import Data.IntMap (IntMap)
import qualified Data.IntMap as IntMap
import Data.IORef
( IORef
, newIORef
, readIORef
, writeIORef
, atomicModifyIORef
)
import Data.Word (Word)
import Control.Applicative (Applicative (..))
import Control.Exception (SomeException, mask, mask_, try, finally)
import Control.Monad (liftM, when)
import Control.Monad.Base (MonadBase (..))
import Control.Monad.Cont.Class (MonadCont(..))
import Control.Monad.Error.Class (MonadError (..))
import Control.Monad.Fork.Class (MonadFork (..))
import Control.Monad.IO.Class (MonadIO (..))
import Control.Monad.Reader.Class (MonadReader (..))
import Control.Monad.RWS.Class (MonadRWS (..))
import Control.Monad.State.Class (MonadState (..))
import Control.Monad.Writer.Class (MonadWriter (..))
import Control.Monad.Trans.Class (MonadTrans (..))
import Control.Monad.Trans.Control
( MonadBaseControl (..)
, MonadTransControl (..)
, control
)
------------------------------------------------------------------------------
-- | A lookup key for a specific release action. This value is returned by
-- 'register' and 'with' and is passed to 'release'.
newtype ReleaseKey = ReleaseKey Int
------------------------------------------------------------------------------
data ReleaseMap = ReleaseMap !Int !Word !(IntMap (IO ()))
------------------------------------------------------------------------------
-- | The Resource transformer. This transformer keeps track of all registered
-- actions, and calls them upon exit (via 'runResourceT'). Actions may be
-- registered via 'register', or resources may be allocated atomically via
-- 'with'. The with function corresponds closely to @bracket@.
--
-- Releasing may be performed before exit via the 'release' function. This is
-- a highly recommended optimization, as it will ensure that scarce resources
-- are freed early. Note that calling @release@ will deregister the action, so
-- that a release action will only ever be called once.
newtype ResourceT m a = ResourceT (IORef ReleaseMap -> m a)
------------------------------------------------------------------------------
instance MonadTrans ResourceT where
lift = ResourceT . const
------------------------------------------------------------------------------
instance MonadTransControl ResourceT where
newtype StT ResourceT a = StReader {unStReader :: a}
liftWith f = ResourceT $ \r -> f $ \(ResourceT t) -> liftM StReader $ t r
restoreT = ResourceT . const . liftM unStReader
------------------------------------------------------------------------------
instance Functor m => Functor (ResourceT m) where
fmap f (ResourceT m) = ResourceT $ \r -> fmap f (m r)
------------------------------------------------------------------------------
instance Applicative m => Applicative (ResourceT m) where
pure = ResourceT . const . pure
ResourceT mf <*> ResourceT ma = ResourceT $ \r -> mf r <*> ma r
------------------------------------------------------------------------------
instance Monad m => Monad (ResourceT m) where
return = ResourceT . const . return
ResourceT m >>= f = ResourceT $ \r -> m r >>= \a ->
let ResourceT m' = f a in m' r
------------------------------------------------------------------------------
instance MonadIO m => MonadIO (ResourceT m) where
liftIO = lift . liftIO
------------------------------------------------------------------------------
instance MonadBase b m => MonadBase b (ResourceT m) where
liftBase = lift . liftBase
------------------------------------------------------------------------------
instance MonadBaseControl b m => MonadBaseControl b (ResourceT m) where
newtype StM (ResourceT m) a = StMT (StM m a)
liftBaseWith f = ResourceT $ \reader ->
liftBaseWith $ \runInBase ->
f $ liftM StMT . runInBase . (\(ResourceT r) -> r reader)
restoreM (StMT base) = ResourceT $ const $ restoreM base
------------------------------------------------------------------------------
instance (MonadFork m, MonadBaseControl IO m) => MonadFork (ResourceT m) where
fork (ResourceT f) = ResourceT $ \istate ->
control $ \run -> mask $ \unmask -> do
stateAlloc istate
run . fork $ control $ \run' -> do
unmask (run' $ f istate) `finally` stateCleanup istate
------------------------------------------------------------------------------
instance MonadCont m => MonadCont (ResourceT m) where
callCC = liftCallCC callCC
where
liftCallCC ccc f = ResourceT $ \r -> ccc $ \ c ->
let ResourceT m = f (ResourceT . const . c) in m r
------------------------------------------------------------------------------
instance MonadError e m => MonadError e (ResourceT m) where
throwError = lift . throwError
catchError = liftCatch catchError
where
liftCatch f (ResourceT m) h = ResourceT $ \r ->
f (m r) (\e -> let ResourceT m' = h e in m' r)
------------------------------------------------------------------------------
instance MonadReader r m => MonadReader r (ResourceT m) where
ask = lift ask
local f (ResourceT m) = ResourceT $ local f . m
------------------------------------------------------------------------------
instance MonadRWS r w s m => MonadRWS r w s (ResourceT m)
------------------------------------------------------------------------------
instance MonadState s m => MonadState s (ResourceT m) where
get = lift get
put s = lift $ put s
------------------------------------------------------------------------------
instance MonadWriter w m => MonadWriter w (ResourceT m) where
tell w = lift $ tell w
listen = transResourceT listen
pass = transResourceT pass
------------------------------------------------------------------------------
-- | Perform some allocation, and automatically register a cleanup action.
with :: MonadBase IO m
=> IO a -- ^ allocate
-> (a -> IO ()) -- ^ free resource
-> ResourceT m (ReleaseKey, a)
with acquire m = ResourceT $ \istate -> liftBase $ mask $ \unmask -> do
a <- unmask acquire
key <- register' istate $ m a
return (key, a)
------------------------------------------------------------------------------
-- | Register some action that will be called precisely once, either when
-- 'runResourceT' is called, or when the 'ReleaseKey' is passed to 'release'.
register :: MonadBase IO m => IO () -> ResourceT m ReleaseKey
register m = ResourceT $ \istate -> liftBase $ register' istate m
register' :: IORef ReleaseMap -> IO () -> IO ReleaseKey
register' istate m = atomicModifyIORef istate $ \(ReleaseMap key ref im) ->
(ReleaseMap (key + 1) ref (IntMap.insert key m im), ReleaseKey key)
------------------------------------------------------------------------------
-- | Call a release action early, and deregister it from the list of cleanup
-- actions to be performed.
release :: MonadBase IO m => ReleaseKey -> ResourceT m ()
release key = ResourceT $ \istate -> liftBase $ release' istate key
release' istate (ReleaseKey key) = mask $ \unmask -> do
atomicModifyIORef istate lookupAction >>= maybe (return ()) unmask
where
lookupAction rm@(ReleaseMap key' ref im) =
case IntMap.lookup key im of
Nothing -> (rm, Nothing)
Just m -> (ReleaseMap key' ref $ IntMap.delete key im, Just m)
------------------------------------------------------------------------------
-- | Transform the monad a @ResourceT@ lives in. This is most often used to
-- strip or add new transformers to a stack, e.g. to run a @ReaderT@.
transResourceT :: (m a -> n b) -> ResourceT m a -> ResourceT n b
transResourceT f (ResourceT mx) = ResourceT (\r -> f (mx r))
------------------------------------------------------------------------------
-- | Unwrap a 'ResourceT' transformer, and call all registered release
-- actions.
--
-- Note that there is some reference counting involved due to the 'MonadFork'
-- instance. If multiple threads are sharing the same collection of resources,
-- only the last call to @runResourceT@ will deallocate the resources.
runResourceT :: MonadBaseControl IO m => ResourceT m a -> m a
runResourceT (ResourceT r) = do
istate <- liftBase $ newIORef $ ReleaseMap 0 0 IntMap.empty
control $ \run -> mask $ \unmask -> do
stateAlloc istate
unmask (run $ r istate) `finally` stateCleanup istate
------------------------------------------------------------------------------
stateAlloc :: IORef ReleaseMap -> IO ()
stateAlloc istate = atomicModifyIORef istate $ \(ReleaseMap key ref im) ->
(ReleaseMap key (ref + 1) im, ())
------------------------------------------------------------------------------
stateCleanup :: IORef ReleaseMap -> IO ()
stateCleanup istate = mask_ $ do
(ref, im) <- atomicModifyIORef istate $ \(ReleaseMap key ref im) ->
(ReleaseMap key (ref - 1) im, (ref - 1, im))
when (ref == 0) $ do
mapM_ (\x -> try' x >> return ()) $ IntMap.elems im
writeIORef istate $ error "Control.Monad.Resource.Trans.stateCleanup:\
\ There is a bug in the implementation. The mutable state is\
\ being accessed after cleanup. Please contact the maintainers."
where
try' = try :: IO a -> IO (Either SomeException a)