1{-# OPTIONS_HADDOCK not-home #-} 2{-# LANGUAGE CPP #-} 3{-# LANGUAGE DeriveDataTypeable #-} 4{-# LANGUAGE FlexibleInstances #-} 5{-# LANGUAGE FlexibleContexts #-} 6{-# LANGUAGE MultiParamTypeClasses #-} 7{-# LANGUAGE TypeFamilies #-} 8{-# LANGUAGE UndecidableInstances #-} 9{-# LANGUAGE RankNTypes #-} 10 11module Control.Monad.Trans.Resource.Internal( 12 InvalidAccess(..) 13 , MonadResource(..) 14 , ReleaseKey(..) 15 , ReleaseMap(..) 16 , ResIO 17 , ResourceT(..) 18 , stateAlloc 19 , stateCleanup 20 , transResourceT 21 , register' 22 , registerType 23 , ResourceCleanupException (..) 24 , stateCleanupChecked 25) where 26 27import Control.Exception (throw,Exception,SomeException) 28import Control.Applicative (Applicative (..), Alternative(..)) 29import Control.Monad (MonadPlus(..)) 30import Control.Monad.Fail (MonadFail(..)) 31import Control.Monad.Fix (MonadFix(..)) 32import Control.Monad.IO.Unlift 33import Control.Monad.Trans.Class (MonadTrans (..)) 34import Control.Monad.Trans.Cont ( ContT ) 35import Control.Monad.Cont.Class ( MonadCont (..) ) 36import Control.Monad.Error.Class ( MonadError (..) ) 37import Control.Monad.RWS.Class ( MonadRWS ) 38import Control.Monad.Reader.Class ( MonadReader (..) ) 39import Control.Monad.State.Class ( MonadState (..) ) 40import Control.Monad.Writer.Class ( MonadWriter (..) ) 41 42import Control.Monad.Trans.Identity ( IdentityT) 43import Control.Monad.Trans.List ( ListT ) 44import Control.Monad.Trans.Maybe ( MaybeT ) 45import Control.Monad.Trans.Except ( ExceptT ) 46import Control.Monad.Trans.Reader ( ReaderT ) 47import Control.Monad.Trans.State ( StateT ) 48import Control.Monad.Trans.Writer ( WriterT ) 49import Control.Monad.Trans.RWS ( RWST ) 50 51import qualified Control.Monad.Trans.RWS.Strict as Strict ( RWST ) 52import qualified Control.Monad.Trans.State.Strict as Strict ( StateT ) 53import qualified Control.Monad.Trans.Writer.Strict as Strict ( WriterT ) 54 55import Control.Monad.IO.Class (MonadIO (..)) 56import Control.Monad.Primitive (PrimMonad (..)) 57import qualified Control.Exception as E 58 59-- FIXME Do we want to only support MonadThrow? 60import Control.Monad.Catch (MonadThrow (..), MonadCatch (..), MonadMask (..)) 61import Data.IntMap (IntMap) 62import qualified Data.IntMap as IntMap 63import qualified Data.IORef as I 64import Data.Typeable 65import Data.Word(Word) 66import Data.Acquire.Internal (ReleaseType (..)) 67 68-- | A @Monad@ which allows for safe resource allocation. In theory, any monad 69-- transformer stack which includes a @ResourceT@ can be an instance of 70-- @MonadResource@. 71-- 72-- Note: @runResourceT@ has a requirement for a @MonadUnliftIO m@ monad, 73-- which allows control operations to be lifted. A @MonadResource@ does not 74-- have this requirement. This means that transformers such as @ContT@ can be 75-- an instance of @MonadResource@. However, the @ContT@ wrapper will need to be 76-- unwrapped before calling @runResourceT@. 77-- 78-- Since 0.3.0 79class MonadIO m => MonadResource m where 80 -- | Lift a @ResourceT IO@ action into the current @Monad@. 81 -- 82 -- Since 0.4.0 83 liftResourceT :: ResourceT IO a -> m a 84 85 86-- | A lookup key for a specific release action. This value is returned by 87-- 'register' and 'allocate', and is passed to 'release'. 88-- 89-- Since 0.3.0 90data ReleaseKey = ReleaseKey !(I.IORef ReleaseMap) !Int 91 deriving Typeable 92 93type RefCount = Word 94type NextKey = Int 95 96data ReleaseMap = 97 ReleaseMap !NextKey !RefCount !(IntMap (ReleaseType -> IO ())) 98 | ReleaseMapClosed 99 100-- | Convenient alias for @ResourceT IO@. 101type ResIO = ResourceT IO 102 103 104instance MonadCont m => MonadCont (ResourceT m) where 105 callCC f = ResourceT $ \i -> callCC $ \c -> unResourceT (f (ResourceT . const . c)) i 106 107instance MonadError e m => MonadError e (ResourceT m) where 108 throwError = lift . throwError 109 catchError r h = ResourceT $ \i -> unResourceT r i `catchError` \e -> unResourceT (h e) i 110 111instance MonadRWS r w s m => MonadRWS r w s (ResourceT m) 112 113instance MonadReader r m => MonadReader r (ResourceT m) where 114 ask = lift ask 115 local = mapResourceT . local 116 117mapResourceT :: (m a -> n b) -> ResourceT m a -> ResourceT n b 118mapResourceT f = ResourceT . (f .) . unResourceT 119 120instance MonadState s m => MonadState s (ResourceT m) where 121 get = lift get 122 put = lift . put 123 124instance MonadWriter w m => MonadWriter w (ResourceT m) where 125 tell = lift . tell 126 listen = mapResourceT listen 127 pass = mapResourceT pass 128 129instance MonadThrow m => MonadThrow (ResourceT m) where 130 throwM = lift . throwM 131instance MonadCatch m => MonadCatch (ResourceT m) where 132 catch (ResourceT m) c = 133 ResourceT $ \r -> m r `catch` \e -> unResourceT (c e) r 134instance MonadMask m => MonadMask (ResourceT m) where 135 mask a = ResourceT $ \e -> mask $ \u -> unResourceT (a $ q u) e 136 where q u (ResourceT b) = ResourceT (u . b) 137 uninterruptibleMask a = 138 ResourceT $ \e -> uninterruptibleMask $ \u -> unResourceT (a $ q u) e 139 where q u (ResourceT b) = ResourceT (u . b) 140#if MIN_VERSION_exceptions(0, 10, 0) 141 generalBracket acquire release use = 142 ResourceT $ \r -> 143 generalBracket 144 ( unResourceT acquire r ) 145 ( \resource exitCase -> 146 unResourceT ( release resource exitCase ) r 147 ) 148 ( \resource -> unResourceT ( use resource ) r ) 149#elif MIN_VERSION_exceptions(0, 9, 0) 150#error exceptions 0.9.0 is not supported 151#endif 152instance MonadIO m => MonadResource (ResourceT m) where 153 liftResourceT = transResourceT liftIO 154instance PrimMonad m => PrimMonad (ResourceT m) where 155 type PrimState (ResourceT m) = PrimState m 156 primitive = lift . primitive 157 158-- | Transform the monad a @ResourceT@ lives in. This is most often used to 159-- strip or add new transformers to a stack, e.g. to run a @ReaderT@. 160-- 161-- Note that this function is a slight generalization of 'hoist'. 162-- 163-- Since 0.3.0 164transResourceT :: (m a -> n b) 165 -> ResourceT m a 166 -> ResourceT n b 167transResourceT f (ResourceT mx) = ResourceT (\r -> f (mx r)) 168 169-- | The Resource transformer. This transformer keeps track of all registered 170-- actions, and calls them upon exit (via 'runResourceT'). Actions may be 171-- registered via 'register', or resources may be allocated atomically via 172-- 'allocate'. @allocate@ corresponds closely to @bracket@. 173-- 174-- Releasing may be performed before exit via the 'release' function. This is a 175-- highly recommended optimization, as it will ensure that scarce resources are 176-- freed early. Note that calling @release@ will deregister the action, so that 177-- a release action will only ever be called once. 178-- 179-- Since 0.3.0 180newtype ResourceT m a = ResourceT { unResourceT :: I.IORef ReleaseMap -> m a } 181#if __GLASGOW_HASKELL__ >= 707 182 deriving Typeable 183#else 184instance Typeable1 m => Typeable1 (ResourceT m) where 185 typeOf1 = goType undefined 186 where 187 goType :: Typeable1 m => m a -> ResourceT m a -> TypeRep 188 goType m _ = 189 mkTyConApp 190#if __GLASGOW_HASKELL__ >= 704 191 (mkTyCon3 "resourcet" "Control.Monad.Trans.Resource" "ResourceT") 192#else 193 (mkTyCon "Control.Monad.Trans.Resource.ResourceT") 194#endif 195 [ typeOf1 m 196 ] 197#endif 198 199-- | Indicates either an error in the library, or misuse of it (e.g., a 200-- @ResourceT@'s state is accessed after being released). 201-- 202-- Since 0.3.0 203data InvalidAccess = InvalidAccess { functionName :: String } 204 deriving Typeable 205 206instance Show InvalidAccess where 207 show (InvalidAccess f) = concat 208 [ "Control.Monad.Trans.Resource." 209 , f 210 , ": The mutable state is being accessed after cleanup. Please contact the maintainers." 211 ] 212 213instance Exception InvalidAccess 214 215-------- All of our monad et al instances 216instance Functor m => Functor (ResourceT m) where 217 fmap f (ResourceT m) = ResourceT $ \r -> fmap f (m r) 218 219instance Applicative m => Applicative (ResourceT m) where 220 pure = ResourceT . const . pure 221 ResourceT mf <*> ResourceT ma = ResourceT $ \r -> 222 mf r <*> ma r 223 224-- | Since 1.1.5 225instance Alternative m => Alternative (ResourceT m) where 226 empty = ResourceT $ \_ -> empty 227 (ResourceT mf) <|> (ResourceT ma) = ResourceT $ \r -> mf r <|> ma r 228 229-- | Since 1.1.5 230instance MonadPlus m => MonadPlus (ResourceT m) where 231 mzero = ResourceT $ \_ -> mzero 232 (ResourceT mf) `mplus` (ResourceT ma) = ResourceT $ \r -> mf r `mplus` ma r 233 234instance Monad m => Monad (ResourceT m) where 235 return = pure 236 ResourceT ma >>= f = ResourceT $ \r -> do 237 a <- ma r 238 let ResourceT f' = f a 239 f' r 240 241-- | @since 1.2.2 242instance MonadFail m => MonadFail (ResourceT m) where 243 fail = lift . Control.Monad.Fail.fail 244 245-- | @since 1.1.8 246instance MonadFix m => MonadFix (ResourceT m) where 247 mfix f = ResourceT $ \r -> mfix $ \a -> unResourceT (f a) r 248 249instance MonadTrans ResourceT where 250 lift = ResourceT . const 251 252instance MonadIO m => MonadIO (ResourceT m) where 253 liftIO = lift . liftIO 254 255-- | @since 1.1.10 256instance MonadUnliftIO m => MonadUnliftIO (ResourceT m) where 257 {-# INLINE withRunInIO #-} 258 withRunInIO inner = 259 ResourceT $ \r -> 260 withRunInIO $ \run -> 261 inner (run . flip unResourceT r) 262 263#define GO(T) instance (MonadResource m) => MonadResource (T m) where liftResourceT = lift . liftResourceT 264#define GOX(X, T) instance (X, MonadResource m) => MonadResource (T m) where liftResourceT = lift . liftResourceT 265GO(IdentityT) 266GO(ListT) 267GO(MaybeT) 268GO(ExceptT e) 269GO(ReaderT r) 270GO(ContT r) 271GO(StateT s) 272GOX(Monoid w, WriterT w) 273GOX(Monoid w, RWST r w s) 274GOX(Monoid w, Strict.RWST r w s) 275GO(Strict.StateT s) 276GOX(Monoid w, Strict.WriterT w) 277#undef GO 278#undef GOX 279 280stateAlloc :: I.IORef ReleaseMap -> IO () 281stateAlloc istate = do 282 I.atomicModifyIORef istate $ \rm -> 283 case rm of 284 ReleaseMap nk rf m -> 285 (ReleaseMap nk (rf + 1) m, ()) 286 ReleaseMapClosed -> throw $ InvalidAccess "stateAlloc" 287 288stateCleanup :: ReleaseType -> I.IORef ReleaseMap -> IO () 289stateCleanup rtype istate = E.mask_ $ do 290 mm <- I.atomicModifyIORef istate $ \rm -> 291 case rm of 292 ReleaseMap nk rf m -> 293 let rf' = rf - 1 294 in if rf' == minBound 295 then (ReleaseMapClosed, Just m) 296 else (ReleaseMap nk rf' m, Nothing) 297 ReleaseMapClosed -> throw $ InvalidAccess "stateCleanup" 298 case mm of 299 Just m -> 300 mapM_ (\x -> try (x rtype) >> return ()) $ IntMap.elems m 301 Nothing -> return () 302 where 303 try :: IO a -> IO (Either SomeException a) 304 try = E.try 305 306register' :: I.IORef ReleaseMap 307 -> IO () 308 -> IO ReleaseKey 309register' istate rel = I.atomicModifyIORef istate $ \rm -> 310 case rm of 311 ReleaseMap key rf m -> 312 ( ReleaseMap (key - 1) rf (IntMap.insert key (const rel) m) 313 , ReleaseKey istate key 314 ) 315 ReleaseMapClosed -> throw $ InvalidAccess "register'" 316 317-- | 318-- 319-- Since 1.1.2 320registerType :: I.IORef ReleaseMap 321 -> (ReleaseType -> IO ()) 322 -> IO ReleaseKey 323registerType istate rel = I.atomicModifyIORef istate $ \rm -> 324 case rm of 325 ReleaseMap key rf m -> 326 ( ReleaseMap (key - 1) rf (IntMap.insert key rel m) 327 , ReleaseKey istate key 328 ) 329 ReleaseMapClosed -> throw $ InvalidAccess "register'" 330 331-- | Thrown when one or more cleanup functions themselves throw an 332-- exception during cleanup. 333-- 334-- @since 1.1.11 335data ResourceCleanupException = ResourceCleanupException 336 { rceOriginalException :: !(Maybe SomeException) 337 -- ^ If the 'ResourceT' block exited due to an exception, this is 338 -- that exception. 339 -- 340 -- @since 1.1.11 341 , rceFirstCleanupException :: !SomeException 342 -- ^ The first cleanup exception. We keep this separate from 343 -- 'rceOtherCleanupExceptions' to prove that there's at least one 344 -- (i.e., a non-empty list). 345 -- 346 -- @since 1.1.11 347 , rceOtherCleanupExceptions :: ![SomeException] 348 -- ^ All other exceptions in cleanups. 349 -- 350 -- @since 1.1.11 351 } 352 deriving (Show, Typeable) 353instance Exception ResourceCleanupException 354 355-- | Clean up a release map, but throw a 'ResourceCleanupException' if 356-- anything goes wrong in the cleanup handlers. 357-- 358-- @since 1.1.11 359stateCleanupChecked 360 :: Maybe SomeException -- ^ exception that killed the 'ResourceT', if present 361 -> I.IORef ReleaseMap -> IO () 362stateCleanupChecked morig istate = E.mask_ $ do 363 mm <- I.atomicModifyIORef istate $ \rm -> 364 case rm of 365 ReleaseMap nk rf m -> 366 let rf' = rf - 1 367 in if rf' == minBound 368 then (ReleaseMapClosed, Just m) 369 else (ReleaseMap nk rf' m, Nothing) 370 ReleaseMapClosed -> throw $ InvalidAccess "stateCleanupChecked" 371 case mm of 372 Just m -> do 373 res <- mapMaybeReverseM (\x -> try (x rtype)) $ IntMap.elems m 374 case res of 375 [] -> return () -- nothing went wrong 376 e:es -> E.throwIO $ ResourceCleanupException morig e es 377 Nothing -> return () 378 where 379 try :: IO () -> IO (Maybe SomeException) 380 try io = fmap (either Just (\() -> Nothing)) (E.try io) 381 382 rtype = maybe ReleaseNormal (const ReleaseException) morig 383 384-- Note that this returns values in reverse order, which is what we 385-- want in the specific case of this function. 386mapMaybeReverseM :: Monad m => (a -> m (Maybe b)) -> [a] -> m [b] 387mapMaybeReverseM f = 388 go [] 389 where 390 go bs [] = return bs 391 go bs (a:as) = do 392 mb <- f a 393 case mb of 394 Nothing -> go bs as 395 Just b -> go (b:bs) as 396