1{-# LANGUAGE UndecidableInstances #-}
4-- Module : Streamly.Internal.Data.Stream.Parallel
5-- Copyright : (c) 2017 Composewell Technologies
8-- Maintainer : streamly@composewell.com
9-- Stability : experimental
12-- To run examples in this module:
14-- >>> import qualified Streamly.Prelude as Stream
15-- >>> import Control.Concurrent (threadDelay)
18-- threadDelay (n * 1000000) -- sleep for n seconds
19-- putStrLn (show n ++ " sec") -- print "n sec"
23module Streamly.Internal.Data.Stream.Parallel
25 -- * Parallel Stream Type
30 -- * Merge Concurrently
35 -- * Evaluate Concurrently
48import Control.Concurrent (myThreadId, takeMVar)
49import Control.Monad (when)
50import Control.Monad.Base (MonadBase(..), liftBaseDefault)
51import Control.Monad.Catch (MonadThrow, throwM)
52-- import Control.Monad.Error.Class (MonadError(..))
53import Control.Monad.IO.Class (MonadIO(..))
54import Control.Monad.Reader.Class (MonadReader(..))
55import Control.Monad.State.Class (MonadState(..))
56import Control.Monad.Trans.Class (MonadTrans(lift))
57import Data.Functor (void)
58import Data.IORef (readIORef, writeIORef)
59import Data.Maybe (fromJust)
60#if __GLASGOW_HASKELL__ < 808
61import Data.Semigroup (Semigroup(..))
63import Prelude hiding (map)
65import qualified Data.Set as Set
67import Streamly.Internal.Control.Concurrent (MonadAsync)
68import Streamly.Internal.Data.Fold.Type (Fold)
69import Streamly.Internal.Data.Stream.Serial (SerialT(..))
70import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))
71import Streamly.Internal.Data.Stream.StreamK.Type (Stream)
73import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
74import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
75import qualified Streamly.Internal.Data.Stream.SVar.Generate as SVar
76import qualified Streamly.Internal.Data.Stream.SVar.Eliminate as SVar
78import Streamly.Internal.Data.SVar
81#include "Instances.hs"
85-- >>> import qualified Streamly.Prelude as Stream
86-- >>> import Control.Concurrent (threadDelay)
89-- threadDelay (n * 1000000) -- sleep for n seconds
90-- putStrLn (show n ++ " sec") -- print "n sec"
94-------------------------------------------------------------------------------
96-------------------------------------------------------------------------------
98-------------------------------------------------------------------------------
99-- StreamK based worker routines
100-------------------------------------------------------------------------------
102{-# NOINLINE runOne #-}
105 => State Stream m a -> Stream m a -> Maybe WorkerInfo -> m ()
107 case getYieldLimit st of
109 Just _ -> runOneLimited st m0 winfo
114 liftIO $ decrementBufferLimit sv
115 K.foldStreamShared st yieldk single stop m
117 sv = fromJust $ streamVar st
120 incrementBufferLimit sv
122 sendit a = liftIO $ void $ send sv (ChildYield a)
123 single a = sendit a >> liftIO (sendStop sv winfo)
124 yieldk a r = sendit a >> go r
128 => State Stream m a -> Stream m a -> Maybe WorkerInfo -> m ()
129runOneLimited st m0 winfo = go m0
134 yieldLimitOk <- liftIO $ decrementYieldLimit sv
137 liftIO $ decrementBufferLimit sv
138 K.foldStreamShared st yieldk single stop m
140 liftIO $ cleanupSVarFromWorker sv
141 liftIO $ sendStop sv winfo
143 sv = fromJust $ streamVar st
146 incrementBufferLimit sv
147 incrementYieldLimit sv
149 sendit a = liftIO $ void $ send sv (ChildYield a)
150 single a = sendit a >> liftIO (sendStop sv winfo)
151 yieldk a r = sendit a >> go r
153-------------------------------------------------------------------------------
154-- Consing and appending a stream in parallel style
155-------------------------------------------------------------------------------
157-- Note that consing and appending requires StreamK as it would not scale well
158-- with StreamD unless we are only consing a very small number of streams or
159-- elements in a stream. StreamK allows us to manipulate control flow in a way
160-- which StreamD cannot allow. StreamK can make a jump without having to
161-- remember the past state.
163{-# NOINLINE forkSVarPar #-}
164forkSVarPar :: MonadAsync m
165 => SVarStopStyle -> Stream m a -> Stream m a -> Stream m a
166forkSVarPar ss m r = K.mkStream $ \st yld sng stp -> do
167 sv <- newParallelVar ss st
168 pushWorkerPar sv (runOne st{streamVar = Just sv} m)
170 StopBy -> liftIO $ do
171 set <- readIORef (workerThreads sv)
172 writeIORef (svarStopBy sv) $ Set.elemAt 0 set
174 pushWorkerPar sv (runOne st{streamVar = Just sv} r)
175 K.foldStream st yld sng stp $ getSerialT (SVar.fromSVar sv)
177{-# INLINE joinStreamVarPar #-}
178joinStreamVarPar :: MonadAsync m
179 => SVarStyle -> SVarStopStyle -> Stream m a -> Stream m a -> Stream m a
180joinStreamVarPar style ss m1 m2 = K.mkStream $ \st yld sng stp ->
182 Just sv | svarStyle sv == style && svarStopStyle sv == ss -> do
183 -- Here, WE ARE IN THE WORKER/PRODUCER THREAD, we know that because
184 -- the SVar exists. We are running under runOne and the output we
185 -- produce ultimately will be sent to the SVar by runOne.
187 -- If we came here the worker/runOne is evaluating a `parallel`
188 -- combinator. In this case, we always fork a new worker for the
189 -- first component (m1) in the parallel composition and continue to
190 -- evaluate the second component (m2) in the current worker thread.
192 -- When m1 is serially composed, the worker would evaluate it
193 -- without any further forks and the resulting output is sent to
194 -- the SVar and the evaluation terminates. If m1 is a `parallel`
195 -- composition of two streams the worker would again recurses here.
197 -- Similarly, when m2 is serially composed it gets evaluated here
198 -- and the resulting output is sent to the SVar by the runOne
199 -- wrapper. When m2 is composed with `parallel` it will again
200 -- recurse here and so on until it finally terminates.
202 -- When we create a right associated expression using `parallel`,
203 -- then m1 would always terminate without further forks or
204 -- recursion into this routine, therefore, the worker returns
205 -- immediately after evaluating it. And m2 would continue to
206 -- fork/recurse, therefore, the current thread always recurses and
207 -- forks new workers one after the other. This is a tail recursive
208 -- style execution, m2, the recursive expression always executed at
211 -- When the expression is left associated, the worker spawned would
212 -- get the forking/recursing responsibility and then again the
213 -- worker spawned by that worker would fork, thus creating layer
214 -- over layer of workers and a chain of threads leading to a very
215 -- inefficient execution.
216 pushWorkerPar sv (runOne st m1)
217 K.foldStreamShared st yld sng stp m2
219 -- Here WE ARE IN THE CONSUMER THREAD, we create a new SVar, fork
220 -- worker threads to execute m1 and m2 and this thread starts
221 -- pulling the stream from the SVar.
222 K.foldStreamShared st yld sng stp (forkSVarPar ss m1 m2)
224-------------------------------------------------------------------------------
226-------------------------------------------------------------------------------
228{-# INLINE parallelK #-}
229parallelK :: MonadAsync m => Stream m a -> Stream m a -> Stream m a
230parallelK = joinStreamVarPar ParallelVar StopNone
232-- | XXX we can implement it more efficienty by directly implementing instead
233-- of combining streams using parallel.
235{-# SPECIALIZE consM :: IO a -> ParallelT IO a -> ParallelT IO a #-}
236consM :: MonadAsync m => m a -> ParallelT m a -> ParallelT m a
237consM m (ParallelT r) = ParallelT $ parallelK (K.fromEffect m) r
239-- This is a co-parallel like combinator for streams, where first stream is the
240-- main stream and the rest are just supporting it, when the first ends
243-- | Like `parallel` but stops the output as soon as the first stream stops.
246{-# INLINE parallelFstK #-}
247parallelFstK :: MonadAsync m => Stream m a -> Stream m a -> Stream m a
248parallelFstK = joinStreamVarPar ParallelVar StopBy
250-- This is a race like combinator for streams.
252-- | Like `parallel` but stops the output as soon as any of the two streams
256{-# INLINE parallelMinK #-}
257parallelMinK :: MonadAsync m => Stream m a -> Stream m a -> Stream m a
258parallelMinK = joinStreamVarPar ParallelVar StopAny
260------------------------------------------------------------------------------
261-- Convert a stream to parallel
262------------------------------------------------------------------------------
264-- | Like 'mkParallel' but uses StreamK internally.
268mkParallelK :: MonadAsync m => Stream m a -> Stream m a
269mkParallelK m = K.mkStream $ \st yld sng stp -> do
270 sv <- newParallelVar StopNone (adaptState st)
271 -- pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream m)
272 SVar.toSVarParallel st sv $ D.fromStreamK m
273 K.foldStream st yld sng stp $ getSerialT $ SVar.fromSVar sv
275-- | Same as 'mkParallel' but for StreamD stream.
277{-# INLINE_NORMAL mkParallelD #-}
278mkParallelD :: MonadAsync m => D.Stream m a -> D.Stream m a
279mkParallelD m = D.Stream step Nothing
282 step gst Nothing = do
283 sv <- newParallelVar StopNone gst
284 SVar.toSVarParallel gst sv m
285 -- XXX use unfold instead?
286 return $ Skip $ Just $ SVar.fromSVarD sv
288 step gst (Just (D.UnStream step1 st)) = do
291 Yield a s -> Yield a (Just $ D.Stream step1 s)
292 Skip s -> Skip (Just $ D.Stream step1 s)
295-------------------------------------------------------------------------------
297-------------------------------------------------------------------------------
299-- NOTE: In regular pull style streams, the consumer stream is pulling elements
300-- from the SVar and we have several workers producing elements and pushing to
301-- SVar. In case of folds, we, the parent stream driving the fold, are the
302-- stream producing worker, we start an SVar and start pushing to the SVar, the
303-- fold on the other side of the SVar is the consumer stream.
305-- In the pull stream case exceptions are propagated from the producing workers
306-- to the consumer stream, the exceptions are propagated on the same channel as
307-- the produced stream elements. However, in case of push style folds the
308-- current stream itself is the worker and the fold is the consumer, in this
309-- case we have to propagate the exceptions from the consumer to the producer.
310-- This is reverse of the pull case and we need a reverse direction channel
311-- to propagate the exception.
313-- | Redirect a copy of the stream to a supplied fold and run it concurrently
314-- in an independent thread. The fold may buffer some elements. The buffer size
315-- is determined by the prevailing 'Streamly.Prelude.maxBuffer' setting.
320-- -----stream m a ---------------stream m a-----
325-- > S.drain $ S.tapAsync (S.mapM_ print) (S.enumerateFromTo 1 2)
330-- Exceptions from the concurrently running fold are propagated to the current
331-- computation. Note that, because of buffering in the fold, exceptions may be
332-- delayed and may not correspond to the current element being processed in the
333-- parent stream, but we guarantee that before the parent stream stops the tap
334-- finishes and all exceptions from it are drained.
337-- Compare with 'tap'.
340{-# INLINE tapAsyncK #-}
341tapAsyncK :: MonadAsync m => (Stream m a -> m b) -> Stream m a -> Stream m a
342tapAsyncK f m = K.mkStream $ \st yld sng stp -> do
343 sv <- SVar.newFoldSVar st (f . getSerialT)
344 K.foldStreamShared st yld sng stp
345 $ getSerialT (SVar.teeToSVar sv $ SerialT m)
347data TapState fs st a = TapInit | Tapping !fs st | TapDone st
349-- | Like 'tapAsync' but uses a 'Fold' instead of a fold function.
351{-# INLINE_NORMAL tapAsyncF #-}
352tapAsyncF :: MonadAsync m => Fold m a b -> D.Stream m a -> D.Stream m a
353tapAsyncF f (D.Stream step1 state1) = D.Stream step TapInit
357 -- In general, a Stop event would come equipped with the result
358 -- of the fold. It is not used here but it would be useful in
359 -- applicative and distribute.
360 done <- SVar.fromConsumer svr
362 liftIO $ withDiagMVar svr "teeToSVar: waiting to drain"
363 $ takeMVar (outputDoorBellFromConsumer svr)
367 liftIO $ sendStop svr Nothing
368 -- drain/wait until a stop event arrives from the fold.
371 {-# INLINE_LATE step #-}
372 step gst TapInit = do
373 sv <- SVar.newFoldSVarF gst f
374 return $ Skip (Tapping sv state1)
376 step gst (Tapping sv st) = do
380 done <- SVar.pushToFold sv a
383 -- XXX we do not need to wait synchronously here
385 return $ Yield a (TapDone s)
386 else return $ Yield a (Tapping sv s)
387 Skip s -> return $ Skip (Tapping sv s)
392 step gst (TapDone st) = do
395 Yield a s -> Yield a (TapDone s)
396 Skip s -> Skip (TapDone s)
399------------------------------------------------------------------------------
401------------------------------------------------------------------------------
403-- | For 'ParallelT' streams:
406-- (<>) = 'Streamly.Prelude.parallel'
407-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.parallel'
410-- See 'Streamly.Prelude.AsyncT', 'ParallelT' is similar except that all
411-- iterations are strictly concurrent while in 'AsyncT' it depends on the
412-- consumer demand and available threads. See 'parallel' for more details.
414-- /Since: 0.1.0 ("Streamly")/
416-- /Since: 0.7.0 (maxBuffer applies to ParallelT streams)/
419newtype ParallelT m a = ParallelT {getParallelT :: Stream m a}
420 deriving (MonadTrans)
422-- | A parallely composing IO stream of elements of type @a@.
423-- See 'ParallelT' documentation for more details.
425-- /Since: 0.2.0 ("Streamly")/
428type Parallel = ParallelT IO
430------------------------------------------------------------------------------
432------------------------------------------------------------------------------
435{-# SPECIALIZE append :: ParallelT IO a -> ParallelT IO a -> ParallelT IO a #-}
436append :: MonadAsync m => ParallelT m a -> ParallelT m a -> ParallelT m a
437append (ParallelT m1) (ParallelT m2) = ParallelT $ parallelK m1 m2
439instance MonadAsync m => Semigroup (ParallelT m a) where
442------------------------------------------------------------------------------
444------------------------------------------------------------------------------
446instance MonadAsync m => Monoid (ParallelT m a) where
447 mempty = ParallelT K.nil
450------------------------------------------------------------------------------
452------------------------------------------------------------------------------
454{-# INLINE apParallel #-}
455{-# SPECIALIZE apParallel ::
456 ParallelT IO (a -> b) -> ParallelT IO a -> ParallelT IO b #-}
457apParallel :: MonadAsync m =>
458 ParallelT m (a -> b) -> ParallelT m a -> ParallelT m b
459apParallel (ParallelT m1) (ParallelT m2) =
460 let f x1 = K.concatMapWith parallelK (pure . x1) m2
461 in ParallelT $ K.concatMapWith parallelK f m1
463instance (Monad m, MonadAsync m) => Applicative (ParallelT m) where
465 pure = ParallelT . K.fromPure
470------------------------------------------------------------------------------
472------------------------------------------------------------------------------
475{-# SPECIALIZE bind ::
476 ParallelT IO a -> (a -> ParallelT IO b) -> ParallelT IO b #-}
477bind :: MonadAsync m => ParallelT m a -> (a -> ParallelT m b) -> ParallelT m b
478bind (ParallelT m) f = ParallelT $ K.bindWith parallelK m (getParallelT . f)
480instance MonadAsync m => Monad (ParallelT m) where
486------------------------------------------------------------------------------
488------------------------------------------------------------------------------
490MONAD_COMMON_INSTANCES(ParallelT, MONADPARALLEL)
492-------------------------------------------------------------------------------
494-------------------------------------------------------------------------------
496-- Note: we can use another API with two callbacks stop and yield if we want
497-- the callback to be able to indicate end of stream.
499-- | Generates a callback and a stream pair. The callback returned is used to
500-- queue values to the stream. The stream is infinite, there is no way for the
501-- callback to indicate that it is done now.
505{-# INLINE_NORMAL newCallbackStream #-}
506newCallbackStream :: MonadAsync m => m (a -> m (), Stream m a)
507newCallbackStream = do
508 sv <- newParallelVar StopNone defState
510 -- XXX Add our own thread-id to the SVar as we can not know the callback's
511 -- thread-id and the callback is not run in a managed worker. We need to
512 -- handle this better.
513 liftIO myThreadId >>= modifyThread sv
515 let callback a = liftIO $ void $ send sv (ChildYield a)
516 -- XXX we can return an SVar and then the consumer can unfold from the
518 return (callback, D.toStreamK (SVar.fromSVarD sv))
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list