on master help sign in
Add concurrency rate control using maxRate
commit610c33a504906fce2aea919575ba7f08220d11c8 authorHarendra Kumar <harendra.kumar@gmail.com> dateSun 8 Jul 2018, 07:23 (8 years ago) parentacbb70a
13 files changed+1789−586
Changelog.md+1 src/Streamly.hs+8−1 src/Streamly/Prelude.hs+2−1 src/Streamly/SVar.hs+862−202 src/Streamly/Streams/Ahead.hs+191−55 src/Streamly/Streams/Async.hs+186−84 src/Streamly/Streams/Parallel.hs+8−2 src/Streamly/Streams/SVar.hs+60−10 stack.yaml+1−1 streamly.cabal+23−4 test/Main.hs+15 test/MaxRate.hs+121 test/Prop.hs+311−226
Changelog.md+1
@@ -8,6 +8,7 @@
88
99### Enhancements
1010
11+* Added `maxRate` combinator to control the yield rate of a stream.
1112* Add `foldl1'`, `foldr1`, `intersperseM`, `find`, `lookup`, `and`, `or`,
1213 `findIndices`, `findIndex`, `elemIndices`, `elemIndex`, `init` to Prelude
1314
src/Streamly.hs+8−1
@@ -112,6 +112,7 @@ module Streamly
112112 -- $concurrency
113113 , maxThreads
114114 , maxBuffer
115+ , maxRate
115116
116117 -- * Folding Containers of Streams
117118 -- $foldutils
@@ -172,7 +173,7 @@ import Streamly.Streams.Ahead
172173import Streamly.Streams.Parallel
173174import Streamly.Streams.Zip
174175import Streamly.Streams.Prelude
175−import Streamly.Streams.SVar (maxThreads, maxBuffer)
176+import Streamly.Streams.SVar (maxThreads, maxBuffer, maxRate)
176177import Streamly.SVar (MonadAsync)
177178import Data.Semigroup (Semigroup(..))
178179
@@ -296,6 +297,12 @@ runZipAsync = runStream . K.adapt
296297-- which can be used to combine two streams in a predetermined way irrespective
297298-- of the type.
298299
300+-- XXX An alternative design choice would be to let a control parameter affect
301+-- the nearest SVar only and then it gets cleared. The benefit of the current
302+-- choice is that it is simply just like global configuration, just like state
303+-- behaves, so should be easy to comprehend. But it has the downside of leaking
304+-- to undesired actions, that is we can forget to reset it.
305+--
299306-- $concurrency
300307--
301308-- These combinators can be used at any point in a stream composition to
src/Streamly/Prelude.hs+2−1
@@ -771,7 +771,8 @@ filterM p m = fromStreamD $ D.filterM p $ toStreamD m
771771-- @since 0.1.0
772772{-# INLINE take #-}
773773take :: (IsStream t, Monad m) => Int -> t m a -> t m a
774−take n m = fromStreamS $ S.take n $ toStreamS (maxYields (Just n) m)
774+take n m = fromStreamS $ S.take n $ toStreamS
775+ (maxYields (Just (fromIntegral n)) m)
775776
776777-- | End the stream as soon as the predicate fails on an element.
777778--
src/Streamly/SVar.hs+862−202
@@ -1,12 +1,13 @@
1−{-# LANGUAGE CPP #-}
2−{-# LANGUAGE KindSignatures #-}
3−{-# LANGUAGE ConstraintKinds #-}
4−{-# LANGUAGE FlexibleContexts #-}
5−{-# LANGUAGE FlexibleInstances #-}
6−{-# LANGUAGE LambdaCase #-}
7−{-# LANGUAGE MagicHash #-}
8−{-# LANGUAGE MultiParamTypeClasses #-}
9−{-# LANGUAGE UnboxedTuples #-}
1+{-# LANGUAGE CPP #-}
2+{-# LANGUAGE KindSignatures #-}
3+{-# LANGUAGE ConstraintKinds #-}
4+{-# LANGUAGE FlexibleContexts #-}
5+{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
7+{-# LANGUAGE LambdaCase #-}
8+{-# LANGUAGE MagicHash #-}
9+{-# LANGUAGE MultiParamTypeClasses #-}
10+{-# LANGUAGE UnboxedTuples #-}
1011
1112-- |
1213-- Module : Streamly.SVar
@@ -21,27 +22,37 @@
2122module Streamly.SVar
2223 (
2324 MonadAsync
24− , SVar (..)
2525 , SVarStyle (..)
26− , defaultMaxBuffer
27− , defaultMaxThreads
28− , State (..)
26+ , SVar (..)
27+
28+ -- State threaded around the stream
29+ , Limit (..)
30+ , State (streamVar)
2931 , defState
3032 , rstState
31−
33+ , getMaxThreads
34+ , setMaxThreads
35+ , getMaxBuffer
36+ , setMaxBuffer
37+ , getMaxStreamRate
38+ , setMaxStreamRate
39+ , setStreamLatency
40+ , getYieldLimit
41+ , setYieldLimit
42+
43+ -- SVar related
3244 , newAheadVar
3345 , newParallelVar
3446
35− , toStreamVar
36−
3747 , atomicModifyIORefCAS
48+ , WorkerInfo (..)
49+ , YieldRateInfo (..)
3850 , ChildEvent (..)
3951 , AheadHeapEntry (..)
52+ , send
4053 , sendYield
4154 , sendStop
4255 , enqueueLIFO
43− , workLoopLIFO
44− , workLoopFIFO
4556 , enqueueFIFO
4657 , enqueueAhead
4758 , pushWorkerPar
@@ -50,18 +61,30 @@ module Streamly.SVar
5061 , dequeueAhead
5162 , dequeueFromHeap
5263
64+ , getYieldRateInfo
65+ , collectLatency
66+ , workerUpdateLatency
67+ , workerRateControl
68+ , updateYieldCount
69+ , decrementYieldLimit
70+ , incrementYieldLimit
5371 , postProcessBounded
72+ , postProcessPaced
5473 , readOutputQBounded
55− , sendWorker
74+ , readOutputQPaced
75+ , dispatchWorkerPaced
76+ , sendFirstWorker
5677 , delThread
78+
79+ , toStreamVar
5780 )
5881where
5982
6083import Control.Concurrent
6184 (ThreadId, myThreadId, threadDelay, getNumCapabilities)
6285import Control.Concurrent.MVar
63− (MVar, newEmptyMVar, tryPutMVar, takeMVar)
64−import Control.Exception (SomeException(..), catch, mask)
86+ (MVar, newEmptyMVar, tryPutMVar, takeMVar, newMVar)
87+import Control.Exception (SomeException(..), catch, mask, assert)
6588import Control.Monad (when)
6689import Control.Monad.Catch (MonadThrow)
6790import Control.Monad.IO.Class (MonadIO(..))
@@ -69,17 +92,20 @@ import Control.Monad.Trans.Control (MonadBaseControl, control)
6992import Data.Atomics
7093 (casIORef, readForCAS, peekTicket, atomicModifyIORefCAS_,
7194 writeBarrier, storeLoadBarrier)
72−import Data.Concurrent.Queue.MichaelScott
73− (LinkedQueue, pushL, tryPopR)
95+import Data.Concurrent.Queue.MichaelScott (LinkedQueue, pushL)
7496import Data.Functor (void)
7597import Data.Heap (Heap, Entry(..))
98+import Data.Int (Int64)
7699import Data.IORef
77− (IORef, modifyIORef, newIORef, readIORef, atomicModifyIORef)
100+ (IORef, modifyIORef, newIORef, readIORef, writeIORef, atomicModifyIORef)
78101import Data.Maybe (fromJust)
79102import Data.Set (Set)
103+import Data.Word (Word64)
80104import GHC.Conc (ThreadId(..))
81105import GHC.Exts
82106import GHC.IO (IO(..))
107+import System.Clock
108+ (TimeSpec, Clock(Monotonic), getTime, toNanoSecs, fromNanoSecs)
83109
84110import qualified Data.Heap as H
85111import qualified Data.Set as S
@@ -94,10 +120,33 @@ import Control.Concurrent.MVar (tryTakeMVar)
94120import Control.Exception
95121 (catches, throwIO, Handler(..), BlockedIndefinitelyOnMVar(..),
96122 BlockedIndefinitelyOnSTM(..))
97−import Data.IORef (writeIORef)
98123import System.IO (hPutStrLn, stderr)
99124#endif
100125
126+newtype NanoSecs = NanoSecs Word64
127+ deriving ( Eq
128+ , Read
129+ , Show
130+ , Enum
131+ , Bounded
132+ , Num
133+ , Real
134+ , Integral
135+ , Ord
136+ )
137+
138+newtype Count = Count Word64
139+ deriving ( Eq
140+ , Read
141+ , Show
142+ , Enum
143+ , Bounded
144+ , Num
145+ , Real
146+ , Integral
147+ , Ord
148+ )
149+
101150------------------------------------------------------------------------------
102151-- Parent child thread communication type
103152------------------------------------------------------------------------------
@@ -148,61 +197,155 @@ data SVarStyle =
148197-- New work is enqueued either at the time of creation of the SVar or as a
149198-- result of executing the parallel combinators i.e. '<|' and '<|>' when the
150199-- already enqueued computations get evaluated. See 'joinStreamVarAsync'.
151−--
152−-- XXX can we use forall t m.
153−data SVar t m a =
154− SVar {
155− -- Read only state
156− svarStyle :: SVarStyle
157−
158− -- Shared output queue (events, length)
159− , outputQueue :: IORef ([ChildEvent a], Int)
160− , maxYieldLimit :: Maybe (IORef Int)
161− , outputDoorBell :: MVar () -- signal the consumer about output
162− , readOutputQ :: m [ChildEvent a]
163− , postProcess :: m Bool
164−
165− -- Used only by bounded SVar types
166− , enqueue :: t m a -> IO ()
167− , isWorkDone :: IO Bool
168− , needDoorBell :: IORef Bool
169− , workLoop :: m ()
170−
171− -- Shared, thread tracking
172− , workerThreads :: IORef (Set ThreadId)
173− , workerCount :: IORef Int
174− , accountThread :: ThreadId -> m ()
200+
201+-- We measure the individual worker latencies to estimate the number of workers
202+-- needed or the amount of time we have to sleep between dispatches to achieve
203+-- a particular rate when controlled pace mode it used.
204+data WorkerInfo = WorkerInfo
205+ { workerYieldMax :: Count -- 0 means unlimited
206+ -- total number of yields by the worker till now
207+ , workerYieldCount :: IORef Count
208+ -- yieldCount at start, timestamp
209+ , workerLatencyStart :: IORef (Count, TimeSpec)
210+ }
211+
212+-- Rate control.
213+data YieldRateInfo = YieldRateInfo
214+ { expectedYieldLatency :: NanoSecs
215+
216+ -- XXX Worker latency specified by the user to be used before the first
217+ -- actual measurement arrives. Not yet implemented
218+ , workerBootstrapLatency :: Maybe NanoSecs
219+
220+ -- After how many yields the worker should update the latency information.
221+ -- If the latency is high, this count is kept lower and vice-versa. XXX If
222+ -- the latency suddenly becomes too high this count may remain too high for
223+ -- long time, in such cases the consumer can change it.
224+ -- 0 means no latency computation
225+ -- XXX this is derivable from workerMeasuredLatency, can be removed.
226+ , workerLatencyPeriod :: IORef Count
227+
228+ -- This is in progress latency stats maintained by the workers which we
229+ -- empty into workerCollectedLatency stats at certain intervals - whenever
230+ -- we process the stream elements yielded in this period.
231+ -- (yieldCount, timeTaken)
232+ , workerCurrentLatency :: IORef (Count, NanoSecs)
233+
234+ -- This is the second level stat which is an accmulation from
235+ -- workerCurrentLatency stats. We keep accumulating latencies in this
236+ -- bucket until we have stats for a sufficient period and then we reset it
237+ -- to start collecting for the next period and retain the computed average
238+ -- latency for the last period in workerMeasuredLatency.
239+ -- (yieldCount, timeTaken)
240+ , workerCollectedLatency :: IORef (Count, NanoSecs)
241+
242+ -- Latency as measured by workers, aggregated for the last period.
243+ , workerMeasuredLatency :: IORef NanoSecs
244+
245+ -- Actual latency/througput as seen from the consumer side, we count the
246+ -- yields and the time it took to generates those yields. This is used to
247+ -- increase or decrease the number of workers needed to achieve the desired
248+ -- rate. The idle time of workers is adjusted in this, so that we only
249+ -- account for the rate when the consumer actually demands data.
250+ , workerLongTermLatency :: IORef (Count, TimeSpec)
251+
252+ -- When the last worker exits it updates this timestamp. We use this to
253+ -- adjust workerLongTermLatency so that we do not account the idle time in
254+ -- that. XXX Note that not all workers stop at the same time therefore
255+ -- there is a certain inaccuracy in this mechanism.
256+ , workerStopTimeStamp :: IORef TimeSpec
257+ , workerStopMVar :: MVar ()
258+ }
259+
260+data Limit = Unlimited | Limited Word deriving Show
261+
262+data SVar t m a = SVar
263+ {
264+ -- Read only state
265+ svarStyle :: SVarStyle
266+
267+ -- Shared output queue (events, length)
268+ , outputQueue :: IORef ([ChildEvent a], Int)
269+ , outputDoorBell :: MVar () -- signal the consumer about output
270+ , readOutputQ :: m [ChildEvent a]
271+ , postProcess :: m Bool
272+
273+ -- Combined/aggregate parameters
274+ , maxWorkerLimit :: Limit
275+ , maxBufferLimit :: Limit
276+ , remainingYields :: Maybe (IORef Count)
277+ , yieldRateInfo :: Maybe YieldRateInfo
278+
279+ -- Used only by bounded SVar types
280+ , enqueue :: t m a -> IO ()
281+ , isWorkDone :: IO Bool
282+ , needDoorBell :: IORef Bool
283+ , workLoop :: WorkerInfo -> m ()
284+
285+ -- Shared, thread tracking
286+ , workerThreads :: IORef (Set ThreadId)
287+ , workerCount :: IORef Int
288+ , accountThread :: ThreadId -> m ()
289+
175290#ifdef DIAGNOSTICS
176− , outputHeap :: IORef (Heap (Entry Int (AheadHeapEntry t m a))
177− , Int
178− )
179− -- Shared work queue (stream, seqNo)
180− , aheadWorkQueue :: IORef ([t m a], Int)
181− , totalDispatches :: IORef Int
182− , maxWorkers :: IORef Int
183− , maxOutQSize :: IORef Int
184− , maxHeapSize :: IORef Int
185− , maxWorkQSize :: IORef Int
291+ , outputHeap :: IORef (Heap (Entry Int (AheadHeapEntry t m a)) , Int)
292+ -- Shared work queue (stream, seqNo)
293+ , aheadWorkQueue :: IORef ([t m a], Int)
294+ , totalDispatches :: IORef Int
295+ , maxWorkers :: IORef Int
296+ , maxOutQSize :: IORef Int
297+ , maxHeapSize :: IORef Int
298+ , maxWorkQSize :: IORef Int
186299#endif
187− }
300+ }
301+
302+-------------------------------------------------------------------------------
303+-- State for concurrency control
304+-------------------------------------------------------------------------------
188305
306+-- XXX we can put the resettable fields in a oneShotConfig field and others in
307+-- a persistentConfig field. That way reset would be fast and scalable
308+-- irrespective of the number of fields.
309+--
310+-- XXX make all these Limited types and use phantom types to distinguish them
189311data State t m a = State
190− { streamVar :: Maybe (SVar t m a)
191− , yieldLimit :: Maybe Int
192− , threadsHigh :: Int
193− , bufferHigh :: Int
312+ { -- one shot configuration, automatically reset for each API call
313+ streamVar :: Maybe (SVar t m a)
314+ , _yieldLimit :: Maybe Count
315+
316+ -- persistent configuration, state that remains valid until changed by
317+ -- an explicit setting via a combinator.
318+ , _threadsHigh :: Limit
319+ , _bufferHigh :: Limit
320+ , _streamLatency :: Maybe NanoSecs
321+ , _maxStreamRate :: Maybe Double -- yields per second
194322 }
195323
196−defaultMaxThreads, defaultMaxBuffer :: Int
197−defaultMaxThreads = 1500
198−defaultMaxBuffer = 1500
324+-------------------------------------------------------------------------------
325+-- State defaults and reset
326+-------------------------------------------------------------------------------
199327
328+-- A magical value for the buffer size arrived at by running the smallest
329+-- possible task and measuring the optimal value of the buffer for that. This
330+-- is obviously dependent on hardware, this figure is based on a 2.2GHz intel
331+-- core-i7 processor.
332+magicMaxBuffer :: Word
333+magicMaxBuffer = 1500
334+
335+defaultMaxThreads, defaultMaxBuffer :: Limit
336+defaultMaxThreads = Limited magicMaxBuffer
337+defaultMaxBuffer = Limited magicMaxBuffer
338+
339+-- The fields prefixed by an _ are not to be accessed or updated directly but
340+-- via smart accessor APIs.
200341defState :: State t m a
201342defState = State
202343 { streamVar = Nothing
203− , yieldLimit = Nothing
204− , threadsHigh = defaultMaxThreads
205− , bufferHigh = defaultMaxBuffer
344+ , _yieldLimit = Nothing
345+ , _threadsHigh = defaultMaxThreads
346+ , _bufferHigh = defaultMaxBuffer
347+ , _maxStreamRate = Nothing
348+ , _streamLatency = Nothing
206349 }
207350
208351-- XXX if perf gets affected we can have all the Nothing params in a single
@@ -215,9 +358,84 @@ defState = State
215358rstState :: State t m a -> State t m b
216359rstState st = st
217360 { streamVar = Nothing
218− , yieldLimit = Nothing
361+ , _yieldLimit = Nothing
219362 }
220363
364+-------------------------------------------------------------------------------
365+-- Smart get/set routines for State
366+-------------------------------------------------------------------------------
367+
368+-- Use get/set routines instead of directly accessing the State fields
369+setYieldLimit :: Maybe Int64 -> State t m a -> State t m a
370+setYieldLimit lim st =
371+ st { _yieldLimit =
372+ case lim of
373+ Nothing -> Nothing
374+ Just n ->
375+ if n <= 0
376+ then Just 0
377+ else Just (fromIntegral n)
378+ }
379+
380+getYieldLimit :: State t m a -> Maybe Count
381+getYieldLimit = _yieldLimit
382+
383+setMaxThreads :: Int -> State t m a -> State t m a
384+setMaxThreads n st =
385+ st { _threadsHigh =
386+ if n < 0
387+ then Unlimited
388+ else if n == 0
389+ then defaultMaxThreads
390+ else Limited (fromIntegral n)
391+ }
392+
393+getMaxThreads :: State t m a -> Limit
394+getMaxThreads = _threadsHigh
395+
396+setMaxBuffer :: Int -> State t m a -> State t m a
397+setMaxBuffer n st =
398+ st { _bufferHigh =
399+ if n < 0
400+ then Unlimited
401+ else if n == 0
402+ then defaultMaxBuffer
403+ else Limited (fromIntegral n)
404+ }
405+
406+getMaxBuffer :: State t m a -> Limit
407+getMaxBuffer = _bufferHigh
408+
409+setMaxStreamRate :: Double -> State t m a -> State t m a
410+setMaxStreamRate n st =
411+ st { _maxStreamRate =
412+ if n < 0
413+ then Nothing
414+ else if n == 0
415+ then Nothing
416+ else Just n
417+ }
418+
419+getMaxStreamRate :: State t m a -> Maybe Double
420+getMaxStreamRate = _maxStreamRate
421+
422+setStreamLatency :: Int -> State t m a -> State t m a
423+setStreamLatency n st =
424+ st { _streamLatency =
425+ if n < 0
426+ then Nothing
427+ else if n == 0
428+ then Nothing
429+ else Just (fromIntegral n)
430+ }
431+
432+getStreamLatency :: State t m a -> Maybe NanoSecs
433+getStreamLatency = _streamLatency
434+
435+-------------------------------------------------------------------------------
436+-- Dumping the SVar for debug/diag
437+-------------------------------------------------------------------------------
438+
221439#ifdef DIAGNOSTICS
222440{-# NOINLINE dumpSVar #-}
223441dumpSVar :: SVar t m a -> IO String
@@ -287,6 +505,10 @@ withDBGMVar :: SVar t m a -> String -> IO () -> IO ()
287505withDBGMVar _ _ action = action
288506#endif
289507
508+-------------------------------------------------------------------------------
509+-- CAS
510+-------------------------------------------------------------------------------
511+
290512-- Slightly faster version of CAS. Gained some improvement by avoiding the use
291513-- of "evaluate" because we know we do not have exceptions in fn.
292514{-# INLINE atomicModifyIORefCAS #-}
@@ -346,8 +568,10 @@ doFork action exHandler =
346568
347569-- | This function is used by the producer threads to queue output for the
348570-- consumer thread to consume. Returns whether the queue has more space.
349−send :: Int -> SVar t m a -> ChildEvent a -> IO Bool
350−send maxOutputQLen sv msg = do
571+send :: SVar t m a -> ChildEvent a -> IO Bool
572+send sv msg = do
573+ -- XXX can the access to outputQueue and maxBufferLimit be made faster
574+ -- somehow?
351575 len <- atomicModifyIORefCAS (outputQueue sv) $ \(es, n) ->
352576 ((msg : es, n + 1), n)
353577 when (len <= 0) $ do
@@ -361,22 +585,116 @@ send maxOutputQLen sv msg = do
361585 -- The important point is that the consumer is guaranteed to receive a
362586 -- doorbell if something was added to the queue after it empties it.
363587 void $ tryPutMVar (outputDoorBell sv) ()
364− return (len < maxOutputQLen || maxOutputQLen < 0)
365588
366−{-# NOINLINE sendYield #-}
367−sendYield :: Int -> SVar t m a -> ChildEvent a -> IO Bool
368−sendYield maxOutputQLen sv msg = do
369− ylimit <- case maxYieldLimit sv of
370− Nothing -> return True
371− Just ref -> atomicModifyIORefCAS ref $ \x -> (x - 1, x > 1)
372− r <- send maxOutputQLen sv msg
373− return $ r && ylimit
589+ -- XXX we should reserve the buffer when we pick up the work from the
590+ -- queue, instead of checking it here when it is too late.
591+ let limit = maxBufferLimit sv
592+ case limit of
593+ Unlimited -> return True
594+ Limited lim -> do
595+ active <- readIORef (workerCount sv)
596+ return $ len < ((fromIntegral lim) - active)
597+
598+-- XXX We assume that a worker always yields a value. If we can have
599+-- workers that return without yielding anything our computations to
600+-- determine the number of workers may be off.
601+workerUpdateLatency :: YieldRateInfo -> WorkerInfo -> IO ()
602+workerUpdateLatency yinfo winfo = do
603+ cnt1 <- readIORef (workerYieldCount winfo)
604+ (cnt0, t0) <- readIORef (workerLatencyStart winfo)
605+ t1 <- getTime Monotonic
606+ writeIORef (workerLatencyStart winfo) (cnt1, t1)
607+ let period = fromInteger $ toNanoSecs (t1 - t0)
608+ let ref = workerCurrentLatency yinfo
609+ atomicModifyIORefCAS ref $ \(ycnt, ytime) ->
610+ ((ycnt + cnt1 - cnt0, ytime + period), ())
611+
612+updateYieldCount :: WorkerInfo -> IO Count
613+updateYieldCount winfo = do
614+ cnt <- readIORef (workerYieldCount winfo)
615+ let cnt1 = cnt + 1
616+ writeIORef (workerYieldCount winfo) cnt1
617+ return cnt1
618+
619+isBeyondMaxYield :: Count -> WorkerInfo -> Bool
620+isBeyondMaxYield cnt winfo =
621+ let ymax = workerYieldMax winfo
622+ in ymax /= 0 && cnt >= ymax
623+
624+-- XXX we should do rate control periodically based on the total yields rather
625+-- than based on the worker local yields as other workers may have yielded more
626+-- and we should stop based on the aggregate yields. However, latency update
627+-- period can be based on individual worker yields.
628+{-# NOINLINE checkRatePeriodic #-}
629+checkRatePeriodic :: SVar t m a
630+ -> YieldRateInfo
631+ -> WorkerInfo
632+ -> Count
633+ -> IO Bool
634+checkRatePeriodic sv yinfo winfo ycnt = do
635+ i <- readIORef (workerLatencyPeriod yinfo)
636+ if (i /= 0 && (ycnt `mod` i) == 0)
637+ then do
638+ workerUpdateLatency yinfo winfo
639+ isBeyondMaxRate sv yinfo
640+ else return False
374641
375−{-# NOINLINE sendStop #-}
376−sendStop :: SVar t m a -> IO ()
377−sendStop sv = do
378− liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n - 1
379− myThreadId >>= \tid -> void $ send (-1) sv (ChildStop tid Nothing)
642+-- CAUTION! this also updates the yield count and therefore should be called
643+-- only when we are actually yielding an element.
644+{-# NOINLINE workerRateControl #-}
645+workerRateControl :: SVar t m a -> YieldRateInfo -> WorkerInfo -> IO Bool
646+workerRateControl sv yinfo winfo = do
647+ cnt <- updateYieldCount winfo
648+ beyondMaxRate <- checkRatePeriodic sv yinfo winfo cnt
649+ return $ not (isBeyondMaxYield cnt winfo || beyondMaxRate)
650+
651+-- XXX Can we make access to remainingYields and yieldRateInfo fields in sv
652+-- faster, along with the fields in sv required by send?
653+-- XXX make it noinline
654+decrementYieldLimit :: SVar t m a -> IO Bool
655+decrementYieldLimit sv =
656+ case remainingYields sv of
657+ Nothing -> return True
658+ Just ref -> do
659+ r <- atomicModifyIORefCAS ref $ \x ->
660+ (if x >= 1 then x - 1 else 0, x)
661+ return $ r >= 1
662+
663+-- XXX increment is not safe when the yield limit was already 0
664+-- and therefore was not decremented. We need yield limit to be
665+-- int and negative if we want this to work.
666+incrementYieldLimit :: SVar t m a -> IO ()
667+incrementYieldLimit sv =
668+ case remainingYields sv of
669+ Nothing -> return ()
670+ Just ref -> atomicModifyIORefCAS_ ref (+ 1)
671+
672+-- XXX we should do rate control here but not latency update in case of ahead
673+-- streams. latency update must be done when we yield directly to outputQueue
674+-- or when we yield to heap.
675+{-# INLINE sendYield #-}
676+sendYield :: SVar t m a -> WorkerInfo -> ChildEvent a -> IO Bool
677+sendYield sv winfo msg = do
678+ r <- send sv msg
679+ rateLimitOk <-
680+ case yieldRateInfo sv of
681+ Nothing -> return True
682+ Just yinfo -> workerRateControl sv yinfo winfo
683+ return $ r && rateLimitOk
684+
685+{-# INLINABLE sendStop #-}
686+sendStop :: SVar t m a -> WorkerInfo -> IO ()
687+sendStop sv winfo = do
688+ n <- liftIO $ atomicModifyIORefCAS (workerCount sv) $ \n -> (n - 1, n)
689+ case yieldRateInfo sv of
690+ Nothing -> return ()
691+ Just info -> do
692+ i <- readIORef (workerLatencyPeriod info)
693+ when (i /= 0) $ workerUpdateLatency info winfo
694+ when (n == 1) $ do
695+ t <- getTime Monotonic
696+ writeIORef (workerStopTimeStamp info) t
697+ myThreadId >>= \tid -> void $ send sv (ChildStop tid Nothing)
380698
381699-------------------------------------------------------------------------------
382700-- Async
@@ -401,25 +719,6 @@ enqueueLIFO sv q m = do
401719 atomicModifyIORefCAS_ (needDoorBell sv) (const False)
402720 void $ tryPutMVar (outputDoorBell sv) ()
403721
404−{-# INLINE workLoopLIFO #-}
405−workLoopLIFO :: MonadIO m
406− => (State t m a -> IORef [t m a] -> t m a -> m () -> m ())
407− -> State t m a -> IORef [t m a] -> m ()
408−workLoopLIFO f st q = run
409−
410− where
411−
412− sv = fromJust $ streamVar st
413− run = do
414− work <- dequeue
415− case work of
416− Nothing -> liftIO $ sendStop sv
417− Just m -> f st q m run
418−
419− dequeue = liftIO $ atomicModifyIORefCAS q $ \case
420− [] -> ([], Nothing)
421− x : xs -> (xs, Just x)
422−
423722-------------------------------------------------------------------------------
424723-- WAsync
425724-------------------------------------------------------------------------------
@@ -443,21 +742,6 @@ enqueueFIFO sv q m = do
443742 atomicModifyIORefCAS_ (needDoorBell sv) (const False)
444743 void $ tryPutMVar (outputDoorBell sv) ()
445744
446−{-# INLINE workLoopFIFO #-}
447−workLoopFIFO :: MonadIO m
448− => (State t m a -> LinkedQueue (t m a) -> t m a -> m () -> m ())
449− -> State t m a -> LinkedQueue (t m a) -> m ()
450−workLoopFIFO f st q = run
451−
452− where
453−
454− sv = fromJust $ streamVar st
455− run = do
456− work <- liftIO $ tryPopR q
457− case work of
458− Nothing -> liftIO $ sendStop sv
459− Just m -> f st q m run
460−
461745-------------------------------------------------------------------------------
462746-- Ahead
463747-------------------------------------------------------------------------------
@@ -510,10 +794,18 @@ workLoopFIFO f st q = run
510794-- that they do not hog the resources and hinder the progress of the threads in
511795-- front of them.
512796
513−-- Left associated ahead expressions are expensive. We start a new SVar for
797+-- XXX Left associated ahead expressions are expensive. We start a new SVar for
514798-- each left associative expression. The queue is used only for right
515799-- associated expression, we queue the right expression and execute the left.
516−-- Thererefore the queue never has more than on item in it.
800+-- Thererefore the queue never has more than one item in it.
801+--
802+-- XXX we can fix this. When we queue more than one item on the queue we can
803+-- mark the previously queued item as not-runnable. The not-runnable item is
804+-- not dequeued until the already running one has finished and at that time we
805+-- would also know the exact sequence number of the already queued item.
806+--
807+-- we can even run the already queued items but they will have to be sorted in
808+-- layers in the heap. We can use a list of heaps for that.
517809{-# INLINE enqueueAhead #-}
518810enqueueAhead :: SVar t m a -> IORef ([t m a], Int) -> t m a -> IO ()
519811enqueueAhead sv q m = do
@@ -571,7 +863,7 @@ dequeueFromHeap
571863 :: IORef (Heap (Entry Int (AheadHeapEntry t m a)), Int)
572864 -> IO (Maybe (Entry Int (AheadHeapEntry t m a)))
573865dequeueFromHeap hpRef = do
574− atomicModifyIORefCAS hpRef $ \hp@(h, snum) -> do
866+ atomicModifyIORef hpRef $ \hp@(h, snum) -> do
575867 let r = H.uncons h
576868 case r of
577869 Nothing -> (hp, Nothing)
@@ -637,7 +929,7 @@ allThreadsDone sv = liftIO $ S.null <$> readIORef (workerThreads sv)
637929handleChildException :: SVar t m a -> SomeException -> IO ()
638930handleChildException sv e = do
639931 tid <- myThreadId
640− void $ send (-1) sv (ChildStop tid (Just e))
932+ void $ send sv (ChildStop tid (Just e))
641933
642934#ifdef DIAGNOSTICS
643935recordMaxWorkers :: MonadIO m => SVar t m a -> m ()
@@ -649,13 +941,24 @@ recordMaxWorkers sv = liftIO $ do
649941#endif
650942
651943{-# NOINLINE pushWorker #-}
652−pushWorker :: MonadAsync m => SVar t m a -> m ()
653−pushWorker sv = do
944+pushWorker :: MonadAsync m => Count -> SVar t m a -> m ()
945+pushWorker yieldMax sv = do
654946 liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n + 1
655947#ifdef DIAGNOSTICS
656948 recordMaxWorkers sv
657949#endif
658− doFork (workLoop sv) (handleChildException sv) >>= addThread sv
950+ -- XXX we can make this allocation conditional, it might matter when
951+ -- significant number of workers are being sent.
952+ winfo <- do
953+ cntRef <- liftIO $ newIORef 0
954+ t <- liftIO $ getTime Monotonic
955+ lat <- liftIO $ newIORef (0, t)
956+ return $ WorkerInfo
957+ { workerYieldMax = yieldMax
958+ , workerYieldCount = cntRef
959+ , workerLatencyStart = lat
960+ }
961+ doFork (workLoop sv winfo) (handleChildException sv) >>= addThread sv
659962
660963-- XXX we can push the workerCount modification in accountThread and use the
661964-- same pushWorker for Parallel case as well.
@@ -676,49 +979,313 @@ pushWorkerPar sv wloop = do
676979#endif
677980 doFork wloop (handleChildException sv) >>= modifyThread sv
678981
679−dispatchWorker :: MonadAsync m => Int -> SVar t m a -> m ()
680−dispatchWorker maxWorkerLimit sv = do
982+-- Returns:
983+-- True: can dispatch more
984+-- False: cannot dispatch any more
985+dispatchWorker :: MonadAsync m => Count -> SVar t m a -> m Bool
986+dispatchWorker yieldCount sv = do
987+ let workerLimit = maxWorkerLimit sv
988+ -- XXX in case of Ahead streams we should not send more than one worker
989+ -- when the work queue is done but heap is not done.
681990 done <- liftIO $ isWorkDone sv
682− when (not done) $ do
991+ if (not done)
992+ then do
683993 -- Note that the worker count is only decremented during event
684994 -- processing in fromStreamVar and therefore it is safe to read and
685995 -- use it without a lock.
686− cnt <- liftIO $ readIORef $ workerCount sv
996+ active <- liftIO $ readIORef $ workerCount sv
687997 -- Note that we may deadlock if the previous workers (tasks in the
688998 -- stream) wait/depend on the future workers (tasks in the stream)
689999 -- executing. In that case we should either configure the maxWorker
6901000 -- count to higher or use parallel style instead of ahead or async
6911001 -- style.
692− limit <- case maxYieldLimit sv of
693− Nothing -> return maxWorkerLimit
1002+ limit <- case remainingYields sv of
1003+ Nothing -> return workerLimit
6941004 Just x -> do
695− lim <- liftIO $ readIORef x
1005+ n <- liftIO $ readIORef x
6961006 return $
697− if maxWorkerLimit > 0
698− then min maxWorkerLimit lim
699− else lim
700− when (cnt < limit || limit < 0) $ pushWorker sv
1007+ case workerLimit of
1008+ Unlimited -> Limited (fromIntegral n)
1009+ Limited lim -> Limited $ min lim (fromIntegral n)
1010+
1011+ -- XXX for ahead streams shall we take the heap yields into account for
1012+ -- controlling the dispatch? We should not dispatch if the heap has
1013+ -- already got the limit covered.
1014+ let dispatch = pushWorker yieldCount sv >> return True
1015+ in case limit of
1016+ Unlimited -> dispatch
1017+ -- Note that the use of remainingYields and workerCount is not
1018+ -- atomic and the counts may even have changed between reading and
1019+ -- using them here, so this is just approximate logic and we cannot
1020+ -- rely on it for correctness. We may actually dispatch more
1021+ -- workers than required.
1022+ Limited lim | active < (fromIntegral lim) -> dispatch
1023+ _ -> return False
1024+ else return False
7011025
702−{-# NOINLINE sendWorkerWait #-}
703−sendWorkerWait :: MonadAsync m => Int -> SVar t m a -> m ()
704−sendWorkerWait maxWorkerLimit sv = do
705− -- Note that we are guaranteed to have at least one outstanding worker when
706− -- we enter this function. So if we sleep we are guaranteed to be woken up
707− -- by a outputDoorBell, when the worker exits.
1026+-- | This is a magic number and it is overloaded, and used at several places to
1027+-- achieve batching:
1028+--
1029+-- 1. If we have to sleep to slowdown this is the minimum period that we
1030+-- accumulate before we sleep. Also, workers do not stop until this much
1031+-- sleep time is accumulated.
1032+-- 3. Collected latencies are computed and transferred to measured latency
1033+-- after a minimum of this period.
1034+minThreadDelay :: NanoSecs
1035+minThreadDelay = 10^(6 :: Int)
1036+
1037+-- | Another magic number! When we have to start more workers to cover up a
1038+-- number of yields that we are lagging by then we cannot start one worker for
1039+-- each yield because that may be a very big number and if the latency of the
1040+-- workers is low these number of yields could be very high. We use this number
1041+-- as the duration in which we want cover any lag. In other words we accumulate
1042+-- the lag (lagging number of yields) such that it covers this much duration
1043+-- for a single worker to produce those yields, before we start an extra
1044+-- worker.
1045+coverUpLatency :: NanoSecs
1046+coverUpLatency = 1000000
1047+
1048+nanoToMicroSecs :: NanoSecs -> Int
1049+nanoToMicroSecs s = (fromIntegral s) `div` 1000
1050+
1051+-- XXX we can use phantom types to distinguish the duration/latency/expectedLat
1052+countDispatchWorkers
1053+ :: Limit -> Count -> NanoSecs -> NanoSecs -> NanoSecs -> Int
1054+countDispatchWorkers workerLimit count duration wLatency expectedLat =
1055+ -- Calculate how many workers do we need to maintain the rate.
1056+
1057+ -- XXX Increasing the workers may reduce the overall latency per
1058+ -- yield of the workers, sometimes quite a bit. We can keep a
1059+ -- latency histogram for 1,2,4..2^20 workers so that we can decide
1060+ -- which bucket is optimal and decide the number of workers based
1061+ -- on that.
1062+ --
1063+ -- XXX put a cap on extraworkers? Should not be more than nWorkers.
1064+ -- Bridge the gap slowly rather than in a burst in one go.
1065+ --
1066+ -- XXX we can have a maxEfficiency combinator as well which runs the
1067+ -- producer at the maximal efficiency i.e. the number of workers are chosen
1068+ -- such that the latency is minimum or within a range. Or we can call it
1069+ -- maxLatency.
1070+ --
1071+ let nWorkers = (fromIntegral wLatency) / (fromIntegral expectedLat)
1072+ -- Calculate how many yields are we ahead or behind to match the exact
1073+ -- required rate. Based on that we increase or decrease the effective
1074+ -- workers.
1075+ d = fromIntegral duration
1076+ e = fromIntegral expectedLat
1077+ n = max 1 (fromIntegral coverUpLatency / fromIntegral wLatency)
1078+ :: Double
1079+ extraYields = (d / e) - (fromIntegral count)
1080+ extraWorkers = extraYields / n
1081+ requiredWorkers = round $ nWorkers + extraWorkers
1082+ in case workerLimit of
1083+ Unlimited -> requiredWorkers
1084+ Limited x -> min requiredWorkers (fromIntegral x)
1085+
1086+-- | Get the worker latency without resetting workerCurrentLatency
1087+-- Returns (total yield count, base time, measured latency)
1088+getWorkerLatency :: YieldRateInfo -> IO (Count, TimeSpec, NanoSecs)
1089+getWorkerLatency yinfo = do
1090+ let cur = workerCurrentLatency yinfo
1091+ col = workerCollectedLatency yinfo
1092+ longTerm = workerLongTermLatency yinfo
1093+ measured = workerMeasuredLatency yinfo
1094+
1095+ (count, time) <- readIORef cur
1096+ (colCount, colTime) <- readIORef col
1097+ (lcount, ltime) <- readIORef longTerm
1098+ prev <- readIORef measured
1099+
1100+ let pendingCount = colCount + count
1101+ pendingTime = colTime + time
1102+ -- Take the average of previous and the latest
1103+ new =
1104+ if pendingCount > 0
1105+ then ((pendingTime `div` (fromIntegral pendingCount)) + prev)
1106+ `div` 2
1107+ else prev
1108+ return (lcount + pendingCount, ltime, new)
1109+
1110+isBeyondMaxRate :: SVar t m a -> YieldRateInfo -> IO Bool
1111+isBeyondMaxRate sv yinfo = do
1112+ (count, tstamp, wLatency) <- getWorkerLatency yinfo
1113+ now <- getTime Monotonic
1114+ let duration = fromInteger $ toNanoSecs $ now - tstamp
1115+ let expectedLat = expectedYieldLatency yinfo
1116+ let netWorkers = countDispatchWorkers (maxWorkerLimit sv) count duration
1117+ wLatency expectedLat
1118+ cnt <- readIORef $ workerCount sv
1119+ if netWorkers <= 0
1120+ then do
1121+ let sleepTime = ((fromIntegral count) * expectedLat) - duration
1122+ if sleepTime >= minThreadDelay
1123+ then return True
1124+ else return False
1125+ else if cnt > netWorkers
1126+ then return True
1127+ else return False
1128+
1129+useCollectedBatch :: YieldRateInfo
1130+ -> NanoSecs
1131+ -> IORef (Count, NanoSecs)
1132+ -> IORef NanoSecs
1133+ -> IO ()
1134+useCollectedBatch yinfo latency colRef measRef = do
1135+ let periodRef = workerLatencyPeriod yinfo
1136+ cnt = max 1 $ minThreadDelay `div` latency
1137+ period = min cnt (fromIntegral magicMaxBuffer)
1138+
1139+ -- XXX we should use some weight for the old latency as well in arriving at
1140+ -- the new latency, we have the counts, we can use that for some weighting.
1141+ writeIORef periodRef (fromIntegral period)
1142+ writeIORef colRef (0, 0)
1143+ writeIORef measRef latency
1144+
1145+-- Returns a triple, (1) yield count since last collection, (2) the base time
1146+-- when we started counting, (3) average latency in the last measurement
1147+-- period. The former two are used for accurate measurement of the going rate
1148+-- whereas the average is used for future estimates e.g. how many workers
1149+-- should be maintained to maintain the rate.
1150+collectLatency :: YieldRateInfo -> IO (Count, TimeSpec, NanoSecs)
1151+collectLatency yinfo = do
1152+ let cur = workerCurrentLatency yinfo
1153+ col = workerCollectedLatency yinfo
1154+ longTerm = workerLongTermLatency yinfo
1155+ measured = workerMeasuredLatency yinfo
1156+
1157+ (count, time) <- atomicModifyIORefCAS cur $ \v -> ((0,0), v)
1158+ (colCount, colTime) <- readIORef col
1159+ (lcount, ltime) <- readIORef longTerm
1160+ prev <- readIORef measured
1161+
1162+ let pendingCount = colCount + count
1163+ pendingTime = colTime + time
1164+
1165+ lcount' = lcount + pendingCount
1166+ notUpdated = (lcount', ltime, prev)
1167+
1168+ if (pendingCount > 0)
1169+ then do
1170+ let new = pendingTime `div` (fromIntegral pendingCount)
1171+ -- To avoid minor fluctuations update in batches
1172+ if (pendingCount > fromIntegral magicMaxBuffer)
1173+ || (pendingTime > minThreadDelay)
1174+ || (let r = (fromIntegral new) / (fromIntegral prev) :: Double
1175+ in prev > 0 && (r > 2 || r < 0.5))
1176+ || (prev == 0)
1177+ then do
1178+ useCollectedBatch yinfo new col measured
1179+ modifyIORef longTerm $ \(_, t) -> (lcount', t)
1180+ return (lcount', ltime, new)
1181+ else do
1182+ writeIORef col (pendingCount, pendingTime)
1183+ return notUpdated
1184+ else return notUpdated
1185+
1186+adjustLatencies :: YieldRateInfo -> IO ()
1187+adjustLatencies yinfo = do
1188+ let longTerm = workerLongTermLatency yinfo
1189+ t0 <- readIORef (workerStopTimeStamp yinfo)
1190+ t1 <- getTime Monotonic
1191+ modifyIORef longTerm $ \(c, t) -> (c, t + t1 - t0)
1192+ return ()
1193+
1194+-- XXX in case of ahead style stream we need to take the heap size into account
1195+-- because we return the workers on the basis of that which causes a condition
1196+-- where we keep dispatching and they keep returning. So we must have exactly
1197+-- the same logic for not dispatching and for returning.
1198+--
1199+-- Returns:
1200+-- True: can dispatch more
1201+-- False: full, no more dispatches
1202+dispatchWorkerPaced :: MonadAsync m => SVar t m a -> m Bool
1203+dispatchWorkerPaced sv = do
1204+ let workerLimit = maxWorkerLimit sv
1205+ let yinfo = fromJust $ yieldRateInfo sv
1206+ (count, tstamp, wLatency) <- liftIO $ collectLatency yinfo
1207+ -- XXX use workerBootstrapLatency
1208+ if wLatency == 0
1209+ -- Need to measure the latency with a single worker before we can perform
1210+ -- any computation.
1211+ then return False
1212+ else do
1213+ now <- liftIO $ getTime Monotonic
1214+ let duration = fromInteger $ toNanoSecs $ now - tstamp
1215+ let expectedLat = expectedYieldLatency yinfo
1216+ let netWorkers = countDispatchWorkers workerLimit count duration
1217+ wLatency expectedLat
1218+
1219+ -- XXX we need to take yieldLimit into account here. If we are at the
1220+ -- end of the limit as well as the time, we should not be sleeping.
1221+ -- If we are not actually planning to dispatch any more workers we need
1222+ -- to take that in account.
1223+ if netWorkers <= 0
1224+ then do
1225+ let sleepTime = ((fromIntegral count) * expectedLat) - duration
1226+
1227+ when (sleepTime >= minThreadDelay) $ do
1228+ liftIO $ threadDelay $ nanoToMicroSecs sleepTime
1229+
1230+ cnt <- liftIO $ readIORef $ workerCount sv
1231+ when (cnt < 1) $ void $ do
1232+ let n = countDispatchYields (expectedLat - wLatency)
1233+ in dispatchWorker (fromIntegral (max n 1)) sv
1234+ return False
1235+ else do
1236+ -- XXX stagger the workers over a period
1237+ cnt <- liftIO $ readIORef $ workerCount sv
1238+ if (cnt < netWorkers)
1239+ then dispatchWorker 0 sv
1240+ else return False
1241+
1242+ where
7081243
1244+ countDispatchYields :: NanoSecs -> Int
1245+ countDispatchYields idleTime =
1246+ assert (idleTime >= 0) $
1247+ -- we do not want to sleep for less than minThreadDelay
1248+ let maxYieldsPerWorker = fromIntegral magicMaxBuffer
1249+ in if idleTime < minThreadDelay
1250+ then
1251+ if idleTime == 0
1252+ then maxYieldsPerWorker
1253+ else
1254+ let n = (minThreadDelay + idleTime) `div` idleTime
1255+ in min (fromIntegral n) maxYieldsPerWorker
1256+ else 1
1257+
1258+sendWorkerDelayPaced :: SVar t m a -> IO ()
1259+sendWorkerDelayPaced _ = return ()
1260+
1261+sendWorkerDelay :: SVar t m a -> IO ()
1262+sendWorkerDelay sv = do
7091263 -- XXX we need a better way to handle this than hardcoded delays. The
7101264 -- delays may be different for different systems.
711− ncpu <- liftIO $ getNumCapabilities
1265+ ncpu <- getNumCapabilities
7121266 if ncpu <= 1
7131267 then
7141268 if (svarStyle sv == AheadVar)
715− then liftIO $ threadDelay 100
716− else liftIO $ threadDelay 25
1269+ then threadDelay 100
1270+ else threadDelay 25
7171271 else
7181272 if (svarStyle sv == AheadVar)
719− then liftIO $ threadDelay 100
720− else liftIO $ threadDelay 10
1273+ then threadDelay 100
1274+ else threadDelay 10
7211275
1276+{-# NOINLINE sendWorkerWait #-}
1277+sendWorkerWait
1278+ :: MonadAsync m
1279+ => (SVar t m a -> IO ())
1280+ -> (SVar t m a -> m Bool)
1281+ -> SVar t m a
1282+ -> m ()
1283+sendWorkerWait delay dispatch sv = do
1284+ -- Note that we are guaranteed to have at least one outstanding worker when
1285+ -- we enter this function. So if we sleep we are guaranteed to be woken up
1286+ -- by a outputDoorBell, when the worker exits.
1287+
1288+ liftIO $ delay sv
7221289 (_, n) <- liftIO $ readIORef (outputQueue sv)
7231290 when (n <= 0) $ do
7241291 -- The queue may be empty temporarily if the worker has dequeued the
@@ -753,7 +1320,7 @@ sendWorkerWait maxWorkerLimit sv = do
7531320
7541321 liftIO $ atomicModifyIORefCAS_ (needDoorBell sv) $ const True
7551322 liftIO $ storeLoadBarrier
756− dispatchWorker maxWorkerLimit sv
1323+ canDoMore <- dispatch sv
7571324
7581325 -- XXX test for the case when we miss sending a worker when the worker
7591326 -- count is more than 1500.
@@ -762,14 +1329,13 @@ sendWorkerWait maxWorkerLimit sv = do
7621329 -- least one outstanding worker. Otherwise we could be sleeping
7631330 -- forever.
7641331
765− done <- liftIO $ isWorkDone sv
766− if done
767− then do
1332+ if canDoMore
1333+ then sendWorkerWait delay dispatch sv
1334+ else do
7681335 liftIO $ withDBGMVar sv "sendWorkerWait: nothing to do"
7691336 $ takeMVar (outputDoorBell sv)
7701337 (_, len) <- liftIO $ readIORef (outputQueue sv)
771− when (len <= 0) $ sendWorkerWait maxWorkerLimit sv
772− else sendWorkerWait maxWorkerLimit sv
1338+ when (len <= 0) $ sendWorkerWait delay dispatch sv
7731339
7741340{-# INLINE readOutputQRaw #-}
7751341readOutputQRaw :: SVar t m a -> IO ([ChildEvent a], Int)
@@ -781,8 +1347,8 @@ readOutputQRaw sv = do
7811347#endif
7821348 return (list, len)
7831349
784−readOutputQBounded :: MonadAsync m => Int -> SVar t m a -> m [ChildEvent a]
785−readOutputQBounded n sv = do
1350+readOutputQBounded :: MonadAsync m => SVar t m a -> m [ChildEvent a]
1351+readOutputQBounded sv = do
7861352 (list, len) <- liftIO $ readOutputQRaw sv
7871353 -- When there is no output seen we dispatch more workers to help
7881354 -- out if there is work pending in the work queue.
@@ -802,32 +1368,96 @@ readOutputQBounded n sv = do
8021368 cnt <- liftIO $ readIORef $ workerCount sv
8031369 when (cnt <= 0) $ do
8041370 done <- liftIO $ isWorkDone sv
805− when (not done) $ pushWorker sv
1371+ when (not done) $ pushWorker 0 sv
8061372
8071373 {-# INLINE blockingRead #-}
8081374 blockingRead = do
809− sendWorkerWait n sv
1375+ sendWorkerWait sendWorkerDelay (dispatchWorker 0) sv
1376+ liftIO $ (readOutputQRaw sv >>= return . fst)
1377+
1378+readOutputQPaced :: MonadAsync m => SVar t m a -> m [ChildEvent a]
1379+readOutputQPaced sv = do
1380+ (list, len) <- liftIO $ readOutputQRaw sv
1381+ if len <= 0
1382+ then blockingRead
1383+ else do
1384+ -- XXX send a worker proactively, if needed, even before we start
1385+ -- processing the output.
1386+ void $ dispatchWorkerPaced sv
1387+ return list
1388+
1389+ where
1390+
1391+ {-# INLINE blockingRead #-}
1392+ blockingRead = do
1393+ sendWorkerWait sendWorkerDelayPaced dispatchWorkerPaced sv
8101394 liftIO $ (readOutputQRaw sv >>= return . fst)
8111395
8121396postProcessBounded :: MonadAsync m => SVar t m a -> m Bool
8131397postProcessBounded sv = do
8141398 workersDone <- allThreadsDone sv
815− -- There may still be work pending even if there are no workers
816− -- pending because all the workers may return if the
817− -- outputQueue becomes full. In that case send off a worker to
818− -- kickstart the work again.
1399+ -- There may still be work pending even if there are no workers pending
1400+ -- because all the workers may return if the outputQueue becomes full. In
1401+ -- that case send off a worker to kickstart the work again.
1402+ if workersDone
1403+ then do
1404+ r <- liftIO $ isWorkDone sv
1405+ -- XXX do we need to dispatch many here?
1406+ when (not r) $ pushWorker 0 sv
1407+ -- void $ dispatchWorker sv
1408+ return r
1409+ else return False
1410+
1411+postProcessPaced :: MonadAsync m => SVar t m a -> m Bool
1412+postProcessPaced sv = do
1413+ workersDone <- allThreadsDone sv
1414+ -- XXX If during consumption we figure out we are getting delayed then we
1415+ -- should trigger dispatch there as well. We should try to check on the
1416+ -- workers after consuming every n item from the buffer?
8191417 if workersDone
8201418 then do
8211419 r <- liftIO $ isWorkDone sv
822− when (not r) $ pushWorker sv
1420+ when (not r) $ do
1421+ let yinfo = fromJust $ yieldRateInfo sv
1422+ liftIO $ adjustLatencies yinfo
1423+ void $ dispatchWorkerPaced sv
8231424 return r
8241425 else return False
8251426
1427+getYieldRateInfo :: State t m a -> IO (Maybe YieldRateInfo)
1428+getYieldRateInfo st = do
1429+ case getMaxStreamRate st of
1430+ Just rate -> do
1431+ let latency = round $ 1.0e9 / rate
1432+ measured <- newIORef 0
1433+ wcur <- newIORef (0,0)
1434+ wcol <- newIORef (0,0)
1435+ now <- getTime Monotonic
1436+ wlong <- newIORef (0,now)
1437+ period <- newIORef 1
1438+ stopTime <- newIORef $ fromNanoSecs 0
1439+ stopMVar <- newMVar ()
1440+
1441+ return $ Just YieldRateInfo
1442+ { expectedYieldLatency = latency
1443+ , workerBootstrapLatency = getStreamLatency st
1444+ , workerLatencyPeriod = period
1445+ , workerMeasuredLatency = measured
1446+ , workerCurrentLatency = wcur
1447+ , workerStopTimeStamp = stopTime
1448+ , workerCollectedLatency = wcol
1449+ , workerLongTermLatency = wlong
1450+ , workerStopMVar = stopMVar
1451+ }
1452+ Nothing -> return Nothing
1453+
8261454getAheadSVar :: MonadAsync m
8271455 => State t m a
828− -> ( State t m a
829− -> IORef ([t m a], Int)
1456+ -> ( IORef ([t m a], Int)
8301457 -> IORef (Heap (Entry Int (AheadHeapEntry t m a)), Int)
1458+ -> State t m a
1459+ -> SVar t m a
1460+ -> WorkerInfo
8311461 -> m ())
8321462 -> IO (SVar t m a)
8331463getAheadSVar st f = do
@@ -838,53 +1468,71 @@ getAheadSVar st f = do
8381468 wfw <- newIORef False
8391469 running <- newIORef S.empty
8401470 q <- newIORef ([], -1)
841− yl <- case yieldLimit st of
1471+ yl <- case getYieldLimit st of
8421472 Nothing -> return Nothing
8431473 Just x -> Just <$> newIORef x
1474+ rateInfo <- getYieldRateInfo st
8441475
8451476#ifdef DIAGNOSTICS
846− disp <- newIORef 0
1477+ disp <- newIORef 0
8471478 maxWrk <- newIORef 0
8481479 maxOq <- newIORef 0
8491480 maxHs <- newIORef 0
8501481 maxWq <- newIORef 0
8511482#endif
852− let sv =
853− SVar { outputQueue = outQ
854− , maxYieldLimit = yl
855− , outputDoorBell = outQMv
856− , readOutputQ = readOutputQBounded (threadsHigh st) sv
857− , postProcess = postProcessBounded sv
858− , workerThreads = running
859− -- , workLoop = workLoopAhead sv q outH
860− , workLoop = f st{streamVar = Just sv} q outH
861− , enqueue = enqueueAhead sv q
862− , isWorkDone = isWorkDoneAhead q outH
863− , needDoorBell = wfw
864− , svarStyle = AheadVar
865− , workerCount = active
866− , accountThread = delThread sv
1483+
1484+ let getSVar sv readOutput postProc = SVar
1485+ { outputQueue = outQ
1486+ , remainingYields = yl
1487+ , maxBufferLimit = getMaxBuffer st
1488+ , maxWorkerLimit = getMaxThreads st
1489+ , yieldRateInfo = rateInfo
1490+ , outputDoorBell = outQMv
1491+ , readOutputQ = readOutput sv
1492+ , postProcess = postProc sv
1493+ , workerThreads = running
1494+ , workLoop = f q outH st{streamVar = Just sv} sv
1495+ , enqueue = enqueueAhead sv q
1496+ , isWorkDone = isWorkDoneAhead sv q outH
1497+ , needDoorBell = wfw
1498+ , svarStyle = AheadVar
1499+ , workerCount = active
1500+ , accountThread = delThread sv
8671501#ifdef DIAGNOSTICS
868− , aheadWorkQueue = q
869− , outputHeap = outH
870− , totalDispatches = disp
871− , maxWorkers = maxWrk
872− , maxOutQSize = maxOq
873− , maxHeapSize = maxHs
874− , maxWorkQSize = maxWq
1502+ , aheadWorkQueue = q
1503+ , outputHeap = outH
1504+ , totalDispatches = disp
1505+ , maxWorkers = maxWrk
1506+ , maxOutQSize = maxOq
1507+ , maxHeapSize = maxHs
1508+ , maxWorkQSize = maxWq
8751509#endif
876− }
1510+ }
1511+
1512+ let sv =
1513+ case getMaxStreamRate st of
1514+ Nothing -> getSVar sv readOutputQBounded postProcessBounded
1515+ Just _ -> getSVar sv readOutputQPaced postProcessPaced
8771516 in return sv
8781517
8791518 where
8801519
8811520 {-# INLINE isWorkDoneAhead #-}
882− isWorkDoneAhead q ref = do
1521+ isWorkDoneAhead sv q ref = do
8831522 heapDone <- do
8841523 (hp, _) <- readIORef ref
8851524 return (H.size hp <= 0)
8861525 queueDone <- checkEmpty q
887− return $ queueDone && heapDone
1526+ yieldsDone <-
1527+ case remainingYields sv of
1528+ Just yref -> do
1529+ n <- readIORef yref
1530+ return (n <= 0)
1531+ Nothing -> return False
1532+ -- XXX note that yieldsDone can only be authoritative only when there
1533+ -- are no workers running. If there are active workers they can
1534+ -- increment the yield count and therefore change the result.
1535+ return $ yieldsDone || (queueDone && heapDone)
8881536
8891537 checkEmpty q = do
8901538 (xs, _) <- readIORef q
@@ -905,7 +1553,10 @@ getParallelSVar = do
9051553#endif
9061554 let sv =
9071555 SVar { outputQueue = outQ
908− , maxYieldLimit = Nothing
1556+ , remainingYields = Nothing
1557+ , maxBufferLimit = Unlimited
1558+ , maxWorkerLimit = undefined
1559+ , yieldRateInfo = Nothing
9091560 , outputDoorBell = outQMv
9101561 , readOutputQ = readOutputQPar sv
9111562 , postProcess = allThreadsDone sv
@@ -935,27 +1586,31 @@ getParallelSVar = do
9351586 withDBGMVar sv "readOutputQPar: doorbell" $ takeMVar (outputDoorBell sv)
9361587 readOutputQRaw sv >>= return . fst
9371588
938−sendWorker :: MonadAsync m => SVar t m a -> t m a -> m (SVar t m a)
939−sendWorker sv m = do
1589+sendFirstWorker :: MonadAsync m => SVar t m a -> t m a -> m (SVar t m a)
1590+sendFirstWorker sv m = do
9401591 -- Note: We must have all the work on the queue before sending the
9411592 -- pushworker, otherwise the pushworker may exit before we even get a
9421593 -- chance to push.
9431594 liftIO $ enqueue sv m
944− pushWorker sv
1595+ case yieldRateInfo sv of
1596+ Nothing -> pushWorker 0 sv
1597+ Just _ -> pushWorker 1 sv
9451598 return sv
9461599
9471600{-# INLINABLE newAheadVar #-}
9481601newAheadVar :: MonadAsync m
9491602 => State t m a
9501603 -> t m a
951− -> ( State t m a
952− -> IORef ([t m a], Int)
1604+ -> ( IORef ([t m a], Int)
9531605 -> IORef (Heap (Entry Int (AheadHeapEntry t m a)), Int)
1606+ -> State t m a
1607+ -> SVar t m a
1608+ -> WorkerInfo
9541609 -> m ())
9551610 -> m (SVar t m a)
9561611newAheadVar st m wloop = do
9571612 sv <- liftIO $ getAheadSVar st wloop
958− sendWorker sv m
1613+ sendFirstWorker sv m
9591614
9601615{-# INLINABLE newParallelVar #-}
9611616newParallelVar :: MonadAsync m => m (SVar t m a)
@@ -971,4 +1626,9 @@ toStreamVar sv m = do
9711626 -- XXX This is safe only when called from the consumer thread or when no
9721627 -- consumer is present. There may be a race if we are not running in the
9731628 -- consumer thread.
974− when done $ pushWorker sv
1629+ -- XXX do this only if the work queue is not empty. The work may have been
1630+ -- carried out by existing workers.
1631+ when done $
1632+ case yieldRateInfo sv of
1633+ Nothing -> pushWorker 0 sv
1634+ Just _ -> pushWorker 1 sv
src/Streamly/Streams/Ahead.hs+191−55
@@ -27,6 +27,7 @@ module Streamly.Streams.Ahead
2727 )
2828where
2929
30+import Control.Concurrent.MVar (putMVar, takeMVar)
3031import Control.Monad (ap)
3132import Control.Monad.Base (MonadBase(..), liftBaseDefault)
3233import Control.Monad.Catch (MonadThrow, throwM)
@@ -35,9 +36,8 @@ import Control.Monad.IO.Class (MonadIO(..))
3536import Control.Monad.Reader.Class (MonadReader(..))
3637import Control.Monad.State.Class (MonadState(..))
3738import Control.Monad.Trans.Class (MonadTrans(lift))
38−import Data.Atomics (atomicModifyIORefCAS_)
3939import Data.Heap (Heap, Entry(..))
40−import Data.IORef (IORef, readIORef)
40+import Data.IORef (IORef, readIORef, atomicModifyIORef)
4141import Data.Maybe (fromJust)
4242import Data.Semigroup (Semigroup(..))
4343
@@ -113,88 +113,197 @@ import Prelude hiding (map)
113113-- each left associative expression. The queue is used only for right
114114-- associated expression, we queue the right expression and execute the left.
115115-- Thererefore the queue never has more than on item in it.
116+--
117+-- XXX Also note that limiting concurrency for cases like "take 10" would not
118+-- work well with left associative expressions, because we have no visibility
119+-- about how much the left side of the expression would yield.
120+--
121+-- XXX It may be a good idea to increment sequence numbers for each yield,
122+-- currently a stream on the left side of the expression may yield many
123+-- elements with the same sequene number. We can then use the seq number to
124+-- enforce yieldMax and yieldLImit as well.
125+
126+-- XXX the only difference between this and workerRateControl is that this
127+-- updates the heap count and the other updates the yield count.
128+-- we can pass that as an update function instead.
116129
117130workLoopAhead :: MonadIO m
118− => State Stream m a
119− -> IORef ([Stream m a], Int)
131+ => IORef ([Stream m a], Int)
120132 -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)) , Int)
133+ -> State Stream m a
134+ -> SVar Stream m a
135+ -> WorkerInfo
121136 -> m ()
122−workLoopAhead st q heap = runHeap
137+workLoopAhead q heap st sv winfo = runHeap
123138
124139 where
125140
126− sv = fromJust $ streamVar st
127− maxBuf = bufferHigh st
141+ maxBuf = maxBufferLimit sv
142+
143+ underMaxHeap hp = do
144+ (_, len) <- liftIO $ readIORef (outputQueue sv)
145+
146+ -- XXX simplify this
147+ let maxHeap = case maxBuf of
148+ Limited lim -> Limited $
149+ if (fromIntegral lim) >= len
150+ then lim - (fromIntegral len)
151+ else 0
152+ Unlimited -> Unlimited
153+
154+ case maxHeap of
155+ Limited lim -> do
156+ active <- liftIO $ readIORef (workerCount sv)
157+ return $ H.size hp + active <= (fromIntegral lim)
158+ Unlimited -> return True
159+
160+ -- XXX to reduce contention each CPU can have its own heap
128161 toHeap seqNo ent = do
129− hp <- liftIO $ atomicModifyIORefCAS heap $ \(h, snum) ->
162+ -- Heap insertion is an expensive affair so we use a non CAS based
163+ -- modification, otherwise contention and retries can make a thread
164+ -- context switch and throw it behind other threads which come later in
165+ -- sequence.
166+ hp <- liftIO $ atomicModifyIORef heap $ \(h, snum) ->
130167 ((H.insert (Entry seqNo ent) h, snum), h)
131− (_, len) <- liftIO $ readIORef (outputQueue sv)
132− let maxHeap = maxBuf - len
133− limit <- case maxYieldLimit sv of
134− Nothing -> return maxHeap
135− Just ref -> do
136− r <- liftIO $ readIORef ref
137− return $ if r >= 0 then r else maxHeap
138− if H.size hp <= limit
139− then runHeap
140− else liftIO $ sendStop sv
168+ -- XXX heap check should also be under MVar
169+ heapOk <- underMaxHeap hp
170+ if heapOk
171+ then
172+ case yieldRateInfo sv of
173+ Nothing -> runHeap
174+ Just yinfo -> do
175+ -- check the stop condition under a lock before actually
176+ -- stopping so that the whole herd does not stop at once.
177+ liftIO $ takeMVar (workerStopMVar yinfo)
178+ rateOk <- liftIO $ workerRateControl sv yinfo winfo
179+ if rateOk
180+ then do
181+ liftIO $ putMVar (workerStopMVar yinfo) ()
182+ runHeap
183+ else do
184+ liftIO $ sendStop sv winfo
185+ liftIO $ putMVar (workerStopMVar yinfo) ()
186+ else liftIO $ sendStop sv winfo
141187
142188 singleToHeap seqNo a = toHeap seqNo (AheadEntryPure a)
143189 yieldToHeap seqNo a r = toHeap seqNo (AheadEntryStream (a `K.cons` r))
144190
145191 singleOutput seqNo a = do
146− continue <- liftIO $ sendYield maxBuf sv (ChildYield a)
192+ continue <- liftIO $ sendYield sv winfo (ChildYield a)
147193 if continue
148194 then runQueueToken seqNo
149195 else liftIO $ do
150− atomicModifyIORefCAS_ heap $ \(h, _) -> (h, seqNo + 1)
151− sendStop sv
196+ atomicModifyIORef heap $ \(h, _) -> ((h, seqNo + 1), ())
197+ sendStop sv winfo
152198
153− yieldOutput seqNo a r = do
154− continue <- liftIO $ sendYield maxBuf sv (ChildYield a)
199+ singleFromHeap seqNo a = do
200+ continue <- liftIO $ send sv (ChildYield a)
201+ liftIO $ atomicModifyIORef heap $ \(h, _) -> ((h, seqNo + 1), ())
202+ if continue
203+ then runHeap
204+ else liftIO $ sendStop sv winfo
205+
206+ singleStreamFromHeap seqNo a = do
207+ continue <- liftIO $ sendYield sv winfo (ChildYield a)
208+ liftIO $ atomicModifyIORef heap $ \(h, _) -> ((h, seqNo + 1), ())
155209 if continue
156− then unStream r st (runQueueToken seqNo)
210+ then runHeap
211+ else liftIO $ sendStop sv winfo
212+
213+ -- XXX use a wrapper function around stop so that we never miss
214+ -- incrementing the yield in a stop continuation. Essentiatlly all
215+ -- "unstream" calls in this function must increment yield limit on stop.
216+ yieldOutput seqNo a r = do
217+ continue <- liftIO $ sendYield sv winfo (ChildYield a)
218+ yieldLimitOk <- liftIO $ decrementYieldLimit sv
219+ if continue && yieldLimitOk
220+ then do
221+ let stop = liftIO (incrementYieldLimit sv) >> runQueueToken seqNo
222+ unStream r st stop
157223 (singleOutput seqNo)
158224 (yieldOutput seqNo)
159225 else liftIO $ do
160− atomicModifyIORefCAS_ heap $ \(h, _) ->
161− (H.insert (Entry seqNo (AheadEntryStream r)) h, seqNo)
162− sendStop sv
226+ atomicModifyIORef heap $ \(h, _) ->
227+ ((H.insert (Entry seqNo (AheadEntryStream r)) h, seqNo), ())
228+ incrementYieldLimit sv
229+ sendStop sv winfo
230+
231+ yieldStreamFromHeap seqNo a r = do
232+ continue <- liftIO $ sendYield sv winfo (ChildYield a)
233+ yieldLimitOk <- liftIO $ decrementYieldLimit sv
234+ if continue && yieldLimitOk
235+ then do
236+ let stop = do
237+ liftIO $ atomicModifyIORef heap $ \(h, _) ->
238+ ((h, seqNo + 1), ())
239+ liftIO (incrementYieldLimit sv)
240+ runHeap
241+ unStream r st stop
242+ (singleStreamFromHeap seqNo)
243+ (yieldStreamFromHeap seqNo)
244+ else liftIO $ do
245+ atomicModifyIORef heap $ \(h, _) ->
246+ ((H.insert (Entry seqNo (AheadEntryStream r)) h, seqNo), ())
247+ incrementYieldLimit sv
248+ sendStop sv winfo
163249
164250 {-# INLINE runQueueToken #-}
165251 runQueueToken prevSeqNo = do
166252 work <- dequeueAhead q
167253 case work of
168254 Nothing -> do
169− liftIO $ atomicModifyIORefCAS_ heap $ \(h, _) ->
170− (h, prevSeqNo + 1)
255+ liftIO $ atomicModifyIORef heap $ \(h, _) ->
256+ ((h, prevSeqNo + 1), ())
171257 runHeap
172258 Just (m, seqNo) -> do
173− if seqNo == prevSeqNo + 1
174− then
175− unStream m st (runQueueToken seqNo)
176− (singleOutput seqNo)
177− (yieldOutput seqNo)
178− else do
179− liftIO $ atomicModifyIORefCAS_ heap $ \(h, _) ->
180− (h, prevSeqNo + 1)
181− unStream m st runHeap
182− (singleToHeap seqNo)
183− (yieldToHeap seqNo)
259+ yieldLimitOk <- liftIO $ decrementYieldLimit sv
260+ if yieldLimitOk
261+ then do
262+ if seqNo == prevSeqNo + 1
263+ then do
264+ let stop = liftIO (incrementYieldLimit sv)
265+ >> runQueueToken seqNo
266+ unStream m st stop
267+ (singleOutput seqNo)
268+ (yieldOutput seqNo)
269+ else do
270+ liftIO $ atomicModifyIORef heap $ \(h, _) ->
271+ ((h, prevSeqNo + 1), ())
272+ let stop = liftIO (incrementYieldLimit sv)
273+ >> runHeap
274+ unStream m st stop
275+ (singleToHeap seqNo)
276+ (yieldToHeap seqNo)
277+ else liftIO $ do
278+ atomicModifyIORef heap $ \(h, _) ->
279+ ((h, prevSeqNo + 1), ())
280+ incrementYieldLimit sv
281+ sendStop sv winfo
282+
184283 runQueueNoToken = do
185284 work <- dequeueAhead q
186285 case work of
187286 Nothing -> runHeap
188287 Just (m, seqNo) -> do
189− if seqNo == 0
190− then
191− unStream m st (runQueueToken seqNo)
192− (singleOutput seqNo)
193− (yieldOutput seqNo)
194− else
195− unStream m st runHeap
196− (singleToHeap seqNo)
197− (yieldToHeap seqNo)
288+ yieldLimitOk <- liftIO $ decrementYieldLimit sv
289+ if yieldLimitOk
290+ then do
291+ if seqNo == 0
292+ then do
293+ let stop = liftIO (incrementYieldLimit sv)
294+ >> runQueueToken seqNo
295+ unStream m st stop
296+ (singleOutput seqNo)
297+ (yieldOutput seqNo)
298+ else do
299+ let stop = liftIO (incrementYieldLimit sv)
300+ >> runHeap
301+ unStream m st stop
302+ (singleToHeap seqNo)
303+ (yieldToHeap seqNo)
304+ else liftIO $ do
305+ incrementYieldLimit sv
306+ sendStop sv winfo
198307
199308 {-# NOINLINE runHeap #-}
200309 runHeap = do
@@ -207,17 +316,44 @@ workLoopAhead st q heap = runHeap
207316 ent <- liftIO $ dequeueFromHeap heap
208317 case ent of
209318 Nothing -> do
319+ -- Before we pick up the next item from the work queue we check
320+ -- if we are beyond the yield limit. It is better to check the
321+ -- yield limit before we pick up the next item. Otherwise we
322+ -- may have already started more tasks even though we may have
323+ -- reached the yield limit. We can avoid this by taking active
324+ -- workers into account, but that is not as reliable, because
325+ -- workers may go away without picking up work and yielding a
326+ -- value.
327+ --
328+ -- XXX do the same for Async stream as well.
329+ --
330+ -- Rate control can be done either based on actual yields in
331+ -- the output queue or based on any yield either to the heap or
332+ -- to the output queue. In both cases we may have one issue or
333+ -- the other. We chose to do this based on actual yields to the
334+ -- output queue because it makes the code common to both async
335+ -- and ahead streams.
336+ --
210337 done <- queueEmptyAhead q
211338 if done
212− then liftIO $ sendStop sv
339+ -- XXX need to run heap here if not empty
340+ then liftIO $ sendStop sv winfo
213341 else runQueueNoToken
214342 Just (Entry seqNo hent) -> do
215343 case hent of
216− AheadEntryPure a -> singleOutput seqNo a
217− AheadEntryStream r ->
218− unStream r st (runQueueToken seqNo)
219− (singleOutput seqNo)
220− (yieldOutput seqNo)
344+ -- Do not account this in worker latency as this will not
345+ -- be the real latency.
346+ AheadEntryPure a -> singleFromHeap seqNo a
347+ AheadEntryStream r -> do
348+ let stp = do
349+ liftIO $ atomicModifyIORef heap $ \(h, _) ->
350+ ((h, seqNo + 1), ())
351+ liftIO (incrementYieldLimit sv)
352+ runHeap
353+
354+ unStream r st stp
355+ (singleStreamFromHeap seqNo)
356+ (yieldStreamFromHeap seqNo)
221357
222358-------------------------------------------------------------------------------
223359-- WAhead
src/Streamly/Streams/Async.hs+186−84
@@ -4,6 +4,7 @@
44{-# LANGUAGE FlexibleInstances #-}
55{-# LANGUAGE GeneralizedNewtypeDeriving#-}
66{-# LANGUAGE InstanceSigs #-}
7+{-# LANGUAGE LambdaCase #-}
78{-# LANGUAGE MultiParamTypeClasses #-}
89{-# LANGUAGE StandaloneDeriving #-}
910{-# LANGUAGE UndecidableInstances #-} -- XXX
@@ -44,7 +45,7 @@ import Control.Monad.IO.Class (MonadIO(..))
4445import Control.Monad.Reader.Class (MonadReader(..))
4546import Control.Monad.State.Class (MonadState(..))
4647import Control.Monad.Trans.Class (MonadTrans(lift))
47−import Data.Concurrent.Queue.MichaelScott (LinkedQueue, newQ, nullQ)
48+import Data.Concurrent.Queue.MichaelScott (LinkedQueue, newQ, nullQ, tryPopR)
4849import Data.IORef (IORef, newIORef, readIORef)
4950import Data.Maybe (fromJust)
5051import Data.Semigroup (Semigroup(..))
@@ -64,45 +65,108 @@ import qualified Streamly.Streams.StreamK as K
6465-- Async
6566-------------------------------------------------------------------------------
6667
67−{-# INLINE runStreamLIFO #-}
68−runStreamLIFO :: MonadIO m
69− => State Stream m a -> IORef [Stream m a] -> Stream m a -> m () -> m ()
70−runStreamLIFO st q m stop = unStream m st stop single yieldk
68+{-# INLINE workLoopLIFO #-}
69+workLoopLIFO
70+ :: MonadIO m
71+ => IORef [Stream m a]
72+ -> State Stream m a
73+ -> SVar Stream m a
74+ -> WorkerInfo
75+ -> m ()
76+workLoopLIFO q st sv winfo = run
77+
7178 where
72− sv = fromJust $ streamVar st
73− maxBuf = bufferHigh st
79+
80+ run = do
81+ work <- dequeue
82+ case work of
83+ Nothing -> liftIO $ sendStop sv winfo
84+ Just m -> do
85+ -- XXX This is just a best effort minimization of concurrency
86+ -- to the yield limit. If the stream is made of concurrent
87+ -- streams we do not reserve the yield limit in the constituent
88+ -- streams before executing the action. This can be done
89+ -- though, by sharing the yield limit ref with downstream
90+ -- actions via state passing. Just a todo.
91+ yieldLimitOk <- liftIO $ decrementYieldLimit sv
92+ if yieldLimitOk
93+ then do
94+ -- XXX increment is not safe when the yield limit was already 0
95+ -- and therefore was not decremented. We need yield limit to be
96+ -- int and negative if we want this to work.
97+ let stop = liftIO (incrementYieldLimit sv) >> run
98+ unStream m st stop single yieldk
99+ -- Avoid any side effects, undo the yield limit decrement if we
100+ -- never yielded anything.
101+ else liftIO $ do
102+ incrementYieldLimit sv
103+ sendStop sv winfo
104+
74105 single a = do
75− res <- liftIO $ sendYield maxBuf sv (ChildYield a)
76− if res then stop else liftIO $ sendStop sv
106+ res <- liftIO $ sendYield sv winfo (ChildYield a)
107+ if res then run else liftIO $ sendStop sv winfo
108+
109+ -- XXX can we pass on the yield limit downstream to limit the concurrency
110+ -- of constituent streams.
77111 yieldk a r = do
78− res <- liftIO $ sendYield maxBuf sv (ChildYield a)
79− if res
80− then (unStream r) st stop single yieldk
81− else liftIO $ enqueueLIFO sv q r >> sendStop sv
112+ res <- liftIO $ sendYield sv winfo (ChildYield a)
113+ yieldLimitOk <- liftIO $ decrementYieldLimit sv
114+ let stop = liftIO (incrementYieldLimit sv) >> run
115+ if res && yieldLimitOk
116+ then unStream r st stop single yieldk
117+ else liftIO $ do
118+ incrementYieldLimit sv
119+ enqueueLIFO sv q r
120+ sendStop sv winfo
121+
122+ dequeue = liftIO $ atomicModifyIORefCAS q $ \case
123+ [] -> ([], Nothing)
124+ x : xs -> (xs, Just x)
82125
83126-------------------------------------------------------------------------------
84127-- WAsync
85128-------------------------------------------------------------------------------
86129
87−{-# INLINE runStreamFIFO #-}
88−runStreamFIFO
130+{-# INLINE workLoopFIFO #-}
131+workLoopFIFO
89132 :: MonadIO m
90− => State Stream m a
91− -> LinkedQueue (Stream m a)
92− -> Stream m a
133+ => LinkedQueue (Stream m a)
134+ -> State Stream m a
135+ -> SVar Stream m a
136+ -> WorkerInfo
93137 -> m ()
94− -> m ()
95−runStreamFIFO st q m stop = unStream m st stop single yieldk
138+workLoopFIFO q st sv winfo = run
139+
96140 where
97− sv = fromJust $ streamVar st
98− maxBuf = bufferHigh st
141+
142+ run = do
143+ work <- liftIO $ tryPopR q
144+ case work of
145+ Nothing -> liftIO $ sendStop sv winfo
146+ Just m -> do
147+ yieldLimitOk <- liftIO $ decrementYieldLimit sv
148+ if yieldLimitOk
149+ then do
150+ let stop = liftIO (incrementYieldLimit sv) >> run
151+ unStream m st stop single yieldk
152+ else liftIO $ do
153+ incrementYieldLimit sv
154+ sendStop sv winfo
155+
99156 single a = do
100− res <- liftIO $ sendYield maxBuf sv (ChildYield a)
101− if res then stop else liftIO $ sendStop sv
157+ res <- liftIO $ sendYield sv winfo (ChildYield a)
158+ if res then run else liftIO $ sendStop sv winfo
159+
102160 yieldk a r = do
103− res <- liftIO $ sendYield maxBuf sv (ChildYield a)
104− liftIO (enqueueFIFO sv q r)
105− if res then stop else liftIO $ sendStop sv
161+ res <- liftIO $ sendYield sv winfo (ChildYield a)
162+ yieldLimitOk <- liftIO $ decrementYieldLimit sv
163+ let stop = liftIO (incrementYieldLimit sv) >> run
164+ if res && yieldLimitOk
165+ then unStream r st stop single yieldk
166+ else liftIO $ do
167+ incrementYieldLimit sv
168+ enqueueFIFO sv q r
169+ sendStop sv winfo
106170
107171-------------------------------------------------------------------------------
108172-- SVar creation
@@ -120,43 +184,61 @@ getLifoSVar st = do
120184 active <- newIORef 0
121185 wfw <- newIORef False
122186 running <- newIORef S.empty
123− q <- newIORef []
124− yl <- case yieldLimit st of
125− Nothing -> return Nothing
126− Just x -> Just <$> newIORef x
187+ q <- newIORef []
188+ yl <- case getYieldLimit st of
189+ Nothing -> return Nothing
190+ Just x -> Just <$> newIORef x
191+ rateInfo <- getYieldRateInfo st
192+
127193#ifdef DIAGNOSTICS
128− disp <- newIORef 0
194+ disp <- newIORef 0
129195 maxWrk <- newIORef 0
130196 maxOq <- newIORef 0
131197 maxHs <- newIORef 0
132198 maxWq <- newIORef 0
133199#endif
134− let checkEmpty = null <$> readIORef q
135− let sv =
136− SVar { outputQueue = outQ
137− , maxYieldLimit = yl
138− , outputDoorBell = outQMv
139− , readOutputQ = readOutputQBounded (threadsHigh st) sv
140− , postProcess = postProcessBounded sv
141− , workerThreads = running
142− , workLoop = workLoopLIFO runStreamLIFO
143− st{streamVar = Just sv} q
144− , enqueue = enqueueLIFO sv q
145− , isWorkDone = checkEmpty
146− , needDoorBell = wfw
147− , svarStyle = AsyncVar
148− , workerCount = active
149− , accountThread = delThread sv
200+ let isWorkFinished sv = do
201+ yieldsDone <-
202+ case remainingYields sv of
203+ Just ref -> do
204+ n <- readIORef ref
205+ return (n <= 0)
206+ Nothing -> return False
207+ qEmpty <- null <$> readIORef q
208+ return $ qEmpty || yieldsDone
209+
210+ let getSVar sv readOutput postProc = SVar
211+ { outputQueue = outQ
212+ , remainingYields = yl
213+ , maxBufferLimit = getMaxBuffer st
214+ , maxWorkerLimit = getMaxThreads st
215+ , yieldRateInfo = rateInfo
216+ , outputDoorBell = outQMv
217+ , readOutputQ = readOutput sv
218+ , postProcess = postProc sv
219+ , workerThreads = running
220+ , workLoop = workLoopLIFO q st{streamVar = Just sv} sv
221+ , enqueue = enqueueLIFO sv q
222+ , isWorkDone = isWorkFinished sv
223+ , needDoorBell = wfw
224+ , svarStyle = AsyncVar
225+ , workerCount = active
226+ , accountThread = delThread sv
150227#ifdef DIAGNOSTICS
151− , aheadWorkQueue = undefined
152− , outputHeap = undefined
153− , maxWorkers = maxWrk
154− , totalDispatches = disp
155− , maxOutQSize = maxOq
156− , maxHeapSize = maxHs
157− , maxWorkQSize = maxWq
228+ , aheadWorkQueue = undefined
229+ , outputHeap = undefined
230+ , maxWorkers = maxWrk
231+ , totalDispatches = disp
232+ , maxOutQSize = maxOq
233+ , maxHeapSize = maxHs
234+ , maxWorkQSize = maxWq
158235#endif
159− }
236+ }
237+
238+ let sv =
239+ case getMaxStreamRate st of
240+ Nothing -> getSVar sv readOutputQBounded postProcessBounded
241+ Just _ -> getSVar sv readOutputQPaced postProcessPaced
160242 in return sv
161243
162244getFifoSVar :: MonadAsync m => State Stream m a -> IO (SVar Stream m a)
@@ -167,9 +249,11 @@ getFifoSVar st = do
167249 wfw <- newIORef False
168250 running <- newIORef S.empty
169251 q <- newQ
170− yl <- case yieldLimit st of
171− Nothing -> return Nothing
172− Just x -> Just <$> newIORef x
252+ yl <- case getYieldLimit st of
253+ Nothing -> return Nothing
254+ Just x -> Just <$> newIORef x
255+ rateInfo <- getYieldRateInfo st
256+
173257#ifdef DIAGNOSTICS
174258 disp <- newIORef 0
175259 maxWrk <- newIORef 0
@@ -177,31 +261,49 @@ getFifoSVar st = do
177261 maxHs <- newIORef 0
178262 maxWq <- newIORef 0
179263#endif
180− let sv =
181− SVar { outputQueue = outQ
182− , maxYieldLimit = yl
183− , outputDoorBell = outQMv
184− , readOutputQ = readOutputQBounded (threadsHigh st) sv
185− , postProcess = postProcessBounded sv
186− , workerThreads = running
187− , workLoop = workLoopFIFO runStreamFIFO
188− st{streamVar = Just sv} q
189− , enqueue = enqueueFIFO sv q
190− , isWorkDone = nullQ q
191− , needDoorBell = wfw
192− , svarStyle = WAsyncVar
193− , workerCount = active
194− , accountThread = delThread sv
264+
265+ let isWorkFinished sv = do
266+ yieldsDone <-
267+ case remainingYields sv of
268+ Just ref -> do
269+ n <- readIORef ref
270+ return (n <= 0)
271+ Nothing -> return False
272+ qEmpty <- nullQ q
273+ return $ qEmpty || yieldsDone
274+
275+ let getSVar sv readOutput postProc = SVar
276+ { outputQueue = outQ
277+ , remainingYields = yl
278+ , maxBufferLimit = getMaxBuffer st
279+ , maxWorkerLimit = getMaxThreads st
280+ , yieldRateInfo = rateInfo
281+ , outputDoorBell = outQMv
282+ , readOutputQ = readOutput sv
283+ , postProcess = postProc sv
284+ , workerThreads = running
285+ , workLoop = workLoopFIFO q st{streamVar = Just sv} sv
286+ , enqueue = enqueueFIFO sv q
287+ , isWorkDone = isWorkFinished sv
288+ , needDoorBell = wfw
289+ , svarStyle = WAsyncVar
290+ , workerCount = active
291+ , accountThread = delThread sv
195292#ifdef DIAGNOSTICS
196− , aheadWorkQueue = undefined
197− , outputHeap = undefined
198− , totalDispatches = disp
199− , maxWorkers = maxWrk
200− , maxOutQSize = maxOq
201− , maxHeapSize = maxHs
202− , maxWorkQSize = maxWq
293+ , aheadWorkQueue = undefined
294+ , outputHeap = undefined
295+ , totalDispatches = disp
296+ , maxWorkers = maxWrk
297+ , maxOutQSize = maxOq
298+ , maxHeapSize = maxHs
299+ , maxWorkQSize = maxWq
203300#endif
204− }
301+ }
302+
303+ let sv =
304+ case getMaxStreamRate st of
305+ Nothing -> getSVar sv readOutputQBounded postProcessBounded
306+ Just _ -> getSVar sv readOutputQPaced postProcessPaced
205307 in return sv
206308
207309{-# INLINABLE newAsyncVar #-}
@@ -209,7 +311,7 @@ newAsyncVar :: MonadAsync m
209311 => State Stream m a -> Stream m a -> m (SVar Stream m a)
210312newAsyncVar st m = do
211313 sv <- liftIO $ getLifoSVar st
212− sendWorker sv m
314+ sendFirstWorker sv m
213315
214316-- XXX Get rid of this?
215317-- | Make a stream asynchronous, triggers the computation and returns a stream
@@ -233,7 +335,7 @@ newWAsyncVar :: MonadAsync m
233335 => State Stream m a -> Stream m a -> m (SVar Stream m a)
234336newWAsyncVar st m = do
235337 sv <- liftIO $ getFifoSVar st
236− sendWorker sv m
338+ sendFirstWorker sv m
237339
238340------------------------------------------------------------------------------
239341-- Running streams concurrently
src/Streamly/Streams/Parallel.hs+8−2
@@ -43,9 +43,11 @@ import Control.Monad.Reader.Class (MonadReader(..))
4343import Control.Monad.State.Class (MonadState(..))
4444import Control.Monad.Trans.Class (MonadTrans(lift))
4545import Data.Functor (void)
46+import Data.IORef (newIORef)
4647import Data.Maybe (fromJust)
4748import Data.Semigroup (Semigroup(..))
4849import Prelude hiding (map)
50+import System.Clock
4951
5052import Streamly.Streams.SVar (fromSVar)
5153import Streamly.Streams.Serial (map)
@@ -65,9 +67,13 @@ runOne st m = unStream m st stop single yieldk
6567
6668 where
6769
70+ winfo = do
71+ yc <- newIORef 0
72+ wls <- newIORef (0, fromNanoSecs 0)
73+ return $ WorkerInfo 0 yc wls
6874 sv = fromJust $ streamVar st
69− stop = liftIO $ sendStop sv
70− sendit a = liftIO $ sendYield (-1) sv (ChildYield a)
75+ stop = liftIO $ winfo >>= sendStop sv
76+ sendit a = liftIO $ winfo >>= \x -> sendYield sv x (ChildYield a)
7177 single a = sendit a >> stop
7278 -- XXX there is no flow control in parallel case. We should perhaps use a
7379 -- queue and queue it back on that and exit the thread when the outputQueue
src/Streamly/Streams/SVar.hs+60−10
@@ -28,10 +28,15 @@ module Streamly.Streams.SVar
2828 , maxThreads
2929 , maxBuffer
3030 , maxYields
31+ , maxRate
3132 )
3233where
3334
3435import Control.Monad.Catch (throwM)
36+#ifdef DIAGNOSTICS_VERBOSE
37+import Control.Monad.IO.Class (liftIO)
38+#endif
39+import Data.Int (Int64)
3540
3641import Streamly.SVar
3742import Streamly.Streams.StreamK
@@ -73,8 +78,11 @@ fromStreamVar sv = Stream $ \st stp sng yld -> do
7378 processEvents (ev : es) = Stream $ \st stp sng yld -> do
7479 let rest = processEvents es
7580 case ev of
76− ChildYield a -> yld a rest
81+ ChildYield a -> do
82+-- void $ dispatchWorkerPaced sv
83+ yld a rest
7784 ChildStop tid e -> do
85+ -- void $ dispatchWorkerPaced sv
7886 accountThread sv tid
7987 case e of
8088 Nothing -> unStream rest (rstState st) stp sng yld
@@ -96,8 +104,10 @@ toSVar sv m = toStreamVar sv (toStream m)
96104-- XXX need to write these in direct style otherwise they will break fusion.
97105--
98106-- | Specify the maximum number of threads that can be spawned concurrently
99−-- when using concurrent streams. This is not the grand total number of threads
100−-- but the maximum number of threads at each point of concurrency.
107+-- when using concurrent streams. This values denotes maximum concurrent
108+-- requests, or tasks in progress at any given point of time. Note that this is
109+-- not the grand total number of threads but maximum threads at an individual
110+-- point of concurrency.
101111-- A value of 0 resets the thread limit to default, a negative value means
102112-- there is no limit. The default value is 1500.
103113--
@@ -105,8 +115,7 @@ toSVar sv m = toStreamVar sv (toStream m)
105115{-# INLINE_NORMAL maxThreads #-}
106116maxThreads :: IsStream t => Int -> t m a -> t m a
107117maxThreads n m = fromStream $ Stream $ \st stp sng yld -> do
108− let n' = if n == 0 then defaultMaxThreads else n
109− unStream (toStream m) (st {threadsHigh = n'}) stp sng yld
118+ unStream (toStream m) (setMaxThreads n st) stp sng yld
110119
111120{-# RULES "maxThreadsSerial serial" maxThreads = maxThreadsSerial #-}
112121maxThreadsSerial :: Int -> SerialT m a -> SerialT m a
@@ -118,26 +127,67 @@ maxThreadsSerial _ = id
118127-- A value of 0 resets the buffer size to default, a negative value means
119128-- there is no limit. The default value is 1500.
120129--
130+-- CAUTION! using an unbounded 'maxBuffer' value (i.e. a negative value)
131+-- coupled with an unbounded 'maxThreads' value is a recipe for disaster in
132+-- presence of infinite streams, or very large streams. Especially, it must
133+-- not be used when 'pure' is used in 'ZipAsyncM' streams as 'pure' in
134+-- applicative zip streams generates an infinite stream causing unbounded
135+-- concurrent generation with no limit on the buffer or threads.
136+--
121137-- @since 0.4.0
122138{-# INLINE_NORMAL maxBuffer #-}
123139maxBuffer :: IsStream t => Int -> t m a -> t m a
124140maxBuffer n m = fromStream $ Stream $ \st stp sng yld -> do
125− let n' = if n == 0 then defaultMaxBuffer else n
126− unStream (toStream m) (st {bufferHigh = n'}) stp sng yld
141+ unStream (toStream m) (setMaxBuffer n st) stp sng yld
127142
128143{-# RULES "maxBuffer serial" maxBuffer = maxBufferSerial #-}
129144maxBufferSerial :: Int -> SerialT m a -> SerialT m a
130145maxBufferSerial _ = id
131146
147+-- | Specify the maximum rate in number of yields per second at which the
148+-- stream can be generated. A value of 0 resets the rate to default, a negative
149+-- value means there is no limit. The default value is no limit.
150+--
151+-- @since 0.5.0
152+{-# INLINE_NORMAL maxRate #-}
153+maxRate :: IsStream t => Double -> t m a -> t m a
154+maxRate n m = fromStream $ Stream $ \st stp sng yld -> do
155+ unStream (toStream m) (setMaxStreamRate n st) stp sng yld
156+
157+{-# RULES "maxRate serial" maxRate = maxRateSerial #-}
158+maxRateSerial :: Double -> SerialT m a -> SerialT m a
159+maxRateSerial _ = id
160+
161+-- | Specify the average latency, in nanoseconds, of a single threaded action
162+-- in a concurrent composition. Streamly can measure the latencies, but that is
163+-- possible only after at least one task has completed. This combinator can be
164+-- used to provide a latency hint so that rate control using 'maxRate' can take
165+-- that into account right from the beginning. When not specified then a
166+-- default behavior is chosen which could be too slow or too fast, and would be
167+-- restricted by any other control parameters configured.
168+-- A value of 0 indicates default behavior, a negative value means there is no
169+-- limit i.e. zero latency.
170+-- This would normally be useful only in high latency and high throughput
171+-- cases.
172+--
173+{-# INLINE_NORMAL serialLatency #-}
174+serialLatency :: IsStream t => Int -> t m a -> t m a
175+serialLatency n m = fromStream $ Stream $ \st stp sng yld -> do
176+ unStream (toStream m) (setStreamLatency n st) stp sng yld
177+
178+{-# RULES "serialLatency serial" serialLatency = serialLatencySerial #-}
179+serialLatencySerial :: Int -> SerialT m a -> SerialT m a
180+serialLatencySerial _ = id
181+
132182-- Stop concurrent dispatches after this limit. This is useful in API's like
133183-- "take" where we want to dispatch only upto the number of elements "take"
134184-- needs. This value applies only to the immediate next level and is not
135185-- inherited by everything in enclosed scope.
136186{-# INLINE_NORMAL maxYields #-}
137−maxYields :: IsStream t => Maybe Int -> t m a -> t m a
187+maxYields :: IsStream t => Maybe Int64 -> t m a -> t m a
138188maxYields n m = fromStream $ Stream $ \st stp sng yld -> do
139− unStream (toStream m) (st {yieldLimit = n}) stp sng yld
189+ unStream (toStream m) (setYieldLimit n st) stp sng yld
140190
141191{-# RULES "maxYields serial" maxYields = maxYieldsSerial #-}
142−maxYieldsSerial :: Maybe Int -> SerialT m a -> SerialT m a
192+maxYieldsSerial :: Maybe Int64 -> SerialT m a -> SerialT m a
143193maxYieldsSerial _ = id
stack.yaml+1−1
@@ -5,7 +5,7 @@ allow-newer: true
55extra-deps:
66 - SDL-0.6.6.0
77 - gauge-0.2.3
8− - bench-graph-0.1.1
8+ - bench-graph-0.1.3
99 - Chart-1.9
1010 - Chart-diagrams-1.9
1111 - SVGFonts-1.6.0.3
streamly.cabal+23−4
@@ -173,6 +173,7 @@ library
173173 -- concurrency
174174 , atomic-primops >= 0.8 && < 0.9
175175 , lockfree-queue >= 0.2.3 && < 0.3
176+ , clock >= 0.7.1 && < 0.8
176177
177178 -- transfomers
178179 , exceptions >= 0.8 && < 0.11
@@ -193,7 +194,7 @@ test-suite test
193194 type: exitcode-stdio-1.0
194195 main-is: Main.hs
195196 hs-source-dirs: test
196− ghc-options: -O0 -Wall -threaded -with-rtsopts=-N
197+ ghc-options: -O2 -Wall -threaded -with-rtsopts=-N
197198 if flag(dev)
198199 ghc-options: -Wmissed-specialisations
199200 -Wall-missed-specialisations
@@ -220,7 +221,7 @@ test-suite properties
220221 type: exitcode-stdio-1.0
221222 main-is: Prop.hs
222223 hs-source-dirs: test
223− ghc-options: -O0 -Wall -threaded -with-rtsopts=-N4
224+ ghc-options: -O2 -Wall -threaded -with-rtsopts=-N
224225 if flag(dev)
225226 cpp-options: -DDEVBUILD
226227 ghc-options: -Wmissed-specialisations
@@ -241,6 +242,23 @@ test-suite properties
241242 , hspec >= 2.0 && < 3
242243 default-language: Haskell2010
243244
245+test-suite maxrate
246+ type: exitcode-stdio-1.0
247+ default-language: Haskell2010
248+ main-is: MaxRate.hs
249+ hs-source-dirs: test
250+ ghc-options: -O2 -Wall -threaded -with-rtsopts=-N
251+ if flag(dev)
252+ buildable: True
253+ build-Depends:
254+ streamly
255+ , base >= 4.8 && < 5
256+ , clock >= 0.7.1 && < 0.8
257+ , hspec >= 2.0 && < 3
258+ , random >= 1.0.0 && < 1.2
259+ else
260+ buildable: False
261+
244262test-suite loops
245263 type: exitcode-stdio-1.0
246264 default-language: Haskell2010
@@ -376,6 +394,7 @@ benchmark base
376394 -- concurrency
377395 , atomic-primops >= 0.8 && < 0.9
378396 , lockfree-queue >= 0.2.3 && < 0.3
397+ , clock >= 0.7.1 && < 0.8
379398
380399 , exceptions >= 0.8 && < 0.11
381400 , monad-control >= 1.0 && < 2
@@ -395,7 +414,7 @@ executable chart-linear
395414 buildable: True
396415 build-Depends:
397416 base >= 4.8 && < 5
398− , bench-graph
417+ , bench-graph >= 0.1 && < 0.2
399418 , split
400419 else
401420 buildable: False
@@ -408,7 +427,7 @@ executable chart-nested
408427 buildable: True
409428 build-Depends:
410429 base >= 4.8 && < 5
411− , bench-graph
430+ , bench-graph >= 0.1 && < 0.2
412431 , split
413432 else
414433 buildable: False
test/Main.hs+15
@@ -363,6 +363,14 @@ main = hspec $ do
363363 describe "take on infinite concurrent stream" $ takeInfinite wAsyncly
364364 describe "take on infinite concurrent stream" $ takeInfinite aheadly
365365
366+ ---------------------------------------------------------------------------
367+ -- Some ad-hoc tests that failed at times
368+ ---------------------------------------------------------------------------
369+
370+ it "takes n from stream of streams" (takeCombined 1 aheadly)
371+ it "takes n from stream of streams" (takeCombined 2 asyncly)
372+ it "takes n from stream of streams" (takeCombined 3 wAsyncly)
373+
366374 ---------------------------------------------------------------------------
367375 -- Folds are strict enough
368376 ---------------------------------------------------------------------------
@@ -417,6 +425,13 @@ main = hspec $ do
417425 replicate 4000 $ S.yieldM $ threadDelay 1000000)
418426 `shouldReturn` ()
419427
428+takeCombined :: (Monad m, Semigroup (t m Int), Show a, Eq a, IsStream t)
429+ => Int -> (t m Int -> SerialT IO a) -> IO ()
430+takeCombined n t = do
431+ let constr = S.fromFoldable
432+ r <- (S.toList . t) $
433+ S.take n ((constr ([] :: [Int])) <> constr ([] :: [Int]))
434+ r `shouldBe` []
420435
421436checkFoldxStrictness :: IO ()
422437checkFoldxStrictness = do
test/MaxRate.hsnew file+121
@@ -0,0 +1,121 @@
1+{-# LANGUAGE FlexibleContexts #-}
2+
3+import Streamly
4+import qualified Streamly.Prelude as S
5+import Control.Concurrent
6+import Control.Monad
7+import System.Clock
8+import Test.Hspec
9+import System.Random
10+
11+durationShouldBe :: (Double, Double) -> IO () -> Expectation
12+durationShouldBe d@(tMin, tMax) action = do
13+ t0 <- getTime Monotonic
14+ action
15+ t1 <- getTime Monotonic
16+ let t = (fromIntegral $ toNanoSecs (t1 - t0)) / 1e9
17+ -- tMax = fromNanoSecs (round $ d*10^9*1.2)
18+ -- tMin = fromNanoSecs (round $ d*10^9*0.8)
19+ putStrLn $ "Expected: " ++ show d ++ " Took: " ++ show t
20+ (t <= tMax && t >= tMin) `shouldBe` True
21+
22+toMicroSecs :: Num a => a -> a
23+toMicroSecs x = x * 10^(6 :: Int)
24+
25+measureRate' :: IsStream t
26+ => String
27+ -> (t IO Int -> SerialT IO Int)
28+ -> Double
29+ -> Int
30+ -> (Double, Double)
31+ -> (Double, Double)
32+ -> Spec
33+measureRate' desc t rate consumerDelay producerDelay dur = do
34+ it (desc ++ " rate: " ++ show rate
35+ ++ ", consumer latency: " ++ show consumerDelay
36+ ++ ", producer latency: " ++ show producerDelay)
37+ $ durationShouldBe dur $ do
38+ runStream
39+ $ (if consumerDelay > 0
40+ then S.mapM $ \x ->
41+ threadDelay (toMicroSecs consumerDelay) >> return x
42+ else id)
43+ $ t
44+ $ maxBuffer (-1)
45+ $ maxThreads (-1)
46+ $ maxRate rate
47+ $ S.take (round $ rate * 10)
48+ $ S.repeatM $ do
49+ let (t1, t2) = producerDelay
50+ r <- if t1 == t2
51+ then return $ round $ toMicroSecs t1
52+ else randomRIO ( round $ toMicroSecs t1
53+ , round $ toMicroSecs t2)
54+ when (r > 0) $ do
55+ t1 <- getTime Monotonic
56+ threadDelay r
57+ -- t2 <- getTime Monotonic
58+ -- let delta = fromIntegral (toNanoSecs (t2 - t1)) / 1000000000
59+ -- putStrLn $ "delay took: " ++ show delta
60+ -- when (delta > 2) $ do
61+ -- putStrLn $ "delay took high: " ++ show delta
62+ return 1
63+
64+measureRate :: IsStream t
65+ => String
66+ -> (t IO Int -> SerialT IO Int)
67+ -> Double
68+ -> Int
69+ -> Int
70+ -> (Double, Double)
71+ -> Spec
72+measureRate desc t rate consumerDelay producerDelay dur =
73+ let d = fromIntegral producerDelay
74+ in measureRate' desc t rate consumerDelay (d, d) dur
75+
76+main :: IO ()
77+main = hspec $ do
78+ let range = (8,12)
79+
80+ -- XXX very low rates (e.g. 0.1) fail because we introduce the delay
81+ -- after yield and not before.
82+ let rates = [1, 10, 100, 1000, 10000, 100000, 1000000]
83+ in describe "asyncly no consumer delay no producer delay" $ do
84+ forM_ rates (\r -> measureRate "asyncly" asyncly r 0 0 range)
85+
86+ -- XXX try staggering the dispatches to achieve higher rates
87+ let rates = [1, 10, 100, 1000, 10000, 25000]
88+ in describe "asyncly no consumer delay and 1 sec producer delay" $ do
89+ forM_ rates (\r -> measureRate "asyncly" asyncly r 0 1 range)
90+ -}
91+
92+ let rates = [1, 10, 100, 1000, 10000, 25000]
93+ in describe "asyncly no consumer delay and variable producer delay" $ do
94+ forM_ rates $ \r ->
95+ measureRate' "asyncly" asyncly r 0 (0.1, 3) range
96+
97+ let rates = [1, 10, 100, 1000, 10000, 100000, 1000000]
98+ in describe "wAsyncly no consumer delay no producer delay" $ do
99+ forM_ rates (\r -> measureRate "wAsyncly" wAsyncly r 0 0 range)
100+
101+ let rates = [1, 10, 100, 1000, 10000, 25000]
102+ in describe "wAsyncly no consumer delay and 1 sec producer delay" $ do
103+ forM_ rates (\r -> measureRate "wAsyncly" wAsyncly r 0 1 range)
104+
105+ let rates = [1, 10, 100, 1000, 10000, 100000]
106+ in describe "aheadly no consumer delay no producer delay" $ do
107+ forM_ rates (\r -> measureRate "aheadly" aheadly r 0 0 range)
108+
109+ let rates = [1, 10, 100, 1000, 10000, 25000]
110+ in describe "aheadly no consumer delay and 1 sec producer delay" $ do
111+ forM_ rates (\r -> measureRate "aheadly" aheadly r 0 1 range)
112+
113+ describe "asyncly with 1 sec producer delay and some consumer delay" $ do
114+ forM_ [1] (\r -> measureRate "asyncly" asyncly r 1 1 (11, 16))
115+ forM_ [1] (\r -> measureRate "asyncly" asyncly r 2 1 (21, 23))
116+ forM_ [1] (\r -> measureRate "asyncly" asyncly r 3 1 (31, 33))
117+
118+ describe "aheadly with 1 sec producer delay and some consumer delay" $ do
119+ forM_ [1] (\r -> measureRate "aheadly" aheadly r 1 1 (11, 16))
120+ forM_ [1] (\r -> measureRate "aheadly" aheadly r 2 1 (21, 23))
121+ forM_ [1] (\r -> measureRate "aheadly" aheadly r 3 1 (31, 33))
test/Prop.hs+311−226
@@ -4,7 +4,7 @@ module Main (main) where
44
55import Control.Exception (BlockedIndefinitelyOnMVar(..), catches,
66 BlockedIndefinitelyOnSTM(..), Handler(..))
7−import Control.Monad (when)
7+import Control.Monad (when, forM_)
88import Control.Applicative (ZipList(..))
99import Control.Concurrent (MVar, takeMVar, putMVar, newEmptyMVar)
1010import Control.Monad (replicateM, replicateM_)
@@ -52,15 +52,12 @@ equals eq stream list = do
5252constructWithReplicateM
5353 :: IsStream t
5454 => (t IO Int -> SerialT IO Int)
55− -> Int
56− -> Int
5755 -> Word8
5856 -> Property
59−constructWithReplicateM op thr buf len = withMaxSuccess maxTestCount $
57+constructWithReplicateM op len = withMaxSuccess maxTestCount $
6058 monadicIO $ do
6159 let x = return (1 :: Int)
62− stream <- run $ (S.toList . op) (maxThreads thr $ maxBuffer buf $
63− S.replicateM (fromIntegral len) x)
60+ stream <- run $ (S.toList . op) (S.replicateM (fromIntegral len) x)
6461 list <- run $ replicateM (fromIntegral len) x
6562 equals (==) stream list
6663
@@ -183,8 +180,12 @@ concurrentUnfoldrM eq op n =
183180 return x
184181 equals eq stream list
185182
186−concurrentApplication :: Word8 -> Property
187−concurrentApplication n =
183+concurrentApplication :: IsStream t
184+ => ([Word8] -> [Word8] -> Bool)
185+ -> (t IO Word8 -> SerialT IO Word8)
186+ -> Word8
187+ -> Property
188+concurrentApplication eq t n = withMaxSuccess maxTestCount $
188189 monadicIO $ do
189190 -- XXX we should test empty list case as well
190191 let list = [0..n]
@@ -194,7 +195,7 @@ concurrentApplication n =
194195 -- since unfoldr happens in parallel with the stream processing we
195196 -- can do two takeMVar in one iteration. If it is not parallel then
196197 -- this will not work and the test will fail.
197− S.toList $ do
198+ (S.toList . t) $ do
198199 sourceUnfoldrM mv n |&
199200 (S.mapM $ \x -> do
200201 let msg = show x ++ "/" ++ show n
@@ -208,7 +209,7 @@ concurrentApplication n =
208209 else return ()
209210 else return ()
210211 return x)
211− equals (==) stream list
212+ equals eq stream list
212213
213214sourceUnfoldrM1 :: IsStream t => Word8 -> t IO Word8
214215sourceUnfoldrM1 n = S.unfoldrM step 0
@@ -295,10 +296,10 @@ functorOps
295296 :: Functor (t IO)
296297 => ([Int] -> t IO Int)
297298 -> String
298− -> (t IO Int -> SerialT IO Int)
299299 -> ([Int] -> [Int] -> Bool)
300+ -> (t IO Int -> SerialT IO Int)
300301 -> Spec
301−functorOps constr desc t eq = do
302+functorOps constr desc eq t = do
302303 prop (desc ++ " id") $ transformFromList constr eq id $ t
303304 prop (desc ++ " fmap (+1)") $ transformFromList constr eq (fmap (+1)) $ t . (fmap (+1))
304305
@@ -306,10 +307,10 @@ transformOps
306307 :: IsStream t
307308 => ([Int] -> t IO Int)
308309 -> String
309− -> (t IO Int -> SerialT IO Int)
310310 -> ([Int] -> [Int] -> Bool)
311+ -> (t IO Int -> SerialT IO Int)
311312 -> Spec
312−transformOps constr desc t eq = do
313+transformOps constr desc eq t = do
313314 let transform = transformFromList constr eq
314315 -- Filtering
315316 prop (desc ++ " filter False") $
@@ -360,10 +361,10 @@ concurrentOps
360361 :: IsStream t
361362 => ([Word8] -> t IO Word8)
362363 -> String
363− -> (t IO Word8 -> SerialT IO Word8)
364364 -> ([Word8] -> [Word8] -> Bool)
365+ -> (t IO Word8 -> SerialT IO Word8)
365366 -> Spec
366−concurrentOps constr desc t eq = do
367+concurrentOps constr desc eq t = do
367368 let prop1 d p = prop d $ withMaxSuccess maxTestCount p
368369
369370 prop1 (desc ++ " fromFoldableM") $ concurrentFromFoldable eq t
@@ -384,10 +385,10 @@ transformCombineOpsCommon
384385 :: (IsStream t, Semigroup (t IO Int))
385386 => ([Int] -> t IO Int)
386387 -> String
387− -> (t IO Int -> SerialT IO Int)
388388 -> ([Int] -> [Int] -> Bool)
389+ -> (t IO Int -> SerialT IO Int)
389390 -> Spec
390−transformCombineOpsCommon constr desc t eq = do
391+transformCombineOpsCommon constr desc eq t = do
391392 let transform = transformCombineFromList constr eq
392393 -- Filtering
393394 prop (desc ++ " filter False") $
@@ -448,13 +449,19 @@ transformCombineOpsOrdered
448449 :: (IsStream t, Semigroup (t IO Int))
449450 => ([Int] -> t IO Int)
450451 -> String
451− -> (t IO Int -> SerialT IO Int)
452452 -> ([Int] -> [Int] -> Bool)
453+ -> (t IO Int -> SerialT IO Int)
453454 -> Spec
454−transformCombineOpsOrdered constr desc t eq = do
455+transformCombineOpsOrdered constr desc eq t = do
455456 let transform = transformCombineFromList constr eq
456457 -- Filtering
457458 prop (desc ++ " take 1") $ transform (take 1) t (S.take 1)
459+#ifdef DEVBUILD
460+ prop (desc ++ " take 2") $ transform (take 2) t (S.take 2)
461+ prop (desc ++ " take 3") $ transform (take 3) t (S.take 3)
462+ prop (desc ++ " take 4") $ transform (take 4) t (S.take 4)
463+ prop (desc ++ " take 5") $ transform (take 5) t (S.take 5)
464+#endif
458465 prop (desc ++ " take 10") $ transform (take 10) t (S.take 10)
459466
460467 prop (desc ++ " takeWhile > 0") $
@@ -557,21 +564,21 @@ semigroupOps
557564#endif
558565 , Monoid (t IO Int))
559566 => String
560− -> (t IO Int -> SerialT IO Int)
561567 -> ([Int] -> [Int] -> Bool)
568+ -> (t IO Int -> SerialT IO Int)
562569 -> Spec
563−semigroupOps desc t eq = do
570+semigroupOps desc eq t = do
564571 prop (desc ++ " <>") $ foldFromList (foldMapWith (<>) singleton) t eq
565572 prop (desc ++ " mappend") $ foldFromList (foldMapWith mappend singleton) t eq
566573
567574applicativeOps
568575 :: Applicative (t IO)
569576 => ([Int] -> t IO Int)
570− -> (t IO (Int, Int) -> SerialT IO (Int, Int))
571577 -> ([(Int, Int)] -> [(Int, Int)] -> Bool)
578+ -> (t IO (Int, Int) -> SerialT IO (Int, Int))
572579 -> ([Int], [Int])
573580 -> Property
574−applicativeOps constr t eq (a, b) = withMaxSuccess maxTestCount $
581+applicativeOps constr eq t (a, b) = withMaxSuccess maxTestCount $
575582 monadicIO $ do
576583 stream <- run ((S.toList . t) ((,) <$> (constr a) <*> (constr b)))
577584 let list = (,) <$> a <*> b
@@ -580,11 +587,11 @@ applicativeOps constr t eq (a, b) = withMaxSuccess maxTestCount $
580587zipApplicative
581588 :: (IsStream t, Applicative (t IO))
582589 => ([Int] -> t IO Int)
583− -> (t IO (Int, Int) -> SerialT IO (Int, Int))
584590 -> ([(Int, Int)] -> [(Int, Int)] -> Bool)
591+ -> (t IO (Int, Int) -> SerialT IO (Int, Int))
585592 -> ([Int], [Int])
586593 -> Property
587−zipApplicative constr t eq (a, b) = withMaxSuccess maxTestCount $
594+zipApplicative constr eq t (a, b) = withMaxSuccess maxTestCount $
588595 monadicIO $ do
589596 stream1 <- run ((S.toList . t) ((,) <$> (constr a) <*> (constr b)))
590597 stream2 <- run ((S.toList . t) (pure (,) <*> (constr a) <*> (constr b)))
@@ -597,11 +604,27 @@ zipApplicative constr t eq (a, b) = withMaxSuccess maxTestCount $
597604zipMonadic
598605 :: IsStream t
599606 => ([Int] -> t IO Int)
607+ -> ([(Int, Int)] -> [(Int, Int)] -> Bool)
600608 -> (t IO (Int, Int) -> SerialT IO (Int, Int))
609+ -> ([Int], [Int])
610+ -> Property
611+zipMonadic constr eq t (a, b) = withMaxSuccess maxTestCount $
612+ monadicIO $ do
613+ stream1 <-
614+ run
615+ ((S.toList . t)
616+ (S.zipWithM (\x y -> return (x, y)) (constr a) (constr b)))
617+ let list = getZipList $ (,) <$> ZipList a <*> ZipList b
618+ equals eq stream1 list
619+
620+zipAsyncMonadic
621+ :: IsStream t
622+ => ([Int] -> t IO Int)
601623 -> ([(Int, Int)] -> [(Int, Int)] -> Bool)
624+ -> (t IO (Int, Int) -> SerialT IO (Int, Int))
602625 -> ([Int], [Int])
603626 -> Property
604−zipMonadic constr t eq (a, b) = withMaxSuccess maxTestCount $
627+zipAsyncMonadic constr eq t (a, b) = withMaxSuccess maxTestCount $
605628 monadicIO $ do
606629 stream1 <-
607630 run
@@ -618,11 +641,11 @@ zipMonadic constr t eq (a, b) = withMaxSuccess maxTestCount $
618641monadThen
619642 :: Monad (t IO)
620643 => ([Int] -> t IO Int)
621− -> (t IO Int -> SerialT IO Int)
622644 -> ([Int] -> [Int] -> Bool)
645+ -> (t IO Int -> SerialT IO Int)
623646 -> ([Int], [Int])
624647 -> Property
625−monadThen constr t eq (a, b) = withMaxSuccess maxTestCount $ monadicIO $ do
648+monadThen constr eq t (a, b) = withMaxSuccess maxTestCount $ monadicIO $ do
626649 stream <- run ((S.toList . t) ((constr a) >> (constr b)))
627650 let list = a >> b
628651 equals eq stream list
@@ -630,11 +653,11 @@ monadThen constr t eq (a, b) = withMaxSuccess maxTestCount $ monadicIO $ do
630653monadBind
631654 :: Monad (t IO)
632655 => ([Int] -> t IO Int)
633− -> (t IO Int -> SerialT IO Int)
634656 -> ([Int] -> [Int] -> Bool)
657+ -> (t IO Int -> SerialT IO Int)
635658 -> ([Int], [Int])
636659 -> Property
637−monadBind constr t eq (a, b) = withMaxSuccess maxTestCount $
660+monadBind constr eq t (a, b) = withMaxSuccess maxTestCount $
638661 monadicIO $ do
639662 stream <-
640663 run
@@ -643,22 +666,15 @@ monadBind constr t eq (a, b) = withMaxSuccess maxTestCount $
643666 let list = a >>= \x -> b >>= return . (+ x)
644667 equals eq stream list
645668
646−constructionConcurrent :: Int -> Int -> Spec
647−constructionConcurrent thr buf = do
648− describe (" threads = " ++ show thr ++ "buffer = " ++ show buf) $ do
649− prop "asyncly replicateM" $ constructWithReplicateM asyncly thr buf
650− prop "wAsyncly replicateM" $ constructWithReplicateM wAsyncly thr buf
651− prop "parallely replicateM" $ constructWithReplicateM parallely thr buf
652− prop "aheadly replicateM" $ constructWithReplicateM aheadly thr buf
653−
654−-- XXX test all concurrent ops for all these combinations
655−concurrentAll :: String -> (Int -> Int -> Spec) -> Spec
656−concurrentAll desc f = do
657− describe desc $ do
658− f 0 0 -- default
659− f 0 1 -- single buffer
660− f 1 0 -- single thread
661− f (-1) (-1) -- unbounded threads and buffer
669+constructWithIterate :: IsStream t => (t IO Int -> SerialT IO Int) -> Spec
670+constructWithIterate t = do
671+ it "iterate" $
672+ (S.toList . t . (S.take 100) $ (S.iterate (+ 1) (0 :: Int)))
673+ `shouldReturn` (take 100 $ iterate (+ 1) 0)
674+ it "iterateM" $ do
675+ let addM = (\ y -> return (y + 1))
676+ S.toList . t . (S.take 100) $ S.iterateM addM (0 :: Int)
677+ `shouldReturn` (take 100 $ iterate (+ 1) 0)
662678
663679main :: IO ()
664680main = hspec $ do
@@ -668,153 +684,222 @@ main = hspec $ do
668684 [x] -> return x -- singleton stream case
669685 _ -> foldMapWith (<>) return xs
670686 )
687+
688+ let makeOps t =
689+ [ t
690+ , t . maxRate 0
691+ , t . maxRate (-1)
692+ , t . maxBuffer 0
693+ , t . maxBuffer 1
694+ , t . maxThreads 0
695+ , t . maxThreads 1
696+ , t . maxThreads (-1)
697+ ]
698+
699+ let serialOps :: IsStream t => ((SerialT IO a -> t IO a) -> Spec) -> Spec
700+ serialOps = forM_ $ (makeOps serially)
701+ ++ [serially . maxRate 0.00000001]
702+ ++ [serially . maxBuffer (-1)]
703+ let wSerialOps :: IsStream t => ((WSerialT IO a -> t IO a) -> Spec) -> Spec
704+ wSerialOps = forM_ $ makeOps wSerially
705+ ++ [wSerially . maxRate 0.00000001]
706+ ++ [wSerially . maxBuffer (-1)]
707+ let asyncOps :: IsStream t => ((AsyncT IO a -> t IO a) -> Spec) -> Spec
708+ asyncOps = forM_ $ makeOps asyncly
709+ ++ [asyncly . maxRate 10000]
710+ ++ [asyncly . maxBuffer (-1)]
711+ let wAsyncOps :: IsStream t => ((WAsyncT IO a -> t IO a) -> Spec) -> Spec
712+ wAsyncOps = forM_ $ makeOps wAsyncly
713+ ++ [wAsyncly . maxRate 10000]
714+ ++ [wAsyncly . maxBuffer (-1)]
715+ let aheadOps :: IsStream t => ((AheadT IO a -> t IO a) -> Spec) -> Spec
716+ aheadOps = forM_ $ makeOps aheadly
717+ ++ [aheadly . maxRate 10000]
718+ ++ [aheadly . maxBuffer (-1)]
719+ let parallelOps :: IsStream t => ((ParallelT IO a -> t IO a) -> Spec) -> Spec
720+ parallelOps = forM_ $ makeOps parallely
721+ ++ [parallely . maxRate 0.00000001]
722+ ++ [parallely . maxBuffer (-1)]
723+ let zipSerialOps :: IsStream t => ((ZipSerialM IO a -> t IO a) -> Spec) -> Spec
724+ zipSerialOps = forM_ $ makeOps zipSerially
725+ ++ [zipSerially . maxRate 0.00000001]
726+ ++ [zipSerially . maxBuffer (-1)]
727+ -- Note, the "pure" of applicative Zip streams generates and infinite
728+ -- stream and therefore maxBuffer (-1) must not be used for that case.
729+ let zipAsyncOps :: IsStream t => ((ZipAsyncM IO a -> t IO a) -> Spec) -> Spec
730+ zipAsyncOps = forM_ $ makeOps zipAsyncly
731+ ++ [zipAsyncly . maxRate 10000]
732+
671733 describe "Construction" $ do
672− prop "serially replicateM" $ constructWithReplicateM serially 0 0
673− it "iterate" $
674− (S.toList . serially . (S.take 100) $ (S.iterate (+ 1) (0 :: Int)))
675− `shouldReturn` (take 100 $ iterate (+ 1) 0)
734+ serialOps $ prop "serially replicateM" . constructWithReplicateM
735+ wSerialOps $ prop "wSerially replicateM" . constructWithReplicateM
736+ aheadOps $ prop "aheadly replicateM" . constructWithReplicateM
737+ asyncOps $ prop "asyncly replicateM" . constructWithReplicateM
738+ wAsyncOps $ prop "wAsyncly replicateM" . constructWithReplicateM
739+ parallelOps $ prop "parallely replicateM" . constructWithReplicateM
676740 -- XXX test for all types of streams
677− it "iterateM" $ do
678− let addM = (\ y -> return (y + 1))
679− S.toList . serially . (S.take 100) $ S.iterateM addM (0 :: Int)
680− `shouldReturn` (take 100 $ iterate (+ 1) 0)
681− concurrentAll "Construction" constructionConcurrent
741+ constructWithIterate serially
682742
683743 describe "Functor operations" $ do
684− functorOps S.fromFoldable "serially" serially (==)
685− functorOps folded "serially folded" serially (==)
686− functorOps S.fromFoldable "wSerially" wSerially (==)
687− functorOps folded "wSerially folded" wSerially (==)
688− functorOps S.fromFoldable "aheadly" aheadly (==)
689− functorOps folded "aheadly folded" aheadly (==)
690− functorOps S.fromFoldable "asyncly" asyncly sortEq
691− functorOps folded "asyncly folded" asyncly sortEq
692− functorOps S.fromFoldable "wAsyncly" wAsyncly sortEq
693− functorOps folded "wAsyncly folded" wAsyncly sortEq
694− functorOps S.fromFoldable "parallely" parallely sortEq
695− functorOps folded "parallely folded" parallely sortEq
696− functorOps S.fromFoldable "zipSerially" zipSerially (==)
697− functorOps folded "zipSerially folded" zipSerially (==)
698− functorOps S.fromFoldable "zipAsyncly" zipAsyncly (==)
699− functorOps folded "zipAsyncly folded" zipAsyncly (==)
744+ serialOps $ functorOps S.fromFoldable "serially" (==)
745+ serialOps $ functorOps folded "serially folded" (==)
746+ wSerialOps $ functorOps S.fromFoldable "wSerially" (==)
747+ wSerialOps $ functorOps folded "wSerially folded" (==)
748+ aheadOps $ functorOps S.fromFoldable "aheadly" (==)
749+ aheadOps $ functorOps folded "aheadly folded" (==)
750+ asyncOps $ functorOps S.fromFoldable "asyncly" sortEq
751+ asyncOps $ functorOps folded "asyncly folded" sortEq
752+ wAsyncOps $ functorOps S.fromFoldable "wAsyncly" sortEq
753+ wAsyncOps $ functorOps folded "wAsyncly folded" sortEq
754+ parallelOps $ functorOps S.fromFoldable "parallely" sortEq
755+ parallelOps $ functorOps folded "parallely folded" sortEq
756+ zipSerialOps $ functorOps S.fromFoldable "zipSerially" (==)
757+ zipSerialOps $ functorOps folded "zipSerially folded" (==)
758+ zipAsyncOps $ functorOps S.fromFoldable "zipAsyncly" (==)
759+ zipAsyncOps $ functorOps folded "zipAsyncly folded" (==)
700760
701761 describe "Semigroup operations" $ do
702− semigroupOps "serially" serially (==)
703− semigroupOps "wSerially" wSerially (==)
704− semigroupOps "aheadly" aheadly (==)
705− semigroupOps "asyncly" asyncly sortEq
706− semigroupOps "wAsyncly" wAsyncly sortEq
707− semigroupOps "parallely" parallely sortEq
708− semigroupOps "zipSerially" zipSerially (==)
709− semigroupOps "zipAsyncly" zipAsyncly (==)
762+ serialOps $ semigroupOps "serially" (==)
763+ wSerialOps $ semigroupOps "wSerially" (==)
764+ aheadOps $ semigroupOps "aheadly" (==)
765+ asyncOps $ semigroupOps "asyncly" sortEq
766+ wAsyncOps $ semigroupOps "wAsyncly" sortEq
767+ parallelOps $ semigroupOps "parallely" sortEq
768+ zipSerialOps $ semigroupOps "zipSerially" (==)
769+ zipAsyncOps $ semigroupOps "zipAsyncly" (==)
710770
711771 describe "Applicative operations" $ do
712772 -- The tests using sorted equality are weaker tests
713773 -- We need to have stronger unit tests for all those
714774 -- XXX applicative with three arguments
715− prop "serially applicative" $ applicativeOps S.fromFoldable serially (==)
716− prop "serially applicative folded" $ applicativeOps folded serially (==)
717− prop "aheadly applicative" $ applicativeOps S.fromFoldable aheadly (==)
718− prop "aheadly applicative folded" $ applicativeOps folded aheadly (==)
719− prop "wSerially applicative" $ applicativeOps S.fromFoldable wSerially sortEq
720− prop "wSerially applicative folded" $ applicativeOps folded wSerially sortEq
721− prop "asyncly applicative" $ applicativeOps S.fromFoldable asyncly sortEq
722− prop "asyncly applicative folded" $ applicativeOps folded asyncly sortEq
723− prop "wAsyncly applicative folded" $ applicativeOps folded wAsyncly sortEq
724− prop "parallely applicative folded" $ applicativeOps folded parallely sortEq
775+ serialOps $ prop "serially applicative" . applicativeOps S.fromFoldable (==)
776+ serialOps $ prop "serially applicative folded" . applicativeOps folded (==)
777+ wSerialOps $ prop "wSerially applicative" . applicativeOps S.fromFoldable sortEq
778+ wSerialOps $ prop "wSerially applicative folded" . applicativeOps folded sortEq
779+ aheadOps $ prop "aheadly applicative" . applicativeOps S.fromFoldable (==)
780+ aheadOps $ prop "aheadly applicative folded" . applicativeOps folded (==)
781+ asyncOps $ prop "asyncly applicative" . applicativeOps S.fromFoldable sortEq
782+ asyncOps $ prop "asyncly applicative folded" . applicativeOps folded sortEq
783+ wAsyncOps $ prop "wAsyncly applicative" . applicativeOps S.fromFoldable sortEq
784+ wAsyncOps $ prop "wAsyncly applicative folded" . applicativeOps folded sortEq
785+ parallelOps $ prop "parallely applicative folded" . applicativeOps folded sortEq
725786
726787 describe "Zip operations" $ do
727− prop "zipSerially applicative" $ zipApplicative S.fromFoldable zipSerially (==)
728− prop "zipSerially applicative folded" $ zipApplicative folded zipSerially (==)
729− prop "zipAsyncly applicative" $ zipApplicative S.fromFoldable zipAsyncly (==)
730− prop "zipAsyncly applicative folded" $ zipApplicative folded zipAsyncly (==)
731−
732− prop "zip monadic serially" $ zipMonadic S.fromFoldable serially (==)
733− prop "zip monadic serially folded" $ zipMonadic folded serially (==)
734− prop "zip monadic aheadly" $ zipMonadic S.fromFoldable aheadly (==)
735− prop "zip monadic aheadly folded" $ zipMonadic folded aheadly (==)
736− prop "zip monadic wSerially" $ zipMonadic S.fromFoldable wSerially (==)
737− prop "zip monadic wSerially folded" $ zipMonadic folded wSerially (==)
738− prop "zip monadic asyncly" $ zipMonadic S.fromFoldable asyncly (==)
739− prop "zip monadic asyncly folded" $ zipMonadic folded asyncly (==)
740− prop "zip monadic wAsyncly" $ zipMonadic S.fromFoldable wAsyncly (==)
741− prop "zip monadic wAsyncly folded" $ zipMonadic folded wAsyncly (==)
742− prop "zip monadic parallely" $ zipMonadic S.fromFoldable parallely (==)
743− prop "zip monadic parallely folded" $ zipMonadic folded parallely (==)
788+ zipSerialOps $ prop "zipSerially applicative" . zipApplicative S.fromFoldable (==)
789+ zipSerialOps $ prop "zipSerially applicative folded" . zipApplicative folded (==)
790+ zipAsyncOps $ prop "zipAsyncly applicative" . zipApplicative S.fromFoldable (==)
791+ zipAsyncOps $ prop "zipAsyncly applicative folded" . zipApplicative folded (==)
792+
793+ -- We test only the serial zip with serial streams and the parallel
794+ -- stream, because the maxRate setting in these streams can slow down
795+ -- zipAsync.
796+ serialOps $ prop "zip monadic serially" . zipMonadic S.fromFoldable (==)
797+ serialOps $ prop "zip monadic serially folded" . zipMonadic folded (==)
798+ wSerialOps $ prop "zip monadic wSerially" . zipMonadic S.fromFoldable (==)
799+ wSerialOps $ prop "zip monadic wSerially folded" . zipMonadic folded (==)
800+ aheadOps $ prop "zip monadic aheadly" . zipAsyncMonadic S.fromFoldable (==)
801+ aheadOps $ prop "zip monadic aheadly folded" . zipAsyncMonadic folded (==)
802+ asyncOps $ prop "zip monadic asyncly" . zipAsyncMonadic S.fromFoldable (==)
803+ asyncOps $ prop "zip monadic asyncly folded" . zipAsyncMonadic folded (==)
804+ wAsyncOps $ prop "zip monadic wAsyncly" . zipAsyncMonadic S.fromFoldable (==)
805+ wAsyncOps $ prop "zip monadic wAsyncly folded" . zipAsyncMonadic folded (==)
806+ parallelOps $ prop "zip monadic parallely" . zipMonadic S.fromFoldable (==)
807+ parallelOps $ prop "zip monadic parallely folded" . zipMonadic folded (==)
744808
745809 describe "Monad operations" $ do
746− prop "serially monad then" $ monadThen S.fromFoldable serially (==)
747− prop "aheadly monad then" $ monadThen S.fromFoldable aheadly (==)
748− prop "wSerially monad then" $ monadThen S.fromFoldable wSerially sortEq
749− prop "asyncly monad then" $ monadThen S.fromFoldable asyncly sortEq
750− prop "wAsyncly monad then" $ monadThen S.fromFoldable wAsyncly sortEq
751− prop "parallely monad then" $ monadThen S.fromFoldable parallely sortEq
752−
753− prop "serially monad then folded" $ monadThen folded serially (==)
754− prop "aheadly monad then folded" $ monadThen folded aheadly (==)
755− prop "wSerially monad then folded" $ monadThen folded wSerially sortEq
756− prop "asyncly monad then folded" $ monadThen folded asyncly sortEq
757− prop "wAsyncly monad then folded" $ monadThen folded wAsyncly sortEq
758− prop "parallely monad then folded" $ monadThen folded parallely sortEq
759−
760− prop "serially monad bind" $ monadBind S.fromFoldable serially (==)
761− prop "aheadly monad bind" $ monadBind S.fromFoldable aheadly (==)
762− prop "wSerially monad bind" $ monadBind S.fromFoldable wSerially sortEq
763− prop "asyncly monad bind" $ monadBind S.fromFoldable asyncly sortEq
764− prop "wAsyncly monad bind" $ monadBind S.fromFoldable wAsyncly sortEq
765− prop "parallely monad bind" $ monadBind S.fromFoldable parallely sortEq
810+ serialOps $ prop "serially monad then" . monadThen S.fromFoldable (==)
811+ wSerialOps $ prop "wSerially monad then" . monadThen S.fromFoldable sortEq
812+ aheadOps $ prop "aheadly monad then" . monadThen S.fromFoldable (==)
813+ asyncOps $ prop "asyncly monad then" . monadThen S.fromFoldable sortEq
814+ wAsyncOps $ prop "wAsyncly monad then" . monadThen S.fromFoldable sortEq
815+ parallelOps $ prop "parallely monad then" . monadThen S.fromFoldable sortEq
816+
817+ serialOps $ prop "serially monad then folded" . monadThen folded (==)
818+ wSerialOps $ prop "wSerially monad then folded" . monadThen folded sortEq
819+ aheadOps $ prop "aheadly monad then folded" . monadThen folded (==)
820+ asyncOps $ prop "asyncly monad then folded" . monadThen folded sortEq
821+ wAsyncOps $ prop "wAsyncly monad then folded" . monadThen folded sortEq
822+ parallelOps $ prop "parallely monad then folded" . monadThen folded sortEq
823+
824+ serialOps $ prop "serially monad bind" . monadBind S.fromFoldable (==)
825+ wSerialOps $ prop "wSerially monad bind" . monadBind S.fromFoldable sortEq
826+ aheadOps $ prop "aheadly monad bind" . monadBind S.fromFoldable (==)
827+ asyncOps $ prop "asyncly monad bind" . monadBind S.fromFoldable sortEq
828+ wAsyncOps $ prop "wAsyncly monad bind" . monadBind S.fromFoldable sortEq
829+ parallelOps $ prop "parallely monad bind" . monadBind S.fromFoldable sortEq
830+
831+ serialOps $ prop "serially monad bind folded" . monadBind folded (==)
832+ wSerialOps $ prop "wSerially monad bind folded" . monadBind folded sortEq
833+ aheadOps $ prop "aheadly monad bind folded" . monadBind folded (==)
834+ asyncOps $ prop "asyncly monad bind folded" . monadBind folded sortEq
835+ wAsyncOps $ prop "wAsyncly monad bind folded" . monadBind folded sortEq
836+ parallelOps $ prop "parallely monad bind folded" . monadBind folded sortEq
766837
767838 describe "Stream transform operations" $ do
768− transformOps S.fromFoldable "serially" serially (==)
769− transformOps S.fromFoldable "aheadly" aheadly (==)
770− transformOps S.fromFoldable "wSerially" wSerially (==)
771− transformOps S.fromFoldable "zipSerially" zipSerially (==)
772− transformOps S.fromFoldable "zipAsyncly" zipAsyncly (==)
773− transformOps S.fromFoldable "asyncly" asyncly sortEq
774− transformOps S.fromFoldable "wAsyncly" wAsyncly sortEq
775− transformOps S.fromFoldable "parallely" parallely sortEq
776−
777− transformOps folded "serially folded" serially (==)
778− transformOps folded "aheadly folded" aheadly (==)
779− transformOps folded "wSerially folded" wSerially (==)
780− transformOps folded "zipSerially folded" zipSerially (==)
781− transformOps folded "zipAsyncly folded" zipAsyncly (==)
782− transformOps folded "asyncly folded" asyncly sortEq
783− transformOps folded "wAsyncly folded" wAsyncly sortEq
784− transformOps folded "parallely folded" parallely sortEq
785−
786− transformOpsWord8 S.fromFoldable "serially" serially
787− transformOpsWord8 S.fromFoldable "aheadly" aheadly
788− transformOpsWord8 S.fromFoldable "wSerially" wSerially
789− transformOpsWord8 S.fromFoldable "zipSerially" zipSerially
790− transformOpsWord8 S.fromFoldable "zipAsyncly" zipAsyncly
791− transformOpsWord8 S.fromFoldable "asyncly" asyncly
792− transformOpsWord8 S.fromFoldable "wAsyncly" wAsyncly
793− transformOpsWord8 S.fromFoldable "parallely" parallely
794−
795− transformOpsWord8 folded "serially folded" serially
796− transformOpsWord8 folded "aheadly folded" aheadly
797− transformOpsWord8 folded "wSerially folded" wSerially
798− transformOpsWord8 folded "zipSerially folded" zipSerially
799− transformOpsWord8 folded "zipAsyncly folded" zipAsyncly
800− transformOpsWord8 folded "asyncly folded" asyncly
801− transformOpsWord8 folded "wAsyncly folded" wAsyncly
802− transformOpsWord8 folded "parallely folded" parallely
803−
804− -- XXX add tests with outputQueue size set to 1
839+ serialOps $ transformOps S.fromFoldable "serially" (==)
840+ wSerialOps $ transformOps S.fromFoldable "wSerially" (==)
841+ aheadOps $ transformOps S.fromFoldable "aheadly" (==)
842+ asyncOps $ transformOps S.fromFoldable "asyncly" sortEq
843+ wAsyncOps $ transformOps S.fromFoldable "wAsyncly" sortEq
844+ parallelOps $ transformOps S.fromFoldable "parallely" sortEq
845+ zipSerialOps $ transformOps S.fromFoldable "zipSerially" (==)
846+ zipAsyncOps $ transformOps S.fromFoldable "zipAsyncly" (==)
847+
848+ serialOps $ transformOps folded "serially folded" (==)
849+ wSerialOps $ transformOps folded "wSerially folded" (==)
850+ aheadOps $ transformOps folded "aheadly folded" (==)
851+ asyncOps $ transformOps folded "asyncly folded" sortEq
852+ wAsyncOps $ transformOps folded "wAsyncly folded" sortEq
853+ parallelOps $ transformOps folded "parallely folded" sortEq
854+ zipSerialOps $ transformOps folded "zipSerially folded" (==)
855+ zipAsyncOps $ transformOps folded "zipAsyncly folded" (==)
856+
857+ serialOps $ transformOpsWord8 S.fromFoldable "serially"
858+ wSerialOps $ transformOpsWord8 S.fromFoldable "wSerially"
859+ aheadOps $ transformOpsWord8 S.fromFoldable "aheadly"
860+ asyncOps $ transformOpsWord8 S.fromFoldable "asyncly"
861+ wAsyncOps $ transformOpsWord8 S.fromFoldable "wAsyncly"
862+ parallelOps $ transformOpsWord8 S.fromFoldable "parallely"
863+ zipSerialOps $ transformOpsWord8 S.fromFoldable "zipSerially"
864+ zipAsyncOps $ transformOpsWord8 S.fromFoldable "zipAsyncly"
865+
866+ serialOps $ transformOpsWord8 folded "serially folded"
867+ wSerialOps $ transformOpsWord8 folded "wSerially folded"
868+ aheadOps $ transformOpsWord8 folded "aheadly folded"
869+ asyncOps $ transformOpsWord8 folded "asyncly folded"
870+ wAsyncOps $ transformOpsWord8 folded "wAsyncly folded"
871+ parallelOps $ transformOpsWord8 folded "parallely folded"
872+ zipSerialOps $ transformOpsWord8 folded "zipSerially folded"
873+ zipAsyncOps $ transformOpsWord8 folded "zipAsyncly folded"
874+
875+ -- These tests won't work with maxBuffer or maxThreads set to 1, so we
876+ -- exclude those cases from these.
877+ let mkOps t =
878+ [ t
879+ , t . maxRate 0
880+ , t . maxRate (-1)
881+ , t . maxBuffer 0
882+ , t . maxThreads 0
883+ , t . maxThreads (-1)
884+ ]
885+
805886 describe "Stream concurrent operations" $ do
806− concurrentOps S.fromFoldable "aheadly" aheadly (==)
807− concurrentOps S.fromFoldable "asyncly" asyncly sortEq
808− concurrentOps S.fromFoldable "wAsyncly" wAsyncly sortEq
809− concurrentOps S.fromFoldable "parallely" parallely sortEq
810−
811− concurrentOps folded "aheadly folded" aheadly (==)
812− concurrentOps folded "asyncly folded" asyncly sortEq
813− concurrentOps folded "wAsyncly folded" wAsyncly sortEq
814− concurrentOps folded "parallely folded" parallely sortEq
815−
816− prop "concurrent application" $ withMaxSuccess maxTestCount $
817− concurrentApplication
887+ forM_ (mkOps aheadly) $ concurrentOps S.fromFoldable "aheadly" (==)
888+ forM_ (mkOps asyncly) $ concurrentOps S.fromFoldable "asyncly" sortEq
889+ forM_ (mkOps wAsyncly) $ concurrentOps S.fromFoldable "wAsyncly" sortEq
890+ forM_ (mkOps parallely) $ concurrentOps S.fromFoldable "parallely" sortEq
891+
892+ forM_ (mkOps aheadly) $ concurrentOps folded "aheadly folded" (==)
893+ forM_ (mkOps asyncly) $ concurrentOps folded "asyncly folded" sortEq
894+ forM_ (mkOps wAsyncly) $ concurrentOps folded "wAsyncly folded" sortEq
895+ forM_ (mkOps parallely) $ concurrentOps folded "parallely folded" sortEq
896+
897+ describe "Concurrent application" $ do
898+ serialOps $ prop "concurrent application" . concurrentApplication (==)
899+ asyncOps $ prop "concurrent application" . concurrentApplication sortEq
900+ aheadOps $ prop "concurrent application" . concurrentApplication (==)
901+ parallelOps $ prop "concurrent application" . concurrentApplication sortEq
902+
818903 prop "concurrent foldr application" $ withMaxSuccess maxTestCount $
819904 concurrentFoldrApplication
820905 prop "concurrent foldl application" $ withMaxSuccess maxTestCount $
@@ -823,59 +908,59 @@ main = hspec $ do
823908 -- These tests are specifically targeted towards detecting illegal sharing
824909 -- of SVar across conurrent streams. All transform ops must be added here.
825910 describe "Stream transform and combine operations" $ do
826− transformCombineOpsCommon S.fromFoldable "serially" serially (==)
827− transformCombineOpsCommon S.fromFoldable "aheadly" aheadly (==)
828− transformCombineOpsCommon S.fromFoldable "wSerially" wSerially sortEq
829− transformCombineOpsCommon S.fromFoldable "zipSerially" zipSerially (==)
830− transformCombineOpsCommon S.fromFoldable "zipAsyncly" zipAsyncly (==)
831− transformCombineOpsCommon S.fromFoldable "asyncly" asyncly sortEq
832− transformCombineOpsCommon S.fromFoldable "wAsyncly" wAsyncly sortEq
833− transformCombineOpsCommon S.fromFoldable "parallely" parallely sortEq
834−
835− transformCombineOpsCommon folded "serially" serially (==)
836− transformCombineOpsCommon folded "aheadly" aheadly (==)
837− transformCombineOpsCommon folded "wSerially" wSerially sortEq
838− transformCombineOpsCommon folded "zipSerially" zipSerially (==)
839− transformCombineOpsCommon folded "zipAsyncly" zipAsyncly (==)
840− transformCombineOpsCommon folded "asyncly" asyncly sortEq
841− transformCombineOpsCommon folded "wAsyncly" wAsyncly sortEq
842− transformCombineOpsCommon folded "parallely" parallely sortEq
843−
844− transformCombineOpsOrdered S.fromFoldable "serially" serially (==)
845− transformCombineOpsOrdered S.fromFoldable "serially" aheadly (==)
846− transformCombineOpsOrdered S.fromFoldable "zipSerially" zipSerially (==)
847− transformCombineOpsOrdered S.fromFoldable "zipAsyncly" zipAsyncly (==)
911+ serialOps $ transformCombineOpsCommon S.fromFoldable "serially" (==)
912+ wSerialOps $ transformCombineOpsCommon S.fromFoldable "wSerially" sortEq
913+ aheadOps $ transformCombineOpsCommon S.fromFoldable "aheadly" (==)
914+ asyncOps $ transformCombineOpsCommon S.fromFoldable "asyncly" sortEq
915+ wAsyncOps $ transformCombineOpsCommon S.fromFoldable "wAsyncly" sortEq
916+ parallelOps $ transformCombineOpsCommon S.fromFoldable "parallely" sortEq
917+ zipSerialOps $ transformCombineOpsCommon S.fromFoldable "zipSerially" (==)
918+ zipAsyncOps $ transformCombineOpsCommon S.fromFoldable "zipAsyncly" (==)
919+
920+ serialOps $ transformCombineOpsCommon folded "serially" (==)
921+ wSerialOps $ transformCombineOpsCommon folded "wSerially" sortEq
922+ aheadOps $ transformCombineOpsCommon folded "aheadly" (==)
923+ asyncOps $ transformCombineOpsCommon folded "asyncly" sortEq
924+ wAsyncOps $ transformCombineOpsCommon folded "wAsyncly" sortEq
925+ parallelOps $ transformCombineOpsCommon folded "parallely" sortEq
926+ zipSerialOps $ transformCombineOpsCommon folded "zipSerially" (==)
927+ zipAsyncOps $ transformCombineOpsCommon folded "zipAsyncly" (==)
928+
929+ serialOps $ transformCombineOpsOrdered S.fromFoldable "serially" (==)
930+ aheadOps $ transformCombineOpsOrdered S.fromFoldable "aheadly" (==)
931+ zipSerialOps $ transformCombineOpsOrdered S.fromFoldable "zipSerially" (==)
932+ zipAsyncOps $ transformCombineOpsOrdered S.fromFoldable "zipAsyncly" (==)
848933
849934 describe "Stream elimination operations" $ do
850− eliminationOps S.fromFoldable "serially" serially
851− eliminationOps S.fromFoldable "aheadly" aheadly
852− eliminationOps S.fromFoldable "wSerially" wSerially
853− eliminationOps S.fromFoldable "zipSerially" zipSerially
854− eliminationOps S.fromFoldable "zipAsyncly" zipAsyncly
855− eliminationOps S.fromFoldable "asyncly" asyncly
856− eliminationOps S.fromFoldable "wAsyncly" wAsyncly
857− eliminationOps S.fromFoldable "parallely" parallely
858−
859− eliminationOps folded "serially folded" serially
860− eliminationOps folded "aheadly folded" aheadly
861− eliminationOps folded "wSerially folded" wSerially
862− eliminationOps folded "zipSerially folded" zipSerially
863− eliminationOps folded "zipAsyncly folded" zipAsyncly
864− eliminationOps folded "asyncly folded" asyncly
865− eliminationOps folded "wAsyncly folded" wAsyncly
866− eliminationOps folded "parallely folded" parallely
935+ serialOps $ eliminationOps S.fromFoldable "serially"
936+ wSerialOps $ eliminationOps S.fromFoldable "wSerially"
937+ aheadOps $ eliminationOps S.fromFoldable "aheadly"
938+ asyncOps $ eliminationOps S.fromFoldable "asyncly"
939+ wAsyncOps $ eliminationOps S.fromFoldable "wAsyncly"
940+ parallelOps $ eliminationOps S.fromFoldable "parallely"
941+ zipSerialOps $ eliminationOps S.fromFoldable "zipSerially"
942+ zipAsyncOps $ eliminationOps S.fromFoldable "zipAsyncly"
943+
944+ serialOps $ eliminationOps folded "serially folded"
945+ wSerialOps $ eliminationOps folded "wSerially folded"
946+ aheadOps $ eliminationOps folded "aheadly folded"
947+ asyncOps $ eliminationOps folded "asyncly folded"
948+ wAsyncOps $ eliminationOps folded "wAsyncly folded"
949+ parallelOps $ eliminationOps folded "parallely folded"
950+ zipSerialOps $ eliminationOps folded "zipSerially folded"
951+ zipAsyncOps $ eliminationOps folded "zipAsyncly folded"
867952
868953 -- XXX Add a test where we chain all transformation APIs and make sure that
869954 -- the state is being passed through all of them.
870955 describe "Stream serial elimination operations" $ do
871− serialEliminationOps S.fromFoldable "serially" serially
872− serialEliminationOps S.fromFoldable "aheadly" aheadly
873− serialEliminationOps S.fromFoldable "wSerially" wSerially
874− serialEliminationOps S.fromFoldable "zipSerially" zipSerially
875− serialEliminationOps S.fromFoldable "zipAsyncly" zipAsyncly
876−
877− serialEliminationOps folded "serially folded" serially
878− serialEliminationOps folded "aheadly folded" aheadly
879− serialEliminationOps folded "wSerially folded" wSerially
880− serialEliminationOps folded "zipSerially folded" zipSerially
881− serialEliminationOps folded "zipAsyncly folded" zipAsyncly
956+ serialOps $ serialEliminationOps S.fromFoldable "serially"
957+ wSerialOps $ serialEliminationOps S.fromFoldable "wSerially"
958+ aheadOps $ serialEliminationOps S.fromFoldable "aheadly"
959+ zipSerialOps $ serialEliminationOps S.fromFoldable "zipSerially"
960+ zipAsyncOps $ serialEliminationOps S.fromFoldable "zipAsyncly"
961+
962+ serialOps $ serialEliminationOps folded "serially folded"
963+ wSerialOps $ serialEliminationOps folded "wSerially folded"
964+ aheadOps $ serialEliminationOps folded "aheadly folded"
965+ zipSerialOps $ serialEliminationOps folded "zipSerially folded"
966+ zipAsyncOps $ serialEliminationOps folded "zipAsyncly folded"