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 Control.Monad.IO.Class (MonadIO(..))
32import Control.Monad.Trans.Control (MonadBaseControl, liftBaseOp_)
33import Data.Map.Strict (Map)
34import GHC.Exts (inline)
35import Streamly.Internal.Control.Concurrent (MonadAsync)
36import Streamly.Internal.Data.IOFinalizer
37 (newIOFinalizer, runIOFinalizer, clearingIOFinalizer)
39import qualified Control.Monad.Catch as MC
40import qualified Data.Map.Strict as Map
42import Streamly.Internal.Data.Stream.StreamD.Type
44data GbracketState s1 s2 v
47 | GBracketException s2
49-- | Like 'gbracket' but with following differences:
51-- * alloc action @m c@ runs with async exceptions enabled
52-- * cleanup action @c -> m d@ won't run if the stream is garbage collected
53-- after partial evaluation.
54-- * does not require a 'MonadAsync' constraint.
56-- /Inhibits stream fusion/
60{-# INLINE_NORMAL gbracket_ #-}
64 -> (forall s. m s -> m (Either e s)) -- ^ try (exception handling)
65 -> (c -> m d) -- ^ after, on normal stop
66 -> (c -> e -> Stream m b -> Stream m b) -- ^ on exception
67 -> (c -> Stream m b) -- ^ stream generator
69gbracket_ bef exc aft fexc fnormal =
70 Stream step GBracketInit
74 {-# INLINE_LATE step #-}
75 step _ GBracketInit = do
77 return $ Skip $ GBracketNormal (fnormal r) r
79 step gst (GBracketNormal (UnStream step1 st) v) = do
80 res <- exc $ step1 gst st
84 return $ Yield x (GBracketNormal (Stream step1 s) v)
85 Skip s -> return $ Skip (GBracketNormal (Stream step1 s) v)
86 Stop -> aft v >> return Stop
87 -- XXX Do not handle async exceptions, just rethrow them.
89 return $ Skip (GBracketException (fexc v e (UnStream step1 st)))
90 step gst (GBracketException (UnStream step1 st)) = do
93 Yield x s -> return $ Yield x (GBracketException (Stream step1 s))
94 Skip s -> return $ Skip (GBracketException (Stream step1 s))
97data GbracketIOState s1 s2 v wref
99 | GBracketIONormal s1 v wref
100 | GBracketIOException s2
102-- | Run the alloc action @m c@ with async exceptions disabled but keeping
103-- blocking operations interruptible (see 'Control.Exception.mask'). Use the
104-- output @c@ as input to @c -> Stream m b@ to generate an output stream. When
105-- generating the stream use the supplied @try@ operation @forall s. m s -> m
106-- (Either e s)@ to catch synchronous exceptions. If an exception occurs run
107-- the exception handler @c -> e -> Stream m b -> m (Stream m b)@. Note that
108-- 'gbracket' does not rethrow the exception, it has to be done by the
109-- exception handler if desired.
111-- The cleanup action @c -> m d@, runs whenever the stream ends normally, due
112-- to a sync or async exception or if it gets garbage collected after a partial
113-- lazy evaluation. See 'bracket' for the semantics of the cleanup action.
115-- 'gbracket' can express all other exception handling combinators.
117-- /Inhibits stream fusion/
120{-# INLINE_NORMAL gbracket #-}
122 :: (MonadIO m, MonadBaseControl IO m)
124 -> (forall s. m s -> m (Either e s)) -- ^ try (exception handling)
125 -> (c -> m d1) -- ^ on normal stop
126 -> (c -> m d2) -- ^ on GC without normal stop or exception
127 -> (c -> e -> Stream m b -> m (Stream m b)) -- ^ on exception
128 -> (c -> Stream m b) -- ^ stream generator
130gbracket bef exc aft gc fexc fnormal =
131 Stream step GBracketIOInit
135 -- If the stream is never evaluated the "aft" action will never be
136 -- called. For that to occur we will need the user of this API to pass a
137 -- weak pointer to us.
138 {-# INLINE_LATE step #-}
139 step _ GBracketIOInit = do
140 -- We mask asynchronous exceptions to make the execution
141 -- of 'bef' and the registration of 'aft' atomic.
142 -- A similar thing is done in the resourcet package: https://git.io/JvKV3
143 -- Tutorial: https://markkarpov.com/tutorial/exceptions.html
144 (r, ref) <- liftBaseOp_ mask_ $ do
146 ref <- newIOFinalizer (gc r)
148 return $ Skip $ GBracketIONormal (fnormal r) r ref
150 step gst (GBracketIONormal (UnStream step1 st) v ref) = do
151 res <- exc $ step1 gst st
155 return $ Yield x (GBracketIONormal (Stream step1 s) v ref)
157 return $ Skip (GBracketIONormal (Stream step1 s) v ref)
159 clearingIOFinalizer ref (aft v) >> return Stop
160 -- XXX Do not handle async exceptions, just rethrow them.
162 -- Clearing of finalizer and running of exception handler must
163 -- be atomic wrt async exceptions. Otherwise if we have cleared
164 -- the finalizer and have not run the exception handler then we
165 -- may leak the resource.
166 stream <- clearingIOFinalizer ref (fexc 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.IsStream.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.IsStream.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.IsStream.after'.
212{-# INLINE_NORMAL after #-}
213after :: (MonadIO m, MonadBaseControl IO 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.IsStream.onException'.
237{-# INLINE_NORMAL onException #-}
238onException :: MonadCatch m => m b -> Stream m a -> Stream m a
239onException action str =
240 gbracket_ (return ()) (inline MC.try) return
241 (\_ (e :: MC.SomeException) _ -> nilM (action >> MC.throwM e))
244{-# INLINE_NORMAL _onException #-}
245_onException :: MonadCatch m => m b -> Stream m a -> Stream m a
246_onException action (Stream step state) = Stream step' state
250 {-# INLINE_LATE step' #-}
252 res <- step gst st `MC.onException` action
254 Yield x s -> return $ Yield x s
255 Skip s -> return $ Skip s
258-- | See 'Streamly.Internal.Data.Stream.IsStream.bracket_'.
260{-# INLINE_NORMAL bracket_ #-}
261bracket_ :: MonadCatch m
262 => m b -> (b -> m c) -> (b -> Stream m a) -> Stream m a
264 gbracket_ bef (inline MC.try) aft
265 (\a (e :: SomeException) _ -> nilM (aft a >> MC.throwM e))
267-- | See 'Streamly.Internal.Data.Stream.IsStream.bracket'.
269{-# INLINE_NORMAL bracket' #-}
270bracket' :: (MonadAsync m, MonadCatch m) =>
277bracket' bef aft exc gc =
278 gbracket bef (inline MC.try) aft gc
279 (\a (e :: SomeException) _ -> exc a >> return (nilM (MC.throwM e)))
281data BracketState s v = BracketInit | BracketRun s v
283-- | Alternate (custom) implementation of 'bracket'.
285{-# INLINE_NORMAL _bracket #-}
286_bracket :: MonadCatch m
287 => m b -> (b -> m c) -> (b -> Stream m a) -> Stream m a
288_bracket bef aft bet = Stream step' BracketInit
292 {-# INLINE_LATE step' #-}
293 step' _ BracketInit = bef >>= \x -> return (Skip (BracketRun (bet x) x))
295 -- NOTE: It is important to use UnStream instead of the Stream pattern
296 -- here, otherwise we get huge perf degradation, see note in concatMap.
297 step' gst (BracketRun (UnStream step state) v) = do
298 -- res <- step gst state `MC.onException` aft v
299 res <- inline MC.try $ step gst state
301 Left (e :: SomeException) -> aft v >> MC.throwM e >> return Stop
303 Yield x s -> return $ Yield x (BracketRun (Stream step s) v)
304 Skip s -> return $ Skip (BracketRun (Stream step s) v)
305 Stop -> aft v >> return Stop
307-- | See 'Streamly.Internal.Data.Stream.IsStream.finally_'.
309{-# INLINE finally_ #-}
310finally_ :: MonadCatch m => m b -> Stream m a -> Stream m a
311finally_ action xs = bracket_ (return ()) (const action) (const xs)
313-- | See 'Streamly.Internal.Data.Stream.IsStream.finally'.
315-- finally action xs = after action $ onException action xs
317{-# INLINE finally #-}
318finally :: (MonadAsync m, MonadCatch m) => m b -> Stream m a -> Stream m a
319finally action xs = bracket' (return ()) act act act (const xs)
322-- | See 'Streamly.Internal.Data.Stream.IsStream.ghandle'.
324{-# INLINE_NORMAL ghandle #-}
325ghandle :: (MonadCatch m, Exception e)
326 => (e -> Stream m a -> Stream m a) -> Stream m a -> Stream m a
328 gbracket_ (return ()) (inline MC.try) return (const f) (const str)
330-- | See 'Streamly.Internal.Data.Stream.IsStream.handle'.
332{-# INLINE_NORMAL handle #-}
333handle :: (MonadCatch m, Exception e)
334 => (e -> Stream m a) -> Stream m a -> Stream m a
336 gbracket_ (return ()) (inline MC.try) return (\_ e _ -> f e) (const str)
338-- | Alternate (custom) implementation of 'handle'.
340{-# INLINE_NORMAL _handle #-}
341_handle :: (MonadCatch m, Exception e)
342 => (e -> Stream m a) -> Stream m a -> Stream m a
343_handle f (Stream step state) = Stream step' (Left state)
347 {-# INLINE_LATE step' #-}
348 step' gst (Left st) = do
349 res <- inline MC.try $ step gst st
351 Left e -> return $ Skip $ Right (f e)
353 Yield x s -> return $ Yield x (Left s)
354 Skip s -> return $ Skip (Left s)
357 step' gst (Right (UnStream step1 st)) = do
360 Yield x s -> return $ Yield x (Right (Stream step1 s))
361 Skip s -> return $ Skip (Right (Stream step1 s))
364data RetryState emap s1 s2
365 = RetryWithMap emap s1
368-- | See 'Streamly.Internal.Data.Stream.IsStream.retry'
370{-# INLINE_NORMAL retry #-}
372 :: forall e m a. (Exception e, Ord e, MonadCatch m)
374 -- ^ map from exception to retry count
376 -- ^ default handler for those exceptions that are not in the map
379retry emap0 defaultHandler (Stream step0 state0) = Stream step state
383 state = RetryWithMap emap0 state0
385 {-# INLINE_LATE step #-}
386 step gst (RetryWithMap emap st) = do
387 eres <- MC.try $ step0 gst st
389 Left e -> handler e emap st
393 Yield x st1 -> Yield x $ RetryWithMap emap st1
394 Skip st1 -> Skip $ RetryWithMap emap st1
396 step gst (RetryDefault (UnStream step1 state1)) = do
397 res <- step1 gst state1
400 Yield x st1 -> Yield x $ RetryDefault (Stream step1 st1)
401 Skip st1 -> Skip $ RetryDefault (Stream step1 st1)
404 {-# INLINE handler #-}
408 $ case Map.lookup e emap of
411 let emap1 = Map.insert e (i - 1) emap
412 in RetryWithMap emap1 st
413 | otherwise -> RetryDefault $ defaultHandler e
414 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