on fa417eb help sign in
282 lines 9.1 kB history raw
1-- |
2-- Module : Streamly.Internal.Data.Stream.IsStream.Exception
3-- Copyright : (c) 2019 Composewell Technologies
4-- License : BSD-3-Clause
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
7-- Portability : GHC
9module Streamly.Internal.Data.Stream.IsStream.Exception
10 (
11 before
12 , after_
13 , after
14 , bracket_
15 , bracket
16 , bracket'
17 , onException
18 , finally_
19 , finally
20 , ghandle
21 , handle
22 , retry
23 )
24where
26import Control.Exception (Exception)
27import Control.Monad.Catch (MonadCatch)
28import Data.Map.Strict (Map)
29import Streamly.Internal.Control.Concurrent (MonadRunInIO, MonadAsync)
30import Streamly.Internal.Data.Stream.IsStream.Type
31 (IsStream(..), fromStreamD, toStreamD)
33import qualified Streamly.Internal.Data.Stream.StreamD.Exception as D
34 ( before
35 , after_
36 , after
37 , onException
38 , bracket_
39 , bracket'
40 , finally_
41 , finally
42 , ghandle
43 , handle
44 , retry
45 )
47-- $setup
48-- >>> :m
49-- >>> import qualified Streamly.Prelude as Stream
50-- >>> import qualified Streamly.Internal.Data.Stream.IsStream as Stream (nilM)
52------------------------------------------------------------------------------
53-- Exceptions
54------------------------------------------------------------------------------
56-- | Run the action @m b@ before the stream yields its first element.
57--
58-- Same as the following but more efficient due to fusion:
59--
60-- >>> before action xs = Stream.nilM action <> xs
61-- >>> before action xs = Stream.concatMap (const xs) (Stream.fromEffect action)
62--
63-- @since 0.7.0
64{-# INLINE before #-}
65before :: (IsStream t, Monad m) => m b -> t m a -> t m a
66before action xs = fromStreamD $ D.before action $ toStreamD xs
68-- | Like 'after', with following differences:
69--
70-- * action @m b@ won't run if the stream is garbage collected
71-- after partial evaluation.
72-- * Monad @m@ does not require any other constraints.
73-- * has slightly better performance than 'after'.
74--
75-- Same as the following, but with stream fusion:
76--
77-- > after_ action xs = xs <> 'nilM' action
78--
79-- /Pre-release/
80--
81{-# INLINE after_ #-}
82after_ :: (IsStream t, Monad m) => m b -> t m a -> t m a
83after_ action xs = fromStreamD $ D.after_ action $ toStreamD xs
85-- | Run the action @m b@ whenever the stream @t m a@ stops normally, or if it
86-- is garbage collected after a partial lazy evaluation.
87--
88-- The semantics of the action @m b@ are similar to the semantics of cleanup
89-- action in 'bracket'.
90--
91-- /See also 'after_'/
92--
93-- @since 0.7.0
94--
95{-# INLINE after #-}
96after :: (IsStream t, MonadRunInIO m)
97 => m b -> t m a -> t m a
98after action xs = fromStreamD $ D.after action $ toStreamD xs
100-- | Run the action @m b@ if the stream aborts due to an exception. The
101-- exception is not caught, simply rethrown.
102--
103-- /Inhibits stream fusion/
104--
105-- @since 0.7.0
106{-# INLINE onException #-}
107onException :: (IsStream t, MonadCatch m) => m b -> t m a -> t m a
108onException action xs = fromStreamD $ D.onException action $ toStreamD xs
110-- | Like 'finally' with following differences:
111--
112-- * action @m b@ won't run if the stream is garbage collected
113-- after partial evaluation.
114-- * does not require a 'MonadAsync' constraint.
115-- * has slightly better performance than 'finally'.
116--
117-- /Inhibits stream fusion/
118--
119-- /Pre-release/
120--
121{-# INLINE finally_ #-}
122finally_ :: (IsStream t, MonadCatch m) => m b -> t m a -> t m a
123finally_ action xs = fromStreamD $ D.finally_ action $ toStreamD xs
125-- | Run the action @m b@ whenever the stream @t m a@ stops normally, aborts
126-- due to an exception or if it is garbage collected after a partial lazy
127-- evaluation.
128--
129-- The semantics of running the action @m b@ are similar to the cleanup action
130-- semantics described in 'bracket'.
131--
132-- @
133-- finally release = bracket (return ()) (const release)
134-- @
135--
136-- /See also 'finally_'/
137--
138-- /Inhibits stream fusion/
139--
140-- @since 0.7.0
141--
142{-# INLINE finally #-}
143finally :: (IsStream t, MonadAsync m, MonadCatch m) => m b -> t m a -> t m a
144finally action xs = fromStreamD $ D.finally action $ toStreamD xs
146-- | Like 'bracket' but with following differences:
147--
148-- * alloc action @m b@ runs with async exceptions enabled
149-- * cleanup action @b -> m c@ won't run if the stream is garbage collected
150-- after partial evaluation.
151-- * does not require a 'MonadAsync' constraint.
152-- * has slightly better performance than 'bracket'.
153--
154-- /Inhibits stream fusion/
155--
156-- /Pre-release/
157--
158{-# INLINE bracket_ #-}
159bracket_ :: (IsStream t, MonadCatch m)
160 => m b -> (b -> m c) -> (b -> t m a) -> t m a
161bracket_ bef aft bet = fromStreamD $
162 D.bracket_ bef aft (toStreamD . bet)
164-- | Run the alloc action @m b@ with async exceptions disabled but keeping
165-- blocking operations interruptible (see 'Control.Exception.mask'). Use the
166-- output @b@ as input to @b -> t m a@ to generate an output stream.
167--
168-- @b@ is usually a resource under the state of monad @m@, e.g. a file
169-- handle, that requires a cleanup after use. The cleanup action @b -> m c@,
170-- runs whenever the stream ends normally, due to a sync or async exception or
171-- if it gets garbage collected after a partial lazy evaluation.
172--
173-- 'bracket' only guarantees that the cleanup action runs, and it runs with
174-- async exceptions enabled. The action must ensure that it can successfully
175-- cleanup the resource in the face of sync or async exceptions.
176--
177-- When the stream ends normally or on a sync exception, cleanup action runs
178-- immediately in the current thread context, whereas in other cases it runs in
179-- the GC context, therefore, cleanup may be delayed until the GC gets to run.
180--
181-- /See also: 'bracket_'/
182--
183-- /Inhibits stream fusion/
184--
185-- @since 0.7.0
186--
187{-# INLINE bracket #-}
188bracket :: (IsStream t, MonadAsync m, MonadCatch m)
189 => m b -> (b -> m c) -> (b -> t m a) -> t m a
190bracket bef aft = bracket' bef aft aft aft
192-- For a use case of this see the "streamly-process" package. It needs to kill
193-- the process in case of exception or garbage collection, but waits for the
194-- process to terminate in normal cases.
195--
196-- | Like 'bracket' but can use separate cleanup actions depending on the mode
197-- of termination. @bracket' before onStop onGC onException action@ runs
198-- @action@ using the result of @before@. If the stream stops, @onStop@ action
199-- is executed, if the stream is abandoned @onGC@ is executed, if the stream
200-- encounters an exception @onException@ is executed.
201--
202-- /Pre-release/
203{-# INLINE bracket' #-}
204bracket' :: (IsStream t, MonadAsync m, MonadCatch m)
205 => m b -> (b -> m c) -> (b -> m d) -> (b -> m e) -> (b -> t m a) -> t m a
206bracket' bef aft gc exc bet = fromStreamD $
207 D.bracket' bef aft exc gc (toStreamD . bet)
209-- | Like 'handle' but the exception handler is also provided with the stream
210-- that generated the exception as input. The exception handler can thus
211-- re-evaluate the stream to retry the action that failed. The exception
212-- handler can again call 'ghandle' on it to retry the action multiple times.
213--
214-- This is highly experimental. In a stream of actions we can map the stream
215-- with a retry combinator to retry each action on failure.
216--
217-- /Inhibits stream fusion/
218--
219-- /Pre-release/
220--
221{-# INLINE ghandle #-}
222ghandle :: (IsStream t, MonadCatch m, Exception e)
223 => (e -> t m a -> t m a) -> t m a -> t m a
224ghandle handler =
225 fromStreamD
226 . D.ghandle (\e xs -> toStreamD $ handler e (fromStreamD xs))
227 . toStreamD
229-- | When evaluating a stream if an exception occurs, stream evaluation aborts
230-- and the specified exception handler is run with the exception as argument.
231--
232-- /Inhibits stream fusion/
233--
234-- @since 0.7.0
235{-# INLINE handle #-}
236handle :: (IsStream t, MonadCatch m, Exception e)
237 => (e -> t m a) -> t m a -> t m a
238handle handler xs =
239 fromStreamD $ D.handle (toStreamD . handler) $ toStreamD xs
242-- | @retry@ takes 3 arguments
243--
244-- 1. A map @m@ whose keys are exceptions and values are the number of times to
245-- retry the action given that the exception occurs.
246--
247-- 2. A handler @han@ that decides how to handle an exception when the exception
248-- cannot be retried.
249--
250-- 3. The stream itself that we want to run this mechanism on.
251--
252-- When evaluating a stream if an exception occurs,
253--
254-- 1. The stream evaluation aborts
255--
256-- 2. The exception is looked up in @m@
257--
258-- a. If the exception exists and the mapped value is > 0 then,
259--
260-- i. The value is decreased by 1.
261--
262-- ii. The stream is resumed from where the exception was called, retrying
263-- the action.
264--
265-- b. If the exception exists and the mapped value is == 0 then the stream
266-- evaluation stops.
267--
268-- c. If the exception does not exist then we handle the exception using
269-- @han@.
270--
271-- /Internal/
272--
273{-# INLINE retry #-}
274retry :: (IsStream t, MonadCatch m, Exception e, Ord e)
275 => Map e Int
276 -- ^ map from exception to retry count
277 -> (e -> t m a)
278 -- ^ default handler for those exceptions that are not in the map
279 -> t m a
280 -> t m a
281retry emap handler inp =
282 fromStreamD $ D.retry emap (toStreamD . handler) $ toStreamD inp