on 81bfdec help sign in
222 lines 7.6 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 , afterUnsafe
13 , afterIO
14 , bracketUnsafe
15 , bracketIO
16 , bracketIO3
17 , onException
18 , finallyUnsafe
19 , finallyIO
20 , ghandle
21 , handle
22 )
23where
25import Control.Exception (Exception)
26import Control.Monad.Catch (MonadCatch)
27import Control.Monad.IO.Class (MonadIO)
28import Streamly.Internal.Data.Stream.Type (Stream, fromStreamD, toStreamD)
30import qualified Streamly.Internal.Data.Stream.StreamD as D
32-- $setup
33-- >>> :m
34-- >>> import qualified Streamly.Internal.Data.Stream as Stream
36------------------------------------------------------------------------------
37-- Exceptions
38------------------------------------------------------------------------------
40-- | Run the action @m b@ before the stream yields its first element.
41--
42-- Same as the following but more efficient due to fusion:
43--
44-- >>> before action xs = Stream.nilM action <> xs
45-- >>> before action xs = Stream.concatMap (const xs) (Stream.fromEffect action)
46--
47{-# INLINE before #-}
48before :: Monad m => m b -> Stream m a -> Stream m a
49before action xs = fromStreamD $ D.before action $ toStreamD xs
51-- | Like 'after', with following differences:
52--
53-- * action @m b@ won't run if the stream is garbage collected
54-- after partial evaluation.
55-- * Monad @m@ does not require any other constraints.
56-- * has slightly better performance than 'after'.
57--
58-- Same as the following, but with stream fusion:
59--
60-- >>> afterUnsafe action xs = xs <> Stream.nilM action
61--
62-- /Pre-release/
63--
64{-# INLINE afterUnsafe #-}
65afterUnsafe :: Monad m => m b -> Stream m a -> Stream m a
66afterUnsafe action xs = fromStreamD $ D.after_ action $ toStreamD xs
68-- | Run the action @IO b@ whenever the stream is evaluated to completion, or
69-- if it is garbage collected after a partial lazy evaluation.
70--
71-- The semantics of the action @IO b@ are similar to the semantics of cleanup
72-- action in 'bracketIO'.
73--
74-- /See also 'afterUnsafe'/
75--
76{-# INLINE afterIO #-}
77afterIO :: MonadIO m => IO b -> Stream m a -> Stream m a
78afterIO action xs = fromStreamD $ D.after action $ toStreamD xs
80-- | Run the action @m b@ if the stream evaluation is aborted due to an
81-- exception. The exception is not caught, simply rethrown.
82--
83-- /Inhibits stream fusion/
84--
85{-# INLINE onException #-}
86onException :: MonadCatch m => m b -> Stream m a -> Stream m a
87onException action xs = fromStreamD $ D.onException action $ toStreamD xs
89-- | Like 'finally' with following differences:
90--
91-- * action @m b@ won't run if the stream is garbage collected
92-- after partial evaluation.
93-- * has slightly better performance than 'finallyIO'.
94--
95-- /Inhibits stream fusion/
96--
97-- /Pre-release/
98--
99{-# INLINE finallyUnsafe #-}
100finallyUnsafe :: MonadCatch m => m b -> Stream m a -> Stream m a
101finallyUnsafe action xs = fromStreamD $ D.finally_ action $ toStreamD xs
103-- | Run the action @IO b@ whenever the stream stream stops normally, aborts
104-- due to an exception or if it is garbage collected after a partial lazy
105-- evaluation.
106--
107-- The semantics of running the action @IO b@ are similar to the cleanup action
108-- semantics described in 'bracketIO'.
109--
110-- >>> finallyIO release = Stream.bracketIO (return ()) (const release)
111--
112-- /See also 'finallyUnsafe'/
113--
114-- /Inhibits stream fusion/
115--
116{-# INLINE finallyIO #-}
117finallyIO :: (MonadIO m, MonadCatch m) =>
118 IO b -> Stream m a -> Stream m a
119finallyIO action xs = fromStreamD $ D.finally action $ toStreamD xs
121-- | Like 'bracket' but with following differences:
122--
123-- * alloc action @m b@ runs with async exceptions enabled
124-- * cleanup action @b -> m c@ won't run if the stream is garbage collected
125-- after partial evaluation.
126-- * has slightly better performance than 'bracketIO'.
127--
128-- /Inhibits stream fusion/
129--
130-- /Pre-release/
131--
132{-# INLINE bracketUnsafe #-}
133bracketUnsafe :: MonadCatch m
134 => m b -> (b -> m c) -> (b -> Stream m a) -> Stream m a
135bracketUnsafe bef aft bet = fromStreamD $ D.bracket_ bef aft (toStreamD . bet)
137-- | Run the alloc action @IO b@ with async exceptions disabled but keeping
138-- blocking operations interruptible (see 'Control.Exception.mask'). Use the
139-- output @b@ as input to @b -> Stream m a@ to generate an output stream.
140--
141-- @b@ is usually a resource under the IO monad, e.g. a file handle, that
142-- requires a cleanup after use. The cleanup action @b -> IO c@, runs whenever
143-- the stream ends normally, due to a sync or async exception or if it gets
144-- garbage collected after a partial lazy evaluation.
145--
146-- 'bracketIO' only guarantees that the cleanup action runs, and it runs with
147-- async exceptions enabled. The action must ensure that it can successfully
148-- cleanup the resource in the face of sync or async exceptions.
149--
150-- When the stream ends normally or on a sync exception, cleanup action runs
151-- immediately in the current thread context, whereas in other cases it runs in
152-- the GC context, therefore, cleanup may be delayed until the GC gets to run.
153--
154-- /See also: 'bracketUnsafe'/
155--
156-- /Inhibits stream fusion/
157--
158{-# INLINE bracketIO #-}
159bracketIO :: (MonadIO m, MonadCatch m)
160 => IO b -> (b -> IO c) -> (b -> Stream m a) -> Stream m a
161bracketIO bef aft = bracketIO3 bef aft aft aft
163-- For a use case of this see the "streamly-process" package. It needs to kill
164-- the process in case of exception or garbage collection, but waits for the
165-- process to terminate in normal cases.
167-- | Like 'bracketIO' but can use 3 separate cleanup actions depending on the
168-- mode of termination:
169--
170-- 1. When the stream stops normally
171-- 2. When the stream is garbage collected
172-- 3. When the stream encounters an exception
173--
174-- @bracketIO3 before onStop onGC onException action@ runs @action@ using the
175-- result of @before@. If the stream stops, @onStop@ action is executed, if the
176-- stream is abandoned @onGC@ is executed, if the stream encounters an
177-- exception @onException@ is executed.
178--
179-- /Inhibits stream fusion/
180--
181-- /Pre-release/
182{-# INLINE bracketIO3 #-}
183bracketIO3 :: (MonadIO m, MonadCatch m)
184 => IO b
185 -> (b -> IO c)
186 -> (b -> IO d)
187 -> (b -> IO e)
188 -> (b -> Stream m a)
189 -> Stream m a
190bracketIO3 bef aft gc exc bet = fromStreamD $
191 D.bracket' bef aft exc gc (toStreamD . bet)
193-- | Like 'handle' but the exception handler is also provided with the stream
194-- that generated the exception as input. The exception handler can thus
195-- re-evaluate the stream to retry the action that failed. The exception
196-- handler can again call 'ghandle' on it to retry the action multiple times.
197--
198-- This is highly experimental. In a stream of actions we can map the stream
199-- with a retry combinator to retry each action on failure.
200--
201-- /Inhibits stream fusion/
202--
203-- /Pre-release/
204--
205{-# INLINE ghandle #-}
206ghandle :: (MonadCatch m, Exception e)
207 => (e -> Stream m a -> Stream m a) -> Stream m a -> Stream m a
208ghandle handler =
209 fromStreamD
210 . D.ghandle (\e xs -> toStreamD $ handler e (fromStreamD xs))
211 . toStreamD
213-- | When evaluating a stream if an exception occurs, stream evaluation aborts
214-- and the specified exception handler is run with the exception as argument.
215--
216-- /Inhibits stream fusion/
217--
218{-# INLINE handle #-}
219handle :: (MonadCatch m, Exception e)
220 => (e -> Stream m a) -> Stream m a -> Stream m a
221handle handler xs =
222 fromStreamD $ D.handle (toStreamD . handler) $ toStreamD xs