{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE UndecidableInstances #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE CPP #-} {-# LANGUAGE DeriveDataTypeable #-} -- | Allocate resources which are guaranteed to be released. -- -- For more information, see . -- -- One point to note: all register cleanup actions live in the base monad, not -- the main monad. This allows both more efficient code, and for monads to be -- transformed. module Control.Monad.Trans.Resource ( -- * Data types ResourceT , ReleaseKey -- * Unwrap , runResourceT -- * Resource allocation , with , withIO , register , release -- * Use references , readRef , writeRef , newRef -- * Special actions , resourceForkIO -- * Monad transformation , transResourceT -- * A specific Exception transformer , ExceptionT (..) , runExceptionT_ -- * Type class/associated types , Resource (..) , ResourceUnsafeIO (..) , ResourceIO , ResourceBaseIO (..) , ResourceThrow (..) -- ** Low-level , HasRef (..) ) where import Data.Typeable import Data.IntMap (IntMap) import qualified Data.IntMap as IntMap import Control.Exception (SomeException) import Control.Monad.Trans.Control ( MonadTransControl (..), MonadBaseControl (..) , ComposeSt, defaultLiftBaseWith, defaultRestoreM , liftBaseDiscard ) import qualified Data.IORef as I import Control.Monad.Base (MonadBase, liftBase) import Control.Applicative (Applicative (..)) import Control.Monad.Trans.Class (MonadTrans (..)) import Control.Monad.IO.Class (MonadIO (..)) import Control.Monad (liftM) import qualified Control.Exception as E import Control.Monad.ST (ST, unsafeIOToST) import qualified Control.Monad.ST.Lazy as Lazy import qualified Data.STRef as S import qualified Data.STRef.Lazy as SL import Data.Monoid (Monoid) import qualified Control.Exception.Lifted as L import Control.Monad.Trans.Identity ( IdentityT) import Control.Monad.Trans.List ( ListT ) import Control.Monad.Trans.Maybe ( MaybeT ) import Control.Monad.Trans.Error ( ErrorT, Error) import Control.Monad.Trans.Reader ( ReaderT ) import Control.Monad.Trans.State ( StateT ) import Control.Monad.Trans.Writer ( WriterT ) import Control.Monad.Trans.RWS ( RWST ) import Data.Word (Word) import qualified Control.Monad.Trans.RWS.Strict as Strict ( RWST ) import qualified Control.Monad.Trans.State.Strict as Strict ( StateT ) import qualified Control.Monad.Trans.Writer.Strict as Strict ( WriterT ) import Control.Concurrent (ThreadId, forkIO) -- | Create a new reference. newRef :: Resource m => a -> ResourceT m (Ref (Base m) a) newRef = lift . resourceLiftBase . newRef' {-# INLINE newRef #-} -- | Read a value from a reference. readRef :: Resource m => Ref (Base m) a -> ResourceT m a readRef = lift . resourceLiftBase . readRef' {-# INLINE readRef #-} -- | Write a value to a reference. writeRef :: Resource m => Ref (Base m) a -> a -> ResourceT m () writeRef r = lift . resourceLiftBase . writeRef' r {-# INLINE writeRef #-} -- | A base monad which provides mutable references and some exception-safe way -- of interacting with them. For monads which cannot handle exceptions (e.g., -- 'ST'), exceptions may be ignored. However, in such cases, scarce resources -- should /not/ be allocated in those monads, as exceptions may cause the -- cleanup functions to not run. -- -- The instance for 'IO', however, is fully exception-safe. -- -- Minimal complete definition: @Ref@, @newRef'@, @readRef'@ and @writeRef'@. class Monad m => HasRef m where type Ref m :: * -> * newRef' :: a -> m (Ref m a) readRef' :: Ref m a -> m a writeRef' :: Ref m a -> a -> m () -- | For monads supporting multi-threaded access (e.g., @IO@), this much be -- an atomic modification. atomicModifyRef' :: Ref m a -> (a -> (a, b)) -> m b atomicModifyRef' sa f = do a0 <- readRef' sa let (a, b) = f a0 writeRef' sa a return b mask :: ((forall a. m a -> m a) -> m b) -> m b mask f = f id mask_ :: m a -> m a mask_ = mask . const try :: m a -> m (Either SomeException a) try = liftM Right instance HasRef IO where type Ref IO = I.IORef newRef' = I.newIORef {-# INLINE newRef' #-} atomicModifyRef' = I.atomicModifyIORef {-# INLINE atomicModifyRef' #-} readRef' = I.readIORef {-# INLINE readRef' #-} writeRef' = I.writeIORef {-# INLINE writeRef' #-} mask = E.mask {-# INLINE mask #-} mask_ = E.mask_ {-# INLINE mask_ #-} try = E.try {-# INLINE try #-} instance HasRef (ST s) where type Ref (ST s) = S.STRef s newRef' = S.newSTRef readRef' = S.readSTRef writeRef' = S.writeSTRef instance HasRef (Lazy.ST s) where type Ref (Lazy.ST s) = SL.STRef s newRef' = SL.newSTRef readRef' = SL.readSTRef writeRef' = SL.writeSTRef -- | A 'Monad' with a base that has mutable references, and allows some way to -- run base actions and clean up properly. class (HasRef (Base m), Monad m) => Resource m where -- | The base monad for the current monad stack. This will usually be @IO@ -- or @ST@. type Base m :: * -> * -- | Run some action in the @Base@ monad. This function corresponds to -- 'liftBase', but due to various type issues, we need to have our own -- version here. resourceLiftBase :: Base m a -> m a -- | Guarantee that some initialization and cleanup code is called before -- and after some action. Note that the initialization and cleanup lives in -- the base monad, while the body is in the top monad. resourceBracket_ :: Base m () -- ^ init -> Base m () -- ^ cleanup -> m c -- ^ body -> m c instance Resource IO where type Base IO = IO resourceLiftBase = id resourceBracket_ = E.bracket_ instance Resource (ST s) where type Base (ST s) = ST s resourceLiftBase = id resourceBracket_ ma mb mc = do ma c <- mc mb return c instance Resource (Lazy.ST s) where type Base (Lazy.ST s) = Lazy.ST s resourceLiftBase = id resourceBracket_ ma mb mc = do ma c <- mc mb return c instance (MonadTransControl t, Resource m, Monad (t m)) => Resource (t m) where type Base (t m) = Base m resourceLiftBase = lift . resourceLiftBase resourceBracket_ a b c = control' $ \run -> resourceBracket_ a b (run c) where control' f = liftWith f >>= restoreT . return -- | A 'Resource' based on some monad which allows running of some 'IO' -- actions, via unsafe calls. This applies to 'IO' and 'ST', for instance. class Resource m => ResourceUnsafeIO m where unsafeFromIO :: IO a -> m a instance ResourceUnsafeIO IO where unsafeFromIO = id instance ResourceUnsafeIO (ST s) where unsafeFromIO = unsafeIOToST instance ResourceUnsafeIO (Lazy.ST s) where unsafeFromIO = Lazy.unsafeIOToST instance (MonadTransControl t, ResourceUnsafeIO m, Monad (t m)) => ResourceUnsafeIO (t m) where unsafeFromIO = lift . unsafeFromIO -- | A helper class for 'ResourceIO', stating that the base monad provides @IO@ -- actions. class ResourceBaseIO m where safeFromIOBase :: IO a -> m a instance ResourceBaseIO IO where safeFromIOBase = id -- | A 'Resource' which can safely run 'IO' calls. class (ResourceBaseIO (Base m), ResourceUnsafeIO m, ResourceThrow m, MonadIO m, MonadBaseControl IO m) => ResourceIO m instance ResourceIO IO instance (MonadTransControl t, ResourceIO m, Monad (t m), ResourceThrow (t m), MonadBaseControl IO (t m), MonadIO (t m)) => ResourceIO (t m) -- | A lookup key for a specific release action. This value is returned by -- 'register', 'with' and 'withIO', and is passed to 'release'. newtype ReleaseKey = ReleaseKey Int deriving Typeable type RefCount = Word type NextKey = Int data ReleaseMap base = ReleaseMap !NextKey !RefCount !(IntMap (base ())) -- | 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' or 'withIO'. The with functions correspond 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 (Ref (Base m) (ReleaseMap (Base m)) -> m a) instance Typeable1 m => Typeable1 (ResourceT m) where typeOf1 = goType undefined where goType :: Typeable1 m => m a -> ResourceT m a -> TypeRep goType m _ = mkTyConApp (mkTyCon "Control.Monad.Trans.Resource.ResourceT") [ typeOf1 m ] -- | Perform some allocation, and automatically register a cleanup action. -- -- If you are performing an @IO@ action, it will likely be easier to use the -- 'withIO' function, which handles types more cleanly. with :: Resource m => Base m a -- ^ allocate -> (a -> Base m ()) -- ^ free resource -> ResourceT m (ReleaseKey, a) with acquire rel = ResourceT $ \istate -> resourceLiftBase $ mask $ \restore -> do a <- restore acquire key <- register' istate $ rel a return (key, a) -- | Same as 'with', but explicitly uses @IO@ as a base. withIO :: ResourceIO m => IO a -- ^ allocate -> (a -> IO ()) -- ^ free resource -> ResourceT m (ReleaseKey, a) withIO acquire rel = ResourceT $ \istate -> resourceLiftBase $ mask $ \restore -> do a <- restore $ safeFromIOBase acquire key <- register' istate $ safeFromIOBase $ safeFromIOBase $ rel 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 :: Resource m => Base m () -> ResourceT m ReleaseKey register rel = ResourceT $ \istate -> resourceLiftBase $ register' istate rel register' :: HasRef base => Ref base (ReleaseMap base) -> base () -> base ReleaseKey register' istate rel = atomicModifyRef' istate $ \(ReleaseMap key rf m) -> ( ReleaseMap (key + 1) rf (IntMap.insert key rel m) , ReleaseKey key ) -- | Call a release action early, and deregister it from the list of cleanup -- actions to be performed. release :: Resource m => ReleaseKey -> ResourceT m () release rk = ResourceT $ \istate -> resourceLiftBase $ release' istate rk release' :: HasRef base => Ref base (ReleaseMap base) -> ReleaseKey -> base () release' istate (ReleaseKey key) = mask $ \restore -> do maction <- atomicModifyRef' istate lookupAction maybe (return ()) restore maction where lookupAction rm@(ReleaseMap next rf m) = case IntMap.lookup key m of Nothing -> (rm, Nothing) Just action -> ( ReleaseMap next rf $ IntMap.delete key m , Just action ) stateAlloc :: HasRef m => Ref m (ReleaseMap m) -> m () stateAlloc istate = do atomicModifyRef' istate $ \(ReleaseMap nk rf m) -> (ReleaseMap nk (rf + 1) m, ()) stateCleanup :: HasRef m => Ref m (ReleaseMap m) -> m () stateCleanup istate = mask_ $ do (rf, m) <- atomicModifyRef' istate $ \(ReleaseMap nk rf m) -> (ReleaseMap nk (rf - 1) m, (rf - 1, m)) if rf == minBound then do mapM_ (\x -> try x >> return ()) $ IntMap.elems m -- Trigger an exception consistently for one race condition: -- let's put an undefined value in the state. If somehow -- another thread is still able to access it, at least we get -- clearer error messages. writeRef' istate $ error "Control.Monad.Trans.Resource.stateCleanup: There is a bug in the implementation. The mutable state is being accessed after cleanup. Please contact the maintainers." else return () -- | Unwrap a 'ResourceT' transformer, and call all registered release actions. -- -- Note that there is some reference counting involved due to 'resourceForkIO'. -- If multiple threads are sharing the same collection of resources, only the -- last call to @runResourceT@ will deallocate the resources. runResourceT :: Resource m => ResourceT m a -> m a runResourceT (ResourceT r) = do istate <- resourceLiftBase $ newRef' $ ReleaseMap minBound minBound IntMap.empty resourceBracket_ (stateAlloc istate) (stateCleanup istate) (r istate) -- | 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@. Note that -- the original and new monad must both have the same 'Base' monad. transResourceT :: (Base m ~ Base n) => (m a -> n a) -> ResourceT m a -> ResourceT n a transResourceT f (ResourceT mx) = ResourceT (\r -> f (mx r)) -------- All of our monad et al instances instance Monad m => Functor (ResourceT m) where fmap f (ResourceT m) = ResourceT $ \r -> liftM f (m r) instance Monad m => Applicative (ResourceT m) where pure = ResourceT . const . return ResourceT mf <*> ResourceT ma = ResourceT $ \r -> do f <- mf r a <- ma r return $ f a instance Monad m => Monad (ResourceT m) where return = pure ResourceT ma >>= f = ResourceT $ \r -> ma r >>= flip un r . f where un (ResourceT x) = x instance MonadTrans ResourceT where lift = ResourceT . const instance MonadIO m => MonadIO (ResourceT m) where liftIO = lift . liftIO instance MonadBase b m => MonadBase b (ResourceT m) where liftBase = lift . liftBase {- 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 {-# INLINE liftWith #-} {-# INLINE restoreT #-} -} 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 -- | The express purpose of this transformer is to allow the 'ST' monad to -- catch exceptions via the 'ResourceThrow' typeclass. newtype ExceptionT m a = ExceptionT { runExceptionT :: m (Either SomeException a) } -- | Same as 'runExceptionT', but immediately 'E.throw' any exception returned. runExceptionT_ :: Monad m => ExceptionT m a -> m a runExceptionT_ = liftM (either E.throw id) . runExceptionT instance Monad m => Functor (ExceptionT m) where fmap f = ExceptionT . (liftM . fmap) f . runExceptionT instance Monad m => Applicative (ExceptionT m) where pure = ExceptionT . return . Right ExceptionT mf <*> ExceptionT ma = ExceptionT $ do ef <- mf case ef of Left e -> return (Left e) Right f -> do ea <- ma case ea of Left e -> return (Left e) Right x -> return (Right (f x)) instance Monad m => Monad (ExceptionT m) where return = pure ExceptionT ma >>= f = ExceptionT $ do ea <- ma case ea of Left e -> return (Left e) Right a -> runExceptionT (f a) instance MonadBase b m => MonadBase b (ExceptionT m) where liftBase = lift . liftBase instance MonadTrans ExceptionT where lift = ExceptionT . liftM Right instance MonadTransControl ExceptionT where newtype StT ExceptionT a = StExc { unStExc :: Either SomeException a } liftWith f = ExceptionT $ liftM return $ f $ liftM StExc . runExceptionT restoreT = ExceptionT . liftM unStExc instance MonadBaseControl b m => MonadBaseControl b (ExceptionT m) where newtype StM (ExceptionT m) a = StE { unStE :: ComposeSt ExceptionT m a } liftBaseWith = defaultLiftBaseWith StE restoreM = defaultRestoreM unStE instance (Resource m, MonadBaseControl (Base m) m) => ResourceThrow (ExceptionT m) where resourceThrow = ExceptionT . return . Left . E.toException -- | A 'Resource' which can throw exceptions. Note that this does not work in a -- vanilla @ST@ monad. Instead, you should use the 'ExceptionT' transformer on -- top of @ST@. class Resource m => ResourceThrow m where resourceThrow :: E.Exception e => e -> m a instance ResourceThrow IO where resourceThrow = E.throwIO #define GO(T) instance (ResourceThrow m) => ResourceThrow (T m) where resourceThrow = lift . resourceThrow #define GOX(X, T) instance (X, ResourceThrow m) => ResourceThrow (T m) where resourceThrow = lift . resourceThrow GO(IdentityT) GO(ListT) GO(MaybeT) GOX(Error e, ErrorT e) GO(ReaderT r) GO(StateT s) GOX(Monoid w, WriterT w) GOX(Monoid w, RWST r w s) GOX(Monoid w, Strict.RWST r w s) GO(Strict.StateT s) GOX(Monoid w, Strict.WriterT w) #undef GO #undef GOX -- | Introduce a reference-counting scheme to allow a resource context to be -- shared by multiple threads. Once the last thread exits, all remaining -- resources will be released. -- -- Note that abuse of this function will greatly delay the deallocation of -- registered resources. This function should be used with care. A general -- guideline: -- -- If you are allocating a resource that should be shared by multiple threads, -- and will be held for a long time, you should allocate it at the beginning of -- a new @ResourceT@ block and then call @resourceForkIO@ from there. resourceForkIO :: ResourceIO m => ResourceT m () -> ResourceT m ThreadId resourceForkIO (ResourceT f) = ResourceT $ \r -> L.mask $ \restore -> -- We need to make sure the counter is incremented before this call -- returns. Otherwise, the parent thread may call runResourceT before -- the child thread increments, and all resources will be freed -- before the child gets called. resourceBracket_ (stateAlloc r) (return ()) (liftBaseDiscard forkIO $ resourceBracket_ (return ()) (stateCleanup r) (restore $ f r))