on fa417eb help sign in
613 lines 23 kB history raw
1{-# LANGUAGE UndecidableInstances #-}
3-- |
4-- Module : Streamly.Internal.Data.Stream.IsStream.Parallel
5-- Copyright : (c) 2017 Composewell Technologies
6--
7-- License : BSD3
8-- Maintainer : streamly@composewell.com
9-- Stability : experimental
10-- Portability : GHC
11--
12-- To run examples in this module:
13--
14-- >>> import qualified Streamly.Data.Stream as Stream
15-- >>> import Control.Concurrent (threadDelay)
16-- >>> :{
17-- delay n = do
18-- threadDelay (n * 1000000) -- sleep for n seconds
19-- putStrLn (show n ++ " sec") -- print "n sec"
20-- return n -- IO Int
21-- :}
22--
23module Streamly.Internal.Data.Stream.IsStream.Parallel
24 (
25 -- * Parallel Stream Type
26 ParallelT(..)
27 , Parallel
28 , consM
30 -- * Merge Concurrently
31 , parallelK
32 , parallelFstK
33 , parallelMinK
35 -- * Evaluate Concurrently
36 , mkParallelD
37 , mkParallelK
39 -- * Tap Concurrently
40 , tapAsyncK
41 , tapAsyncF
43 -- * Callbacks
44 , newCallbackStream
46 -- * Combinators
47 , interjectSuffix
48 , takeInterval
49 , dropInterval
50 )
51where
53import Control.Concurrent (myThreadId, takeMVar, threadDelay)
54import Control.Monad (when)
55import Control.Monad.Base (MonadBase(..), liftBaseDefault)
56import Control.Monad.Catch (MonadThrow, throwM)
57-- import Control.Monad.Error.Class (MonadError(..))
58import Control.Monad.IO.Class (MonadIO(..))
59import Control.Monad.Reader.Class (MonadReader(..))
60import Control.Monad.State.Class (MonadState(..))
61import Control.Monad.Trans.Class (MonadTrans(lift))
62import Data.Functor (void)
63import Data.IORef (readIORef, writeIORef)
64import Data.Maybe (fromJust, isNothing)
66import Streamly.Data.Fold (Fold)
67import Streamly.Internal.Control.Concurrent (MonadAsync)
68import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))
69import Streamly.Internal.Data.Stream.Type (Stream)
71import qualified Data.Set as Set
72import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
73 (Stream, foldStreamShared, mkStream, foldStream, fromEffect
74 , nil, concatMapWith, fromPure, bindWith, withLocal)
75import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
76 (Stream(..), mapM, toStreamK, fromStreamK)
77import qualified Streamly.Internal.Data.Stream.SVar.Generate as SVar
78import qualified Streamly.Internal.Data.Stream.SVar.Eliminate as SVar
79import qualified Streamly.Internal.Data.Stream as Stream
80 (catMaybes, dropWhile, fromStreamK, repeat, sequence, takeWhile, toStreamK)
82import Streamly.Internal.Data.SVar
83import Prelude hiding (map)
85#include "inline.hs"
86#include "Instances.hs"
88--
89-- $setup
90-- >>> import qualified Streamly.Data.Stream as Stream
91-- >>> import Control.Concurrent (threadDelay)
92-- >>> :{
93-- delay n = do
94-- threadDelay (n * 1000000) -- sleep for n seconds
95-- putStrLn (show n ++ " sec") -- print "n sec"
96-- return n -- IO Int
97-- :}
99-------------------------------------------------------------------------------
100-- Parallel
101-------------------------------------------------------------------------------
103-------------------------------------------------------------------------------
104-- StreamK based worker routines
105-------------------------------------------------------------------------------
107{-# NOINLINE runOne #-}
108runOne
109 :: MonadIO m
110 => State K.Stream m a -> K.Stream m a -> Maybe WorkerInfo -> m ()
111runOne st m0 winfo =
112 case getYieldLimit st of
113 Nothing -> go m0
114 Just _ -> runOneLimited st m0 winfo
116 where
118 go m = do
119 liftIO $ decrementBufferLimit sv
120 K.foldStreamShared st yieldk single stop m
122 sv = fromJust $ streamVar st
124 stop = liftIO $ do
125 incrementBufferLimit sv
126 sendStop sv winfo
127 sendit a = liftIO $ void $ send sv (ChildYield a)
128 single a = sendit a >> liftIO (sendStop sv winfo)
129 yieldk a r = sendit a >> go r
131runOneLimited
132 :: MonadIO m
133 => State K.Stream m a -> K.Stream m a -> Maybe WorkerInfo -> m ()
134runOneLimited st m0 winfo = go m0
136 where
138 go m = do
139 yieldLimitOk <- liftIO $ decrementYieldLimit sv
140 if yieldLimitOk
141 then do
142 liftIO $ decrementBufferLimit sv
143 K.foldStreamShared st yieldk single stop m
144 else do
145 liftIO $ cleanupSVarFromWorker sv
146 liftIO $ sendStop sv winfo
148 sv = fromJust $ streamVar st
150 stop = liftIO $ do
151 incrementBufferLimit sv
152 incrementYieldLimit sv
153 sendStop sv winfo
154 sendit a = liftIO $ void $ send sv (ChildYield a)
155 single a = sendit a >> liftIO (sendStop sv winfo)
156 yieldk a r = sendit a >> go r
158-------------------------------------------------------------------------------
159-- Consing and appending a stream in parallel style
160-------------------------------------------------------------------------------
162-- Note that consing and appending requires StreamK as it would not scale well
163-- with StreamD unless we are only consing a very small number of streams or
164-- elements in a stream. StreamK allows us to manipulate control flow in a way
165-- which StreamD cannot allow. StreamK can make a jump without having to
166-- remember the past state.
168{-# NOINLINE forkSVarPar #-}
169forkSVarPar :: MonadAsync m
170 => SVarStopStyle -> K.Stream m a -> K.Stream m a -> K.Stream m a
171forkSVarPar ss m r = K.mkStream $ \st yld sng stp -> do
172 sv <- newParallelVar ss st
173 pushWorkerPar sv (runOne st{streamVar = Just sv} m)
174 case ss of
175 StopBy -> liftIO $ do
176 set <- readIORef (workerThreads sv)
177 writeIORef (svarStopBy sv) $ Set.elemAt 0 set
178 _ -> return ()
179 pushWorkerPar sv (runOne st{streamVar = Just sv} r)
180 K.foldStream st yld sng stp $ Stream.toStreamK (SVar.fromSVar sv)
182{-# INLINE joinStreamVarPar #-}
183joinStreamVarPar ::
184 MonadAsync m
185 => SVarStyle
186 -> SVarStopStyle
187 -> K.Stream m a
188 -> K.Stream m a
189 -> K.Stream m a
190joinStreamVarPar style ss m1 m2 = K.mkStream $ \st yld sng stp ->
191 case streamVar st of
192 Just sv | svarStyle sv == style && svarStopStyle sv == ss -> do
193 -- Here, WE ARE IN THE WORKER/PRODUCER THREAD, we know that because
194 -- the SVar exists. We are running under runOne and the output we
195 -- produce ultimately will be sent to the SVar by runOne.
196 --
197 -- If we came here the worker/runOne is evaluating a `parallel`
198 -- combinator. In this case, we always fork a new worker for the
199 -- first component (m1) in the parallel composition and continue to
200 -- evaluate the second component (m2) in the current worker thread.
201 --
202 -- When m1 is serially composed, the worker would evaluate it
203 -- without any further forks and the resulting output is sent to
204 -- the SVar and the evaluation terminates. If m1 is a `parallel`
205 -- composition of two streams the worker would again recurses here.
206 --
207 -- Similarly, when m2 is serially composed it gets evaluated here
208 -- and the resulting output is sent to the SVar by the runOne
209 -- wrapper. When m2 is composed with `parallel` it will again
210 -- recurse here and so on until it finally terminates.
211 --
212 -- When we create a right associated expression using `parallel`,
213 -- then m1 would always terminate without further forks or
214 -- recursion into this routine, therefore, the worker returns
215 -- immediately after evaluating it. And m2 would continue to
216 -- fork/recurse, therefore, the current thread always recurses and
217 -- forks new workers one after the other. This is a tail recursive
218 -- style execution, m2, the recursive expression always executed at
219 -- the tail.
220 --
221 -- When the expression is left associated, the worker spawned would
222 -- get the forking/recursing responsibility and then again the
223 -- worker spawned by that worker would fork, thus creating layer
224 -- over layer of workers and a chain of threads leading to a very
225 -- inefficient execution.
226 pushWorkerPar sv (runOne st m1)
227 K.foldStreamShared st yld sng stp m2
228 _ ->
229 -- Here WE ARE IN THE CONSUMER THREAD, we create a new SVar, fork
230 -- worker threads to execute m1 and m2 and this thread starts
231 -- pulling the stream from the SVar.
232 K.foldStreamShared st yld sng stp (forkSVarPar ss m1 m2)
234-------------------------------------------------------------------------------
235-- User facing APIs
236-------------------------------------------------------------------------------
238{-# INLINE parallelK #-}
239parallelK :: MonadAsync m => K.Stream m a -> K.Stream m a -> K.Stream m a
240parallelK = joinStreamVarPar ParallelVar StopNone
242-- | XXX we can implement it more efficienty by directly implementing instead
243-- of combining streams using parallel.
244{-# INLINE consM #-}
245{-# SPECIALIZE consM :: IO a -> ParallelT IO a -> ParallelT IO a #-}
246consM :: MonadAsync m => m a -> ParallelT m a -> ParallelT m a
247consM m (ParallelT r) = ParallelT $ parallelK (K.fromEffect m) r
249-- This is a co-parallel like combinator for streams, where first stream is the
250-- main stream and the rest are just supporting it, when the first ends
251-- everything ends.
252--
253-- | Like `parallel` but stops the output as soon as the first stream stops.
254--
255-- /Pre-release/
256{-# INLINE parallelFstK #-}
257parallelFstK :: MonadAsync m => K.Stream m a -> K.Stream m a -> K.Stream m a
258parallelFstK = joinStreamVarPar ParallelVar StopBy
260-- This is a race like combinator for streams.
261--
262-- | Like `parallel` but stops the output as soon as any of the two streams
263-- stops.
264--
265-- /Pre-release/
266{-# INLINE parallelMinK #-}
267parallelMinK :: MonadAsync m => K.Stream m a -> K.Stream m a -> K.Stream m a
268parallelMinK = joinStreamVarPar ParallelVar StopAny
270------------------------------------------------------------------------------
271-- Convert a stream to parallel
272------------------------------------------------------------------------------
274-- | Like 'mkParallel' but uses StreamK internally.
275--
276-- /Pre-release/
277--
278mkParallelK :: MonadAsync m => K.Stream m a -> K.Stream m a
279mkParallelK m = K.mkStream $ \st yld sng stp -> do
280 sv <- newParallelVar StopNone (adaptState st)
281 -- pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream m)
282 SVar.toSVarParallel st sv $ D.fromStreamK m
283 K.foldStream st yld sng stp $ Stream.toStreamK $ SVar.fromSVar sv
285-- | Same as 'mkParallel' but for StreamD stream.
286--
287{-# INLINE_NORMAL mkParallelD #-}
288mkParallelD :: MonadAsync m => D.Stream m a -> D.Stream m a
289mkParallelD m = D.Stream step Nothing
290 where
292 step gst Nothing = do
293 sv <- newParallelVar StopNone gst
294 SVar.toSVarParallel gst sv m
295 -- XXX use unfold instead?
296 return $ Skip $ Just $ SVar.fromSVarD sv
298 step gst (Just (D.UnStream step1 st)) = do
299 r <- step1 gst st
300 return $ case r of
301 Yield a s -> Yield a (Just $ D.Stream step1 s)
302 Skip s -> Skip (Just $ D.Stream step1 s)
303 Stop -> Stop
305-------------------------------------------------------------------------------
306-- Concurrent tap
307-------------------------------------------------------------------------------
309-- NOTE: In regular pull style streams, the consumer stream is pulling elements
310-- from the SVar and we have several workers producing elements and pushing to
311-- SVar. In case of folds, we, the parent stream driving the fold, are the
312-- stream producing worker, we start an SVar and start pushing to the SVar, the
313-- fold on the other side of the SVar is the consumer stream.
314--
315-- In the pull stream case exceptions are propagated from the producing workers
316-- to the consumer stream, the exceptions are propagated on the same channel as
317-- the produced stream elements. However, in case of push style folds the
318-- current stream itself is the worker and the fold is the consumer, in this
319-- case we have to propagate the exceptions from the consumer to the producer.
320-- This is reverse of the pull case and we need a reverse direction channel
321-- to propagate the exception.
322--
323-- | Redirect a copy of the stream to a supplied fold and run it concurrently
324-- in an independent thread. The fold may buffer some elements. The buffer size
325-- is determined by the prevailing 'Streamly.Prelude.maxBuffer' setting.
326--
327-- @
328-- Stream m a -> m b
329-- |
330-- -----stream m a ---------------stream m a-----
331--
332-- @
333--
334-- @
335-- > S.drain $ S.tapAsync (S.mapM_ print) (S.enumerateFromTo 1 2)
336-- 1
337-- 2
338-- @
339--
340-- Exceptions from the concurrently running fold are propagated to the current
341-- computation. Note that, because of buffering in the fold, exceptions may be
342-- delayed and may not correspond to the current element being processed in the
343-- parent stream, but we guarantee that before the parent stream stops the tap
344-- finishes and all exceptions from it are drained.
345--
346--
347-- Compare with 'tap'.
348--
349-- /Pre-release/
350{-# INLINE tapAsyncK #-}
351tapAsyncK ::
352 MonadAsync m => (K.Stream m a -> m b) -> K.Stream m a -> K.Stream m a
353tapAsyncK f m = K.mkStream $ \st yld sng stp -> do
354 sv <- SVar.newFoldSVar st (f . Stream.toStreamK)
355 K.foldStreamShared st yld sng stp
356 $ Stream.toStreamK (SVar.teeToSVar sv $ Stream.fromStreamK m)
358data TapState fs st a = TapInit | Tapping !fs st | TapDone st
360-- | Like 'tapAsync' but uses a 'Fold' instead of a fold function.
361--
362{-# INLINE_NORMAL tapAsyncF #-}
363tapAsyncF :: MonadAsync m => Fold m a b -> D.Stream m a -> D.Stream m a
364tapAsyncF f (D.Stream step1 state1) = D.Stream step TapInit
365 where
367 drainFold svr = do
368 -- In general, a Stop event would come equipped with the result
369 -- of the fold. It is not used here but it would be useful in
370 -- applicative and distribute.
371 done <- SVar.fromConsumer svr
372 when (not done) $ do
373 liftIO $ withDiagMVar svr "teeToSVar: waiting to drain"
374 $ takeMVar (outputDoorBellFromConsumer svr)
375 drainFold svr
377 stopFold svr = do
378 liftIO $ sendStop svr Nothing
379 -- drain/wait until a stop event arrives from the fold.
380 drainFold svr
382 {-# INLINE_LATE step #-}
383 step gst TapInit = do
384 sv <- SVar.newFoldSVarF gst f
385 return $ Skip (Tapping sv state1)
387 step gst (Tapping sv st) = do
388 r <- step1 gst st
389 case r of
390 Yield a s -> do
391 done <- SVar.pushToFold sv a
392 if done
393 then do
394 -- XXX we do not need to wait synchronously here
395 stopFold sv
396 return $ Yield a (TapDone s)
397 else return $ Yield a (Tapping sv s)
398 Skip s -> return $ Skip (Tapping sv s)
399 Stop -> do
400 stopFold sv
401 return Stop
403 step gst (TapDone st) = do
404 r <- step1 gst st
405 return $ case r of
406 Yield a s -> Yield a (TapDone s)
407 Skip s -> Skip (TapDone s)
408 Stop -> Stop
410------------------------------------------------------------------------------
411-- ParallelT
412------------------------------------------------------------------------------
414-- | For 'ParallelT' streams:
415--
416-- @
417-- (<>) = 'Streamly.Prelude.parallel'
418-- (>>=) = flip . 'Streamly.Prelude.concatMapWith' 'Streamly.Prelude.parallel'
419-- @
420--
421-- See 'Streamly.Prelude.AsyncT', 'ParallelT' is similar except that all
422-- iterations are strictly concurrent while in 'AsyncT' it depends on the
423-- consumer demand and available threads. See 'parallel' for more details.
424--
425-- /Since: 0.1.0 ("Streamly")/
426--
427-- /Since: 0.7.0 (maxBuffer applies to ParallelT streams)/
428--
429-- @since 0.8.0
430newtype ParallelT m a = ParallelT {getParallelT :: K.Stream m a}
431 deriving (MonadTrans)
433-- | A parallely composing IO stream of elements of type @a@.
434-- See 'ParallelT' documentation for more details.
435--
436-- /Since: 0.2.0 ("Streamly")/
437--
438-- @since 0.8.0
439type Parallel = ParallelT IO
441------------------------------------------------------------------------------
442-- Semigroup
443------------------------------------------------------------------------------
445{-# INLINE append #-}
446{-# SPECIALIZE append :: ParallelT IO a -> ParallelT IO a -> ParallelT IO a #-}
447append :: MonadAsync m => ParallelT m a -> ParallelT m a -> ParallelT m a
448append (ParallelT m1) (ParallelT m2) = ParallelT $ parallelK m1 m2
450instance MonadAsync m => Semigroup (ParallelT m a) where
451 (<>) = append
453------------------------------------------------------------------------------
454-- Monoid
455------------------------------------------------------------------------------
457instance MonadAsync m => Monoid (ParallelT m a) where
458 mempty = ParallelT K.nil
459 mappend = (<>)
461------------------------------------------------------------------------------
462-- Applicative
463------------------------------------------------------------------------------
465{-# INLINE apParallel #-}
466{-# SPECIALIZE apParallel ::
467 ParallelT IO (a -> b) -> ParallelT IO a -> ParallelT IO b #-}
468apParallel :: MonadAsync m =>
469 ParallelT m (a -> b) -> ParallelT m a -> ParallelT m b
470apParallel (ParallelT m1) (ParallelT m2) =
471 let f x1 = K.concatMapWith parallelK (pure . x1) m2
472 in ParallelT $ K.concatMapWith parallelK f m1
474instance (Monad m, MonadAsync m) => Applicative (ParallelT m) where
475 {-# INLINE pure #-}
476 pure = ParallelT . K.fromPure
478 {-# INLINE (<*>) #-}
479 (<*>) = apParallel
481------------------------------------------------------------------------------
482-- Monad
483------------------------------------------------------------------------------
485{-# INLINE bind #-}
486{-# SPECIALIZE bind ::
487 ParallelT IO a -> (a -> ParallelT IO b) -> ParallelT IO b #-}
488bind :: MonadAsync m => ParallelT m a -> (a -> ParallelT m b) -> ParallelT m b
489bind (ParallelT m) f = ParallelT $ K.bindWith parallelK m (getParallelT . f)
491instance MonadAsync m => Monad (ParallelT m) where
492 return = pure
494 {-# INLINE (>>=) #-}
495 (>>=) = bind
497------------------------------------------------------------------------------
498-- Other instances
499------------------------------------------------------------------------------
501MONAD_COMMON_INSTANCES(ParallelT, MONADPARALLEL)
503-------------------------------------------------------------------------------
504-- From callback
505-------------------------------------------------------------------------------
507-- Note: we can use another API with two callbacks stop and yield if we want
508-- the callback to be able to indicate end of stream.
509--
510-- | Generates a callback and a stream pair. The callback returned is used to
511-- queue values to the stream. The stream is infinite, there is no way for the
512-- callback to indicate that it is done now.
513--
514-- /Pre-release/
515--
516{-# INLINE_NORMAL newCallbackStream #-}
517newCallbackStream :: MonadAsync m => m (a -> m (), K.Stream m a)
518newCallbackStream = do
519 sv <- newParallelVar StopNone defState
521 -- XXX Add our own thread-id to the SVar as we can not know the callback's
522 -- thread-id and the callback is not run in a managed worker. We need to
523 -- handle this better.
524 liftIO myThreadId >>= modifyThread sv
526 let callback a = liftIO $ void $ send sv (ChildYield a)
527 -- XXX we can return an SVar and then the consumer can unfold from the
528 -- SVar?
529 return (callback, D.toStreamK (SVar.fromSVarD sv))
531------------------------------------------------------------------------------
532-- Combinators
533------------------------------------------------------------------------------
535{-# INLINE parallelFst #-}
536parallelFst :: MonadAsync m => Stream m a -> Stream m a -> Stream m a
537parallelFst m1 m2 =
538 Stream.fromStreamK
539 $ parallelFstK (Stream.toStreamK m1) (Stream.toStreamK m2)
541-- | Intersperse a monadic action into the input stream after every @n@
542-- seconds.
543--
544-- >>> import qualified Streamly.Data.Fold as Fold
545-- >>> import qualified Streamly.Internal.Data.Stream.IsStream.Parallel as Parallel
546-- >>> Stream.fold Fold.drain $ Parallel.interjectSuffix 1.05 (putChar ',') $ Stream.mapM (\x -> threadDelay 1000000 >> putChar x) $ Stream.fromList "hello"
547-- h,e,l,l,o
548--
549-- /Pre-release/
550{-# INLINE interjectSuffix #-}
551interjectSuffix :: MonadAsync m => Double -> m a -> Stream m a -> Stream m a
552interjectSuffix n f xs = xs `parallelFst` repeatM timed
553 where timed = liftIO (threadDelay (round $ n * 1000000)) >> f
554 repeatM = Stream.sequence . Stream.repeat
556-- XXX Notes from D.takeByTime (which was removed)
557-- XXX using getTime in the loop can be pretty expensive especially for
558-- computations where iterations are lightweight. We have the following
559-- options:
560--
561-- 1) Run a timeout thread updating a flag asynchronously and check that
562-- flag here, that way we can have a cheap termination check.
563--
564-- 2) Use COARSE clock to get time with lower resolution but more efficiently.
565--
566-- 3) Use rdtscp/rdtsc to get time directly from the processor, compute the
567-- termination value of rdtsc in the beginning and then in each iteration just
568-- get rdtsc and check if we should terminate.
571-- | @takeInterval duration@ yields stream elements upto specified time
572-- @duration@ in seconds. The duration starts when the stream is evaluated for
573-- the first time, before the first element is yielded. The time duration is
574-- checked before generating each element, if the duration has expired the
575-- stream stops.
576--
577-- The total time taken in executing the stream is guaranteed to be /at least/
578-- @duration@, however, because the duration is checked before generating an
579-- element, the upper bound is indeterminate and depends on the time taken in
580-- generating and processing the last element.
581--
582-- No element is yielded if the duration is zero. At least one element is
583-- yielded if the duration is non-zero.
584--
585-- /Pre-release/
586--
587{-# INLINE takeInterval #-}
588takeInterval :: MonadAsync m => Double -> Stream m a -> Stream m a
589takeInterval d =
590 Stream.catMaybes
591 . Stream.takeWhile isNothing
592 . interjectSuffix d (return Nothing) . fmap Just
594-- | @dropInterval duration@ drops stream elements until specified @duration@ in
595-- seconds has passed. The duration begins when the stream is evaluated for the
596-- first time. The time duration is checked /after/ generating a stream element,
597-- the element is yielded if the duration has expired otherwise it is dropped.
598--
599-- The time elapsed before starting to generate the first element is /at most/
600-- @duration@, however, because the duration expiry is checked after the
601-- element is generated, the lower bound is indeterminate and depends on the
602-- time taken in generating an element.
603--
604-- All elements are yielded if the duration is zero.
605--
606-- /Pre-release/
607--
608{-# INLINE dropInterval #-}
609dropInterval :: MonadAsync m => Double -> Stream m a -> Stream m a
610dropInterval d =
611 Stream.catMaybes
612 . Stream.dropWhile isNothing
613 . interjectSuffix d (return Nothing) . fmap Just