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