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