on master help sign in
Propagate exceptions from a concurrent tap
4ee8d64Harendra Kumar
src/Streamly/Internal/Data/SVar.hs+44−9
@@ -62,6 +62,7 @@ module Streamly.Internal.Data.SVar
62 , ChildEvent (..)
62 , ChildEvent (..)
63 , AheadHeapEntry (..)
63 , AheadHeapEntry (..)
64 , send
64 , send
65+ , sendToProducer
65 , sendYield
66 , sendYield
66 , sendStop
67 , sendStop
67 , enqueueLIFO
68 , enqueueLIFO
@@ -98,6 +99,7 @@ module Streamly.Internal.Data.SVar
98 , postProcessPaced
99 , postProcessPaced
99 , readOutputQBounded
100 , readOutputQBounded
100 , readOutputQPaced
101 , readOutputQPaced
102+ , readOutputQBasic
101 , dispatchWorkerPaced
103 , dispatchWorkerPaced
102 , sendFirstWorker
104 , sendFirstWorker
103 , delThread
105 , delThread
@@ -109,6 +111,7 @@ module Streamly.Internal.Data.SVar
109 , SVarStats (..)
111 , SVarStats (..)
110 , dumpSVar
112 , dumpSVar
111 , printSVar
113 , printSVar
114+ , withDiagMVar
112 )
115 )
113where
116where
114
117
@@ -424,6 +427,13 @@ data SVar t m a = SVar
424 , readOutputQ :: m [ChildEvent a]
427 , readOutputQ :: m [ChildEvent a]
425 , postProcess :: m Bool
428 , postProcess :: m Bool
426
429
430+ -- channel to send events from the consumer to the worker. Used to send
431+ -- exceptions from a fold driver to the fold computation running as a
432+ -- consumer thread in the concurrent fold cases. Currently only one event
433+ -- is sent by the fold so we do not really need a queue for it.
434+ , outputQueueFromConsumer :: IORef ([ChildEvent a], Int)
435+ , outputDoorBellFromConsumer :: MVar ()
436+
427 -- Combined/aggregate parameters
437 -- Combined/aggregate parameters
428 -- This is truncated to maxBufferLimit if set to more than that. Otherwise
438 -- This is truncated to maxBufferLimit if set to more than that. Otherwise
429 -- potentially each worker may yield one value to the buffer in the worst
439 -- potentially each worker may yield one value to the buffer in the worst
@@ -1055,13 +1065,12 @@ resetBufferLimit sv =
1055 Limited n -> atomicModifyIORefCAS_ (pushBufferSpace sv)
1065 Limited n -> atomicModifyIORefCAS_ (pushBufferSpace sv)
1056 (const (fromIntegral n))
1066 (const (fromIntegral n))
1057
1067
1058−-- | This function is used by the producer threads to queue output for the
1068+{-# INLINE sendWithDoorBell #-}
1059−-- consumer thread to consume. Returns whether the queue has more space.
1069+sendWithDoorBell ::
1060−send :: SVar t m a -> ChildEvent a -> IO Int
1070+ IORef ([ChildEvent a], Int) -> MVar () -> ChildEvent a -> IO Int
1061−send sv msg = do
1071+sendWithDoorBell q bell msg = do
1062− -- XXX can the access to outputQueue and maxBufferLimit be made faster
1072+ -- XXX can the access to outputQueue be made faster somehow?
1063− -- somehow?
1064− oldlen <- atomicModifyIORefCAS (outputQueue sv) $ \(es, n) ->
1073+ oldlen <- atomicModifyIORefCAS q $ \(es, n) ->
1065 ((msg : es, n + 1), n)
1074 ((msg : es, n + 1), n)
1066 when (oldlen <= 0) $ do
1075 when (oldlen <= 0) $ do
1067 -- The wake up must happen only after the store has finished otherwise
1076 -- The wake up must happen only after the store has finished otherwise
@@ -1073,9 +1082,25 @@ send sv msg = do
1073 -- to read the queue again and find it empty.
1082 -- to read the queue again and find it empty.
1074 -- The important point is that the consumer is guaranteed to receive a
1083 -- The important point is that the consumer is guaranteed to receive a
1075 -- doorbell if something was added to the queue after it empties it.
1084 -- doorbell if something was added to the queue after it empties it.
1076− void $ tryPutMVar (outputDoorBell sv) ()
1085+ void $ tryPutMVar bell ()
1077 return oldlen
1086 return oldlen
1078
1087
1088+-- | This function is used by the producer threads to queue output for the
1089+-- consumer thread to consume. Returns whether the queue has more space.
1090+send :: SVar t m a -> ChildEvent a -> IO Int
1091+send sv msg = sendWithDoorBell (outputQueue sv) (outputDoorBell sv) msg
1092+
1093+-- There is no bound implemented on the buffer, this is assumed to be low
1094+-- traffic.
1095+sendToProducer :: SVar t m a -> ChildEvent a -> IO Int
1096+sendToProducer sv msg = do
1097+ -- In case the producer stream is blocked on pushing to the fold buffer
1098+ -- then wake it up so that it can check for the stop event or exception
1099+ -- being sent to it otherwise we will be deadlocked.
1100+ void $ tryPutMVar (pushBufferMVar sv) ()
1101+ sendWithDoorBell (outputQueueFromConsumer sv)
1102+ (outputDoorBellFromConsumer sv) msg
1103+
1079workerCollectLatency :: WorkerInfo -> IO (Maybe (Count, NanoSecond64))
1104workerCollectLatency :: WorkerInfo -> IO (Maybe (Count, NanoSecond64))
1080workerCollectLatency winfo = do
1105workerCollectLatency winfo = do
1081 (cnt0, t0) <- readIORef (workerLatencyStart winfo)
1106 (cnt0, t0) <- readIORef (workerLatencyStart winfo)
@@ -2014,10 +2039,14 @@ sendWorkerWait delay dispatch sv = do
2014-- Reading from the workers' output queue/buffer
2039-- Reading from the workers' output queue/buffer
2015-------------------------------------------------------------------------------
2040-------------------------------------------------------------------------------
2016
2041
2042+{-# INLINE readOutputQBasic #-}
2043+readOutputQBasic :: IORef ([ChildEvent a], Int) -> IO ([ChildEvent a], Int)
2044+readOutputQBasic q = atomicModifyIORefCAS q $ \x -> (([],0), x)
2045+
2017{-# INLINE readOutputQRaw #-}
2046{-# INLINE readOutputQRaw #-}
2018readOutputQRaw :: SVar t m a -> IO ([ChildEvent a], Int)
2047readOutputQRaw :: SVar t m a -> IO ([ChildEvent a], Int)
2019readOutputQRaw sv = do
2048readOutputQRaw sv = do
2020− (list, len) <- atomicModifyIORefCAS (outputQueue sv) $ \x -> (([],0), x)
2049+ (list, len) <- readOutputQBasic (outputQueue sv)
2021 when (svarInspectMode sv) $ do
2050 when (svarInspectMode sv) $ do
2022 let ref = maxOutQSize $ svarStats sv
2051 let ref = maxOutQSize $ svarStats sv
2023 oqLen <- readIORef ref
2052 oqLen <- readIORef ref
@@ -2211,6 +2240,7 @@ getAheadSVar st f mrun = do
2211
2240
2212 let getSVar sv readOutput postProc = SVar
2241 let getSVar sv readOutput postProc = SVar
2213 { outputQueue = outQ
2242 { outputQueue = outQ
2243+ , outputQueueFromConsumer = undefined
2214 , remainingWork = yl
2244 , remainingWork = yl
2215 , maxBufferLimit = getMaxBuffer st
2245 , maxBufferLimit = getMaxBuffer st
2216 , pushBufferSpace = undefined
2246 , pushBufferSpace = undefined
@@ -2219,6 +2249,7 @@ getAheadSVar st f mrun = do
2219 , maxWorkerLimit = min (getMaxThreads st) (getMaxBuffer st)
2249 , maxWorkerLimit = min (getMaxThreads st) (getMaxBuffer st)
2220 , yieldRateInfo = rateInfo
2250 , yieldRateInfo = rateInfo
2221 , outputDoorBell = outQMv
2251 , outputDoorBell = outQMv
2252+ , outputDoorBellFromConsumer = undefined
2222 , readOutputQ = readOutput sv
2253 , readOutputQ = readOutput sv
2223 , postProcess = postProc sv
2254 , postProcess = postProc sv
2224 , workerThreads = running
2255 , workerThreads = running
@@ -2280,7 +2311,9 @@ getParallelSVar :: MonadIO m
2280 => SVarStopStyle -> State t m a -> RunInIO m -> IO (SVar t m a)
2311 => SVarStopStyle -> State t m a -> RunInIO m -> IO (SVar t m a)
2281getParallelSVar ss st mrun = do
2312getParallelSVar ss st mrun = do
2282 outQ <- newIORef ([], 0)
2313 outQ <- newIORef ([], 0)
2314+ outQRev <- newIORef ([], 0)
2283 outQMv <- newEmptyMVar
2315 outQMv <- newEmptyMVar
2316+ outQMvRev <- newEmptyMVar
2284 active <- newIORef 0
2317 active <- newIORef 0
2285 running <- newIORef S.empty
2318 running <- newIORef S.empty
2286 yl <- case getYieldLimit st of
2319 yl <- case getYieldLimit st of
@@ -2304,6 +2337,7 @@ getParallelSVar ss st mrun = do
2304
2337
2305 let sv =
2338 let sv =
2306 SVar { outputQueue = outQ
2339 SVar { outputQueue = outQ
2340+ , outputQueueFromConsumer = outQRev
2307 , remainingWork = yl
2341 , remainingWork = yl
2308 , maxBufferLimit = getMaxBuffer st
2342 , maxBufferLimit = getMaxBuffer st
2309 , pushBufferSpace = remBuf
2343 , pushBufferSpace = remBuf
@@ -2313,6 +2347,7 @@ getParallelSVar ss st mrun = do
2313 -- Used only for diagnostics
2347 -- Used only for diagnostics
2314 , yieldRateInfo = rateInfo
2348 , yieldRateInfo = rateInfo
2315 , outputDoorBell = outQMv
2349 , outputDoorBell = outQMv
2350+ , outputDoorBellFromConsumer = outQMvRev
2316 , readOutputQ = readOutputQPar sv
2351 , readOutputQ = readOutputQPar sv
2317 , postProcess = allThreadsDone sv
2352 , postProcess = allThreadsDone sv
2318 , workerThreads = running
2353 , workerThreads = running
src/Streamly/Internal/Data/Stream/Async.hs+4
@@ -294,6 +294,7 @@ getLifoSVar st mrun = do
294 -> SVar Stream m a
294 -> SVar Stream m a
295 getSVar sv readOutput postProc workDone wloop = SVar
295 getSVar sv readOutput postProc workDone wloop = SVar
296 { outputQueue = outQ
296 { outputQueue = outQ
297+ , outputQueueFromConsumer = undefined
297 , remainingWork = yl
298 , remainingWork = yl
298 , maxBufferLimit = getMaxBuffer st
299 , maxBufferLimit = getMaxBuffer st
299 , pushBufferSpace = undefined
300 , pushBufferSpace = undefined
@@ -302,6 +303,7 @@ getLifoSVar st mrun = do
302 , maxWorkerLimit = min (getMaxThreads st) (getMaxBuffer st)
303 , maxWorkerLimit = min (getMaxThreads st) (getMaxBuffer st)
303 , yieldRateInfo = rateInfo
304 , yieldRateInfo = rateInfo
304 , outputDoorBell = outQMv
305 , outputDoorBell = outQMv
306+ , outputDoorBellFromConsumer = undefined
305 , readOutputQ = readOutput sv
307 , readOutputQ = readOutput sv
306 , postProcess = postProc sv
308 , postProcess = postProc sv
307 , workerThreads = running
309 , workerThreads = running
@@ -389,6 +391,7 @@ getFifoSVar st mrun = do
389 -> SVar Stream m a
391 -> SVar Stream m a
390 getSVar sv readOutput postProc workDone wloop = SVar
392 getSVar sv readOutput postProc workDone wloop = SVar
391 { outputQueue = outQ
393 { outputQueue = outQ
394+ , outputQueueFromConsumer = undefined
392 , remainingWork = yl
395 , remainingWork = yl
393 , maxBufferLimit = getMaxBuffer st
396 , maxBufferLimit = getMaxBuffer st
394 , pushBufferSpace = undefined
397 , pushBufferSpace = undefined
@@ -397,6 +400,7 @@ getFifoSVar st mrun = do
397 , maxWorkerLimit = min (getMaxThreads st) (getMaxBuffer st)
400 , maxWorkerLimit = min (getMaxThreads st) (getMaxBuffer st)
398 , yieldRateInfo = rateInfo
401 , yieldRateInfo = rateInfo
399 , outputDoorBell = outQMv
402 , outputDoorBell = outQMv
403+ , outputDoorBellFromConsumer = undefined
400 , readOutputQ = readOutput sv
404 , readOutputQ = readOutput sv
401 , postProcess = postProc sv
405 , postProcess = postProc sv
402 , workerThreads = running
406 , workerThreads = running
src/Streamly/Internal/Data/Stream/Parallel.hs+117−42
@@ -34,9 +34,9 @@ module Streamly.Internal.Data.Stream.Parallel
34 )
34 )
35where
35where
36
36
37−import Control.Concurrent (myThreadId)
37+import Control.Concurrent (myThreadId, takeMVar)
38−import Control.Exception (SomeException(..), throwIO)
38+import Control.Exception (SomeException(..))
39−import Control.Monad (ap)
39+import Control.Monad (ap, when)
40import Control.Monad.Base (MonadBase(..), liftBaseDefault)
40import Control.Monad.Base (MonadBase(..), liftBaseDefault)
41import Control.Monad.Catch (MonadThrow, throwM)
41import Control.Monad.Catch (MonadThrow, throwM)
42-- import Control.Monad.Error.Class (MonadError(..))
42-- import Control.Monad.Error.Class (MonadError(..))
@@ -54,12 +54,13 @@ import Prelude hiding (map)
54
54
55import qualified Data.Set as Set
55import qualified Data.Set as Set
56
56
57−import Streamly.Internal.Data.Stream.SVar (fromSVar, fromStreamVar)
57+import Streamly.Internal.Data.Stream.SVar
58+ (fromSVar, fromProducer, fromConsumer)
58import Streamly.Internal.Data.Stream.Serial (map)
59import Streamly.Internal.Data.Stream.Serial (map)
59−import Streamly.Internal.Data.Stream.StreamK (IsStream(..), Stream, mkStream, foldStream,
60+import Streamly.Internal.Data.Stream.StreamK
60− foldStreamShared, adapt)
61+ (IsStream(..), Stream, mkStream, foldStream, foldStreamShared, adapt)
61
62
62−import Streamly.Internal.Data.SVar hiding (handleChildException)
63+import Streamly.Internal.Data.SVar
63
64
64import qualified Streamly.Internal.Data.Stream.StreamK as K
65import qualified Streamly.Internal.Data.Stream.StreamK as K
65import qualified Streamly.Internal.Data.Stream.StreamD as D
66import qualified Streamly.Internal.Data.Stream.StreamD as D
@@ -274,49 +275,130 @@ foldParallel f m = do
274 f $ fromSVar sv
275 f $ fromSVar sv
275
276
276------------------------------------------------------------------------------
277------------------------------------------------------------------------------
277−-- Parallel tee
278+-- Clone and distribute a stream in parallel
278------------------------------------------------------------------------------
279------------------------------------------------------------------------------
279
280
281+-- push values to a fold worker via an SVar. Returns whether the fold is done.
282+{-# INLINE pushToFold #-}
283+pushToFold :: MonadAsync m => SVar Stream m a -> a -> m Bool
284+pushToFold sv a = do
285+ -- Check for exceptions before decrement so that we do not
286+ -- block forever if the child already exited with an exception.
287+ --
288+ -- We avoid a race between the consumer fold sending an event and we
289+ -- blocking on decrementBufferLimit by waking up the producer thread in
290+ -- sendToProducer before any event is sent by the fold to the producer
291+ -- stream.
292+ let qref = outputQueueFromConsumer sv
293+ done <- do
294+ (_, n) <- liftIO $ readIORef qref
295+ if (n > 0)
296+ then fromConsumer sv
297+ else return False
298+ if done
299+ then return True
300+ else liftIO $ do
301+ decrementBufferLimit sv
302+ void $ send sv (ChildYield a)
303+ return False
304+
305+-- Tap a stream and send the elements to the specified SVar in addition to
306+-- yielding them again.
307+--
308+-- XXX this could be written in StreamD style for better efficiency with fusion.
309+--
280{-# INLINE teeToSVar #-}
310{-# INLINE teeToSVar #-}
281teeToSVar :: (IsStream t, MonadAsync m) => SVar Stream m a -> t m a -> t m a
311teeToSVar :: (IsStream t, MonadAsync m) => SVar Stream m a -> t m a -> t m a
282teeToSVar svr m = mkStream $ \st yld sng stp -> do
312teeToSVar svr m = mkStream $ \st yld sng stp -> do
283− foldStreamShared st yld sng stp (go svr m)
313+ foldStreamShared st yld sng stp (go False m)
284
314
285 where
315 where
286
316
287− go sv m0 = mkStream $ \st yld sng stp -> do
317+ go False m0 = mkStream $ \st yld _ stp -> do
288− let stop = do
318+ let drain = do
289− liftIO $ do
319+ -- In general, a Stop event would come equipped with the result
290− incrementBufferLimit sv
320+ -- of the fold. It is not used here but it would be useful in
291− sendStop sv Nothing
321+ -- applicative and distribute.
292− -- XXX wait for Stop response event on exception channel,
322+ done <- fromConsumer svr
293− -- drain the exception channel.
323+ when (not done) $ do
294− stp
324+ liftIO $ withDiagMVar svr "teeToSVar: waiting to drain"
295− sendit a = liftIO $ do
325+ $ takeMVar (outputDoorBellFromConsumer svr)
296− -- XXX check for exceptions before decrement so that we do not
326+ drain
297− -- block forever if the child already exited with an exception.
327+
298− decrementBufferLimit sv
328+ stopFold = do
299− void $ send sv (ChildYield a)
329+ liftIO $ sendStop svr Nothing
300− single a = sendit a >> (liftIO $ sendStop sv Nothing) >> sng a
330+ -- drain/wait until a stop event arrives from the fold.
331+ drain
332+
333+ stop = stopFold >> stp
334+ single a = do
335+ done <- pushToFold svr a
336+ yld a (go done (K.nilM stopFold))
301− yieldk a r = sendit a >> yld a (go sv r)
337+ yieldk a r = pushToFold svr a >>= \done -> yld a (go done r)
302 in foldStreamShared st yieldk single stop m0
338 in foldStreamShared st yieldk single stop m0
303
339
340+ go True m0 = m0
341+
304-- In case of folds the roles of worker and parent on an SVar are reversed. The
342-- In case of folds the roles of worker and parent on an SVar are reversed. The
305-- parent stream pushes values to an SVar instead of pulling from it and a
343-- parent stream pushes values to an SVar instead of pulling from it and a
306-- worker thread running the fold pulls from the SVar and folds the stream. We
344-- worker thread running the fold pulls from the SVar and folds the stream. We
307-- keep a separate channel for pushing exceptions in the reverse direction i.e.
345-- keep a separate channel for pushing exceptions in the reverse direction i.e.
308-- from the fold to the parent stream.
346-- from the fold to the parent stream.
309--
347--
310−-- NOTE: If we use fromSVar here it will kill the main computation (the parent)
348+-- Note: If we terminate due to an exception, we do not actively terminate the
311−-- when the SVar goes away so we use fromStreamVar instead.
349+-- fold. It gets cleaned up by the GC.
350+
351+{-# NOINLINE handleFoldException #-}
352+handleFoldException :: SVar t m a -> SomeException -> IO ()
353+handleFoldException sv e = do
354+ tid <- myThreadId
355+ void $ sendToProducer sv (ChildStop tid (Just e))
356+
357+{-# NOINLINE sendStopToConsumer #-}
358+sendStopToConsumer :: MonadIO m => SVar t m a -> m ()
359+sendStopToConsumer sv = liftIO $ do
360+ tid <- myThreadId
361+ void $ sendToProducer sv (ChildStop tid Nothing)
362+
363+-- | Create an SVar with a fold consumer that will fold any elements sent to it
364+-- using the supplied fold function.
365+{-# INLINE newFoldSVar #-}
366+newFoldSVar :: (IsStream t, MonadAsync m)
367+ => State Stream m a -> (t m a -> m b) -> m (SVar Stream m a)
368+newFoldSVar stt f = do
369+ -- Buffer size for the SVar is derived from the current state
370+ sv <- newParallelVar StopAny (adaptState stt)
312
371
313−{-# NOINLINE handleChildException #-}
372+ -- Add the producer thread-id to the SVar.
314−handleChildException :: SVar t m a -> SomeException -> IO ()
315−handleChildException _sv e = do
316− -- tid <- myThreadId
373+ liftIO myThreadId >>= modifyThread sv
317− -- void $ sendReverse sv (ChildStop tid (Just e))
318− throwIO e
319
374
375+ -- A wrapper to send a Stop event back to the producer when the fold
376+ -- receives a stop from the producer.
377+ let pull m = mkStream $ \st yld _ stp -> do
378+ let stop = sendStopToConsumer sv >> stp
379+ single a = yld a (K.nilM (sendStopToConsumer sv))
380+ yieldk a r = yld a (pull r)
381+ in foldStreamShared st yieldk single stop m
382+
383+ void $ doFork (void $ f $ fromStream $ pull $ fromProducer sv)
384+ (svarMrun sv)
385+ (handleFoldException sv)
386+ return sv
387+
388+-- NOTE: In regular pull style streams, the consumer stream is pulling elements
389+-- from the SVar and we have several workers producing elements and pushing to
390+-- SVar. In case of folds, we, the parent stream driving the fold, are the
391+-- stream producing worker, we start an SVar and start pushing to the SVar, the
392+-- fold on the other side of the SVar is the consumer stream.
393+--
394+-- In the pull stream case exceptions are propagated from the producing workers
395+-- to the consumer stream, the exceptions are propagated on the same channel as
396+-- the produced stream elements. However, in case of push style folds the
397+-- current stream itself is the worker and the fold is the consumer, in this
398+-- case we have to propagate the exceptions from the consumer to the producer.
399+-- This is reverse of the pull case and we need a reverse direction channel
400+-- to propagate the exception.
401+--
320-- | Redirect a copy of the stream to a supplied fold and run it concurrently
402-- | Redirect a copy of the stream to a supplied fold and run it concurrently
321-- in an independent thread. The fold may buffer some elements. The buffer size
403-- in an independent thread. The fold may buffer some elements. The buffer size
322-- is determined by the prevailing 'maxBuffer' setting.
404-- is determined by the prevailing 'maxBuffer' setting.
@@ -337,8 +419,8 @@ handleChildException _sv e = do
337-- Exceptions from the concurrently running fold are propagated to the current
419-- Exceptions from the concurrently running fold are propagated to the current
338-- computation. Note that, because of buffering in the fold, exceptions may be
420-- computation. Note that, because of buffering in the fold, exceptions may be
339-- delayed and may not correspond to the current element being processed in the
421-- delayed and may not correspond to the current element being processed in the
340−-- parent stream, but we guarantee that the tap finishes and all exceptions
422+-- parent stream, but we guarantee that before the parent stream stops the tap
341−-- from it are drained before the parent stream stops.
423+-- finishes and all exceptions from it are drained.
342--
424--
343--
425--
344-- Compare with 'tap'.
426-- Compare with 'tap'.
@@ -347,14 +429,7 @@ handleChildException _sv e = do
347{-# INLINE tapAsync #-}
429{-# INLINE tapAsync #-}
348tapAsync :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> t m a
430tapAsync :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> t m a
349tapAsync f m = mkStream $ \st yld sng stp -> do
431tapAsync f m = mkStream $ \st yld sng stp -> do
350− -- Buffer size for the SVar is derived from the current state
432+ sv <- newFoldSVar st f
351− sv <- newParallelVar StopNone (adaptState st)
352− -- XXX exception handling
353− -- XXX if we terminate due to an exception, do we need to actively
354− -- terminate the fold?
355− liftIO myThreadId >>= modifyThread sv
356− void $ doFork (void $ f $ fromStream $ fromStreamVar sv)
357− (svarMrun sv) (handleChildException sv)
358 foldStreamShared st yld sng stp (teeToSVar sv m)
433 foldStreamShared st yld sng stp (teeToSVar sv m)
359
434
360------------------------------------------------------------------------------
435------------------------------------------------------------------------------
src/Streamly/Internal/Data/Stream/SVar.hs+69
@@ -14,6 +14,8 @@
14module Streamly.Internal.Data.Stream.SVar
14module Streamly.Internal.Data.Stream.SVar
15 ( fromSVar
15 ( fromSVar
16 , fromStreamVar
16 , fromStreamVar
17+ , fromProducer
18+ , fromConsumer
17 , toSVar
19 , toSVar
18 )
20 )
19where
21where
@@ -108,3 +110,70 @@ fromSVar sv =
108-- be read back from the SVar using 'fromSVar'.
110-- be read back from the SVar using 'fromSVar'.
109toSVar :: (IsStream t, MonadAsync m) => SVar Stream m a -> t m a -> m ()
111toSVar :: (IsStream t, MonadAsync m) => SVar Stream m a -> t m a -> m ()
110toSVar sv m = toStreamVar sv (toStream m)
112toSVar sv m = toStreamVar sv (toStream m)
113+
114+-------------------------------------------------------------------------------
115+-- Process events received by a fold consumer from a stream producer
116+-------------------------------------------------------------------------------
117+
118+-- | Pull a stream from an SVar.
119+{-# NOINLINE fromProducer #-}
120+fromProducer :: MonadAsync m => SVar Stream m a -> Stream m a
121+fromProducer sv = mkStream $ \st yld sng stp -> do
122+ list <- readOutputQ sv
123+ -- Reversing the output is important to guarantee that we process the
124+ -- outputs in the same order as they were generated by the constituent
125+ -- streams.
126+ foldStream st yld sng stp $ processEvents $ reverse list
127+
128+ where
129+
130+ allDone stp = do
131+ when (svarInspectMode sv) $ do
132+ t <- liftIO $ getTime Monotonic
133+ liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)
134+ liftIO $ printSVar sv "SVar Done"
135+ stp
136+
137+ {-# INLINE processEvents #-}
138+ processEvents [] = mkStream $ \st yld sng stp -> do
139+ foldStream st yld sng stp $ fromProducer sv
140+
141+ processEvents (ev : es) = mkStream $ \_ yld _ stp -> do
142+ let rest = processEvents es
143+ case ev of
144+ ChildYield a -> yld a rest
145+ ChildStop tid e -> do
146+ accountThread sv tid
147+ case e of
148+ Nothing -> allDone stp
149+ Just _ -> error "Bug: fromProducer: received exception"
150+
151+-------------------------------------------------------------------------------
152+-- Process events received by the producer thread from the consumer side
153+-------------------------------------------------------------------------------
154+
155+-- XXX currently only one event is sent by a fold consumer to the stream
156+-- producer. But we can potentially have multiple events e.g. the fold step can
157+-- generate exception more than once and the producer can ignore those
158+-- exceptions or handle them and still keep driving the fold.
159+--
160+{-# NOINLINE fromConsumer #-}
161+fromConsumer :: MonadAsync m => SVar Stream m a -> m Bool
162+fromConsumer sv = do
163+ (list, _) <- liftIO $ readOutputQBasic (outputQueueFromConsumer sv)
164+ -- Reversing the output is important to guarantee that we process the
165+ -- outputs in the same order as they were generated by the constituent
166+ -- streams.
167+ processEvents $ reverse list
168+
169+ where
170+
171+ {-# INLINE processEvents #-}
172+ processEvents [] = return False
173+ processEvents (ev : _) = do
174+ case ev of
175+ ChildStop _ e -> do
176+ case e of
177+ Nothing -> return True
178+ Just ex -> throwM ex
179+ ChildYield _ -> error "Bug: fromConsumer: invalid ChildYield event"