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
29import Control.Monad.IO.Class (MonadIO(..))
30import Control.Exception (Exception, SomeException, mask_)
31import Control.Monad.Catch (MonadCatch)
32import Data.Map.Strict (Map)
33import GHC.Exts (inline)
34import Streamly.Internal.Data.IOFinalizer
35 (newIOFinalizer, runIOFinalizer, clearingIOFinalizer)
37import qualified Control.Monad.Catch as MC
38import qualified Data.Map.Strict as Map
40import Streamly.Internal.Data.Stream.StreamD.Type
42data GbracketState s1 s2 v
45 | GBracketException s2
47-- | Like 'gbracket' but with following differences:
49-- * alloc action @m c@ runs with async exceptions enabled
50-- * cleanup action @c -> m d@ won't run if the stream is garbage collected
51-- after partial evaluation.
53-- /Inhibits stream fusion/
57{-# INLINE_NORMAL gbracket_ #-}
61 -> (c -> m d) -- ^ after, on normal stop
62 -> (c -> e -> Stream m b -> Stream m b) -- ^ on exception
63 -> (forall s. m s -> m (Either e s)) -- ^ try (exception handling)
64 -> (c -> Stream m b) -- ^ stream generator
66gbracket_ bef aft onExc ftry action =
67 Stream step GBracketInit
71 {-# INLINE_LATE step #-}
72 step _ GBracketInit = do
74 return $ Skip $ GBracketNormal (action r) r
76 step gst (GBracketNormal (UnStream step1 st) v) = do
77 res <- ftry $ step1 gst st
81 return $ Yield x (GBracketNormal (Stream step1 s) v)
82 Skip s -> return $ Skip (GBracketNormal (Stream step1 s) v)
83 Stop -> aft v >> return Stop
84 -- XXX Do not handle async exceptions, just rethrow them.
87 $ Skip (GBracketException (onExc v e (UnStream step1 st)))
88 step gst (GBracketException (UnStream step1 st)) = do
91 Yield x s -> return $ Yield x (GBracketException (Stream step1 s))
92 Skip s -> return $ Skip (GBracketException (Stream step1 s))
95data GbracketIOState s1 s2 v wref
97 | GBracketIONormal s1 v wref
98 | GBracketIOException s2
100-- | Run the alloc action @m c@ with async exceptions disabled but keeping
101-- blocking operations interruptible (see 'Control.Exception.mask'). Use the
102-- output @c@ as input to @c -> Stream m b@ to generate an output stream. When
103-- generating the stream use the supplied @try@ operation @forall s. m s -> m
104-- (Either e s)@ to catch synchronous exceptions. If an exception occurs run
105-- the exception handler @c -> e -> Stream m b -> m (Stream m b)@. Note that
106-- 'gbracket' does not rethrow the exception, it has to be done by the
107-- exception handler if desired.
109-- The cleanup action @c -> m d@, runs whenever the stream ends normally, due
110-- to a sync or async exception or if it gets garbage collected after a partial
111-- lazy evaluation. See 'bracket' for the semantics of the cleanup action.
113-- 'gbracket' can express all other exception handling combinators.
115-- /Inhibits stream fusion/
118{-# INLINE_NORMAL gbracket #-}
122 -> (c -> IO d1) -- ^ on normal stop
123 -> (c -> e -> Stream m b -> IO (Stream m b)) -- ^ on exception
124 -> (c -> IO d2) -- ^ on GC without normal stop or exception
125 -> (forall s. m s -> m (Either e s)) -- ^ try (exception handling)
126 -> (c -> Stream m b) -- ^ stream generator
128gbracket bef aft onExc onGC ftry action =
129 Stream step GBracketIOInit
133 -- If the stream is never evaluated the "aft" action will never be
134 -- called. For that to occur we will need the user of this API to pass a
135 -- weak pointer to us.
136 {-# INLINE_LATE step #-}
137 step _ GBracketIOInit = do
138 -- We mask asynchronous exceptions to make the execution
139 -- of 'bef' and the registration of 'aft' atomic.
140 -- A similar thing is done in the resourcet package: https://git.io/JvKV3
141 -- Tutorial: https://markkarpov.com/tutorial/exceptions.html
142 (r, ref) <- liftIO $ mask_ $ do
144 ref <- newIOFinalizer (onGC r)
146 return $ Skip $ GBracketIONormal (action r) r ref
148 step gst (GBracketIONormal (UnStream step1 st) v ref) = do
149 res <- ftry $ step1 gst st
153 return $ Yield x (GBracketIONormal (Stream step1 s) v ref)
155 return $ Skip (GBracketIONormal (Stream step1 s) v ref)
157 liftIO (clearingIOFinalizer ref (aft v)) >> return Stop
158 -- XXX Do not handle async exceptions, just rethrow them.
160 -- Clearing of finalizer and running of exception handler must
161 -- be atomic wrt async exceptions. Otherwise if we have cleared
162 -- the finalizer and have not run the exception handler then we
163 -- may leak the resource.
165 liftIO (clearingIOFinalizer ref (onExc v e (UnStream step1 st)))
166 return $ Skip (GBracketIOException stream)
167 step gst (GBracketIOException (UnStream step1 st)) = do
171 return $ Yield x (GBracketIOException (Stream step1 s))
172 Skip s -> return $ Skip (GBracketIOException (Stream step1 s))
175-- | See 'Streamly.Internal.Data.Stream.before'.
177{-# INLINE_NORMAL before #-}
178before :: Monad m => m b -> Stream m a -> Stream m a
179before action (Stream step state) = Stream step' Nothing
183 {-# INLINE_LATE step' #-}
184 step' _ Nothing = action >> return (Skip (Just state))
186 step' gst (Just st) = do
189 Yield x s -> return $ Yield x (Just s)
190 Skip s -> return $ Skip (Just s)
193-- | See 'Streamly.Internal.Data.Stream.after_'.
195{-# INLINE_NORMAL after_ #-}
196after_ :: Monad m => m b -> Stream m a -> Stream m a
197after_ action (Stream step state) = Stream step' state
201 {-# INLINE_LATE step' #-}
205 Yield x s -> return $ Yield x s
206 Skip s -> return $ Skip s
207 Stop -> action >> return Stop
209-- | See 'Streamly.Internal.Data.Stream.after'.
211{-# INLINE_NORMAL after #-}
213 => IO b -> Stream m a -> Stream m a
214after action (Stream step state) = Stream step' Nothing
218 {-# INLINE_LATE step' #-}
220 ref <- liftIO $ newIOFinalizer action
221 return $ Skip $ Just (state, ref)
222 step' gst (Just (st, ref)) = do
225 Yield x s -> return $ Yield x (Just (s, ref))
226 Skip s -> return $ Skip (Just (s, ref))
231-- XXX For high performance error checks in busy streams we may need another
232-- Error constructor in step.
234-- | See 'Streamly.Internal.Data.Stream.onException'.
236{-# INLINE_NORMAL onException #-}
237onException :: MonadCatch m => m b -> Stream m a -> Stream m a
238onException action stream =
240 (return ()) -- before
242 (\_ (e :: MC.SomeException) _ -> nilM (action >> MC.throwM e))
246{-# INLINE_NORMAL _onException #-}
247_onException :: MonadCatch m => m b -> Stream m a -> Stream m a
248_onException action (Stream step state) = Stream step' state
252 {-# INLINE_LATE step' #-}
254 res <- step gst st `MC.onException` action
256 Yield x s -> return $ Yield x s
257 Skip s -> return $ Skip s
260-- | See 'Streamly.Internal.Data.Stream.bracket_'.
262{-# INLINE_NORMAL bracket_ #-}
263bracket_ :: MonadCatch m
264 => m b -> (b -> m c) -> (b -> Stream m a) -> Stream m a
269 (\a (e :: SomeException) _ -> nilM (aft a >> MC.throwM e))
272-- | See 'Streamly.Internal.Data.Stream.bracket'.
274{-# INLINE_NORMAL bracket' #-}
275bracket' :: (MonadIO m, MonadCatch m) =>
282bracket' bef aft onExc onGC =
286 (\a (e :: SomeException) _ -> onExc a >> return (nilM (MC.throwM e)))
290data BracketState s v = BracketInit | BracketRun s v
292-- | Alternate (custom) implementation of 'bracket'.
294{-# INLINE_NORMAL _bracket #-}
295_bracket :: MonadCatch m
296 => m b -> (b -> m c) -> (b -> Stream m a) -> Stream m a
297_bracket bef aft bet = Stream step' BracketInit
301 {-# INLINE_LATE step' #-}
302 step' _ BracketInit = bef >>= \x -> return (Skip (BracketRun (bet x) x))
304 -- NOTE: It is important to use UnStream instead of the Stream pattern
305 -- here, otherwise we get huge perf degradation, see note in concatMap.
306 step' gst (BracketRun (UnStream step state) v) = do
307 -- res <- step gst state `MC.onException` aft v
308 res <- inline MC.try $ step gst state
310 Left (e :: SomeException) -> aft v >> MC.throwM e >> return Stop
312 Yield x s -> return $ Yield x (BracketRun (Stream step s) v)
313 Skip s -> return $ Skip (BracketRun (Stream step s) v)
314 Stop -> aft v >> return Stop
316-- | See 'Streamly.Internal.Data.Stream.finally_'.
318{-# INLINE finally_ #-}
319finally_ :: MonadCatch m => m b -> Stream m a -> Stream m a
320finally_ action xs = bracket_ (return ()) (const action) (const xs)
322-- | See 'Streamly.Internal.Data.Stream.finally'.
324-- finally action xs = after action $ onException action xs
326{-# INLINE finally #-}
327finally :: (MonadIO m, MonadCatch m) => IO b -> Stream m a -> Stream m a
328finally action xs = bracket' (return ()) act act act (const xs)
331-- | See 'Streamly.Internal.Data.Stream.ghandle'.
333{-# INLINE_NORMAL ghandle #-}
334ghandle :: (MonadCatch m, Exception e)
335 => (e -> Stream m a -> Stream m a) -> Stream m a -> Stream m a
337 gbracket_ (return ()) return (const f) (inline MC.try) (const stream)
339-- | See 'Streamly.Internal.Data.Stream.handle'.
341{-# INLINE_NORMAL handle #-}
342handle :: (MonadCatch m, Exception e)
343 => (e -> Stream m a) -> Stream m a -> Stream m a
345 gbracket_ (return ()) return (\_ e _ -> f e) (inline MC.try) (const stream)
347-- | Alternate (custom) implementation of 'handle'.
349{-# INLINE_NORMAL _handle #-}
350_handle :: (MonadCatch m, Exception e)
351 => (e -> Stream m a) -> Stream m a -> Stream m a
352_handle f (Stream step state) = Stream step' (Left state)
356 {-# INLINE_LATE step' #-}
357 step' gst (Left st) = do
358 res <- inline MC.try $ step gst st
360 Left e -> return $ Skip $ Right (f e)
362 Yield x s -> return $ Yield x (Left s)
363 Skip s -> return $ Skip (Left s)
366 step' gst (Right (UnStream step1 st)) = do
369 Yield x s -> return $ Yield x (Right (Stream step1 s))
370 Skip s -> return $ Skip (Right (Stream step1 s))
373data RetryState emap s1 s2
374 = RetryWithMap emap s1
377-- | See 'Streamly.Internal.Data.Stream.retry'
379{-# INLINE_NORMAL retry #-}
381 :: forall e m a. (Exception e, Ord e, MonadCatch m)
383 -- ^ map from exception to retry count
385 -- ^ default handler for those exceptions that are not in the map
388retry emap0 defaultHandler (Stream step0 state0) = Stream step state
392 state = RetryWithMap emap0 state0
394 {-# INLINE_LATE step #-}
395 step gst (RetryWithMap emap st) = do
396 eres <- MC.try $ step0 gst st
398 Left e -> handler e emap st
402 Yield x st1 -> Yield x $ RetryWithMap emap st1
403 Skip st1 -> Skip $ RetryWithMap emap st1
405 step gst (RetryDefault (UnStream step1 state1)) = do
406 res <- step1 gst state1
409 Yield x st1 -> Yield x $ RetryDefault (Stream step1 st1)
410 Skip st1 -> Skip $ RetryDefault (Stream step1 st1)
413 {-# INLINE handler #-}
417 $ case Map.lookup e emap of
420 let emap1 = Map.insert e (i - 1) emap
421 in RetryWithMap emap1 st
422 | otherwise -> RetryDefault $ defaultHandler e
423 Nothing -> RetryDefault $ defaultHandler e
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