on e9828b3 help sign in
993 lines 39 kB history raw
1{-# LANGUAGE UndecidableInstances #-}
3#include "inline.hs"
5-- |
6-- Module : Streamly.Internal.Data.Stream.Async
7-- Copyright : (c) 2017 Harendra Kumar
8--
9-- License : BSD3
10-- Maintainer : streamly@composewell.com
11-- Stability : experimental
12-- Portability : GHC
13--
14--
15module Streamly.Internal.Data.Stream.Async
16 (
17 AsyncT
18 , Async
19 , asyncly
20 , async
21 , (<|) --deprecated
22 , mkAsync
23 , mkAsyncK
25 , WAsyncT
26 , WAsync
27 , wAsyncly
28 , wAsync
29 )
30where
32import Control.Concurrent (myThreadId)
33import Control.Monad.Base (MonadBase(..), liftBaseDefault)
34import Control.Monad.Catch (MonadThrow, throwM)
35import Control.Monad.Trans.Control (MonadBaseControl (..))
36import Control.Concurrent.MVar (newEmptyMVar)
37-- import Control.Monad.Error.Class (MonadError(..))
38import Control.Monad.IO.Class (MonadIO(..))
39import Control.Monad.Reader.Class (MonadReader(..))
40import Control.Monad.State.Class (MonadState(..))
41import Control.Monad.Trans.Class (MonadTrans(lift))
42import Data.Concurrent.Queue.MichaelScott (LinkedQueue, newQ, nullQ, tryPopR)
43import Data.IORef (IORef, newIORef, readIORef)
44import Data.Maybe (fromJust)
45#if __GLASGOW_HASKELL__ < 808
46import Data.Semigroup (Semigroup(..))
47#endif
49import Prelude hiding (map)
50import qualified Data.Set as S
52import Streamly.Internal.Data.Atomics (atomicModifyIORefCAS)
53import Streamly.Internal.Data.Stream.SVar (fromSVar)
54import Streamly.Internal.Data.SVar
55import Streamly.Internal.Data.Stream.StreamK
56 (IsStream(..), Stream, mkStream, foldStream, adapt, foldStreamShared)
57import qualified Streamly.Internal.Data.Stream.StreamK as K
58import qualified Streamly.Internal.Data.Stream.StreamD as D
60#include "Instances.hs"
62-------------------------------------------------------------------------------
63-- Async
64-------------------------------------------------------------------------------
66data WorkerStatus = Continue | Suspend
68{-# INLINE workLoopLIFO #-}
69workLoopLIFO
70 :: (MonadIO m, MonadBaseControl IO m)
71 => IORef [Stream m a]
72 -> State Stream m a
73 -> SVar Stream m a
74 -> Maybe WorkerInfo
75 -> m ()
76workLoopLIFO q st sv winfo = run
78 where
80 mrun = runInIO $ svarMrun sv
81 run = do
82 work <- dequeue
83 let stop = liftIO $ sendStop sv winfo
84 case work of
85 Nothing -> stop
86 Just m -> do
87 -- XXX when we finish we need to send the monadic state back to
88 -- the parent so that the state can be merged back. We capture
89 -- and return the state in the stop continuation.
90 --
91 -- Instead of using the run function we can just restore the
92 -- monad state here. That way it can work easily for
93 -- distributed case as well.
94 r <- liftIO $ mrun $
95 foldStreamShared st yieldk single (return Continue) m
96 res <- restoreM r
97 case res of
98 Continue -> run
99 Suspend -> stop
101 single a = do
102 res <- liftIO $ sendYield sv winfo (ChildYield a)
103 return $ if res then Continue else Suspend
105 yieldk a r = do
106 res <- liftIO $ sendYield sv winfo (ChildYield a)
107 if res
108 then foldStreamShared st yieldk single (return Continue) r
109 else liftIO $ do
110 -- XXX we also need to save the monadic state here
111 enqueueLIFO sv q r
112 return Suspend
114 dequeue = liftIO $ atomicModifyIORefCAS q $ \case
115 [] -> ([], Nothing)
116 x : xs -> (xs, Just x)
118-- We duplicate workLoop for yield limit and no limit cases because it has
119-- around 40% performance overhead in the worst case.
120--
121-- XXX we can pass yinfo directly as an argument here so that we do not have to
122-- make a check every time.
123{-# INLINE workLoopLIFOLimited #-}
124workLoopLIFOLimited
125 :: (MonadIO m, MonadBaseControl IO m)
126 => IORef [Stream m a]
127 -> State Stream m a
128 -> SVar Stream m a
129 -> Maybe WorkerInfo
130 -> m ()
131workLoopLIFOLimited q st sv winfo = run
133 where
135 mrun = runInIO $ svarMrun sv
136 incrContinue = liftIO (incrementYieldLimit sv) >> return Continue
137 run = do
138 work <- dequeue
139 let stop = liftIO $ sendStop sv winfo
140 case work of
141 Nothing -> stop
142 Just m -> do
143 -- XXX This is just a best effort minimization of concurrency
144 -- to the yield limit. If the stream is made of concurrent
145 -- streams we do not reserve the yield limit in the constituent
146 -- streams before executing the action. This can be done
147 -- though, by sharing the yield limit ref with downstream
148 -- actions via state passing. Just a todo.
149 yieldLimitOk <- liftIO $ decrementYieldLimit sv
150 if yieldLimitOk
151 then do
152 r <- liftIO $ mrun $
153 foldStreamShared st yieldk single incrContinue m
154 res <- restoreM r
155 case res of
156 Continue -> run
157 Suspend -> stop
158 -- Avoid any side effects, undo the yield limit decrement if we
159 -- never yielded anything.
160 else liftIO $ do
161 enqueueLIFO sv q m
162 incrementYieldLimit sv
163 sendStop sv winfo
165 single a = do
166 res <- liftIO $ sendYield sv winfo (ChildYield a)
167 return $ if res then Continue else Suspend
169 -- XXX can we pass on the yield limit downstream to limit the concurrency
170 -- of constituent streams.
171 yieldk a r = do
172 res <- liftIO $ sendYield sv winfo (ChildYield a)
173 yieldLimitOk <- liftIO $ decrementYieldLimit sv
174 if res && yieldLimitOk
175 then foldStreamShared st yieldk single incrContinue r
176 else liftIO $ do
177 incrementYieldLimit sv
178 enqueueLIFO sv q r
179 return Suspend
181 dequeue = liftIO $ atomicModifyIORefCAS q $ \case
182 [] -> ([], Nothing)
183 x : xs -> (xs, Just x)
185-------------------------------------------------------------------------------
186-- WAsync
187-------------------------------------------------------------------------------
189-- XXX we can remove sv as it is derivable from st
191{-# INLINE workLoopFIFO #-}
192workLoopFIFO
193 :: (MonadIO m, MonadBaseControl IO m)
194 => LinkedQueue (Stream m a)
195 -> State Stream m a
196 -> SVar Stream m a
197 -> Maybe WorkerInfo
198 -> m ()
199workLoopFIFO q st sv winfo = run
201 where
203 mrun = runInIO $ svarMrun sv
204 run = do
205 work <- liftIO $ tryPopR q
206 let stop = liftIO $ sendStop sv winfo
207 case work of
208 Nothing -> stop
209 Just m -> do
210 r <- liftIO $ mrun $
211 foldStreamShared st yieldk single (return Continue) m
212 res <- restoreM r
213 case res of
214 Continue -> run
215 Suspend -> stop
217 single a = do
218 res <- liftIO $ sendYield sv winfo (ChildYield a)
219 return $ if res then Continue else Suspend
221 -- XXX in general we would like to yield "n" elements from a single stream
222 -- before moving on to the next. Single element granularity could be too
223 -- expensive in certain cases. Similarly, we can use time limit for
224 -- yielding.
225 yieldk a r = do
226 res <- liftIO $ sendYield sv winfo (ChildYield a)
227 liftIO $ enqueueFIFO sv q r
228 return $ if res then Continue else Suspend
230{-# INLINE workLoopFIFOLimited #-}
231workLoopFIFOLimited
232 :: (MonadIO m, MonadBaseControl IO m)
233 => LinkedQueue (Stream m a)
234 -> State Stream m a
235 -> SVar Stream m a
236 -> Maybe WorkerInfo
237 -> m ()
238workLoopFIFOLimited q st sv winfo = run
240 where
242 mrun = runInIO $ svarMrun sv
243 incrContinue = liftIO (incrementYieldLimit sv) >> return Continue
244 run = do
245 work <- liftIO $ tryPopR q
246 let stop = liftIO $ sendStop sv winfo
247 case work of
248 Nothing -> stop
249 Just m -> do
250 yieldLimitOk <- liftIO $ decrementYieldLimit sv
251 if yieldLimitOk
252 then do
253 r <- liftIO $ mrun $
254 foldStreamShared st yieldk single incrContinue m
255 res <- restoreM r
256 case res of
257 Continue -> run
258 Suspend -> stop
259 else liftIO $ do
260 enqueueFIFO sv q m
261 incrementYieldLimit sv
262 sendStop sv winfo
264 single a = do
265 res <- liftIO $ sendYield sv winfo (ChildYield a)
266 return $ if res then Continue else Suspend
268 yieldk a r = do
269 res <- liftIO $ sendYield sv winfo (ChildYield a)
270 liftIO $ enqueueFIFO sv q r
271 yieldLimitOk <- liftIO $ decrementYieldLimit sv
272 if res && yieldLimitOk
273 then return Continue
274 else liftIO $ do
275 incrementYieldLimit sv
276 return Suspend
278-------------------------------------------------------------------------------
279-- SVar creation
280-- This code belongs in SVar.hs but is kept here for perf reasons
281-------------------------------------------------------------------------------
283-- XXX we have this function in this file because passing runStreamLIFO as a
284-- function argument to this function results in a perf degradation of more
285-- than 10%. Need to investigate what the root cause is.
286-- Interestingly, the same thing does not make any difference for Ahead.
287getLifoSVar :: forall m a. MonadAsync m
288 => State Stream m a -> RunInIO m -> IO (SVar Stream m a)
289getLifoSVar st mrun = do
290 outQ <- newIORef ([], 0)
291 outQMv <- newEmptyMVar
292 active <- newIORef 0
293 wfw <- newIORef False
294 running <- newIORef S.empty
295 q <- newIORef []
296 yl <- case getYieldLimit st of
297 Nothing -> return Nothing
298 Just x -> Just <$> newIORef x
299 rateInfo <- getYieldRateInfo st
301 stats <- newSVarStats
302 tid <- myThreadId
304 let isWorkFinished _ = null <$> readIORef q
306 let isWorkFinishedLimited sv = do
307 yieldsDone <-
308 case remainingWork sv of
309 Just ref -> do
310 n <- readIORef ref
311 return (n <= 0)
312 Nothing -> return False
313 qEmpty <- null <$> readIORef q
314 return $ qEmpty || yieldsDone
316 let getSVar :: SVar Stream m a
317 -> (SVar Stream m a -> m [ChildEvent a])
318 -> (SVar Stream m a -> m Bool)
319 -> (SVar Stream m a -> IO Bool)
320 -> (IORef [Stream m a]
321 -> State Stream m a
322 -> SVar Stream m a
323 -> Maybe WorkerInfo
324 -> m())
325 -> SVar Stream m a
326 getSVar sv readOutput postProc workDone wloop = SVar
327 { outputQueue = outQ
328 , outputQueueFromConsumer = undefined
329 , remainingWork = yl
330 , maxBufferLimit = getMaxBuffer st
331 , pushBufferSpace = undefined
332 , pushBufferPolicy = undefined
333 , pushBufferMVar = undefined
334 , maxWorkerLimit = min (getMaxThreads st) (getMaxBuffer st)
335 , yieldRateInfo = rateInfo
336 , outputDoorBell = outQMv
337 , outputDoorBellFromConsumer = undefined
338 , readOutputQ = readOutput sv
339 , postProcess = postProc sv
340 , workerThreads = running
341 , workLoop = wloop q st{streamVar = Just sv} sv
342 , enqueue = enqueueLIFO sv q
343 , isWorkDone = workDone sv
344 , isQueueDone = workDone sv
345 , needDoorBell = wfw
346 , svarStyle = AsyncVar
347 , svarStopStyle = StopNone
348 , svarStopBy = undefined
349 , svarMrun = mrun
350 , workerCount = active
351 , accountThread = delThread sv
352 , workerStopMVar = undefined
353 , svarRef = Nothing
354 , svarInspectMode = getInspectMode st
355 , svarCreator = tid
356 , aheadWorkQueue = undefined
357 , outputHeap = undefined
358 , svarStats = stats
359 }
361 let sv =
362 case getStreamRate st of
363 Nothing ->
364 case getYieldLimit st of
365 Nothing -> getSVar sv readOutputQBounded
366 postProcessBounded
367 isWorkFinished
368 workLoopLIFO
369 Just _ -> getSVar sv readOutputQBounded
370 postProcessBounded
371 isWorkFinishedLimited
372 workLoopLIFOLimited
373 Just _ ->
374 case getYieldLimit st of
375 Nothing -> getSVar sv readOutputQPaced
376 postProcessPaced
377 isWorkFinished
378 workLoopLIFO
379 Just _ -> getSVar sv readOutputQPaced
380 postProcessPaced
381 isWorkFinishedLimited
382 workLoopLIFOLimited
383 in return sv
385getFifoSVar :: forall m a. MonadAsync m
386 => State Stream m a -> RunInIO m -> IO (SVar Stream m a)
387getFifoSVar st mrun = do
388 outQ <- newIORef ([], 0)
389 outQMv <- newEmptyMVar
390 active <- newIORef 0
391 wfw <- newIORef False
392 running <- newIORef S.empty
393 q <- newQ
394 yl <- case getYieldLimit st of
395 Nothing -> return Nothing
396 Just x -> Just <$> newIORef x
397 rateInfo <- getYieldRateInfo st
399 stats <- newSVarStats
400 tid <- myThreadId
402 let isWorkFinished _ = nullQ q
403 let isWorkFinishedLimited sv = do
404 yieldsDone <-
405 case remainingWork sv of
406 Just ref -> do
407 n <- readIORef ref
408 return (n <= 0)
409 Nothing -> return False
410 qEmpty <- nullQ q
411 return $ qEmpty || yieldsDone
413 let getSVar :: SVar Stream m a
414 -> (SVar Stream m a -> m [ChildEvent a])
415 -> (SVar Stream m a -> m Bool)
416 -> (SVar Stream m a -> IO Bool)
417 -> (LinkedQueue (Stream m a)
418 -> State Stream m a
419 -> SVar Stream m a
420 -> Maybe WorkerInfo
421 -> m())
422 -> SVar Stream m a
423 getSVar sv readOutput postProc workDone wloop = SVar
424 { outputQueue = outQ
425 , outputQueueFromConsumer = undefined
426 , remainingWork = yl
427 , maxBufferLimit = getMaxBuffer st
428 , pushBufferSpace = undefined
429 , pushBufferPolicy = undefined
430 , pushBufferMVar = undefined
431 , maxWorkerLimit = min (getMaxThreads st) (getMaxBuffer st)
432 , yieldRateInfo = rateInfo
433 , outputDoorBell = outQMv
434 , outputDoorBellFromConsumer = undefined
435 , readOutputQ = readOutput sv
436 , postProcess = postProc sv
437 , workerThreads = running
438 , workLoop = wloop q st{streamVar = Just sv} sv
439 , enqueue = enqueueFIFO sv q
440 , isWorkDone = workDone sv
441 , isQueueDone = workDone sv
442 , needDoorBell = wfw
443 , svarStyle = WAsyncVar
444 , svarStopStyle = StopNone
445 , svarStopBy = undefined
446 , svarMrun = mrun
447 , workerCount = active
448 , accountThread = delThread sv
449 , workerStopMVar = undefined
450 , svarRef = Nothing
451 , svarInspectMode = getInspectMode st
452 , svarCreator = tid
453 , aheadWorkQueue = undefined
454 , outputHeap = undefined
455 , svarStats = stats
456 }
458 let sv =
459 case getStreamRate st of
460 Nothing ->
461 case getYieldLimit st of
462 Nothing -> getSVar sv readOutputQBounded
463 postProcessBounded
464 isWorkFinished
465 workLoopFIFO
466 Just _ -> getSVar sv readOutputQBounded
467 postProcessBounded
468 isWorkFinishedLimited
469 workLoopFIFOLimited
470 Just _ ->
471 case getYieldLimit st of
472 Nothing -> getSVar sv readOutputQPaced
473 postProcessPaced
474 isWorkFinished
475 workLoopFIFO
476 Just _ -> getSVar sv readOutputQPaced
477 postProcessPaced
478 isWorkFinishedLimited
479 workLoopFIFOLimited
480 in return sv
482{-# INLINABLE newAsyncVar #-}
483newAsyncVar :: MonadAsync m
484 => State Stream m a -> Stream m a -> m (SVar Stream m a)
485newAsyncVar st m = do
486 mrun <- captureMonadState
487 sv <- liftIO $ getLifoSVar st mrun
488 sendFirstWorker sv m
490-- | Generate a stream asynchronously to keep it buffered, lazily consume
491-- from the buffer.
492--
493-- /Internal/
494--
495{-# INLINABLE mkAsyncK #-}
496mkAsyncK :: (IsStream t, MonadAsync m) => t m a -> t m a
497mkAsyncK m = mkStream $ \st yld sng stp -> do
498 sv <- newAsyncVar (adaptState st) (toStream m)
499 foldStream st yld sng stp $ fromSVar sv
501{-# INLINE_NORMAL mkAsyncD #-}
502mkAsyncD :: MonadAsync m => D.Stream m a -> D.Stream m a
503mkAsyncD m = D.Stream step Nothing
504 where
506 step gst Nothing = do
507 sv <- newAsyncVar gst (D.fromStreamD m)
508 return $ D.Skip $ Just $ D.fromSVar sv
510 step gst (Just (D.UnStream step1 st)) = do
511 r <- step1 gst st
512 return $ case r of
513 D.Yield a s -> D.Yield a (Just $ D.Stream step1 s)
514 D.Skip s -> D.Skip (Just $ D.Stream step1 s)
515 D.Stop -> D.Stop
517-- This is slightly faster than the CPS version above
518--
519-- | Make the stream producer and consumer run concurrently by introducing a
520-- buffer between them. The producer thread evaluates the input stream until
521-- the buffer fills, it terminates if the buffer is full and a worker thread is
522-- kicked off again to evaluate the remaining stream when there is space in the
523-- buffer. The consumer consumes the stream lazily from the buffer.
524--
525-- /Internal/
526--
527{-# INLINE_NORMAL mkAsync #-}
528mkAsync :: (K.IsStream t, MonadAsync m) => t m a -> t m a
529mkAsync = D.fromStreamD . mkAsyncD . D.toStreamD
531-- | Create a new SVar and enqueue one stream computation on it.
532{-# INLINABLE newWAsyncVar #-}
533newWAsyncVar :: MonadAsync m
534 => State Stream m a -> Stream m a -> m (SVar Stream m a)
535newWAsyncVar st m = do
536 mrun <- captureMonadState
537 sv <- liftIO $ getFifoSVar st mrun
538 sendFirstWorker sv m
540------------------------------------------------------------------------------
541-- Running streams concurrently
542------------------------------------------------------------------------------
544-- Concurrency rate control.
545--
546-- Our objective is to create more threads on demand if the consumer is running
547-- faster than us. As soon as we encounter a concurrent composition we create a
548-- push pull pair of threads. We use an SVar for communication between the
549-- consumer, pulling from the SVar and the producer who is pushing to the SVar.
550-- The producer creates more threads if the SVar drains and becomes empty, that
551-- is the consumer is running faster.
552--
553-- XXX Note 1: This mechanism can be problematic if the initial production
554-- latency is high, we may end up creating too many threads. So we need some
555-- way to monitor and use the latency as well. Having a limit on the dispatches
556-- (programmer controlled) may also help.
557--
558-- TBD Note 2: We may want to run computations at the lower level of the
559-- composition tree serially even when they are composed using a parallel
560-- combinator. We can use 'serial' in place of 'async' and 'wSerial' in
561-- place of 'wAsync'. If we find that an SVar immediately above a computation
562-- gets drained empty we can switch to parallelizing the computation. For that
563-- we can use a state flag to fork the rest of the computation at any point of
564-- time inside the Monad bind operation if the consumer is running at a faster
565-- speed.
566--
567-- TBD Note 3: the binary operation ('parallel') composition allows us to
568-- dispatch a chunkSize of only 1. If we have to dispatch in arbitrary
569-- chunksizes we will need to compose the parallel actions using a data
570-- constructor (A Free container) instead so that we can divide it in chunks of
571-- arbitrary size before dispatching. If the stream is composed of
572-- hierarchically composed grains of different sizes then we can always switch
573-- to a desired granularity depending on the consumer speed.
574--
575-- TBD Note 4: for pure work (when we are not in the IO monad) we can divide it
576-- into just the number of CPUs.
578-- | Join two computations on the currently running 'SVar' queue for concurrent
579-- execution. When we are using parallel composition, an SVar is passed around
580-- as a state variable. We try to schedule a new parallel computation on the
581-- SVar passed to us. The first time, when no SVar exists, a new SVar is
582-- created. Subsequently, 'joinStreamVarAsync' may get called when a computation
583-- already scheduled on the SVar is further evaluated. For example, when (a
584-- `parallel` b) is evaluated it calls a 'joinStreamVarAsync' to put 'a' and 'b' on
585-- the current scheduler queue.
586--
587-- The 'SVarStyle' required by the current composition context is passed as one
588-- of the parameters. If the scheduling and composition style of the new
589-- computation being scheduled is different than the style of the current SVar,
590-- then we create a new SVar and schedule it on that. The newly created SVar
591-- joins as one of the computations on the current SVar queue.
592--
593-- Cases when we need to switch to a new SVar:
594--
595-- * (x `parallel` y) `parallel` (t `parallel` u) -- all of them get scheduled on the same SVar
596-- * (x `parallel` y) `parallel` (t `async` u) -- @t@ and @u@ get scheduled on a new child SVar
597-- because of the scheduling policy change.
598-- * if we 'adapt' a stream of type 'async' to a stream of type
599-- 'Parallel', we create a new SVar at the transitioning bind.
600-- * When the stream is switching from disjunctive composition to conjunctive
601-- composition and vice-versa we create a new SVar to isolate the scheduling
602-- of the two.
604forkSVarAsync :: (IsStream t, MonadAsync m)
605 => SVarStyle -> t m a -> t m a -> t m a
606forkSVarAsync style m1 m2 = mkStream $ \st yld sng stp -> do
607 sv <- case style of
608 AsyncVar -> newAsyncVar st (concurrently (toStream m1) (toStream m2))
609 WAsyncVar -> newWAsyncVar st (concurrently (toStream m1) (toStream m2))
610 _ -> error "illegal svar type"
611 foldStream st yld sng stp $ fromSVar sv
612 where
613 concurrently ma mb = mkStream $ \st yld sng stp -> do
614 liftIO $ enqueue (fromJust $ streamVar st) mb
615 foldStreamShared st yld sng stp ma
617{-# INLINE joinStreamVarAsync #-}
618joinStreamVarAsync :: (IsStream t, MonadAsync m)
619 => SVarStyle -> t m a -> t m a -> t m a
620joinStreamVarAsync style m1 m2 = mkStream $ \st yld sng stp ->
621 case streamVar st of
622 Just sv | svarStyle sv == style -> do
623 liftIO $ enqueue sv (toStream m2)
624 foldStreamShared st yld sng stp m1
625 _ -> foldStreamShared st yld sng stp (forkSVarAsync style m1 m2)
627------------------------------------------------------------------------------
628-- Semigroup and Monoid style compositions for parallel actions
629------------------------------------------------------------------------------
631infixr 6 `async`
633-- | Polymorphic version of the 'Semigroup' operation '<>' of 'AsyncT'.
634-- Merges two streams possibly concurrently, preferring the
635-- elements from the left one when available.
636--
637-- @since 0.2.0
638{-# INLINE async #-}
639async :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
640async = joinStreamVarAsync AsyncVar
642-- | Same as 'async'.
643--
644-- @since 0.1.0
645{-# DEPRECATED (<|) "Please use 'async' instead." #-}
646{-# INLINE (<|) #-}
647(<|) :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
648(<|) = async
650-- IMPORTANT: using a monomorphically typed and SPECIALIZED consMAsync makes a
651-- huge difference in the performance of consM in IsStream instance even we
652-- have a SPECIALIZE in the instance.
653--
654-- | XXX we can implement it more efficienty by directly implementing instead
655-- of combining streams using async.
656{-# INLINE consMAsync #-}
657{-# SPECIALIZE consMAsync :: IO a -> AsyncT IO a -> AsyncT IO a #-}
658consMAsync :: MonadAsync m => m a -> AsyncT m a -> AsyncT m a
659consMAsync m r = fromStream $ K.yieldM m `async` (toStream r)
661------------------------------------------------------------------------------
662-- AsyncT
663------------------------------------------------------------------------------
665-- | The 'Semigroup' operation (@<>@) for 'AsyncT' merges two streams
666-- concurrently with priority given to the first stream. In @s1 <> s2 <> s3
667-- ...@ the streams s1, s2 and s3 are scheduled for execution in that order.
668-- Multiple scheduled streams may be executed concurrently and the elements
669-- generated by them are served to the consumer as and when they become
670-- available. This behavior is similar to the scheduling and execution behavior
671-- of actions in a single async stream.
672--
673-- Since only a finite number of streams are executed concurrently, this
674-- operation can be used to fold an infinite lazy container of streams.
675--
676-- @
677-- import "Streamly"
678-- import qualified "Streamly.Prelude" as S
679-- import Control.Concurrent
680--
681-- main = (S.toList . 'asyncly' $ (S.fromList [1,2]) \<> (S.fromList [3,4])) >>= print
682-- @
683-- @
684-- [1,2,3,4]
685-- @
686--
687-- Any exceptions generated by a constituent stream are propagated to the
688-- output stream. The output and exceptions from a single stream are guaranteed
689-- to arrive in the same order in the resulting stream as they were generated
690-- in the input stream. However, the relative ordering of elements from
691-- different streams in the resulting stream can vary depending on scheduling
692-- and generation delays.
693--
694-- Similarly, the monad instance of 'AsyncT' /may/ run each iteration
695-- concurrently based on demand. More concurrent iterations are started only
696-- if the previous iterations are not able to produce enough output for the
697-- consumer.
698--
699-- @
700-- main = 'drain' . 'asyncly' $ do
701-- n <- return 3 \<\> return 2 \<\> return 1
702-- S.yieldM $ do
703-- threadDelay (n * 1000000)
704-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
705-- @
706-- @
707-- ThreadId 40: Delay 1
708-- ThreadId 39: Delay 2
709-- ThreadId 38: Delay 3
710-- @
711--
712-- @since 0.1.0
713newtype AsyncT m a = AsyncT {getAsyncT :: Stream m a}
714 deriving (MonadTrans)
716-- | A demand driven left biased parallely composing IO stream of elements of
717-- type @a@. See 'AsyncT' documentation for more details.
718--
719-- @since 0.2.0
720type Async = AsyncT IO
722-- | Fix the type of a polymorphic stream as 'AsyncT'.
723--
724-- @since 0.1.0
725asyncly :: IsStream t => AsyncT m a -> t m a
726asyncly = adapt
728instance IsStream AsyncT where
729 toStream = getAsyncT
730 fromStream = AsyncT
731 consM = consMAsync
732 (|:) = consMAsync
734------------------------------------------------------------------------------
735-- Semigroup
736------------------------------------------------------------------------------
738-- Monomorphically typed version of "async" for better performance of Semigroup
739-- instance.
740{-# INLINE mappendAsync #-}
741{-# SPECIALIZE mappendAsync :: AsyncT IO a -> AsyncT IO a -> AsyncT IO a #-}
742mappendAsync :: MonadAsync m => AsyncT m a -> AsyncT m a -> AsyncT m a
743mappendAsync m1 m2 = fromStream $ async (toStream m1) (toStream m2)
745instance MonadAsync m => Semigroup (AsyncT m a) where
746 (<>) = mappendAsync
748------------------------------------------------------------------------------
749-- Monoid
750------------------------------------------------------------------------------
752instance MonadAsync m => Monoid (AsyncT m a) where
753 mempty = K.nil
754 mappend = (<>)
756------------------------------------------------------------------------------
757-- Applicative
758------------------------------------------------------------------------------
760{-# INLINE apAsync #-}
761{-# SPECIALIZE apAsync :: AsyncT IO (a -> b) -> AsyncT IO a -> AsyncT IO b #-}
762apAsync :: MonadAsync m => AsyncT m (a -> b) -> AsyncT m a -> AsyncT m b
763apAsync (AsyncT m1) (AsyncT m2) =
764 let f x1 = K.concatMapBy async (pure . x1) m2
765 in AsyncT $ K.concatMapBy async f m1
767instance (Monad m, MonadAsync m) => Applicative (AsyncT m) where
768 {-# INLINE pure #-}
769 pure = AsyncT . K.yield
770 {-# INLINE (<*>) #-}
771 (<*>) = apAsync
773------------------------------------------------------------------------------
774-- Monad
775------------------------------------------------------------------------------
777-- GHC: if we change the implementation of bindWith with arguments in a
778-- different order we see a significant performance degradation (~2x).
779{-# INLINE bindAsync #-}
780{-# SPECIALIZE bindAsync :: AsyncT IO a -> (a -> AsyncT IO b) -> AsyncT IO b #-}
781bindAsync :: MonadAsync m => AsyncT m a -> (a -> AsyncT m b) -> AsyncT m b
782bindAsync m f = fromStream $ K.bindWith async (adapt m) (\a -> adapt $ f a)
784-- GHC: if we specify arguments in the definition of (>>=) we see a significant
785-- performance degradation (~2x).
786instance MonadAsync m => Monad (AsyncT m) where
787 return = pure
788 (>>=) = bindAsync
790------------------------------------------------------------------------------
791-- Other instances
792------------------------------------------------------------------------------
794MONAD_COMMON_INSTANCES(AsyncT, MONADPARALLEL)
796------------------------------------------------------------------------------
797-- WAsyncT
798------------------------------------------------------------------------------
800-- | XXX we can implement it more efficienty by directly implementing instead
801-- of combining streams using wAsync.
802{-# INLINE consMWAsync #-}
803{-# SPECIALIZE consMWAsync :: IO a -> WAsyncT IO a -> WAsyncT IO a #-}
804consMWAsync :: MonadAsync m => m a -> WAsyncT m a -> WAsyncT m a
805consMWAsync m r = fromStream $ K.yieldM m `wAsync` (toStream r)
807infixr 6 `wAsync`
809-- | Polymorphic version of the 'Semigroup' operation '<>' of 'WAsyncT'.
810-- Merges two streams concurrently choosing elements from both fairly.
811--
812-- @since 0.2.0
813{-# INLINE wAsync #-}
814wAsync :: (IsStream t, MonadAsync m) => t m a -> t m a -> t m a
815wAsync = joinStreamVarAsync WAsyncVar
817-- | 'WAsyncT' is similar to 'WSerialT' but with concurrent execution.
818-- The 'Semigroup' operation (@<>@) for 'WAsyncT' merges two streams
819-- concurrently interleaving the actions from both the streams. In @s1
820-- <> s2 <> s3 ...@, the individual actions from streams @s1@, @s2@ and @s3@
821-- are scheduled for execution in a round-robin fashion. Multiple scheduled
822-- actions may be executed concurrently, the results from concurrent executions
823-- are consumed in the order in which they become available.
824--
825--
826-- The @W@ in the name stands for @wide@ or breadth wise scheduling in
827-- contrast to the depth wise scheduling behavior of 'AsyncT'.
828--
829-- @
830-- import "Streamly"
831-- import qualified "Streamly.Prelude" as S
832-- import Control.Concurrent
833--
834-- main = (S.toList . 'wAsyncly' . maxThreads 1 $ (S.fromList [1,2]) \<> (S.fromList [3,4])) >>= print
835-- @
836-- @
837-- [1,3,2,4]
838-- @
839--
840-- For this example, we are using @maxThreads 1@ so that concurrent thread
841-- scheduling does not affect the results and make them unpredictable. Let's
842-- now take a more general example:
843--
844-- @
845-- main = (S.toList . 'wAsyncly' . maxThreads 1 $ (S.fromList [1,2,3]) \<> (S.fromList [4,5,6]) \<> (S.fromList [7,8,9])) >>= print
846-- @
847-- @
848-- [1,4,2,7,5,3,8,6,9]
849-- @
850--
851-- This is how the execution of the above stream proceeds:
852--
853-- 1. The scheduler queue is initialized with @[S.fromList [1,2,3],
854-- (S.fromList [4,5,6]) \<> (S.fromList [7,8,9])]@ assuming the head of the
855-- queue is represented by the rightmost item.
856-- 2. @S.fromList [1,2,3]@ is executed, yielding the element @1@ and putting
857-- @[2,3]@ at the back of the scheduler queue. The scheduler queue now looks
858-- like @[(S.fromList [4,5,6]) \<> (S.fromList [7,8,9]), S.fromList [2,3]]@.
859-- 3. Now @(S.fromList [4,5,6]) \<> (S.fromList [7,8,9])@ is picked up for
860-- execution, @S.fromList [7,8,9]@ is added at the back of the queue and
861-- @S.fromList [4,5,6]@ is executed, yielding the element @4@ and adding
862-- @S.fromList [5,6]@ at the back of the queue. The queue now looks like
863-- @[S.fromList [2,3], S.fromList [7,8,9], S.fromList [5,6]]@.
864-- 4. Note that the scheduler queue expands by one more stream component in
865-- every pass because one more @<>@ is broken down into two components. At this
866-- point there are no more @<>@ operations to be broken down further and the
867-- queue has reached its maximum size. Now these streams are scheduled in
868-- round-robin fashion yielding @[2,7,5,3,8,6,9]@.
869--
870-- As we see above, in a right associated expression composed with @<>@, only
871-- one @<>@ operation is broken down into two components in one execution,
872-- therefore, if we have @n@ streams composed using @<>@ it will take @n@
873-- scheduler passes to expand the whole expression. By the time @n-th@
874-- component is added to the scheduler queue, the first component would have
875-- received @n@ scheduler passes.
876--
877-- Since all streams get interleaved, this operation is not suitable for
878-- folding an infinite lazy container of infinite size streams. However, if
879-- the streams are small, the streams on the left may get finished before more
880-- streams are added to the scheduler queue from the right side of the
881-- expression, so it may be possible to fold an infinite lazy container of
882-- streams. For example, if the streams are of size @n@ then at most @n@
883-- streams would be in the scheduler queue at a time.
884--
885-- Note that 'WSerialT' and 'WAsyncT' differ in their scheduling behavior,
886-- therefore the output of 'WAsyncT' even with a single thread of execution is
887-- not the same as that of 'WSerialT' See notes in 'WSerialT' for details about
888-- its scheduling behavior.
889--
890-- Any exceptions generated by a constituent stream are propagated to the
891-- output stream. The output and exceptions from a single stream are guaranteed
892-- to arrive in the same order in the resulting stream as they were generated
893-- in the input stream. However, the relative ordering of elements from
894-- different streams in the resulting stream can vary depending on scheduling
895-- and generation delays.
896--
897-- Similarly, the 'Monad' instance of 'WAsyncT' runs /all/ iterations fairly
898-- concurrently using a round robin scheduling.
899--
900-- @
901-- main = 'drain' . 'wAsyncly' $ do
902-- n <- return 3 \<\> return 2 \<\> return 1
903-- S.yieldM $ do
904-- threadDelay (n * 1000000)
905-- myThreadId >>= \\tid -> putStrLn (show tid ++ ": Delay " ++ show n)
906-- @
907-- @
908-- ThreadId 40: Delay 1
909-- ThreadId 39: Delay 2
910-- ThreadId 38: Delay 3
911-- @
912--
913-- @since 0.2.0
914newtype WAsyncT m a = WAsyncT {getWAsyncT :: Stream m a}
915 deriving (MonadTrans)
917-- | A round robin parallely composing IO stream of elements of type @a@.
918-- See 'WAsyncT' documentation for more details.
919--
920-- @since 0.2.0
921type WAsync = WAsyncT IO
923-- | Fix the type of a polymorphic stream as 'WAsyncT'.
924--
925-- @since 0.2.0
926wAsyncly :: IsStream t => WAsyncT m a -> t m a
927wAsyncly = adapt
929instance IsStream WAsyncT where
930 toStream = getWAsyncT
931 fromStream = WAsyncT
932 consM = consMWAsync
933 (|:) = consMWAsync
935------------------------------------------------------------------------------
936-- Semigroup
937------------------------------------------------------------------------------
939{-# INLINE mappendWAsync #-}
940{-# SPECIALIZE mappendWAsync :: WAsyncT IO a -> WAsyncT IO a -> WAsyncT IO a #-}
941mappendWAsync :: MonadAsync m => WAsyncT m a -> WAsyncT m a -> WAsyncT m a
942mappendWAsync m1 m2 = fromStream $ wAsync (toStream m1) (toStream m2)
944instance MonadAsync m => Semigroup (WAsyncT m a) where
945 (<>) = mappendWAsync
947------------------------------------------------------------------------------
948-- Monoid
949------------------------------------------------------------------------------
951instance MonadAsync m => Monoid (WAsyncT m a) where
952 mempty = K.nil
953 mappend = (<>)
955------------------------------------------------------------------------------
956-- Applicative
957------------------------------------------------------------------------------
959{-# INLINE apWAsync #-}
960{-# SPECIALIZE apWAsync :: WAsyncT IO (a -> b) -> WAsyncT IO a -> WAsyncT IO b #-}
961apWAsync :: MonadAsync m => WAsyncT m (a -> b) -> WAsyncT m a -> WAsyncT m b
962apWAsync (WAsyncT m1) (WAsyncT m2) =
963 let f x1 = K.concatMapBy wAsync (pure . x1) m2
964 in WAsyncT $ K.concatMapBy wAsync f m1
966-- GHC: if we specify arguments in the definition of (<*>) we see a significant
967-- performance degradation (~2x).
968instance (Monad m, MonadAsync m) => Applicative (WAsyncT m) where
969 pure = WAsyncT . K.yield
970 (<*>) = apWAsync
972------------------------------------------------------------------------------
973-- Monad
974------------------------------------------------------------------------------
976-- GHC: if we change the implementation of bindWith with arguments in a
977-- different order we see a significant performance degradation (~2x).
978{-# INLINE bindWAsync #-}
979{-# SPECIALIZE bindWAsync :: WAsyncT IO a -> (a -> WAsyncT IO b) -> WAsyncT IO b #-}
980bindWAsync :: MonadAsync m => WAsyncT m a -> (a -> WAsyncT m b) -> WAsyncT m b
981bindWAsync m f = fromStream $ K.bindWith wAsync (adapt m) (\a -> adapt $ f a)
983-- GHC: if we specify arguments in the definition of (>>=) we see a significant
984-- performance degradation (~2x).
985instance MonadAsync m => Monad (WAsyncT m) where
986 return = pure
987 (>>=) = bindWAsync
989------------------------------------------------------------------------------
990-- Other instances
991------------------------------------------------------------------------------
993MONAD_COMMON_INSTANCES(WAsyncT, MONADPARALLEL)