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.Exception (Exception, SomeException, mask_)
30import Control.Monad.Catch (MonadCatch)
31import Data.Map.Strict (Map)
32import GHC.Exts (inline)
33import Streamly.Internal.Control.Concurrent (MonadRunInIO, MonadAsync, withRunInIO)
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.
52-- * does not require a 'MonadAsync' constraint.
54-- /Inhibits stream fusion/
58{-# INLINE_NORMAL gbracket_ #-}
62 -> (c -> m d) -- ^ after, on normal stop
63 -> (c -> e -> Stream m b -> Stream m b) -- ^ on exception
64 -> (forall s. m s -> m (Either e s)) -- ^ try (exception handling)
65 -> (c -> Stream m b) -- ^ stream generator
67gbracket_ bef aft onExc ftry action =
68 Stream step GBracketInit
72 {-# INLINE_LATE step #-}
73 step _ GBracketInit = do
75 return $ Skip $ GBracketNormal (action r) r
77 step gst (GBracketNormal (UnStream step1 st) v) = do
78 res <- ftry $ step1 gst st
82 return $ Yield x (GBracketNormal (Stream step1 s) v)
83 Skip s -> return $ Skip (GBracketNormal (Stream step1 s) v)
84 Stop -> aft v >> return Stop
85 -- XXX Do not handle async exceptions, just rethrow them.
88 $ Skip (GBracketException (onExc v e (UnStream step1 st)))
89 step gst (GBracketException (UnStream step1 st)) = do
92 Yield x s -> return $ Yield x (GBracketException (Stream step1 s))
93 Skip s -> return $ Skip (GBracketException (Stream step1 s))
96data GbracketIOState s1 s2 v wref
98 | GBracketIONormal s1 v wref
99 | GBracketIOException s2
101-- | Run the alloc action @m c@ with async exceptions disabled but keeping
102-- blocking operations interruptible (see 'Control.Exception.mask'). Use the
103-- output @c@ as input to @c -> Stream m b@ to generate an output stream. When
104-- generating the stream use the supplied @try@ operation @forall s. m s -> m
105-- (Either e s)@ to catch synchronous exceptions. If an exception occurs run
106-- the exception handler @c -> e -> Stream m b -> m (Stream m b)@. Note that
107-- 'gbracket' does not rethrow the exception, it has to be done by the
108-- exception handler if desired.
110-- The cleanup action @c -> m d@, runs whenever the stream ends normally, due
111-- to a sync or async exception or if it gets garbage collected after a partial
112-- lazy evaluation. See 'bracket' for the semantics of the cleanup action.
114-- 'gbracket' can express all other exception handling combinators.
116-- /Inhibits stream fusion/
119{-# INLINE_NORMAL gbracket #-}
123 -> (c -> m d1) -- ^ on normal stop
124 -> (c -> e -> Stream m b -> m (Stream m b)) -- ^ on exception
125 -> (c -> m d2) -- ^ on GC without normal stop or exception
126 -> (forall s. m s -> m (Either e s)) -- ^ try (exception handling)
127 -> (c -> Stream m b) -- ^ stream generator
129gbracket bef aft onExc onGC ftry action =
130 Stream step GBracketIOInit
134 -- If the stream is never evaluated the "aft" action will never be
135 -- called. For that to occur we will need the user of this API to pass a
136 -- weak pointer to us.
137 {-# INLINE_LATE step #-}
138 step _ GBracketIOInit = do
139 -- We mask asynchronous exceptions to make the execution
140 -- of 'bef' and the registration of 'aft' atomic.
141 -- A similar thing is done in the resourcet package: https://git.io/JvKV3
142 -- Tutorial: https://markkarpov.com/tutorial/exceptions.html
143 (r, ref) <- withRunInIO $ \run -> mask_ $ run $ do
145 ref <- newIOFinalizer (onGC r)
147 return $ Skip $ GBracketIONormal (action r) r ref
149 step gst (GBracketIONormal (UnStream step1 st) v ref) = do
150 res <- ftry $ step1 gst st
154 return $ Yield x (GBracketIONormal (Stream step1 s) v ref)
156 return $ Skip (GBracketIONormal (Stream step1 s) v ref)
158 clearingIOFinalizer ref (aft v) >> return Stop
159 -- XXX Do not handle async exceptions, just rethrow them.
161 -- Clearing of finalizer and running of exception handler must
162 -- be atomic wrt async exceptions. Otherwise if we have cleared
163 -- the finalizer and have not run the exception handler then we
164 -- may leak the resource.
166 clearingIOFinalizer ref (onExc v e (UnStream step1 st))
167 return $ Skip (GBracketIOException stream)
168 step gst (GBracketIOException (UnStream step1 st)) = do
172 return $ Yield x (GBracketIOException (Stream step1 s))
173 Skip s -> return $ Skip (GBracketIOException (Stream step1 s))
176-- | See 'Streamly.Internal.Data.Stream.before'.
178{-# INLINE_NORMAL before #-}
179before :: Monad m => m b -> Stream m a -> Stream m a
180before action (Stream step state) = Stream step' Nothing
184 {-# INLINE_LATE step' #-}
185 step' _ Nothing = action >> return (Skip (Just state))
187 step' gst (Just st) = do
190 Yield x s -> return $ Yield x (Just s)
191 Skip s -> return $ Skip (Just s)
194-- | See 'Streamly.Internal.Data.Stream.after_'.
196{-# INLINE_NORMAL after_ #-}
197after_ :: Monad m => m b -> Stream m a -> Stream m a
198after_ action (Stream step state) = Stream step' state
202 {-# INLINE_LATE step' #-}
206 Yield x s -> return $ Yield x s
207 Skip s -> return $ Skip s
208 Stop -> action >> return Stop
210-- | See 'Streamly.Internal.Data.Stream.after'.
212{-# INLINE_NORMAL after #-}
213after :: MonadRunInIO m
214 => m b -> Stream m a -> Stream m a
215after action (Stream step state) = Stream step' Nothing
219 {-# INLINE_LATE step' #-}
221 ref <- newIOFinalizer action
222 return $ Skip $ Just (state, ref)
223 step' gst (Just (st, ref)) = do
226 Yield x s -> return $ Yield x (Just (s, ref))
227 Skip s -> return $ Skip (Just (s, ref))
232-- XXX For high performance error checks in busy streams we may need another
233-- Error constructor in step.
235-- | See 'Streamly.Internal.Data.Stream.onException'.
237{-# INLINE_NORMAL onException #-}
238onException :: MonadCatch m => m b -> Stream m a -> Stream m a
239onException action stream =
241 (return ()) -- before
243 (\_ (e :: MC.SomeException) _ -> nilM (action >> MC.throwM e))
247{-# INLINE_NORMAL _onException #-}
248_onException :: MonadCatch m => m b -> Stream m a -> Stream m a
249_onException action (Stream step state) = Stream step' state
253 {-# INLINE_LATE step' #-}
255 res <- step gst st `MC.onException` action
257 Yield x s -> return $ Yield x s
258 Skip s -> return $ Skip s
261-- | See 'Streamly.Internal.Data.Stream.bracket_'.
263{-# INLINE_NORMAL bracket_ #-}
264bracket_ :: MonadCatch m
265 => m b -> (b -> m c) -> (b -> Stream m a) -> Stream m a
270 (\a (e :: SomeException) _ -> nilM (aft a >> MC.throwM e))
273-- | See 'Streamly.Internal.Data.Stream.bracket'.
275{-# INLINE_NORMAL bracket' #-}
276bracket' :: (MonadAsync m, MonadCatch m) =>
283bracket' bef aft onExc onGC =
287 (\a (e :: SomeException) _ -> onExc a >> return (nilM (MC.throwM e)))
291data BracketState s v = BracketInit | BracketRun s v
293-- | Alternate (custom) implementation of 'bracket'.
295{-# INLINE_NORMAL _bracket #-}
296_bracket :: MonadCatch m
297 => m b -> (b -> m c) -> (b -> Stream m a) -> Stream m a
298_bracket bef aft bet = Stream step' BracketInit
302 {-# INLINE_LATE step' #-}
303 step' _ BracketInit = bef >>= \x -> return (Skip (BracketRun (bet x) x))
305 -- NOTE: It is important to use UnStream instead of the Stream pattern
306 -- here, otherwise we get huge perf degradation, see note in concatMap.
307 step' gst (BracketRun (UnStream step state) v) = do
308 -- res <- step gst state `MC.onException` aft v
309 res <- inline MC.try $ step gst state
311 Left (e :: SomeException) -> aft v >> MC.throwM e >> return Stop
313 Yield x s -> return $ Yield x (BracketRun (Stream step s) v)
314 Skip s -> return $ Skip (BracketRun (Stream step s) v)
315 Stop -> aft v >> return Stop
317-- | See 'Streamly.Internal.Data.Stream.finally_'.
319{-# INLINE finally_ #-}
320finally_ :: MonadCatch m => m b -> Stream m a -> Stream m a
321finally_ action xs = bracket_ (return ()) (const action) (const xs)
323-- | See 'Streamly.Internal.Data.Stream.finally'.
325-- finally action xs = after action $ onException action xs
327{-# INLINE finally #-}
328finally :: (MonadAsync m, MonadCatch m) => m b -> Stream m a -> Stream m a
329finally action xs = bracket' (return ()) act act act (const xs)
332-- | See 'Streamly.Internal.Data.Stream.ghandle'.
334{-# INLINE_NORMAL ghandle #-}
335ghandle :: (MonadCatch m, Exception e)
336 => (e -> Stream m a -> Stream m a) -> Stream m a -> Stream m a
338 gbracket_ (return ()) return (const f) (inline MC.try) (const stream)
340-- | See 'Streamly.Internal.Data.Stream.handle'.
342{-# INLINE_NORMAL handle #-}
343handle :: (MonadCatch m, Exception e)
344 => (e -> Stream m a) -> Stream m a -> Stream m a
346 gbracket_ (return ()) return (\_ e _ -> f e) (inline MC.try) (const stream)
348-- | Alternate (custom) implementation of 'handle'.
350{-# INLINE_NORMAL _handle #-}
351_handle :: (MonadCatch m, Exception e)
352 => (e -> Stream m a) -> Stream m a -> Stream m a
353_handle f (Stream step state) = Stream step' (Left state)
357 {-# INLINE_LATE step' #-}
358 step' gst (Left st) = do
359 res <- inline MC.try $ step gst st
361 Left e -> return $ Skip $ Right (f e)
363 Yield x s -> return $ Yield x (Left s)
364 Skip s -> return $ Skip (Left s)
367 step' gst (Right (UnStream step1 st)) = do
370 Yield x s -> return $ Yield x (Right (Stream step1 s))
371 Skip s -> return $ Skip (Right (Stream step1 s))
374data RetryState emap s1 s2
375 = RetryWithMap emap s1
378-- | See 'Streamly.Internal.Data.Stream.retry'
380{-# INLINE_NORMAL retry #-}
382 :: forall e m a. (Exception e, Ord e, MonadCatch m)
384 -- ^ map from exception to retry count
386 -- ^ default handler for those exceptions that are not in the map
389retry emap0 defaultHandler (Stream step0 state0) = Stream step state
393 state = RetryWithMap emap0 state0
395 {-# INLINE_LATE step #-}
396 step gst (RetryWithMap emap st) = do
397 eres <- MC.try $ step0 gst st
399 Left e -> handler e emap st
403 Yield x st1 -> Yield x $ RetryWithMap emap st1
404 Skip st1 -> Skip $ RetryWithMap emap st1
406 step gst (RetryDefault (UnStream step1 state1)) = do
407 res <- step1 gst state1
410 Yield x st1 -> Yield x $ RetryDefault (Stream step1 st1)
411 Skip st1 -> Skip $ RetryDefault (Stream step1 st1)
414 {-# INLINE handler #-}
418 $ case Map.lookup e emap of
421 let emap1 = Map.insert e (i - 1) emap
422 in RetryWithMap emap1 st
423 | otherwise -> RetryDefault $ defaultHandler e
424 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