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