on bf396f0 help sign in
651 lines 25 kB history raw
1{-# LANGUAGE UndecidableInstances #-}
3#include "inline.hs"
5-- |
6-- Module : Streamly.Internal.Data.Stream.Parallel
7-- Copyright : (c) 2017 Composewell Technologies
8--
9-- License : BSD3
10-- Maintainer : streamly@composewell.com
11-- Stability : experimental
12-- Portability : GHC
13--
14--
15module Streamly.Internal.Data.Stream.Parallel
16 (
17 -- * Parallel Stream Type
18 ParallelT
19 , Parallel
20 , parallely
22 -- * Merge Concurrently
23 , parallel
24 , parallelFst
25 , parallelMin
27 -- * Evaluate Concurrently
28 , mkParallel
29 , mkParallelD
30 , mkParallelK
32 -- * Tap Concurrently
33 , tapAsync
34 , tapAsyncF
35 , distributeAsync_
37 -- * Callbacks
38 , newCallbackStream
39 )
40where
42import Control.Concurrent (myThreadId, takeMVar)
43import Control.Monad (when)
44import Control.Monad.Base (MonadBase(..), liftBaseDefault)
45import Control.Monad.Catch (MonadThrow, throwM)
46-- import Control.Monad.Error.Class (MonadError(..))
47import Control.Monad.IO.Class (MonadIO(..))
48import Control.Monad.Reader.Class (MonadReader(..))
49import Control.Monad.State.Class (MonadState(..))
50import Control.Monad.Trans.Class (MonadTrans(lift))
51import Data.Functor (void)
52import Data.IORef (readIORef, writeIORef)
53import Data.Maybe (fromJust)
54#if __GLASGOW_HASKELL__ < 808
55import Data.Semigroup (Semigroup(..))
56#endif
57import Prelude hiding (map)
59import qualified Data.Set as Set
61import Streamly.Internal.Data.Fold.Types (Fold)
62import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))
63import Streamly.Internal.Data.Stream.StreamK
64 (IsStream(..), Stream, mkStream, foldStream, foldStreamShared, adapt)
66import Streamly.Internal.Data.SVar
68import qualified Streamly.Internal.Data.Stream.StreamK as K (withLocal)
69import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
70import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
71import qualified Streamly.Internal.Data.Stream.SVar as SVar
73#include "Instances.hs"
75-------------------------------------------------------------------------------
76-- Parallel
77-------------------------------------------------------------------------------
79-------------------------------------------------------------------------------
80-- StreamK based worker routines
81-------------------------------------------------------------------------------
83{-# NOINLINE runOne #-}
84runOne
85 :: MonadIO m
86 => State Stream m a -> Stream m a -> Maybe WorkerInfo -> m ()
87runOne st m0 winfo =
88 case getYieldLimit st of
89 Nothing -> go m0
90 Just _ -> runOneLimited st m0 winfo
92 where
94 go m = do
95 liftIO $ decrementBufferLimit sv
96 foldStreamShared st yieldk single stop m
98 sv = fromJust $ streamVar st
100 stop = liftIO $ do
101 incrementBufferLimit sv
102 sendStop sv winfo
103 sendit a = liftIO $ void $ send sv (ChildYield a)
104 single a = sendit a >> (liftIO $ sendStop sv winfo)
105 yieldk a r = sendit a >> go r
107runOneLimited
108 :: MonadIO m
109 => State Stream m a -> Stream m a -> Maybe WorkerInfo -> m ()
110runOneLimited st m0 winfo = go m0
112 where
114 go m = do
115 yieldLimitOk <- liftIO $ decrementYieldLimit sv
116 if yieldLimitOk
117 then do
118 liftIO $ decrementBufferLimit sv
119 foldStreamShared st yieldk single stop m
120 else do
121 liftIO $ cleanupSVarFromWorker sv
122 liftIO $ sendStop sv winfo
124 sv = fromJust $ streamVar st
126 stop = liftIO $ do
127 incrementBufferLimit sv
128 incrementYieldLimit sv
129 sendStop sv winfo
130 sendit a = liftIO $ void $ send sv (ChildYield a)
131 single a = sendit a >> (liftIO $ sendStop sv winfo)
132 yieldk a r = sendit a >> go r
134-------------------------------------------------------------------------------
135-- Consing and appending a stream in parallel style
136-------------------------------------------------------------------------------
138-- Note that consing and appending requires StreamK as it would not scale well
139-- with StreamD unless we are only consing a very small number of streams or
140-- elements in a stream. StreamK allows us to manipulate control flow in a way
141-- which StreamD cannot allow. StreamK can make a jump without having to
142-- remember the past state.
144{-# NOINLINE forkSVarPar #-}
145forkSVarPar :: (IsStream t, MonadAsync m)
146 => SVarStopStyle -> t m a -> t m a -> t m a
147forkSVarPar ss m r = mkStream $ \st yld sng stp -> do
148 sv <- newParallelVar ss st
149 pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream m)
150 case ss of
151 StopBy -> liftIO $ do
152 set <- readIORef (workerThreads sv)
153 writeIORef (svarStopBy sv) $ Set.elemAt 0 set
154 _ -> return ()
155 pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream r)
156 foldStream st yld sng stp (SVar.fromSVar sv)
158{-# INLINE joinStreamVarPar #-}
159joinStreamVarPar :: (IsStream t, MonadAsync m)
160 => SVarStyle -> SVarStopStyle -> t m a -> t m a -> t m a
161joinStreamVarPar style ss m1 m2 = mkStream $ \st yld sng stp ->
162 case streamVar st of
163 Just sv | svarStyle sv == style && svarStopStyle sv == ss -> do
164 -- Here, WE ARE IN THE WORKER/PRODUCER THREAD, we know that because
165 -- the SVar exists. We are running under runOne and the output we
166 -- produce ultimately will be sent to the SVar by runOne.
167 --
168 -- If we came here the worker/runOne is evaluating a `parallel`
169 -- combinator. In this case, we always fork a new worker for the
170 -- first component (m1) in the parallel composition and continue to
171 -- evaluate the second component (m2) in the current worker thread.
172 --
173 -- When m1 is serially composed, the worker would evaluate it
174 -- without any further forks and the resulting output is sent to
175 -- the SVar and the evaluation terminates. If m1 is a `parallel`
176 -- composition of two streams the worker would again recurses here.
177 --
178 -- Similarly, when m2 is serially composed it gets evaluated here
179 -- and the resulting output is sent to the SVar by the runOne
180 -- wrapper. When m2 is composed with `parallel` it will again
181 -- recurse here and so on until it finally terminates.
182 --
183 -- When we create a right associated expression using `parallel`,
184 -- then m1 would always terminate without further forks or
185 -- recursion into this routine, therefore, the worker returns
186 -- immediately after evaluating it. And m2 would continue to
187 -- fork/recurse, therefore, the current thread always recurses and
188 -- forks new workers one after the other. This is a tail recursive
189 -- style execution, m2, the recursive expression always executed at
190 -- the tail.
191 --
192 -- When the expression is left associated, the worker spawned would
193 -- get the forking/recursing responsibility and then again the
194 -- worker spawned by that worker would fork, thus creating layer
195 -- over layer of workers and a chain of threads leading to a very
196 -- inefficient execution.
197 pushWorkerPar sv (runOne st $ toStream m1)
198 foldStreamShared st yld sng stp m2
199 _ ->
200 -- Here WE ARE IN THE CONSUMER THREAD, we create a new SVar, fork
201 -- worker threads to execute m1 and m2 and this thread starts
202 -- pulling the stream from the SVar.
203 foldStreamShared st yld sng stp (forkSVarPar ss m1 m2)
205-------------------------------------------------------------------------------
206-- User facing APIs
207-------------------------------------------------------------------------------
209-- | XXX we can implement it more efficienty by directly implementing instead
210-- of combining streams using parallel.
211{-# INLINE consMParallel #-}
212{-# SPECIALIZE consMParallel :: IO a -> ParallelT IO a -> ParallelT IO a #-}
213consMParallel :: MonadAsync m => m a -> ParallelT m a -> ParallelT m a
214consMParallel m r = fromStream $ K.yieldM m `parallel` (toStream r)
216infixr 6 `parallel`
218-- | Execute two streams concurrently and merge their outputs. For example, if
219-- stream @a@ is a serial stream consisting of @a1, a2, a3@ and stream @b@ is a
220-- serial stream consisting of @b1, b2, b3@ then stream @a `parallel` b@ may
221-- produce @a1, b1, a2, b2, a3, b3@ or @a1, a2, b1, a3, b2, b3@ or some other
222-- combination depending on the rate at which the two streams produce elements.
223-- However, the relative order of outputs from a single stream e.g. @a1, a2,
224-- a3@ would remain the same in the resulting stream. The effects in the two
225-- streams may occur concurrently.
226--
227-- To run single actions (instead of streams) in parallel wrap them into
228-- singleton streams. The following trivial example is semantically equivalent
229-- to running the action @putStrLn "hello"@ in the current thread:
230--
231-- >>> S.toList $ S.yieldM (putStrLn "hello") `parallel` S.nil
232-- > hello
233-- > [()]
234--
235-- Run two actions concurrently:
236--
237-- >>> S.toList $ S.yieldM (putStrLn "hello") `parallel` S.yieldM (putStrLn "world")
238-- > hello
239-- > world
240-- > [(),()]
241--
242-- Run effects concurrently, disregarding their outputs:
243--
244-- >>> S.toList $ S.nilM (putStrLn "hello") `parallel` S.nilM (putStrLn "world")
245-- > hello
246-- > world
247-- > []
248--
249-- Run an effectful action, and a pure effect without any output, concurrently:
250--
251-- >>> S.toList $ S.yieldM (return 1) `parallel` S.nilM (putStrLn "world")
252-- world
253-- [1]
254--
255-- Note that 'parallel' is a polymorphic version of the @Semigroup@ operation
256-- @<>@ of 'ParallelT'.
257--
258-- `nilM` is currently @Internal@.
259--
260-- /Since: 0.2.0 ("Streamly")/
261--
262-- @since 0.8.0
263{-# INLINE parallel #-}
264parallel :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
265parallel = joinStreamVarPar ParallelVar StopNone
267-- This is a co-parallel like combinator for streams, where first stream is the
268-- main stream and the rest are just supporting it, when the first ends
269-- everything ends.
270--
271-- | Like `parallel` but stops the output as soon as the first stream stops.
272--
273-- /Internal/
274{-# INLINE parallelFst #-}
275parallelFst :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
276parallelFst = joinStreamVarPar ParallelVar StopBy
278-- This is a race like combinator for streams.
279--
280-- | Like `parallel` but stops the output as soon as any of the two streams
281-- stops.
282--
283-- /Internal/
284{-# INLINE parallelMin #-}
285parallelMin :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
286parallelMin = joinStreamVarPar ParallelVar StopAny
288------------------------------------------------------------------------------
289-- Convert a stream to parallel
290------------------------------------------------------------------------------
292-- | Like 'mkParallel' but uses StreamK internally.
293--
294-- /Internal/
295--
296mkParallelK :: (IsStream t, MonadAsync m) => t m a -> t m a
297mkParallelK m = mkStream $ \st yld sng stp -> do
298 sv <- newParallelVar StopNone (adaptState st)
299 -- pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream m)
300 SVar.toSVarParallel st sv $ D.toStreamD m
301 foldStream st yld sng stp $ SVar.fromSVar sv
303-------------------------------------------------------------------------------
304-- Concurrent application and fold
305-------------------------------------------------------------------------------
307-- | Same as 'mkParallel' but for StreamD stream.
308--
309{-# INLINE_NORMAL mkParallelD #-}
310mkParallelD :: MonadAsync m => D.Stream m a -> D.Stream m a
311mkParallelD m = D.Stream step Nothing
312 where
314 step gst Nothing = do
315 sv <- newParallelVar StopNone gst
316 SVar.toSVarParallel gst sv m
317 -- XXX use unfold instead?
318 return $ Skip $ Just $ SVar.fromSVarD sv
320 step gst (Just (D.UnStream step1 st)) = do
321 r <- step1 gst st
322 return $ case r of
323 Yield a s -> Yield a (Just $ D.Stream step1 s)
324 Skip s -> Skip (Just $ D.Stream step1 s)
325 Stop -> Stop
327-- Compare with mkAsync. mkAsync uses an Async style SVar whereas this uses a
328-- parallel style SVar for evaluation. Currently, parallel style cannot use
329-- rate control whereas Async style can use rate control. In async style SVar
330-- the worker thread terminates when the buffer is full whereas in Parallel
331-- style it blocks.
332--
333-- | Make the stream producer and consumer run concurrently by introducing a
334-- buffer between them. The producer thread evaluates the input stream until
335-- the buffer fills, it blocks if the buffer is full until there is space in
336-- the buffer. The consumer consumes the stream lazily from the buffer.
337--
338-- @mkParallel = D.fromStreamD . mkParallelD . D.toStreamD@
339--
340-- /Internal/
341--
342{-# INLINE_NORMAL mkParallel #-}
343mkParallel :: (K.IsStream t, MonadAsync m) => t m a -> t m a
344mkParallel = D.fromStreamD . mkParallelD . D.toStreamD
346-------------------------------------------------------------------------------
347-- Concurrent tap
348-------------------------------------------------------------------------------
350-- NOTE: In regular pull style streams, the consumer stream is pulling elements
351-- from the SVar and we have several workers producing elements and pushing to
352-- SVar. In case of folds, we, the parent stream driving the fold, are the
353-- stream producing worker, we start an SVar and start pushing to the SVar, the
354-- fold on the other side of the SVar is the consumer stream.
355--
356-- In the pull stream case exceptions are propagated from the producing workers
357-- to the consumer stream, the exceptions are propagated on the same channel as
358-- the produced stream elements. However, in case of push style folds the
359-- current stream itself is the worker and the fold is the consumer, in this
360-- case we have to propagate the exceptions from the consumer to the producer.
361-- This is reverse of the pull case and we need a reverse direction channel
362-- to propagate the exception.
363--
364-- | Redirect a copy of the stream to a supplied fold and run it concurrently
365-- in an independent thread. The fold may buffer some elements. The buffer size
366-- is determined by the prevailing 'maxBuffer' setting.
367--
368-- @
369-- Stream m a -> m b
370-- |
371-- -----stream m a ---------------stream m a-----
372--
373-- @
374--
375-- @
376-- > S.drain $ S.tapAsync (S.mapM_ print) (S.enumerateFromTo 1 2)
377-- 1
378-- 2
379-- @
380--
381-- Exceptions from the concurrently running fold are propagated to the current
382-- computation. Note that, because of buffering in the fold, exceptions may be
383-- delayed and may not correspond to the current element being processed in the
384-- parent stream, but we guarantee that before the parent stream stops the tap
385-- finishes and all exceptions from it are drained.
386--
387--
388-- Compare with 'tap'.
389--
390-- /Internal/
391{-# INLINE tapAsync #-}
392tapAsync :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> t m a
393tapAsync f m = mkStream $ \st yld sng stp -> do
394 sv <- SVar.newFoldSVar st f
395 foldStreamShared st yld sng stp (SVar.teeToSVar sv m)
397data TapState fs st a = TapInit | Tapping !fs st | TapDone st
399-- | Like 'tapAsync' but uses a 'Fold' instead of a fold function.
400--
401{-# INLINE_NORMAL tapAsyncF #-}
402tapAsyncF :: MonadAsync m => Fold m a b -> D.Stream m a -> D.Stream m a
403tapAsyncF f (D.Stream step1 state1) = D.Stream step TapInit
404 where
406 drainFold svr = do
407 -- In general, a Stop event would come equipped with the result
408 -- of the fold. It is not used here but it would be useful in
409 -- applicative and distribute.
410 done <- SVar.fromConsumer svr
411 when (not done) $ do
412 liftIO $ withDiagMVar svr "teeToSVar: waiting to drain"
413 $ takeMVar (outputDoorBellFromConsumer svr)
414 drainFold svr
416 stopFold svr = do
417 liftIO $ sendStop svr Nothing
418 -- drain/wait until a stop event arrives from the fold.
419 drainFold svr
421 {-# INLINE_LATE step #-}
422 step gst TapInit = do
423 sv <- SVar.newFoldSVarF gst f
424 return $ Skip (Tapping sv state1)
426 step gst (Tapping sv st) = do
427 r <- step1 gst st
428 case r of
429 Yield a s -> do
430 done <- SVar.pushToFold sv a
431 if done
432 then do
433 -- XXX we do not need to wait synchronously here
434 stopFold sv
435 return $ Yield a (TapDone s)
436 else return $ Yield a (Tapping sv s)
437 Skip s -> return $ Skip (Tapping sv s)
438 Stop -> do
439 stopFold sv
440 return Stop
442 step gst (TapDone st) = do
443 r <- step1 gst st
444 return $ case r of
445 Yield a s -> Yield a (TapDone s)
446 Skip s -> Skip (TapDone s)
447 Stop -> Stop
449-- | Concurrently distribute a stream to a collection of fold functions,
450-- discarding the outputs of the folds.
451--
452-- >>> S.drain $ distributeAsync_ [S.mapM_ print, S.mapM_ print] (S.enumerateFromTo 1 2)
453--
454-- @
455-- distributeAsync_ = flip (foldr tapAsync)
456-- @
457--
458-- /Internal/
459--
460{-# INLINE distributeAsync_ #-}
461distributeAsync_ :: (Foldable f, IsStream t, MonadAsync m)
462 => f (t m a -> m b) -> t m a -> t m a
463distributeAsync_ = flip (foldr tapAsync)
465------------------------------------------------------------------------------
466-- ParallelT
467------------------------------------------------------------------------------
469-- | Async composition with strict concurrent execution of all streams.
470--
471-- The 'Semigroup' instance of 'ParallelT' executes both the streams
472-- concurrently without any delay or without waiting for the consumer demand
473-- and /merges/ the results as they arrive. If the consumer does not consume
474-- the results, they are buffered upto a configured maximum, controlled by the
475-- 'maxBuffer' primitive. If the buffer becomes full the concurrent tasks will
476-- block until there is space in the buffer.
477--
478-- Both 'WAsyncT' and 'ParallelT', evaluate the constituent streams fairly in a
479-- round robin fashion. The key difference is that 'WAsyncT' might wait for the
480-- consumer demand before it executes the tasks whereas 'ParallelT' starts
481-- executing all the tasks immediately without waiting for the consumer demand.
482-- For 'WAsyncT' the 'maxThreads' limit applies whereas for 'ParallelT' it does
483-- not apply. In other words, 'WAsyncT' can be lazy whereas 'ParallelT' is
484-- strict.
485--
486-- 'ParallelT' is useful for cases when the streams are required to be
487-- evaluated simultaneously irrespective of how the consumer consumes them e.g.
488-- when we want to race two tasks and want to start both strictly at the same
489-- time or if we have timers in the parallel tasks and our results depend on
490-- the timers being started at the same time. If we do not have such
491-- requirements then 'AsyncT' or 'AheadT' are recommended as they can be more
492-- efficient than 'ParallelT'.
493--
494-- @
495-- main = (S.'toList' . S.'parallely' $ (S.fromFoldable [1,2]) \<> (S.fromFoldable [3,4])) >>= print
496-- @
497-- @
498-- [1,3,2,4]
499-- @
500--
501-- When streams with more than one element are merged, it yields whichever
502-- stream yields first without any bias, unlike the 'Async' style streams.
503--
504-- Any exceptions generated by a constituent stream are propagated to the
505-- output stream. The output and exceptions from a single stream are guaranteed
506-- to arrive in the same order in the resulting stream as they were generated
507-- in the input stream. However, the relative ordering of elements from
508-- different streams in the resulting stream can vary depending on scheduling
509-- and generation delays.
510--
511-- Similarly, the 'Monad' instance of 'ParallelT' runs /all/ iterations
512-- of the loop concurrently.
513--
514-- @
515-- import qualified "Streamly.Prelude" as S
516-- import Control.Concurrent
517--
518-- main = S.'drain' . S.'parallely' $ do
519-- n <- return 3 \<\> return 2 \<\> return 1
520-- S.yieldM $ do
521-- threadDelay (n * 1000000)
522-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
523-- @
524-- @
525-- ThreadId 40: Delay 1
526-- ThreadId 39: Delay 2
527-- ThreadId 38: Delay 3
528-- @
529--
530-- Note that parallel composition can only combine a finite number of
531-- streams as it needs to retain state for each unfinished stream.
532--
533-- /Since: 0.1.0 ("Streamly")/
534--
535-- /Since: 0.7.0 (maxBuffer applies to ParallelT streams)/
536--
537-- @since 0.8.0
538newtype ParallelT m a = ParallelT {getParallelT :: Stream m a}
539 deriving (MonadTrans)
541-- | A parallely composing IO stream of elements of type @a@.
542-- See 'ParallelT' documentation for more details.
543--
544-- /Since: 0.2.0 ("Streamly")/
545--
546-- @since 0.8.0
547type Parallel = ParallelT IO
549-- | Fix the type of a polymorphic stream as 'ParallelT'.
550--
551-- /Since: 0.1.0 ("Streamly")/
552--
553-- @since 0.8.0
554parallely :: IsStream t => ParallelT m a -> t m a
555parallely = adapt
557instance IsStream ParallelT where
558 toStream = getParallelT
559 fromStream = ParallelT
561 {-# INLINE consM #-}
562 {-# SPECIALIZE consM :: IO a -> ParallelT IO a -> ParallelT IO a #-}
563 consM = consMParallel
565 {-# INLINE (|:) #-}
566 {-# SPECIALIZE (|:) :: IO a -> ParallelT IO a -> ParallelT IO a #-}
567 (|:) = consM
569------------------------------------------------------------------------------
570-- Semigroup
571------------------------------------------------------------------------------
573{-# INLINE mappendParallel #-}
574{-# SPECIALIZE mappendParallel :: ParallelT IO a -> ParallelT IO a -> ParallelT IO a #-}
575mappendParallel :: MonadAsync m => ParallelT m a -> ParallelT m a -> ParallelT m a
576mappendParallel m1 m2 = fromStream $ parallel (toStream m1) (toStream m2)
578instance MonadAsync m => Semigroup (ParallelT m a) where
579 (<>) = mappendParallel
581------------------------------------------------------------------------------
582-- Monoid
583------------------------------------------------------------------------------
585instance MonadAsync m => Monoid (ParallelT m a) where
586 mempty = K.nil
587 mappend = (<>)
589------------------------------------------------------------------------------
590-- Applicative
591------------------------------------------------------------------------------
593{-# INLINE apParallel #-}
594{-# SPECIALIZE apParallel :: ParallelT IO (a -> b) -> ParallelT IO a -> ParallelT IO b #-}
595apParallel :: MonadAsync m => ParallelT m (a -> b) -> ParallelT m a -> ParallelT m b
596apParallel (ParallelT m1) (ParallelT m2) =
597 let f x1 = K.concatMapBy parallel (pure . x1) m2
598 in ParallelT $ K.concatMapBy parallel f m1
600instance (Monad m, MonadAsync m) => Applicative (ParallelT m) where
601 {-# INLINE pure #-}
602 pure = ParallelT . K.yield
603 {-# INLINE (<*>) #-}
604 (<*>) = apParallel
606------------------------------------------------------------------------------
607-- Monad
608------------------------------------------------------------------------------
610{-# INLINE bindParallel #-}
611{-# SPECIALIZE bindParallel :: ParallelT IO a -> (a -> ParallelT IO b) -> ParallelT IO b #-}
612bindParallel :: MonadAsync m => ParallelT m a -> (a -> ParallelT m b) -> ParallelT m b
613bindParallel m f = fromStream $ K.bindWith parallel (K.adapt m) (\a -> K.adapt $ f a)
615instance MonadAsync m => Monad (ParallelT m) where
616 return = pure
617 (>>=) = bindParallel
619------------------------------------------------------------------------------
620-- Other instances
621------------------------------------------------------------------------------
623MONAD_COMMON_INSTANCES(ParallelT, MONADPARALLEL)
625-------------------------------------------------------------------------------
626-- From callback
627-------------------------------------------------------------------------------
629-- Note: we can use another API with two callbacks stop and yield if we want
630-- the callback to be able to indicate end of stream.
631--
632-- | Generates a callback and a stream pair. The callback returned is used to
633-- queue values to the stream. The stream is infinite, there is no way for the
634-- callback to indicate that it is done now.
635--
636-- /Internal/
637--
638{-# INLINE_NORMAL newCallbackStream #-}
639newCallbackStream :: (K.IsStream t, MonadAsync m) => m (a -> m (), t m a)
640newCallbackStream = do
641 sv <- newParallelVar StopNone defState
643 -- XXX Add our own thread-id to the SVar as we can not know the callback's
644 -- thread-id and the callback is not run in a managed worker. We need to
645 -- handle this better.
646 liftIO myThreadId >>= modifyThread sv
648 let callback a = liftIO $ void $ send sv (ChildYield a)
649 -- XXX we can return an SVar and then the consumer can unfold from the
650 -- SVar?
651 return (callback, D.fromStreamD (SVar.fromSVarD sv))