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