2-- Module : Streamly.Internal.Data.Stream.StreamD.Exception
3-- Copyright : (c) 2020 Composewell Technologies and Contributors
4-- License : BSD-3-Clause
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
9module Streamly.Internal.Data.Stream.StreamD.Exception
34import Control.Exception (Exception, SomeException, mask_)
35import Control.Monad (void)
36import Control.Monad.Catch (MonadCatch)
37import Control.Monad.IO.Class (MonadIO(..))
38import Control.Monad.Trans.Control (MonadBaseControl, liftBaseOp_, control)
39import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef, IORef)
41import qualified Control.Monad.Catch as MC
43import Streamly.Internal.Data.Stream.StreamD.Type
44import Streamly.Internal.Data.SVar
46------------------------------------------------------------------------------
48------------------------------------------------------------------------------
50-- | Make a finalizer from a monadic action @m a@ that can run in IO monad.
51mkIOFinalizer :: MonadBaseControl IO m => m b -> m (IO ())
53 mrun <- captureMonadState
59-- | Run an IO action stored in a finalized IORef.
60runIORefFinalizerGC :: IORef (Maybe (IO ())) -> IO ()
61runIORefFinalizerGC ref = do
67-- | Create an IORef holding a finalizer that is called automatically when
68-- the IORef is garbage collected. The IORef can be written to with a 'Nothing'
69-- value to deactivate the finalizer.
71-- Note: The finalizer is always run with the state of the monad captured at
72-- the time of calling newFinalizedIORef. To run it on garbage collection we
73-- have no option but to take a snapshot of the monadic state at some point of
74-- time. For normal case we could run it with the current state of the monad
75-- but we want to keep both the cases consistent.
77newFinalizedIORef :: (MonadIO m, MonadBaseControl IO m)
78 => m a -> m (IORef (Maybe (IO ())))
79newFinalizedIORef finalizer = do
80 f <- mkIOFinalizer finalizer
81 ref <- liftIO $ newIORef $ Just f
82 _ <- liftIO $ mkWeakIORef ref (runIORefFinalizerGC ref)
85-- | Run the finalizer stored in an IORef and deactivate it so that it is run
86-- only once. Note, the action runs with async exceptions masked.
88runIORefFinalizer :: MonadIO m => IORef (Maybe (IO ())) -> m ()
89runIORefFinalizer ref = liftIO $ do
94 -- if an async exception comes after writing 'Nothing' then the
95 -- finalizing action will never be run. We need to do this
96 -- atomically wrt async exceptions.
98 writeIORef ref Nothing
101-- | Run an action clearing the finalizer IORef atomically wrt async
102-- exceptions. The action is run with async exceptions masked.
103withIORefFinalizer :: MonadBaseControl IO m
104 => IORef (Maybe (IO ())) -> m a -> m a
105withIORefFinalizer ref action = do
106 control $ \runinio ->
108 writeIORef ref Nothing
111------------------------------------------------------------------------------
112-- Exception handling combinators.
113------------------------------------------------------------------------------
115data GbracketState s1 s2 v
117 | GBracketNormal s1 v
118 | GBracketException s2
120-- | Like 'gbracket' but with following differences:
122-- * alloc action @m c@ runs with async exceptions enabled
123-- * cleanup action @c -> m d@ won't run if the stream is garbage collected
124-- after partial evaluation.
125-- * does not require a 'MonadAsync' constraint.
127-- /Inhibits stream fusion/
131{-# INLINE_NORMAL gbracket_ #-}
135 -> (forall s. m s -> m (Either e s)) -- ^ try (exception handling)
136 -> (c -> m d) -- ^ after, on normal stop
137 -> (c -> e -> Stream m b -> Stream m b) -- ^ on exception
138 -> (c -> Stream m b) -- ^ stream generator
140gbracket_ bef exc aft fexc fnormal =
141 Stream step GBracketInit
145 {-# INLINE_LATE step #-}
146 step _ GBracketInit = do
148 return $ Skip $ GBracketNormal (fnormal r) r
150 step gst (GBracketNormal (UnStream step1 st) v) = do
151 res <- exc $ step1 gst st
155 return $ Yield x (GBracketNormal (Stream step1 s) v)
156 Skip s -> return $ Skip (GBracketNormal (Stream step1 s) v)
157 Stop -> aft v >> return Stop
158 -- XXX Do not handle async exceptions, just rethrow them.
160 return $ Skip (GBracketException (fexc v e (UnStream step1 st)))
161 step gst (GBracketException (UnStream step1 st)) = do
164 Yield x s -> return $ Yield x (GBracketException (Stream step1 s))
165 Skip s -> return $ Skip (GBracketException (Stream step1 s))
168data GbracketIOState s1 s2 v wref
170 | GBracketIONormal s1 v wref
171 | GBracketIOException s2
173-- | Run the alloc action @m c@ with async exceptions disabled but keeping
174-- blocking operations interruptible (see 'Control.Exception.mask'). Use the
175-- output @c@ as input to @c -> Stream m b@ to generate an output stream. When
176-- generating the stream use the supplied @try@ operation @forall s. m s -> m
177-- (Either e s)@ to catch synchronous exceptions. If an exception occurs run
178-- the exception handler @c -> e -> Stream m b -> m (Stream m b)@.
180-- The cleanup action @c -> m d@, runs whenever the stream ends normally, due
181-- to a sync or async exception or if it gets garbage collected after a partial
182-- lazy evaluation. See 'bracket' for the semantics of the cleanup action.
184-- 'gbracket' can express all other exception handling combinators.
186-- /Inhibits stream fusion/
189{-# INLINE_NORMAL gbracket #-}
191 :: (MonadIO m, MonadBaseControl IO m)
193 -> (forall s. m s -> m (Either e s)) -- ^ try (exception handling)
194 -> (c -> m d) -- ^ after, on normal stop or GC
195 -> (c -> e -> Stream m b -> m (Stream m b)) -- ^ on exception
196 -> (c -> Stream m b) -- ^ stream generator
198gbracket bef exc aft fexc fnormal =
199 Stream step GBracketIOInit
203 -- If the stream is never evaluated the "aft" action will never be
204 -- called. For that to occur we will need the user of this API to pass a
205 -- weak pointer to us.
206 {-# INLINE_LATE step #-}
207 step _ GBracketIOInit = do
208 -- We mask asynchronous exceptions to make the execution
209 -- of 'bef' and the registration of 'aft' atomic.
210 -- A similar thing is done in the resourcet package: https://git.io/JvKV3
211 -- Tutorial: https://markkarpov.com/tutorial/exceptions.html
212 (r, ref) <- liftBaseOp_ mask_ $ do
214 ref <- newFinalizedIORef (aft r)
216 return $ Skip $ GBracketIONormal (fnormal r) r ref
218 step gst (GBracketIONormal (UnStream step1 st) v ref) = do
219 res <- exc $ step1 gst st
223 return $ Yield x (GBracketIONormal (Stream step1 s) v ref)
225 return $ Skip (GBracketIONormal (Stream step1 s) v ref)
227 runIORefFinalizer ref
229 -- XXX Do not handle async exceptions, just rethrow them.
231 -- Clearing of finalizer and running of exception handler must
232 -- be atomic wrt async exceptions. Otherwise if we have cleared
233 -- the finalizer and have not run the exception handler then we
234 -- may leak the resource.
235 stream <- withIORefFinalizer ref (fexc v e (UnStream step1 st))
236 return $ Skip (GBracketIOException stream)
237 step gst (GBracketIOException (UnStream step1 st)) = do
241 return $ Yield x (GBracketIOException (Stream step1 s))
242 Skip s -> return $ Skip (GBracketIOException (Stream step1 s))
245-- | See 'Streamly.Internal.Data.Stream.IsStream.before'.
247{-# INLINE_NORMAL before #-}
248before :: Monad m => m b -> Stream m a -> Stream m a
249before action (Stream step state) = Stream step' Nothing
253 {-# INLINE_LATE step' #-}
254 step' _ Nothing = action >> return (Skip (Just state))
256 step' gst (Just st) = do
259 Yield x s -> return $ Yield x (Just s)
260 Skip s -> return $ Skip (Just s)
263-- | See 'Streamly.Internal.Data.Stream.IsStream.after_'.
265{-# INLINE_NORMAL after_ #-}
266after_ :: Monad m => m b -> Stream m a -> Stream m a
267after_ action (Stream step state) = Stream step' state
271 {-# INLINE_LATE step' #-}
275 Yield x s -> return $ Yield x s
276 Skip s -> return $ Skip s
277 Stop -> action >> return Stop
279-- | See 'Streamly.Internal.Data.Stream.IsStream.after'.
281{-# INLINE_NORMAL after #-}
282after :: (MonadIO m, MonadBaseControl IO m)
283 => m b -> Stream m a -> Stream m a
284after action (Stream step state) = Stream step' Nothing
288 {-# INLINE_LATE step' #-}
290 ref <- newFinalizedIORef action
291 return $ Skip $ Just (state, ref)
292 step' gst (Just (st, ref)) = do
295 Yield x s -> return $ Yield x (Just (s, ref))
296 Skip s -> return $ Skip (Just (s, ref))
298 runIORefFinalizer ref
301-- XXX For high performance error checks in busy streams we may need another
302-- Error constructor in step.
304-- | See 'Streamly.Internal.Data.Stream.IsStream.onException'.
306{-# INLINE_NORMAL onException #-}
307onException :: MonadCatch m => m b -> Stream m a -> Stream m a
308onException action str =
309 gbracket_ (return ()) MC.try return
310 (\_ (e :: MC.SomeException) _ -> nilM (action >> MC.throwM e))
313{-# INLINE_NORMAL _onException #-}
314_onException :: MonadCatch m => m b -> Stream m a -> Stream m a
315_onException action (Stream step state) = Stream step' state
319 {-# INLINE_LATE step' #-}
321 res <- step gst st `MC.onException` action
323 Yield x s -> return $ Yield x s
324 Skip s -> return $ Skip s
327-- | See 'Streamly.Internal.Data.Stream.IsStream.bracket_'.
329{-# INLINE_NORMAL bracket_ #-}
330bracket_ :: MonadCatch m
331 => m b -> (b -> m c) -> (b -> Stream m a) -> Stream m a
332bracket_ bef aft bet =
333 gbracket_ bef MC.try aft
334 (\a (e :: SomeException) _ -> nilM (aft a >> MC.throwM e)) bet
336-- | See 'Streamly.Internal.Data.Stream.IsStream.bracket'.
338{-# INLINE_NORMAL bracket #-}
339bracket :: (MonadAsync m, MonadCatch m)
340 => m b -> (b -> m c) -> (b -> Stream m a) -> Stream m a
342 gbracket bef MC.try aft
343 (\a (e :: SomeException) _ -> aft a >> return (nilM (MC.throwM e))) bet
345data BracketState s v = BracketInit | BracketRun s v
347-- | Alternate (custom) implementation of 'bracket'.
349{-# INLINE_NORMAL _bracket #-}
350_bracket :: MonadCatch m
351 => m b -> (b -> m c) -> (b -> Stream m a) -> Stream m a
352_bracket bef aft bet = Stream step' BracketInit
356 {-# INLINE_LATE step' #-}
357 step' _ BracketInit = bef >>= \x -> return (Skip (BracketRun (bet x) x))
359 -- NOTE: It is important to use UnStream instead of the Stream pattern
360 -- here, otherwise we get huge perf degradation, see note in concatMap.
361 step' gst (BracketRun (UnStream step state) v) = do
362 -- res <- step gst state `MC.onException` aft v
363 res <- MC.try $ step gst state
365 Left (e :: SomeException) -> aft v >> MC.throwM e >> return Stop
367 Yield x s -> return $ Yield x (BracketRun (Stream step s) v)
368 Skip s -> return $ Skip (BracketRun (Stream step s) v)
369 Stop -> aft v >> return Stop
371-- | See 'Streamly.Internal.Data.Stream.IsStream.finally_'.
373{-# INLINE finally_ #-}
374finally_ :: MonadCatch m => m b -> Stream m a -> Stream m a
375finally_ action xs = bracket_ (return ()) (\_ -> action) (const xs)
377-- | See 'Streamly.Internal.Data.Stream.IsStream.finally'.
379-- finally action xs = after action $ onException action xs
381{-# INLINE finally #-}
382finally :: (MonadAsync m, MonadCatch m) => m b -> Stream m a -> Stream m a
383finally action xs = bracket (return ()) (\_ -> action) (const xs)
385-- | See 'Streamly.Internal.Data.Stream.IsStream.ghandle'.
387{-# INLINE_NORMAL ghandle #-}
388ghandle :: (MonadCatch m, Exception e)
389 => (e -> Stream m a -> Stream m a) -> Stream m a -> Stream m a
391 gbracket_ (return ()) MC.try return (\_ -> f) (\_ -> str)
393-- | See 'Streamly.Internal.Data.Stream.IsStream.handle'.
395{-# INLINE_NORMAL handle #-}
396handle :: (MonadCatch m, Exception e)
397 => (e -> Stream m a) -> Stream m a -> Stream m a
399 gbracket_ (return ()) MC.try return (\_ e _ -> f e) (\_ -> str)
401-- | Alternate (custom) implementation of 'handle'.
403{-# INLINE_NORMAL _handle #-}
404_handle :: (MonadCatch m, Exception e)
405 => (e -> Stream m a) -> Stream m a -> Stream m a
406_handle f (Stream step state) = Stream step' (Left state)
410 {-# INLINE_LATE step' #-}
411 step' gst (Left st) = do
412 res <- MC.try $ step gst st
414 Left e -> return $ Skip $ Right (f e)
416 Yield x s -> return $ Yield x (Left s)
417 Skip s -> return $ Skip (Left s)
420 step' gst (Right (UnStream step1 st)) = do
423 Yield x s -> return $ Yield x (Right (Stream step1 s))
424 Skip s -> return $ Skip (Right (Stream step1 s))
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list