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