packages feed

resourcet (empty) → 0.3.0

raw patch · 4 files changed

+560/−0 lines, 4 filesdep +basedep +containersdep +lifted-basesetup-changed

Dependencies added: base, containers, lifted-base, monad-control, transformers, transformers-base

Files

+ Control/Monad/Trans/Resource.hs view
@@ -0,0 +1,496 @@+{-# 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 <http://www.yesodweb.com/book/conduits>.+--+-- One point to note: all register cleanup actions live in the @IO@ 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+      -- * Special actions+    , resourceForkIO+      -- * Monad transformation+    , transResourceT+      -- * A specific Exception transformer+    , ExceptionT (..)+    , runExceptionT_+      -- * Type class/associated types+    , MonadResource (..)+    , MonadUnsafeIO (..)+    , MonadThrow (..)+    , MonadActive (..)+      -- ** Low-level+    , InvalidAccess (..)+      -- * Re-exports+    , MonadBaseControl+    ) where++import Data.Typeable+import Data.IntMap (IntMap)+import qualified Data.IntMap as IntMap+import Control.Exception (SomeException, throw, Exception)+import Control.Monad.Trans.Control+    ( MonadTransControl (..), MonadBaseControl (..)+    , ComposeSt, defaultLiftBaseWith, defaultRestoreM+    , liftBaseDiscard, control+    )+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 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.Cont     ( ContT  )+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)++import Control.Monad.ST (ST, unsafeIOToST)+import qualified Control.Monad.ST.Lazy as Lazy+import Data.Functor.Identity (Identity)++-- | A lookup key for a specific release action. This value is returned by+-- 'register' and 'allocate', and is passed to 'release'.+--+-- Since 0.3.0+newtype ReleaseKey = ReleaseKey Int+    deriving Typeable++type RefCount = Word+type NextKey = Int++data ReleaseMap =+    ReleaseMap !NextKey !RefCount !(IntMap (IO ()))+  | ReleaseMapClosed++-- | 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+-- 'allocate'. @allocate@ 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.+--+-- Since 0.3.0+newtype ResourceT m a = ResourceT (I.IORef ReleaseMap -> 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+                ]++-- | A @Monad@ which allows for safe resource allocation. In theory, any monad+-- transformer stack included a @ResourceT@ can be an instance of+-- @MonadResource@.+--+-- Note: @runResourceT@ has a requirement for a @MonadBaseControl IO m@ monad,+-- which allows control operations to be lifted. A @MonadResource@ does not+-- have this requirement. This means that transformers such as @ContT@ can be+-- an instance of @MonadResource@. However, the @ContT@ wrapper will need to be+-- unwrapped before calling @runResourceT@.+--+-- Since 0.3.0+class (MonadThrow m, MonadUnsafeIO m, MonadIO m) => MonadResource m where+    -- | Register some action that will be called precisely once, either when+    -- 'runResourceT' is called, or when the 'ReleaseKey' is passed to 'release'.+    --+    -- Since 0.3.0+    register :: IO () -> m ReleaseKey++    -- | Call a release action early, and deregister it from the list of cleanup+    -- actions to be performed.+    --+    -- Since 0.3.0+    release :: ReleaseKey -> m ()++    -- | Perform some allocation, and automatically register a cleanup action.+    --+    -- This is almost identical to calling the allocation and then+    -- @register@ing the release action, but this properly handles masking of+    -- asynchronous exceptions.+    --+    -- Since 0.3.0+    allocate :: IO a -- ^ allocate+             -> (a -> IO ()) -- ^ free resource+             -> m (ReleaseKey, a)++    -- | Perform asynchronous exception masking.+    --+    -- This is more general then @Control.Exception.mask@, yet more efficient+    -- than @Control.Exception.Lifted.mask@.+    --+    -- Since 0.3.0+    resourceMask :: ((forall a. ResourceT IO a -> ResourceT IO a) -> ResourceT IO b) -> m b++instance (MonadThrow m, MonadUnsafeIO m, MonadIO m) => MonadResource (ResourceT m) where+    allocate acquire rel = ResourceT $ \istate -> liftIO $ E.mask $ \restore -> do+        a <- restore acquire+        key <- register' istate $ rel a+        return (key, a)++    register rel = ResourceT $ \istate -> liftIO $ register' istate rel++    release rk = ResourceT $ \istate -> liftIO $ release' istate rk++    resourceMask f = ResourceT $ \istate -> liftIO $ E.mask $ \restore ->+        let ResourceT f' = f (go restore)+         in f' istate+      where+        go :: (forall a. IO a -> IO a) -> (forall a. ResourceT IO a -> ResourceT IO a)+        go r (ResourceT g) = ResourceT (\i -> r (g i))++#define GO(T) instance (MonadResource m) => MonadResource (T m) where allocate a = lift . allocate a; register = lift . register; release = lift . release; resourceMask = lift . resourceMask+#define GOX(X, T) instance (X, MonadResource m) => MonadResource (T m) where allocate a = lift . allocate a; register = lift . register; release = lift . release; resourceMask = lift . resourceMask+GO(IdentityT)+GO(ListT)+GO(MaybeT)+GOX(Error e, ErrorT e)+GO(ReaderT r)+GO(ContT 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++register' :: I.IORef ReleaseMap+          -> IO ()+          -> IO ReleaseKey+register' istate rel = I.atomicModifyIORef istate $ \rm ->+    case rm of+        ReleaseMap key rf m ->+            ( ReleaseMap (key + 1) rf (IntMap.insert key rel m)+            , ReleaseKey key+            )+        ReleaseMapClosed -> throw $ InvalidAccess "register'"++-- | Indicates either an error in the library, or misuse of it (e.g., a+-- @ResourceT@'s state is accessed after being released).+--+-- Since 0.3.0+data InvalidAccess = InvalidAccess { functionName :: String }+    deriving Typeable++instance Show InvalidAccess where+    show (InvalidAccess f) = concat+        [ "Control.Monad.Trans.Resource."+        , f+        , ": The mutable state is being accessed after cleanup. Please contact the maintainers."+        ]++instance Exception InvalidAccess++release' :: I.IORef ReleaseMap+         -> ReleaseKey+         -> IO ()+release' istate (ReleaseKey key) = E.mask $ \restore -> do+    maction <- I.atomicModifyIORef 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+                )+    lookupAction ReleaseMapClosed = throw $ InvalidAccess "release'"++stateAlloc :: I.IORef ReleaseMap -> IO ()+stateAlloc istate = do+    I.atomicModifyIORef istate $ \rm ->+        case rm of+            ReleaseMap nk rf m ->+                (ReleaseMap nk (rf + 1) m, ())+            ReleaseMapClosed -> throw $ InvalidAccess "stateAlloc"++stateCleanup :: I.IORef ReleaseMap -> IO ()+stateCleanup istate = E.mask_ $ do+    mm <- I.atomicModifyIORef istate $ \rm ->+        case rm of+            ReleaseMap nk rf m ->+                let rf' = rf - 1+                 in if rf' == minBound+                        then (ReleaseMapClosed, Just m)+                        else (ReleaseMap nk rf' m, Nothing)+            ReleaseMapClosed -> throw $ InvalidAccess "stateCleanup"+    case mm of+        Just m ->+            mapM_ (\x -> try x >> return ()) $ IntMap.elems m+        Nothing -> return ()+  where+    try :: IO a -> IO (Either SomeException a)+    try = E.try++-- | 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.+--+-- Since 0.3.0+runResourceT :: MonadBaseControl IO m => ResourceT m a -> m a+runResourceT (ResourceT r) = do+    istate <- liftBase $ I.newIORef+        $ ReleaseMap minBound minBound IntMap.empty+    bracket_+        (stateAlloc istate)+        (stateCleanup istate)+        (r istate)++bracket_ :: MonadBaseControl IO m => IO () -> IO () -> m a -> m a+bracket_ alloc cleanup inside =+    control $ \run -> E.bracket_ alloc cleanup (run inside)++-- | 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@.+--+-- Since 0.3.0+transResourceT :: (m a -> n b)+               -> ResourceT m a+               -> ResourceT n b+transResourceT f (ResourceT mx) = ResourceT (\r -> f (mx r))++-------- All of our monad et al instances+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 ma >>= f = ResourceT $ \r -> do+        a <- ma r+        let ResourceT f' = f a+        f' r++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+instance Monad m => MonadThrow (ExceptionT m) where+    monadThrow = ExceptionT . return . Left . E.toException+++-- | The express purpose of this transformer is to allow non-@IO@-based monad+-- stacks to catch exceptions via the 'MonadThrow' typeclass.+--+-- Since 0.3.0+newtype ExceptionT m a = ExceptionT { runExceptionT :: m (Either SomeException a) }++-- | Same as 'runExceptionT', but immediately 'E.throw' any exception returned.+--+-- Since 0.3.0+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++-- | A @Monad@ which can throw exceptions. Note that this does not work in a+-- vanilla @ST@ or @Identity@ monad. Instead, you should use the 'ExceptionT'+-- transformer in your stack if you are dealing with a non-@IO@ base monad.+--+-- Since 0.3.0+class Monad m => MonadThrow m where+    monadThrow :: E.Exception e => e -> m a++instance MonadThrow IO where+    monadThrow = E.throwIO++#define GO(T) instance (MonadThrow m) => MonadThrow (T m) where monadThrow = lift . monadThrow+#define GOX(X, T) instance (X, MonadThrow m) => MonadThrow (T m) where monadThrow = lift . monadThrow+GO(IdentityT)+GO(ListT)+GO(MaybeT)+GOX(Error e, ErrorT e)+GO(ReaderT r)+GO(ContT r)+GO(ResourceT)+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.+--+-- Since 0.3.0+resourceForkIO :: MonadBaseControl IO 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.+    bracket_+        (stateAlloc r)+        (return ())+        (liftBaseDiscard forkIO $ bracket_+            (return ())+            (stateCleanup r)+            (restore $ f r))++-- | A @Monad@ based on some monad which allows running of some 'IO' actions,+-- via unsafe calls. This applies to 'IO' and 'ST', for instance.+--+-- Since 0.3.0+class Monad m => MonadUnsafeIO m where+    unsafeLiftIO :: IO a -> m a++instance MonadUnsafeIO IO where+    unsafeLiftIO = id++instance MonadUnsafeIO (ST s) where+    unsafeLiftIO = unsafeIOToST++instance MonadUnsafeIO (Lazy.ST s) where+    unsafeLiftIO = Lazy.unsafeIOToST++instance (MonadTrans t, MonadUnsafeIO m, Monad (t m)) => MonadUnsafeIO (t m) where+    unsafeLiftIO = lift . unsafeLiftIO++-- | Determine if some monad is still active. This is intended to prevent usage+-- of a monadic state after it has been closed.  This is necessary for such+-- cases as lazy I\/O, where an unevaluated thunk may still refer to a+-- closed @ResourceT@.+--+-- Since 0.3.0+class Monad m => MonadActive m where+    monadActive :: m Bool++instance (MonadIO m, MonadActive m) => MonadActive (ResourceT m) where+    monadActive = ResourceT $ \rmMap -> do+        rm <- liftIO $ I.readIORef rmMap+        case rm of+            ReleaseMapClosed -> return False+            _ -> monadActive -- recurse++instance MonadActive Identity where+    monadActive = return True++instance MonadActive IO where+    monadActive = return True++instance MonadActive (ST s) where+    monadActive = return True++instance MonadActive (Lazy.ST s) where+    monadActive = return True++#define GO(T) instance MonadActive m => MonadActive (T m) where monadActive = lift monadActive+#define GOX(X, T) instance (X, MonadActive m) => MonadActive (T m) where monadActive = lift monadActive+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
+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c)2011, Michael Snoyman++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Michael Snoyman nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.lhs view
@@ -0,0 +1,7 @@+#!/usr/bin/env runhaskell++> module Main where+> import Distribution.Simple++> main :: IO ()+> main = defaultMain
+ resourcet.cabal view
@@ -0,0 +1,27 @@+Name:                resourcet+Version:             0.3.0+Synopsis:            Deterministic allocation and freeing of scarce resources.+Description:+	This package was originally included with the conduit package, and has since been split off. For more information, please see <http://www.yesodweb.com/book/conduits>.+License:             BSD3+License-file:        LICENSE+Author:              Michael Snoyman+Maintainer:          michael@snoyman.com+Category:            Data, Conduit+Build-type:          Simple+Cabal-version:       >=1.8+Homepage:            http://github.com/snoyberg/conduit++Library+  Exposed-modules:     Control.Monad.Trans.Resource+  Build-depends:       base                     >= 4.3          && < 5+                     , lifted-base              >= 0.1          && < 0.2+                     , transformers-base        >= 0.4.1        && < 0.5+                     , monad-control            >= 0.3.1        && < 0.4+                     , containers+                     , transformers             >= 0.2.2        && < 0.3+  ghc-options:     -Wall++source-repository head+  type:     git+  location: git://github.com/snoyberg/conduit.git