on master help sign in
Add concurrency rate control using maxRate
610c33aHarendra Kumar
Changelog.md+1
@@ -8,6 +8,7 @@
8
8
9### Enhancements
9### Enhancements
10
10
11+* Added `maxRate` combinator to control the yield rate of a stream.
11* Add `foldl1'`, `foldr1`, `intersperseM`, `find`, `lookup`, `and`, `or`,
12* Add `foldl1'`, `foldr1`, `intersperseM`, `find`, `lookup`, `and`, `or`,
12 `findIndices`, `findIndex`, `elemIndices`, `elemIndex`, `init` to Prelude
13 `findIndices`, `findIndex`, `elemIndices`, `elemIndex`, `init` to Prelude
13
14
src/Streamly.hs+8−1
@@ -112,6 +112,7 @@ module Streamly
112 -- $concurrency
112 -- $concurrency
113 , maxThreads
113 , maxThreads
114 , maxBuffer
114 , maxBuffer
115+ , maxRate
115
116
116 -- * Folding Containers of Streams
117 -- * Folding Containers of Streams
117 -- $foldutils
118 -- $foldutils
@@ -172,7 +173,7 @@ import Streamly.Streams.Ahead
172import Streamly.Streams.Parallel
173import Streamly.Streams.Parallel
173import Streamly.Streams.Zip
174import Streamly.Streams.Zip
174import Streamly.Streams.Prelude
175import Streamly.Streams.Prelude
175−import Streamly.Streams.SVar (maxThreads, maxBuffer)
176+import Streamly.Streams.SVar (maxThreads, maxBuffer, maxRate)
176import Streamly.SVar (MonadAsync)
177import Streamly.SVar (MonadAsync)
177import Data.Semigroup (Semigroup(..))
178import Data.Semigroup (Semigroup(..))
178
179
@@ -296,6 +297,12 @@ runZipAsync = runStream . K.adapt
296-- which can be used to combine two streams in a predetermined way irrespective
297-- which can be used to combine two streams in a predetermined way irrespective
297-- of the type.
298-- of the type.
298
299
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+--
299-- $concurrency
306-- $concurrency
300--
307--
301-- These combinators can be used at any point in a stream composition to
308-- 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
771-- @since 0.1.0
771-- @since 0.1.0
772{-# INLINE take #-}
772{-# INLINE take #-}
773take :: (IsStream t, Monad m) => Int -> t m a -> t m a
773take :: (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)
775
776
776-- | End the stream as soon as the predicate fails on an element.
777-- | End the stream as soon as the predicate fails on an element.
777--
778--
src/Streamly/SVar.hs+862−202
@@ -1,12 +1,13 @@
1−{-# LANGUAGE CPP #-}
1+{-# LANGUAGE CPP #-}
2−{-# LANGUAGE KindSignatures #-}
2+{-# LANGUAGE KindSignatures #-}
3−{-# LANGUAGE ConstraintKinds #-}
3+{-# LANGUAGE ConstraintKinds #-}
4−{-# LANGUAGE FlexibleContexts #-}
4+{-# LANGUAGE FlexibleContexts #-}
5−{-# LANGUAGE FlexibleInstances #-}
5+{-# LANGUAGE FlexibleInstances #-}
6+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
6−{-# LANGUAGE LambdaCase #-}
7+{-# LANGUAGE LambdaCase #-}
7−{-# LANGUAGE MagicHash #-}
8+{-# LANGUAGE MagicHash #-}
8−{-# LANGUAGE MultiParamTypeClasses #-}
9+{-# LANGUAGE MultiParamTypeClasses #-}
9−{-# LANGUAGE UnboxedTuples #-}
10+{-# LANGUAGE UnboxedTuples #-}
10
11
11-- |
12-- |
12-- Module : Streamly.SVar
13-- Module : Streamly.SVar
@@ -21,27 +22,37 @@
21module Streamly.SVar
22module Streamly.SVar
22 (
23 (
23 MonadAsync
24 MonadAsync
24− , SVar (..)
25 , SVarStyle (..)
25 , SVarStyle (..)
26− , defaultMaxBuffer
26+ , SVar (..)
27− , defaultMaxThreads
27+
28− , State (..)
28+ -- State threaded around the stream
29+ , Limit (..)
30+ , State (streamVar)
29 , defState
31 , defState
30 , rstState
32 , rstState
31−
33+ , getMaxThreads
34+ , setMaxThreads
35+ , getMaxBuffer
36+ , setMaxBuffer
37+ , getMaxStreamRate
38+ , setMaxStreamRate
39+ , setStreamLatency
40+ , getYieldLimit
41+ , setYieldLimit
42+
43+ -- SVar related
32 , newAheadVar
44 , newAheadVar
33 , newParallelVar
45 , newParallelVar
34
46
35− , toStreamVar
36−
37 , atomicModifyIORefCAS
47 , atomicModifyIORefCAS
48+ , WorkerInfo (..)
49+ , YieldRateInfo (..)
38 , ChildEvent (..)
50 , ChildEvent (..)
39 , AheadHeapEntry (..)
51 , AheadHeapEntry (..)
52+ , send
40 , sendYield
53 , sendYield
41 , sendStop
54 , sendStop
42 , enqueueLIFO
55 , enqueueLIFO
43− , workLoopLIFO
44− , workLoopFIFO
45 , enqueueFIFO
56 , enqueueFIFO
46 , enqueueAhead
57 , enqueueAhead
47 , pushWorkerPar
58 , pushWorkerPar
@@ -50,18 +61,30 @@ module Streamly.SVar
50 , dequeueAhead
61 , dequeueAhead
51 , dequeueFromHeap
62 , dequeueFromHeap
52
63
64+ , getYieldRateInfo
65+ , collectLatency
66+ , workerUpdateLatency
67+ , workerRateControl
68+ , updateYieldCount
69+ , decrementYieldLimit
70+ , incrementYieldLimit
53 , postProcessBounded
71 , postProcessBounded
72+ , postProcessPaced
54 , readOutputQBounded
73 , readOutputQBounded
55− , sendWorker
74+ , readOutputQPaced
75+ , dispatchWorkerPaced
76+ , sendFirstWorker
56 , delThread
77 , delThread
78+
79+ , toStreamVar
57 )
80 )
58where
81where
59
82
60import Control.Concurrent
83import Control.Concurrent
61 (ThreadId, myThreadId, threadDelay, getNumCapabilities)
84 (ThreadId, myThreadId, threadDelay, getNumCapabilities)
62import Control.Concurrent.MVar
85import Control.Concurrent.MVar
63− (MVar, newEmptyMVar, tryPutMVar, takeMVar)
86+ (MVar, newEmptyMVar, tryPutMVar, takeMVar, newMVar)
64−import Control.Exception (SomeException(..), catch, mask)
87+import Control.Exception (SomeException(..), catch, mask, assert)
65import Control.Monad (when)
88import Control.Monad (when)
66import Control.Monad.Catch (MonadThrow)
89import Control.Monad.Catch (MonadThrow)
67import Control.Monad.IO.Class (MonadIO(..))
90import Control.Monad.IO.Class (MonadIO(..))
@@ -69,17 +92,20 @@ import Control.Monad.Trans.Control (MonadBaseControl, control)
69import Data.Atomics
92import Data.Atomics
70 (casIORef, readForCAS, peekTicket, atomicModifyIORefCAS_,
93 (casIORef, readForCAS, peekTicket, atomicModifyIORefCAS_,
71 writeBarrier, storeLoadBarrier)
94 writeBarrier, storeLoadBarrier)
72−import Data.Concurrent.Queue.MichaelScott
95+import Data.Concurrent.Queue.MichaelScott (LinkedQueue, pushL)
73− (LinkedQueue, pushL, tryPopR)
74import Data.Functor (void)
96import Data.Functor (void)
75import Data.Heap (Heap, Entry(..))
97import Data.Heap (Heap, Entry(..))
98+import Data.Int (Int64)
76import Data.IORef
99import Data.IORef
77− (IORef, modifyIORef, newIORef, readIORef, atomicModifyIORef)
100+ (IORef, modifyIORef, newIORef, readIORef, writeIORef, atomicModifyIORef)
78import Data.Maybe (fromJust)
101import Data.Maybe (fromJust)
79import Data.Set (Set)
102import Data.Set (Set)
103+import Data.Word (Word64)
80import GHC.Conc (ThreadId(..))
104import GHC.Conc (ThreadId(..))
81import GHC.Exts
105import GHC.Exts
82import GHC.IO (IO(..))
106import GHC.IO (IO(..))
107+import System.Clock
108+ (TimeSpec, Clock(Monotonic), getTime, toNanoSecs, fromNanoSecs)
83
109
84import qualified Data.Heap as H
110import qualified Data.Heap as H
85import qualified Data.Set as S
111import qualified Data.Set as S
@@ -94,10 +120,33 @@ import Control.Concurrent.MVar (tryTakeMVar)
94import Control.Exception
120import Control.Exception
95 (catches, throwIO, Handler(..), BlockedIndefinitelyOnMVar(..),
121 (catches, throwIO, Handler(..), BlockedIndefinitelyOnMVar(..),
96 BlockedIndefinitelyOnSTM(..))
122 BlockedIndefinitelyOnSTM(..))
97−import Data.IORef (writeIORef)
98import System.IO (hPutStrLn, stderr)
123import System.IO (hPutStrLn, stderr)
99#endif
124#endif
100
125
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+
101------------------------------------------------------------------------------
150------------------------------------------------------------------------------
102-- Parent child thread communication type
151-- Parent child thread communication type
103------------------------------------------------------------------------------
152------------------------------------------------------------------------------
@@ -148,61 +197,155 @@ data SVarStyle =
148-- New work is enqueued either at the time of creation of the SVar or as a
197-- New work is enqueued either at the time of creation of the SVar or as a
149-- result of executing the parallel combinators i.e. '<|' and '<|>' when the
198-- result of executing the parallel combinators i.e. '<|' and '<|>' when the
150-- already enqueued computations get evaluated. See 'joinStreamVarAsync'.
199-- already enqueued computations get evaluated. See 'joinStreamVarAsync'.
151−--
200+
152−-- XXX can we use forall t m.
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+
153−data SVar t m a =
262+data SVar t m a = SVar
154− SVar {
263+ {
155− -- Read only state
264+ -- Read only state
156− svarStyle :: SVarStyle
265+ svarStyle :: SVarStyle
157−
266+
158− -- Shared output queue (events, length)
267+ -- Shared output queue (events, length)
159− , outputQueue :: IORef ([ChildEvent a], Int)
268+ , outputQueue :: IORef ([ChildEvent a], Int)
160− , maxYieldLimit :: Maybe (IORef Int)
161− , outputDoorBell :: MVar () -- signal the consumer about output
269+ , outputDoorBell :: MVar () -- signal the consumer about output
162− , readOutputQ :: m [ChildEvent a]
270+ , readOutputQ :: m [ChildEvent a]
163− , postProcess :: m Bool
271+ , postProcess :: m Bool
164−
272+
273+ -- Combined/aggregate parameters
274+ , maxWorkerLimit :: Limit
275+ , maxBufferLimit :: Limit
276+ , remainingYields :: Maybe (IORef Count)
277+ , yieldRateInfo :: Maybe YieldRateInfo
278+
165− -- Used only by bounded SVar types
279+ -- Used only by bounded SVar types
166− , enqueue :: t m a -> IO ()
280+ , enqueue :: t m a -> IO ()
167− , isWorkDone :: IO Bool
281+ , isWorkDone :: IO Bool
168− , needDoorBell :: IORef Bool
282+ , needDoorBell :: IORef Bool
169− , workLoop :: m ()
283+ , workLoop :: WorkerInfo -> m ()
170−
284+
171− -- Shared, thread tracking
285+ -- Shared, thread tracking
172− , workerThreads :: IORef (Set ThreadId)
286+ , workerThreads :: IORef (Set ThreadId)
173− , workerCount :: IORef Int
287+ , workerCount :: IORef Int
174− , accountThread :: ThreadId -> m ()
288+ , accountThread :: ThreadId -> m ()
289+
175#ifdef DIAGNOSTICS
290#ifdef DIAGNOSTICS
176− , outputHeap :: IORef (Heap (Entry Int (AheadHeapEntry t m a))
291+ , outputHeap :: IORef (Heap (Entry Int (AheadHeapEntry t m a)) , Int)
177− , Int
178− )
179− -- Shared work queue (stream, seqNo)
292+ -- Shared work queue (stream, seqNo)
180− , aheadWorkQueue :: IORef ([t m a], Int)
293+ , aheadWorkQueue :: IORef ([t m a], Int)
181− , totalDispatches :: IORef Int
294+ , totalDispatches :: IORef Int
182− , maxWorkers :: IORef Int
295+ , maxWorkers :: IORef Int
183− , maxOutQSize :: IORef Int
296+ , maxOutQSize :: IORef Int
184− , maxHeapSize :: IORef Int
297+ , maxHeapSize :: IORef Int
185− , maxWorkQSize :: IORef Int
298+ , maxWorkQSize :: IORef Int
186#endif
299#endif
187− }
300+ }
301+
302+-------------------------------------------------------------------------------
303+-- State for concurrency control
304+-------------------------------------------------------------------------------
188
305
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
189data State t m a = State
311data State t m a = State
312+ { -- one shot configuration, automatically reset for each API call
190− { streamVar :: Maybe (SVar t m a)
313+ streamVar :: Maybe (SVar t m a)
191− , yieldLimit :: Maybe Int
314+ , _yieldLimit :: Maybe Count
192− , threadsHigh :: Int
315+
193− , bufferHigh :: Int
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
194 }
322 }
195
323
196−defaultMaxThreads, defaultMaxBuffer :: Int
324+-------------------------------------------------------------------------------
197−defaultMaxThreads = 1500
325+-- State defaults and reset
198−defaultMaxBuffer = 1500
326+-------------------------------------------------------------------------------
199
327
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.
200defState :: State t m a
341defState :: State t m a
201defState = State
342defState = State
202 { streamVar = Nothing
343 { streamVar = Nothing
203− , yieldLimit = Nothing
344+ , _yieldLimit = Nothing
204− , threadsHigh = defaultMaxThreads
345+ , _threadsHigh = defaultMaxThreads
205− , bufferHigh = defaultMaxBuffer
346+ , _bufferHigh = defaultMaxBuffer
347+ , _maxStreamRate = Nothing
348+ , _streamLatency = Nothing
206 }
349 }
207
350
208-- XXX if perf gets affected we can have all the Nothing params in a single
351-- XXX if perf gets affected we can have all the Nothing params in a single
@@ -215,9 +358,84 @@ defState = State
215rstState :: State t m a -> State t m b
358rstState :: State t m a -> State t m b
216rstState st = st
359rstState st = st
217 { streamVar = Nothing
360 { streamVar = Nothing
218− , yieldLimit = Nothing
361+ , _yieldLimit = Nothing
219 }
362 }
220
363
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+
221#ifdef DIAGNOSTICS
439#ifdef DIAGNOSTICS
222{-# NOINLINE dumpSVar #-}
440{-# NOINLINE dumpSVar #-}
223dumpSVar :: SVar t m a -> IO String
441dumpSVar :: SVar t m a -> IO String
@@ -287,6 +505,10 @@ withDBGMVar :: SVar t m a -> String -> IO () -> IO ()
287withDBGMVar _ _ action = action
505withDBGMVar _ _ action = action
288#endif
506#endif
289
507
508+-------------------------------------------------------------------------------
509+-- CAS
510+-------------------------------------------------------------------------------
511+
290-- Slightly faster version of CAS. Gained some improvement by avoiding the use
512-- Slightly faster version of CAS. Gained some improvement by avoiding the use
291-- of "evaluate" because we know we do not have exceptions in fn.
513-- of "evaluate" because we know we do not have exceptions in fn.
292{-# INLINE atomicModifyIORefCAS #-}
514{-# INLINE atomicModifyIORefCAS #-}
@@ -346,8 +568,10 @@ doFork action exHandler =
346
568
347-- | This function is used by the producer threads to queue output for the
569-- | This function is used by the producer threads to queue output for the
348-- consumer thread to consume. Returns whether the queue has more space.
570-- consumer thread to consume. Returns whether the queue has more space.
349−send :: Int -> SVar t m a -> ChildEvent a -> IO Bool
571+send :: SVar t m a -> ChildEvent a -> IO Bool
350−send maxOutputQLen sv msg = do
572+send sv msg = do
573+ -- XXX can the access to outputQueue and maxBufferLimit be made faster
574+ -- somehow?
351 len <- atomicModifyIORefCAS (outputQueue sv) $ \(es, n) ->
575 len <- atomicModifyIORefCAS (outputQueue sv) $ \(es, n) ->
352 ((msg : es, n + 1), n)
576 ((msg : es, n + 1), n)
353 when (len <= 0) $ do
577 when (len <= 0) $ do
@@ -361,22 +585,116 @@ send maxOutputQLen sv msg = do
361 -- The important point is that the consumer is guaranteed to receive a
585 -- The important point is that the consumer is guaranteed to receive a
362 -- doorbell if something was added to the queue after it empties it.
586 -- doorbell if something was added to the queue after it empties it.
363 void $ tryPutMVar (outputDoorBell sv) ()
587 void $ tryPutMVar (outputDoorBell sv) ()
364− return (len < maxOutputQLen || maxOutputQLen < 0)
365
588
366−{-# NOINLINE sendYield #-}
589+ -- XXX we should reserve the buffer when we pick up the work from the
367−sendYield :: Int -> SVar t m a -> ChildEvent a -> IO Bool
590+ -- queue, instead of checking it here when it is too late.
368−sendYield maxOutputQLen sv msg = do
591+ let limit = maxBufferLimit sv
369− ylimit <- case maxYieldLimit sv of
592+ case limit of
370− Nothing -> return True
593+ Unlimited -> return True
371− Just ref -> atomicModifyIORefCAS ref $ \x -> (x - 1, x > 1)
594+ Limited lim -> do
372− r <- send maxOutputQLen sv msg
595+ active <- readIORef (workerCount sv)
373− return $ r && ylimit
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
374
641
375−{-# NOINLINE sendStop #-}
642+-- CAUTION! this also updates the yield count and therefore should be called
376−sendStop :: SVar t m a -> IO ()
643+-- only when we are actually yielding an element.
377−sendStop sv = do
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 ()
378− liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n - 1
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
379− myThreadId >>= \tid -> void $ send (-1) sv (ChildStop tid Nothing)
697+ myThreadId >>= \tid -> void $ send sv (ChildStop tid Nothing)
380
698
381-------------------------------------------------------------------------------
699-------------------------------------------------------------------------------
382-- Async
700-- Async
@@ -401,25 +719,6 @@ enqueueLIFO sv q m = do
401 atomicModifyIORefCAS_ (needDoorBell sv) (const False)
719 atomicModifyIORefCAS_ (needDoorBell sv) (const False)
402 void $ tryPutMVar (outputDoorBell sv) ()
720 void $ tryPutMVar (outputDoorBell sv) ()
403
721
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−
423-------------------------------------------------------------------------------
722-------------------------------------------------------------------------------
424-- WAsync
723-- WAsync
425-------------------------------------------------------------------------------
724-------------------------------------------------------------------------------
@@ -443,21 +742,6 @@ enqueueFIFO sv q m = do
443 atomicModifyIORefCAS_ (needDoorBell sv) (const False)
742 atomicModifyIORefCAS_ (needDoorBell sv) (const False)
444 void $ tryPutMVar (outputDoorBell sv) ()
743 void $ tryPutMVar (outputDoorBell sv) ()
445
744
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−
461-------------------------------------------------------------------------------
745-------------------------------------------------------------------------------
462-- Ahead
746-- Ahead
463-------------------------------------------------------------------------------
747-------------------------------------------------------------------------------
@@ -510,10 +794,18 @@ workLoopFIFO f st q = run
510-- that they do not hog the resources and hinder the progress of the threads in
794-- that they do not hog the resources and hinder the progress of the threads in
511-- front of them.
795-- front of them.
512
796
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
514-- each left associative expression. The queue is used only for right
798-- each left associative expression. The queue is used only for right
515-- associated expression, we queue the right expression and execute the left.
799-- 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.
517{-# INLINE enqueueAhead #-}
809{-# INLINE enqueueAhead #-}
518enqueueAhead :: SVar t m a -> IORef ([t m a], Int) -> t m a -> IO ()
810enqueueAhead :: SVar t m a -> IORef ([t m a], Int) -> t m a -> IO ()
519enqueueAhead sv q m = do
811enqueueAhead sv q m = do
@@ -571,7 +863,7 @@ dequeueFromHeap
571 :: IORef (Heap (Entry Int (AheadHeapEntry t m a)), Int)
863 :: IORef (Heap (Entry Int (AheadHeapEntry t m a)), Int)
572 -> IO (Maybe (Entry Int (AheadHeapEntry t m a)))
864 -> IO (Maybe (Entry Int (AheadHeapEntry t m a)))
573dequeueFromHeap hpRef = do
865dequeueFromHeap hpRef = do
574− atomicModifyIORefCAS hpRef $ \hp@(h, snum) -> do
866+ atomicModifyIORef hpRef $ \hp@(h, snum) -> do
575 let r = H.uncons h
867 let r = H.uncons h
576 case r of
868 case r of
577 Nothing -> (hp, Nothing)
869 Nothing -> (hp, Nothing)
@@ -637,7 +929,7 @@ allThreadsDone sv = liftIO $ S.null <$> readIORef (workerThreads sv)
637handleChildException :: SVar t m a -> SomeException -> IO ()
929handleChildException :: SVar t m a -> SomeException -> IO ()
638handleChildException sv e = do
930handleChildException sv e = do
639 tid <- myThreadId
931 tid <- myThreadId
640− void $ send (-1) sv (ChildStop tid (Just e))
932+ void $ send sv (ChildStop tid (Just e))
641
933
642#ifdef DIAGNOSTICS
934#ifdef DIAGNOSTICS
643recordMaxWorkers :: MonadIO m => SVar t m a -> m ()
935recordMaxWorkers :: MonadIO m => SVar t m a -> m ()
@@ -649,13 +941,24 @@ recordMaxWorkers sv = liftIO $ do
649#endif
941#endif
650
942
651{-# NOINLINE pushWorker #-}
943{-# NOINLINE pushWorker #-}
652−pushWorker :: MonadAsync m => SVar t m a -> m ()
944+pushWorker :: MonadAsync m => Count -> SVar t m a -> m ()
653−pushWorker sv = do
945+pushWorker yieldMax sv = do
654 liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n + 1
946 liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n + 1
655#ifdef DIAGNOSTICS
947#ifdef DIAGNOSTICS
656 recordMaxWorkers sv
948 recordMaxWorkers sv
657#endif
949#endif
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+ }
658− doFork (workLoop sv) (handleChildException sv) >>= addThread sv
961+ doFork (workLoop sv winfo) (handleChildException sv) >>= addThread sv
659
962
660-- XXX we can push the workerCount modification in accountThread and use the
963-- XXX we can push the workerCount modification in accountThread and use the
661-- same pushWorker for Parallel case as well.
964-- same pushWorker for Parallel case as well.
@@ -676,49 +979,313 @@ pushWorkerPar sv wloop = do
676#endif
979#endif
677 doFork wloop (handleChildException sv) >>= modifyThread sv
980 doFork wloop (handleChildException sv) >>= modifyThread sv
678
981
982+-- Returns:
983+-- True: can dispatch more
984+-- False: cannot dispatch any more
679−dispatchWorker :: MonadAsync m => Int -> SVar t m a -> m ()
985+dispatchWorker :: MonadAsync m => Count -> SVar t m a -> m Bool
986+dispatchWorker yieldCount sv = do
680−dispatchWorker maxWorkerLimit 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.
681 done <- liftIO $ isWorkDone sv
990 done <- liftIO $ isWorkDone sv
682− when (not done) $ do
991+ if (not done)
992+ then do
683 -- Note that the worker count is only decremented during event
993 -- Note that the worker count is only decremented during event
684 -- processing in fromStreamVar and therefore it is safe to read and
994 -- processing in fromStreamVar and therefore it is safe to read and
685 -- use it without a lock.
995 -- use it without a lock.
686− cnt <- liftIO $ readIORef $ workerCount sv
996+ active <- liftIO $ readIORef $ workerCount sv
687 -- Note that we may deadlock if the previous workers (tasks in the
997 -- Note that we may deadlock if the previous workers (tasks in the
688 -- stream) wait/depend on the future workers (tasks in the stream)
998 -- stream) wait/depend on the future workers (tasks in the stream)
689 -- executing. In that case we should either configure the maxWorker
999 -- executing. In that case we should either configure the maxWorker
690 -- count to higher or use parallel style instead of ahead or async
1000 -- count to higher or use parallel style instead of ahead or async
691 -- style.
1001 -- style.
692− limit <- case maxYieldLimit sv of
1002+ limit <- case remainingYields sv of
693− Nothing -> return maxWorkerLimit
1003+ Nothing -> return workerLimit
694 Just x -> do
1004 Just x -> do
695− lim <- liftIO $ readIORef x
1005+ n <- liftIO $ readIORef x
696 return $
1006 return $
697− if maxWorkerLimit > 0
1007+ case workerLimit of
698− then min maxWorkerLimit lim
1008+ Unlimited -> Limited (fromIntegral n)
699− else lim
1009+ Limited lim -> Limited $ min lim (fromIntegral n)
700− when (cnt < limit || limit < 0) $ pushWorker sv
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
701
1025
702−{-# NOINLINE sendWorkerWait #-}
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
703−sendWorkerWait :: MonadAsync m => Int -> SVar t m a -> m ()
1202+dispatchWorkerPaced :: MonadAsync m => SVar t m a -> m Bool
1203+dispatchWorkerPaced sv = do
704−sendWorkerWait maxWorkerLimit sv = do
1204+ let workerLimit = maxWorkerLimit sv
705− -- Note that we are guaranteed to have at least one outstanding worker when
1205+ let yinfo = fromJust $ yieldRateInfo sv
706− -- we enter this function. So if we sleep we are guaranteed to be woken up
1206+ (count, tstamp, wLatency) <- liftIO $ collectLatency yinfo
707− -- by a outputDoorBell, when the worker exits.
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
708
1243
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
709 -- XXX we need a better way to handle this than hardcoded delays. The
1263 -- XXX we need a better way to handle this than hardcoded delays. The
710 -- delays may be different for different systems.
1264 -- delays may be different for different systems.
711− ncpu <- liftIO $ getNumCapabilities
1265+ ncpu <- getNumCapabilities
712 if ncpu <= 1
1266 if ncpu <= 1
713 then
1267 then
714 if (svarStyle sv == AheadVar)
1268 if (svarStyle sv == AheadVar)
715− then liftIO $ threadDelay 100
1269+ then threadDelay 100
716− else liftIO $ threadDelay 25
1270+ else threadDelay 25
717 else
1271 else
718 if (svarStyle sv == AheadVar)
1272 if (svarStyle sv == AheadVar)
719− then liftIO $ threadDelay 100
1273+ then threadDelay 100
720− else liftIO $ threadDelay 10
1274+ else threadDelay 10
721
1275
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
722 (_, n) <- liftIO $ readIORef (outputQueue sv)
1289 (_, n) <- liftIO $ readIORef (outputQueue sv)
723 when (n <= 0) $ do
1290 when (n <= 0) $ do
724 -- The queue may be empty temporarily if the worker has dequeued the
1291 -- The queue may be empty temporarily if the worker has dequeued the
@@ -753,7 +1320,7 @@ sendWorkerWait maxWorkerLimit sv = do
753
1320
754 liftIO $ atomicModifyIORefCAS_ (needDoorBell sv) $ const True
1321 liftIO $ atomicModifyIORefCAS_ (needDoorBell sv) $ const True
755 liftIO $ storeLoadBarrier
1322 liftIO $ storeLoadBarrier
756− dispatchWorker maxWorkerLimit sv
1323+ canDoMore <- dispatch sv
757
1324
758 -- XXX test for the case when we miss sending a worker when the worker
1325 -- XXX test for the case when we miss sending a worker when the worker
759 -- count is more than 1500.
1326 -- count is more than 1500.
@@ -762,14 +1329,13 @@ sendWorkerWait maxWorkerLimit sv = do
762 -- least one outstanding worker. Otherwise we could be sleeping
1329 -- least one outstanding worker. Otherwise we could be sleeping
763 -- forever.
1330 -- forever.
764
1331
765− done <- liftIO $ isWorkDone sv
1332+ if canDoMore
766− if done
1333+ then sendWorkerWait delay dispatch sv
767− then do
1334+ else do
768 liftIO $ withDBGMVar sv "sendWorkerWait: nothing to do"
1335 liftIO $ withDBGMVar sv "sendWorkerWait: nothing to do"
769 $ takeMVar (outputDoorBell sv)
1336 $ takeMVar (outputDoorBell sv)
770 (_, len) <- liftIO $ readIORef (outputQueue sv)
1337 (_, len) <- liftIO $ readIORef (outputQueue sv)
771− when (len <= 0) $ sendWorkerWait maxWorkerLimit sv
1338+ when (len <= 0) $ sendWorkerWait delay dispatch sv
772− else sendWorkerWait maxWorkerLimit sv
773
1339
774{-# INLINE readOutputQRaw #-}
1340{-# INLINE readOutputQRaw #-}
775readOutputQRaw :: SVar t m a -> IO ([ChildEvent a], Int)
1341readOutputQRaw :: SVar t m a -> IO ([ChildEvent a], Int)
@@ -781,8 +1347,8 @@ readOutputQRaw sv = do
781#endif
1347#endif
782 return (list, len)
1348 return (list, len)
783
1349
784−readOutputQBounded :: MonadAsync m => Int -> SVar t m a -> m [ChildEvent a]
1350+readOutputQBounded :: MonadAsync m => SVar t m a -> m [ChildEvent a]
785−readOutputQBounded n sv = do
1351+readOutputQBounded sv = do
786 (list, len) <- liftIO $ readOutputQRaw sv
1352 (list, len) <- liftIO $ readOutputQRaw sv
787 -- When there is no output seen we dispatch more workers to help
1353 -- When there is no output seen we dispatch more workers to help
788 -- out if there is work pending in the work queue.
1354 -- out if there is work pending in the work queue.
@@ -802,32 +1368,96 @@ readOutputQBounded n sv = do
802 cnt <- liftIO $ readIORef $ workerCount sv
1368 cnt <- liftIO $ readIORef $ workerCount sv
803 when (cnt <= 0) $ do
1369 when (cnt <= 0) $ do
804 done <- liftIO $ isWorkDone sv
1370 done <- liftIO $ isWorkDone sv
805− when (not done) $ pushWorker sv
1371+ when (not done) $ pushWorker 0 sv
806
1372
807 {-# INLINE blockingRead #-}
1373 {-# INLINE blockingRead #-}
808 blockingRead = do
1374 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
810 liftIO $ (readOutputQRaw sv >>= return . fst)
1394 liftIO $ (readOutputQRaw sv >>= return . fst)
811
1395
812postProcessBounded :: MonadAsync m => SVar t m a -> m Bool
1396postProcessBounded :: MonadAsync m => SVar t m a -> m Bool
813postProcessBounded sv = do
1397postProcessBounded sv = do
814 workersDone <- allThreadsDone sv
1398 workersDone <- allThreadsDone sv
815− -- There may still be work pending even if there are no workers
1399+ -- There may still be work pending even if there are no workers pending
816− -- pending because all the workers may return if the
1400+ -- because all the workers may return if the outputQueue becomes full. In
817− -- outputQueue becomes full. In that case send off a worker to
818− -- kickstart the work again.
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?
819 if workersDone
1417 if workersDone
820 then do
1418 then do
821 r <- liftIO $ isWorkDone sv
1419 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
823 return r
1424 return r
824 else return False
1425 else return False
825
1426
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+
826getAheadSVar :: MonadAsync m
1454getAheadSVar :: MonadAsync m
827 => State t m a
1455 => State t m a
828− -> ( State t m a
829− -> IORef ([t m a], Int)
1456+ -> ( IORef ([t m a], Int)
830 -> IORef (Heap (Entry Int (AheadHeapEntry t m a)), Int)
1457 -> IORef (Heap (Entry Int (AheadHeapEntry t m a)), Int)
1458+ -> State t m a
1459+ -> SVar t m a
1460+ -> WorkerInfo
831 -> m ())
1461 -> m ())
832 -> IO (SVar t m a)
1462 -> IO (SVar t m a)
833getAheadSVar st f = do
1463getAheadSVar st f = do
@@ -838,53 +1468,71 @@ getAheadSVar st f = do
838 wfw <- newIORef False
1468 wfw <- newIORef False
839 running <- newIORef S.empty
1469 running <- newIORef S.empty
840 q <- newIORef ([], -1)
1470 q <- newIORef ([], -1)
841− yl <- case yieldLimit st of
1471+ yl <- case getYieldLimit st of
842 Nothing -> return Nothing
1472 Nothing -> return Nothing
843 Just x -> Just <$> newIORef x
1473 Just x -> Just <$> newIORef x
1474+ rateInfo <- getYieldRateInfo st
844
1475
845#ifdef DIAGNOSTICS
1476#ifdef DIAGNOSTICS
846− disp <- newIORef 0
1477+ disp <- newIORef 0
847 maxWrk <- newIORef 0
1478 maxWrk <- newIORef 0
848 maxOq <- newIORef 0
1479 maxOq <- newIORef 0
849 maxHs <- newIORef 0
1480 maxHs <- newIORef 0
850 maxWq <- newIORef 0
1481 maxWq <- newIORef 0
851#endif
1482#endif
852− let sv =
1483+
1484+ let getSVar sv readOutput postProc = SVar
853− SVar { outputQueue = outQ
1485+ { outputQueue = outQ
854− , maxYieldLimit = yl
1486+ , remainingYields = yl
1487+ , maxBufferLimit = getMaxBuffer st
1488+ , maxWorkerLimit = getMaxThreads st
1489+ , yieldRateInfo = rateInfo
855− , outputDoorBell = outQMv
1490+ , outputDoorBell = outQMv
856− , readOutputQ = readOutputQBounded (threadsHigh st) sv
1491+ , readOutputQ = readOutput sv
857− , postProcess = postProcessBounded sv
1492+ , postProcess = postProc sv
858− , workerThreads = running
1493+ , workerThreads = running
859− -- , workLoop = workLoopAhead sv q outH
860− , workLoop = f st{streamVar = Just sv} q outH
1494+ , workLoop = f q outH st{streamVar = Just sv} sv
861− , enqueue = enqueueAhead sv q
1495+ , enqueue = enqueueAhead sv q
862− , isWorkDone = isWorkDoneAhead q outH
1496+ , isWorkDone = isWorkDoneAhead sv q outH
863− , needDoorBell = wfw
1497+ , needDoorBell = wfw
864− , svarStyle = AheadVar
1498+ , svarStyle = AheadVar
865− , workerCount = active
1499+ , workerCount = active
866− , accountThread = delThread sv
1500+ , accountThread = delThread sv
867#ifdef DIAGNOSTICS
1501#ifdef DIAGNOSTICS
868− , aheadWorkQueue = q
1502+ , aheadWorkQueue = q
869− , outputHeap = outH
1503+ , outputHeap = outH
870− , totalDispatches = disp
1504+ , totalDispatches = disp
871− , maxWorkers = maxWrk
1505+ , maxWorkers = maxWrk
872− , maxOutQSize = maxOq
1506+ , maxOutQSize = maxOq
873− , maxHeapSize = maxHs
1507+ , maxHeapSize = maxHs
874− , maxWorkQSize = maxWq
1508+ , maxWorkQSize = maxWq
875#endif
1509#endif
876− }
1510+ }
1511+
1512+ let sv =
1513+ case getMaxStreamRate st of
1514+ Nothing -> getSVar sv readOutputQBounded postProcessBounded
1515+ Just _ -> getSVar sv readOutputQPaced postProcessPaced
877 in return sv
1516 in return sv
878
1517
879 where
1518 where
880
1519
881 {-# INLINE isWorkDoneAhead #-}
1520 {-# INLINE isWorkDoneAhead #-}
882− isWorkDoneAhead q ref = do
1521+ isWorkDoneAhead sv q ref = do
883 heapDone <- do
1522 heapDone <- do
884 (hp, _) <- readIORef ref
1523 (hp, _) <- readIORef ref
885 return (H.size hp <= 0)
1524 return (H.size hp <= 0)
886 queueDone <- checkEmpty q
1525 queueDone <- checkEmpty q
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.
887− return $ queueDone && heapDone
1535+ return $ yieldsDone || (queueDone && heapDone)
888
1536
889 checkEmpty q = do
1537 checkEmpty q = do
890 (xs, _) <- readIORef q
1538 (xs, _) <- readIORef q
@@ -905,7 +1553,10 @@ getParallelSVar = do
905#endif
1553#endif
906 let sv =
1554 let sv =
907 SVar { outputQueue = outQ
1555 SVar { outputQueue = outQ
908− , maxYieldLimit = Nothing
1556+ , remainingYields = Nothing
1557+ , maxBufferLimit = Unlimited
1558+ , maxWorkerLimit = undefined
1559+ , yieldRateInfo = Nothing
909 , outputDoorBell = outQMv
1560 , outputDoorBell = outQMv
910 , readOutputQ = readOutputQPar sv
1561 , readOutputQ = readOutputQPar sv
911 , postProcess = allThreadsDone sv
1562 , postProcess = allThreadsDone sv
@@ -935,27 +1586,31 @@ getParallelSVar = do
935 withDBGMVar sv "readOutputQPar: doorbell" $ takeMVar (outputDoorBell sv)
1586 withDBGMVar sv "readOutputQPar: doorbell" $ takeMVar (outputDoorBell sv)
936 readOutputQRaw sv >>= return . fst
1587 readOutputQRaw sv >>= return . fst
937
1588
938−sendWorker :: MonadAsync m => SVar t m a -> t m a -> m (SVar t m a)
1589+sendFirstWorker :: MonadAsync m => SVar t m a -> t m a -> m (SVar t m a)
939−sendWorker sv m = do
1590+sendFirstWorker sv m = do
940 -- Note: We must have all the work on the queue before sending the
1591 -- Note: We must have all the work on the queue before sending the
941 -- pushworker, otherwise the pushworker may exit before we even get a
1592 -- pushworker, otherwise the pushworker may exit before we even get a
942 -- chance to push.
1593 -- chance to push.
943 liftIO $ enqueue sv m
1594 liftIO $ enqueue sv m
1595+ case yieldRateInfo sv of
1596+ Nothing -> pushWorker 0 sv
944− pushWorker sv
1597+ Just _ -> pushWorker 1 sv
945 return sv
1598 return sv
946
1599
947{-# INLINABLE newAheadVar #-}
1600{-# INLINABLE newAheadVar #-}
948newAheadVar :: MonadAsync m
1601newAheadVar :: MonadAsync m
949 => State t m a
1602 => State t m a
950 -> t m a
1603 -> t m a
951− -> ( State t m a
952− -> IORef ([t m a], Int)
1604+ -> ( IORef ([t m a], Int)
953 -> IORef (Heap (Entry Int (AheadHeapEntry t m a)), Int)
1605 -> IORef (Heap (Entry Int (AheadHeapEntry t m a)), Int)
1606+ -> State t m a
1607+ -> SVar t m a
1608+ -> WorkerInfo
954 -> m ())
1609 -> m ())
955 -> m (SVar t m a)
1610 -> m (SVar t m a)
956newAheadVar st m wloop = do
1611newAheadVar st m wloop = do
957 sv <- liftIO $ getAheadSVar st wloop
1612 sv <- liftIO $ getAheadSVar st wloop
958− sendWorker sv m
1613+ sendFirstWorker sv m
959
1614
960{-# INLINABLE newParallelVar #-}
1615{-# INLINABLE newParallelVar #-}
961newParallelVar :: MonadAsync m => m (SVar t m a)
1616newParallelVar :: MonadAsync m => m (SVar t m a)
@@ -971,4 +1626,9 @@ toStreamVar sv m = do
971 -- XXX This is safe only when called from the consumer thread or when no
1626 -- XXX This is safe only when called from the consumer thread or when no
972 -- consumer is present. There may be a race if we are not running in the
1627 -- consumer is present. There may be a race if we are not running in the
973 -- consumer thread.
1628 -- consumer thread.
1629+ -- XXX do this only if the work queue is not empty. The work may have been
1630+ -- carried out by existing workers.
974− when done $ pushWorker sv
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
27 )
27 )
28where
28where
29
29
30+import Control.Concurrent.MVar (putMVar, takeMVar)
30import Control.Monad (ap)
31import Control.Monad (ap)
31import Control.Monad.Base (MonadBase(..), liftBaseDefault)
32import Control.Monad.Base (MonadBase(..), liftBaseDefault)
32import Control.Monad.Catch (MonadThrow, throwM)
33import Control.Monad.Catch (MonadThrow, throwM)
@@ -35,9 +36,8 @@ import Control.Monad.IO.Class (MonadIO(..))
35import Control.Monad.Reader.Class (MonadReader(..))
36import Control.Monad.Reader.Class (MonadReader(..))
36import Control.Monad.State.Class (MonadState(..))
37import Control.Monad.State.Class (MonadState(..))
37import Control.Monad.Trans.Class (MonadTrans(lift))
38import Control.Monad.Trans.Class (MonadTrans(lift))
38−import Data.Atomics (atomicModifyIORefCAS_)
39import Data.Heap (Heap, Entry(..))
39import Data.Heap (Heap, Entry(..))
40−import Data.IORef (IORef, readIORef)
40+import Data.IORef (IORef, readIORef, atomicModifyIORef)
41import Data.Maybe (fromJust)
41import Data.Maybe (fromJust)
42import Data.Semigroup (Semigroup(..))
42import Data.Semigroup (Semigroup(..))
43
43
@@ -113,88 +113,197 @@ import Prelude hiding (map)
113-- each left associative expression. The queue is used only for right
113-- each left associative expression. The queue is used only for right
114-- associated expression, we queue the right expression and execute the left.
114-- associated expression, we queue the right expression and execute the left.
115-- Thererefore the queue never has more than on item in it.
115-- 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.
116
129
117workLoopAhead :: MonadIO m
130workLoopAhead :: MonadIO m
118− => State Stream m a
119− -> IORef ([Stream m a], Int)
131+ => IORef ([Stream m a], Int)
120 -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)) , Int)
132 -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)) , Int)
133+ -> State Stream m a
134+ -> SVar Stream m a
135+ -> WorkerInfo
121 -> m ()
136 -> m ()
122−workLoopAhead st q heap = runHeap
137+workLoopAhead q heap st sv winfo = runHeap
123
138
124 where
139 where
125
140
126− sv = fromJust $ streamVar st
141+ maxBuf = maxBufferLimit sv
127− maxBuf = bufferHigh st
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
128 toHeap seqNo ent = do
161 toHeap seqNo ent = do
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.
129− hp <- liftIO $ atomicModifyIORefCAS heap $ \(h, snum) ->
166+ hp <- liftIO $ atomicModifyIORef heap $ \(h, snum) ->
130 ((H.insert (Entry seqNo ent) h, snum), h)
167 ((H.insert (Entry seqNo ent) h, snum), h)
131− (_, len) <- liftIO $ readIORef (outputQueue sv)
168+ -- XXX heap check should also be under MVar
132− let maxHeap = maxBuf - len
169+ heapOk <- underMaxHeap hp
133− limit <- case maxYieldLimit sv of
170+ if heapOk
171+ then
172+ case yieldRateInfo sv of
134− Nothing -> return maxHeap
173+ Nothing -> runHeap
135− Just ref -> do
174+ Just yinfo -> do
136− r <- liftIO $ readIORef ref
175+ -- check the stop condition under a lock before actually
137− return $ if r >= 0 then r else maxHeap
176+ -- stopping so that the whole herd does not stop at once.
138− if H.size hp <= limit
177+ liftIO $ takeMVar (workerStopMVar yinfo)
178+ rateOk <- liftIO $ workerRateControl sv yinfo winfo
179+ if rateOk
180+ then do
181+ liftIO $ putMVar (workerStopMVar yinfo) ()
139− then runHeap
182+ runHeap
183+ else do
184+ liftIO $ sendStop sv winfo
185+ liftIO $ putMVar (workerStopMVar yinfo) ()
140− else liftIO $ sendStop sv
186+ else liftIO $ sendStop sv winfo
141
187
142 singleToHeap seqNo a = toHeap seqNo (AheadEntryPure a)
188 singleToHeap seqNo a = toHeap seqNo (AheadEntryPure a)
143 yieldToHeap seqNo a r = toHeap seqNo (AheadEntryStream (a `K.cons` r))
189 yieldToHeap seqNo a r = toHeap seqNo (AheadEntryStream (a `K.cons` r))
144
190
145 singleOutput seqNo a = do
191 singleOutput seqNo a = do
146− continue <- liftIO $ sendYield maxBuf sv (ChildYield a)
192+ continue <- liftIO $ sendYield sv winfo (ChildYield a)
147 if continue
193 if continue
148 then runQueueToken seqNo
194 then runQueueToken seqNo
149 else liftIO $ do
195 else liftIO $ do
150− atomicModifyIORefCAS_ heap $ \(h, _) -> (h, seqNo + 1)
196+ atomicModifyIORef heap $ \(h, _) -> ((h, seqNo + 1), ())
151− sendStop sv
197+ sendStop sv winfo
152
198
153− yieldOutput seqNo a r = do
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
154− continue <- liftIO $ sendYield maxBuf sv (ChildYield a)
207+ continue <- liftIO $ sendYield sv winfo (ChildYield a)
208+ liftIO $ atomicModifyIORef heap $ \(h, _) -> ((h, seqNo + 1), ())
155 if continue
209 if continue
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
156− then unStream r st (runQueueToken seqNo)
222+ unStream r st stop
157 (singleOutput seqNo)
223 (singleOutput seqNo)
158 (yieldOutput seqNo)
224 (yieldOutput seqNo)
159 else liftIO $ do
225 else liftIO $ do
160− atomicModifyIORefCAS_ heap $ \(h, _) ->
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, _) ->
161− (H.insert (Entry seqNo (AheadEntryStream r)) h, seqNo)
246+ ((H.insert (Entry seqNo (AheadEntryStream r)) h, seqNo), ())
247+ incrementYieldLimit sv
162− sendStop sv
248+ sendStop sv winfo
163
249
164 {-# INLINE runQueueToken #-}
250 {-# INLINE runQueueToken #-}
165 runQueueToken prevSeqNo = do
251 runQueueToken prevSeqNo = do
166 work <- dequeueAhead q
252 work <- dequeueAhead q
167 case work of
253 case work of
168 Nothing -> do
254 Nothing -> do
169− liftIO $ atomicModifyIORefCAS_ heap $ \(h, _) ->
255+ liftIO $ atomicModifyIORef heap $ \(h, _) ->
170− (h, prevSeqNo + 1)
256+ ((h, prevSeqNo + 1), ())
171 runHeap
257 runHeap
172 Just (m, seqNo) -> do
258 Just (m, seqNo) -> do
259+ yieldLimitOk <- liftIO $ decrementYieldLimit sv
260+ if yieldLimitOk
261+ then do
173− if seqNo == prevSeqNo + 1
262+ if seqNo == prevSeqNo + 1
174− then
263+ then do
264+ let stop = liftIO (incrementYieldLimit sv)
175− unStream m st (runQueueToken seqNo)
265+ >> runQueueToken seqNo
266+ unStream m st stop
176− (singleOutput seqNo)
267+ (singleOutput seqNo)
177− (yieldOutput seqNo)
268+ (yieldOutput seqNo)
178− else do
269+ else do
179− liftIO $ atomicModifyIORefCAS_ heap $ \(h, _) ->
270+ liftIO $ atomicModifyIORef heap $ \(h, _) ->
180− (h, prevSeqNo + 1)
271+ ((h, prevSeqNo + 1), ())
272+ let stop = liftIO (incrementYieldLimit sv)
181− unStream m st runHeap
273+ >> runHeap
274+ unStream m st stop
182− (singleToHeap seqNo)
275+ (singleToHeap seqNo)
183− (yieldToHeap seqNo)
276+ (yieldToHeap seqNo)
277+ else liftIO $ do
278+ atomicModifyIORef heap $ \(h, _) ->
279+ ((h, prevSeqNo + 1), ())
280+ incrementYieldLimit sv
281+ sendStop sv winfo
282+
184 runQueueNoToken = do
283 runQueueNoToken = do
185 work <- dequeueAhead q
284 work <- dequeueAhead q
186 case work of
285 case work of
187 Nothing -> runHeap
286 Nothing -> runHeap
188 Just (m, seqNo) -> do
287 Just (m, seqNo) -> do
288+ yieldLimitOk <- liftIO $ decrementYieldLimit sv
289+ if yieldLimitOk
290+ then do
189− if seqNo == 0
291+ if seqNo == 0
190− then
292+ then do
293+ let stop = liftIO (incrementYieldLimit sv)
191− unStream m st (runQueueToken seqNo)
294+ >> runQueueToken seqNo
295+ unStream m st stop
192− (singleOutput seqNo)
296+ (singleOutput seqNo)
193− (yieldOutput seqNo)
297+ (yieldOutput seqNo)
194− else
298+ else do
299+ let stop = liftIO (incrementYieldLimit sv)
195− unStream m st runHeap
300+ >> runHeap
301+ unStream m st stop
196− (singleToHeap seqNo)
302+ (singleToHeap seqNo)
197− (yieldToHeap seqNo)
303+ (yieldToHeap seqNo)
304+ else liftIO $ do
305+ incrementYieldLimit sv
306+ sendStop sv winfo
198
307
199 {-# NOINLINE runHeap #-}
308 {-# NOINLINE runHeap #-}
200 runHeap = do
309 runHeap = do
@@ -207,17 +316,44 @@ workLoopAhead st q heap = runHeap
207 ent <- liftIO $ dequeueFromHeap heap
316 ent <- liftIO $ dequeueFromHeap heap
208 case ent of
317 case ent of
209 Nothing -> do
318 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+ --
210 done <- queueEmptyAhead q
337 done <- queueEmptyAhead q
211 if done
338 if done
339+ -- XXX need to run heap here if not empty
212− then liftIO $ sendStop sv
340+ then liftIO $ sendStop sv winfo
213 else runQueueNoToken
341 else runQueueNoToken
214 Just (Entry seqNo hent) -> do
342 Just (Entry seqNo hent) -> do
215 case hent of
343 case hent of
344+ -- Do not account this in worker latency as this will not
345+ -- be the real latency.
216− AheadEntryPure a -> singleOutput seqNo a
346+ AheadEntryPure a -> singleFromHeap seqNo a
217− AheadEntryStream r ->
347+ AheadEntryStream r -> do
348+ let stp = do
349+ liftIO $ atomicModifyIORef heap $ \(h, _) ->
350+ ((h, seqNo + 1), ())
351+ liftIO (incrementYieldLimit sv)
352+ runHeap
353+
218− unStream r st (runQueueToken seqNo)
354+ unStream r st stp
219− (singleOutput seqNo)
355+ (singleStreamFromHeap seqNo)
220− (yieldOutput seqNo)
356+ (yieldStreamFromHeap seqNo)
221
357
222-------------------------------------------------------------------------------
358-------------------------------------------------------------------------------
223-- WAhead
359-- WAhead
src/Streamly/Streams/Async.hs+186−84
@@ -4,6 +4,7 @@
4{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE FlexibleInstances #-}
5{-# LANGUAGE GeneralizedNewtypeDeriving#-}
5{-# LANGUAGE GeneralizedNewtypeDeriving#-}
6{-# LANGUAGE InstanceSigs #-}
6{-# LANGUAGE InstanceSigs #-}
7+{-# LANGUAGE LambdaCase #-}
7{-# LANGUAGE MultiParamTypeClasses #-}
8{-# LANGUAGE MultiParamTypeClasses #-}
8{-# LANGUAGE StandaloneDeriving #-}
9{-# LANGUAGE StandaloneDeriving #-}
9{-# LANGUAGE UndecidableInstances #-} -- XXX
10{-# LANGUAGE UndecidableInstances #-} -- XXX
@@ -44,7 +45,7 @@ import Control.Monad.IO.Class (MonadIO(..))
44import Control.Monad.Reader.Class (MonadReader(..))
45import Control.Monad.Reader.Class (MonadReader(..))
45import Control.Monad.State.Class (MonadState(..))
46import Control.Monad.State.Class (MonadState(..))
46import Control.Monad.Trans.Class (MonadTrans(lift))
47import 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)
48import Data.IORef (IORef, newIORef, readIORef)
49import Data.IORef (IORef, newIORef, readIORef)
49import Data.Maybe (fromJust)
50import Data.Maybe (fromJust)
50import Data.Semigroup (Semigroup(..))
51import Data.Semigroup (Semigroup(..))
@@ -64,45 +65,108 @@ import qualified Streamly.Streams.StreamK as K
64-- Async
65-- Async
65-------------------------------------------------------------------------------
66-------------------------------------------------------------------------------
66
67
67−{-# INLINE runStreamLIFO #-}
68+{-# INLINE workLoopLIFO #-}
69+workLoopLIFO
68−runStreamLIFO :: MonadIO m
70+ :: MonadIO m
69− => State Stream m a -> IORef [Stream m a] -> Stream m a -> m () -> m ()
71+ => IORef [Stream m a]
70−runStreamLIFO st q m stop = unStream m st stop single yieldk
72+ -> State Stream m a
73+ -> SVar Stream m a
74+ -> WorkerInfo
75+ -> m ()
76+workLoopLIFO q st sv winfo = run
77+
71 where
78 where
72− sv = fromJust $ streamVar st
79+
73− maxBuf = bufferHigh st
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+
74 single a = do
105 single a = do
75− res <- liftIO $ sendYield maxBuf sv (ChildYield a)
106+ res <- liftIO $ sendYield sv winfo (ChildYield a)
76− if res then stop else liftIO $ sendStop sv
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.
77 yieldk a r = do
111 yieldk a r = do
78− res <- liftIO $ sendYield maxBuf sv (ChildYield a)
112+ res <- liftIO $ sendYield sv winfo (ChildYield a)
113+ yieldLimitOk <- liftIO $ decrementYieldLimit sv
114+ let stop = liftIO (incrementYieldLimit sv) >> run
79− if res
115+ if res && yieldLimitOk
80− then (unStream r) st stop single yieldk
116+ then unStream r st stop single yieldk
117+ else liftIO $ do
118+ incrementYieldLimit sv
81− else liftIO $ enqueueLIFO sv q r >> sendStop 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)
82
125
83-------------------------------------------------------------------------------
126-------------------------------------------------------------------------------
84-- WAsync
127-- WAsync
85-------------------------------------------------------------------------------
128-------------------------------------------------------------------------------
86
129
87−{-# INLINE runStreamFIFO #-}
130+{-# INLINE workLoopFIFO #-}
88−runStreamFIFO
131+workLoopFIFO
89 :: MonadIO m
132 :: MonadIO m
90− => State Stream m a
91− -> LinkedQueue (Stream m a)
133+ => LinkedQueue (Stream m a)
134+ -> State Stream m a
92− -> Stream m a
135+ -> SVar Stream m a
136+ -> WorkerInfo
93 -> m ()
137 -> m ()
94− -> m ()
138+workLoopFIFO q st sv winfo = run
95−runStreamFIFO st q m stop = unStream m st stop single yieldk
139+
96 where
140 where
97− sv = fromJust $ streamVar st
141+
98− maxBuf = bufferHigh st
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+
99 single a = do
156 single a = do
100− res <- liftIO $ sendYield maxBuf sv (ChildYield a)
157+ res <- liftIO $ sendYield sv winfo (ChildYield a)
101− if res then stop else liftIO $ sendStop sv
158+ if res then run else liftIO $ sendStop sv winfo
159+
102 yieldk a r = do
160 yieldk a r = do
103− res <- liftIO $ sendYield maxBuf sv (ChildYield a)
161+ res <- liftIO $ sendYield sv winfo (ChildYield a)
104− liftIO (enqueueFIFO sv q r)
162+ yieldLimitOk <- liftIO $ decrementYieldLimit sv
163+ let stop = liftIO (incrementYieldLimit sv) >> run
164+ if res && yieldLimitOk
165+ then unStream r st stop single yieldk
105− if res then stop else liftIO $ sendStop sv
166+ else liftIO $ do
167+ incrementYieldLimit sv
168+ enqueueFIFO sv q r
169+ sendStop sv winfo
106
170
107-------------------------------------------------------------------------------
171-------------------------------------------------------------------------------
108-- SVar creation
172-- SVar creation
@@ -120,43 +184,61 @@ getLifoSVar st = do
120 active <- newIORef 0
184 active <- newIORef 0
121 wfw <- newIORef False
185 wfw <- newIORef False
122 running <- newIORef S.empty
186 running <- newIORef S.empty
123− q <- newIORef []
187+ q <- newIORef []
124− yl <- case yieldLimit st of
188+ yl <- case getYieldLimit st of
125− Nothing -> return Nothing
189+ Nothing -> return Nothing
126− Just x -> Just <$> newIORef x
190+ Just x -> Just <$> newIORef x
191+ rateInfo <- getYieldRateInfo st
192+
127#ifdef DIAGNOSTICS
193#ifdef DIAGNOSTICS
128− disp <- newIORef 0
194+ disp <- newIORef 0
129 maxWrk <- newIORef 0
195 maxWrk <- newIORef 0
130 maxOq <- newIORef 0
196 maxOq <- newIORef 0
131 maxHs <- newIORef 0
197 maxHs <- newIORef 0
132 maxWq <- newIORef 0
198 maxWq <- newIORef 0
133#endif
199#endif
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
134− let checkEmpty = null <$> readIORef q
207+ qEmpty <- null <$> readIORef q
135− let sv =
208+ return $ qEmpty || yieldsDone
209+
210+ let getSVar sv readOutput postProc = SVar
136− SVar { outputQueue = outQ
211+ { outputQueue = outQ
137− , maxYieldLimit = yl
212+ , remainingYields = yl
213+ , maxBufferLimit = getMaxBuffer st
214+ , maxWorkerLimit = getMaxThreads st
215+ , yieldRateInfo = rateInfo
138− , outputDoorBell = outQMv
216+ , outputDoorBell = outQMv
139− , readOutputQ = readOutputQBounded (threadsHigh st) sv
217+ , readOutputQ = readOutput sv
140− , postProcess = postProcessBounded sv
218+ , postProcess = postProc sv
141− , workerThreads = running
219+ , workerThreads = running
142− , workLoop = workLoopLIFO runStreamLIFO
143− st{streamVar = Just sv} q
220+ , workLoop = workLoopLIFO q st{streamVar = Just sv} sv
144− , enqueue = enqueueLIFO sv q
221+ , enqueue = enqueueLIFO sv q
145− , isWorkDone = checkEmpty
222+ , isWorkDone = isWorkFinished sv
146− , needDoorBell = wfw
223+ , needDoorBell = wfw
147− , svarStyle = AsyncVar
224+ , svarStyle = AsyncVar
148− , workerCount = active
225+ , workerCount = active
149− , accountThread = delThread sv
226+ , accountThread = delThread sv
150#ifdef DIAGNOSTICS
227#ifdef DIAGNOSTICS
151− , aheadWorkQueue = undefined
228+ , aheadWorkQueue = undefined
152− , outputHeap = undefined
229+ , outputHeap = undefined
153− , maxWorkers = maxWrk
230+ , maxWorkers = maxWrk
154− , totalDispatches = disp
231+ , totalDispatches = disp
155− , maxOutQSize = maxOq
232+ , maxOutQSize = maxOq
156− , maxHeapSize = maxHs
233+ , maxHeapSize = maxHs
157− , maxWorkQSize = maxWq
234+ , maxWorkQSize = maxWq
158#endif
235#endif
159− }
236+ }
237+
238+ let sv =
239+ case getMaxStreamRate st of
240+ Nothing -> getSVar sv readOutputQBounded postProcessBounded
241+ Just _ -> getSVar sv readOutputQPaced postProcessPaced
160 in return sv
242 in return sv
161
243
162getFifoSVar :: MonadAsync m => State Stream m a -> IO (SVar Stream m a)
244getFifoSVar :: MonadAsync m => State Stream m a -> IO (SVar Stream m a)
@@ -167,9 +249,11 @@ getFifoSVar st = do
167 wfw <- newIORef False
249 wfw <- newIORef False
168 running <- newIORef S.empty
250 running <- newIORef S.empty
169 q <- newQ
251 q <- newQ
170− yl <- case yieldLimit st of
252+ yl <- case getYieldLimit st of
171− Nothing -> return Nothing
253+ Nothing -> return Nothing
172− Just x -> Just <$> newIORef x
254+ Just x -> Just <$> newIORef x
255+ rateInfo <- getYieldRateInfo st
256+
173#ifdef DIAGNOSTICS
257#ifdef DIAGNOSTICS
174 disp <- newIORef 0
258 disp <- newIORef 0
175 maxWrk <- newIORef 0
259 maxWrk <- newIORef 0
@@ -177,31 +261,49 @@ getFifoSVar st = do
177 maxHs <- newIORef 0
261 maxHs <- newIORef 0
178 maxWq <- newIORef 0
262 maxWq <- newIORef 0
179#endif
263#endif
180− let 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
181− SVar { outputQueue = outQ
276+ { outputQueue = outQ
182− , maxYieldLimit = yl
277+ , remainingYields = yl
278+ , maxBufferLimit = getMaxBuffer st
279+ , maxWorkerLimit = getMaxThreads st
280+ , yieldRateInfo = rateInfo
183− , outputDoorBell = outQMv
281+ , outputDoorBell = outQMv
184− , readOutputQ = readOutputQBounded (threadsHigh st) sv
282+ , readOutputQ = readOutput sv
185− , postProcess = postProcessBounded sv
283+ , postProcess = postProc sv
186− , workerThreads = running
284+ , workerThreads = running
187− , workLoop = workLoopFIFO runStreamFIFO
188− st{streamVar = Just sv} q
285+ , workLoop = workLoopFIFO q st{streamVar = Just sv} sv
189− , enqueue = enqueueFIFO sv q
286+ , enqueue = enqueueFIFO sv q
190− , isWorkDone = nullQ q
287+ , isWorkDone = isWorkFinished sv
191− , needDoorBell = wfw
288+ , needDoorBell = wfw
192− , svarStyle = WAsyncVar
289+ , svarStyle = WAsyncVar
193− , workerCount = active
290+ , workerCount = active
194− , accountThread = delThread sv
291+ , accountThread = delThread sv
195#ifdef DIAGNOSTICS
292#ifdef DIAGNOSTICS
196− , aheadWorkQueue = undefined
293+ , aheadWorkQueue = undefined
197− , outputHeap = undefined
294+ , outputHeap = undefined
198− , totalDispatches = disp
295+ , totalDispatches = disp
199− , maxWorkers = maxWrk
296+ , maxWorkers = maxWrk
200− , maxOutQSize = maxOq
297+ , maxOutQSize = maxOq
201− , maxHeapSize = maxHs
298+ , maxHeapSize = maxHs
202− , maxWorkQSize = maxWq
299+ , maxWorkQSize = maxWq
203#endif
300#endif
204− }
301+ }
302+
303+ let sv =
304+ case getMaxStreamRate st of
305+ Nothing -> getSVar sv readOutputQBounded postProcessBounded
306+ Just _ -> getSVar sv readOutputQPaced postProcessPaced
205 in return sv
307 in return sv
206
308
207{-# INLINABLE newAsyncVar #-}
309{-# INLINABLE newAsyncVar #-}
@@ -209,7 +311,7 @@ newAsyncVar :: MonadAsync m
209 => State Stream m a -> Stream m a -> m (SVar Stream m a)
311 => State Stream m a -> Stream m a -> m (SVar Stream m a)
210newAsyncVar st m = do
312newAsyncVar st m = do
211 sv <- liftIO $ getLifoSVar st
313 sv <- liftIO $ getLifoSVar st
212− sendWorker sv m
314+ sendFirstWorker sv m
213
315
214-- XXX Get rid of this?
316-- XXX Get rid of this?
215-- | Make a stream asynchronous, triggers the computation and returns a stream
317-- | Make a stream asynchronous, triggers the computation and returns a stream
@@ -233,7 +335,7 @@ newWAsyncVar :: MonadAsync m
233 => State Stream m a -> Stream m a -> m (SVar Stream m a)
335 => State Stream m a -> Stream m a -> m (SVar Stream m a)
234newWAsyncVar st m = do
336newWAsyncVar st m = do
235 sv <- liftIO $ getFifoSVar st
337 sv <- liftIO $ getFifoSVar st
236− sendWorker sv m
338+ sendFirstWorker sv m
237
339
238------------------------------------------------------------------------------
340------------------------------------------------------------------------------
239-- Running streams concurrently
341-- Running streams concurrently
src/Streamly/Streams/Parallel.hs+8−2
@@ -43,9 +43,11 @@ import Control.Monad.Reader.Class (MonadReader(..))
43import Control.Monad.State.Class (MonadState(..))
43import Control.Monad.State.Class (MonadState(..))
44import Control.Monad.Trans.Class (MonadTrans(lift))
44import Control.Monad.Trans.Class (MonadTrans(lift))
45import Data.Functor (void)
45import Data.Functor (void)
46+import Data.IORef (newIORef)
46import Data.Maybe (fromJust)
47import Data.Maybe (fromJust)
47import Data.Semigroup (Semigroup(..))
48import Data.Semigroup (Semigroup(..))
48import Prelude hiding (map)
49import Prelude hiding (map)
50+import System.Clock
49
51
50import Streamly.Streams.SVar (fromSVar)
52import Streamly.Streams.SVar (fromSVar)
51import Streamly.Streams.Serial (map)
53import Streamly.Streams.Serial (map)
@@ -65,9 +67,13 @@ runOne st m = unStream m st stop single yieldk
65
67
66 where
68 where
67
69
70+ winfo = do
71+ yc <- newIORef 0
72+ wls <- newIORef (0, fromNanoSecs 0)
73+ return $ WorkerInfo 0 yc wls
68 sv = fromJust $ streamVar st
74 sv = fromJust $ streamVar st
69− stop = liftIO $ sendStop sv
75+ stop = liftIO $ winfo >>= sendStop sv
70− sendit a = liftIO $ sendYield (-1) sv (ChildYield a)
76+ sendit a = liftIO $ winfo >>= \x -> sendYield sv x (ChildYield a)
71 single a = sendit a >> stop
77 single a = sendit a >> stop
72 -- XXX there is no flow control in parallel case. We should perhaps use a
78 -- XXX there is no flow control in parallel case. We should perhaps use a
73 -- queue and queue it back on that and exit the thread when the outputQueue
79 -- 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
28 , maxThreads
28 , maxThreads
29 , maxBuffer
29 , maxBuffer
30 , maxYields
30 , maxYields
31+ , maxRate
31 )
32 )
32where
33where
33
34
34import Control.Monad.Catch (throwM)
35import Control.Monad.Catch (throwM)
36+#ifdef DIAGNOSTICS_VERBOSE
37+import Control.Monad.IO.Class (liftIO)
38+#endif
39+import Data.Int (Int64)
35
40
36import Streamly.SVar
41import Streamly.SVar
37import Streamly.Streams.StreamK
42import Streamly.Streams.StreamK
@@ -73,8 +78,11 @@ fromStreamVar sv = Stream $ \st stp sng yld -> do
73 processEvents (ev : es) = Stream $ \st stp sng yld -> do
78 processEvents (ev : es) = Stream $ \st stp sng yld -> do
74 let rest = processEvents es
79 let rest = processEvents es
75 case ev of
80 case ev of
76− ChildYield a -> yld a rest
81+ ChildYield a -> do
82+-- void $ dispatchWorkerPaced sv
83+ yld a rest
77 ChildStop tid e -> do
84 ChildStop tid e -> do
85+ -- void $ dispatchWorkerPaced sv
78 accountThread sv tid
86 accountThread sv tid
79 case e of
87 case e of
80 Nothing -> unStream rest (rstState st) stp sng yld
88 Nothing -> unStream rest (rstState st) stp sng yld
@@ -96,8 +104,10 @@ toSVar sv m = toStreamVar sv (toStream m)
96-- XXX need to write these in direct style otherwise they will break fusion.
104-- XXX need to write these in direct style otherwise they will break fusion.
97--
105--
98-- | Specify the maximum number of threads that can be spawned concurrently
106-- | Specify the maximum number of threads that can be spawned concurrently
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
99−-- when using concurrent streams. This is not the grand total number of threads
109+-- not the grand total number of threads but maximum threads at an individual
100−-- but the maximum number of threads at each point of concurrency.
110+-- point of concurrency.
101-- A value of 0 resets the thread limit to default, a negative value means
111-- A value of 0 resets the thread limit to default, a negative value means
102-- there is no limit. The default value is 1500.
112-- there is no limit. The default value is 1500.
103--
113--
@@ -105,8 +115,7 @@ toSVar sv m = toStreamVar sv (toStream m)
105{-# INLINE_NORMAL maxThreads #-}
115{-# INLINE_NORMAL maxThreads #-}
106maxThreads :: IsStream t => Int -> t m a -> t m a
116maxThreads :: IsStream t => Int -> t m a -> t m a
107maxThreads n m = fromStream $ Stream $ \st stp sng yld -> do
117maxThreads 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
110
119
111{-# RULES "maxThreadsSerial serial" maxThreads = maxThreadsSerial #-}
120{-# RULES "maxThreadsSerial serial" maxThreads = maxThreadsSerial #-}
112maxThreadsSerial :: Int -> SerialT m a -> SerialT m a
121maxThreadsSerial :: Int -> SerialT m a -> SerialT m a
@@ -118,26 +127,67 @@ maxThreadsSerial _ = id
118-- A value of 0 resets the buffer size to default, a negative value means
127-- A value of 0 resets the buffer size to default, a negative value means
119-- there is no limit. The default value is 1500.
128-- there is no limit. The default value is 1500.
120--
129--
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+--
121-- @since 0.4.0
137-- @since 0.4.0
122{-# INLINE_NORMAL maxBuffer #-}
138{-# INLINE_NORMAL maxBuffer #-}
123maxBuffer :: IsStream t => Int -> t m a -> t m a
139maxBuffer :: IsStream t => Int -> t m a -> t m a
124maxBuffer n m = fromStream $ Stream $ \st stp sng yld -> do
140maxBuffer 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
127
142
128{-# RULES "maxBuffer serial" maxBuffer = maxBufferSerial #-}
143{-# RULES "maxBuffer serial" maxBuffer = maxBufferSerial #-}
129maxBufferSerial :: Int -> SerialT m a -> SerialT m a
144maxBufferSerial :: Int -> SerialT m a -> SerialT m a
130maxBufferSerial _ = id
145maxBufferSerial _ = id
131
146
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+
132-- Stop concurrent dispatches after this limit. This is useful in API's like
182-- Stop concurrent dispatches after this limit. This is useful in API's like
133-- "take" where we want to dispatch only upto the number of elements "take"
183-- "take" where we want to dispatch only upto the number of elements "take"
134-- needs. This value applies only to the immediate next level and is not
184-- needs. This value applies only to the immediate next level and is not
135-- inherited by everything in enclosed scope.
185-- inherited by everything in enclosed scope.
136{-# INLINE_NORMAL maxYields #-}
186{-# 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
138maxYields n m = fromStream $ Stream $ \st stp sng yld -> do
188maxYields 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
140
190
141{-# RULES "maxYields serial" maxYields = maxYieldsSerial #-}
191{-# 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
143maxYieldsSerial _ = id
193maxYieldsSerial _ = id
stack.yaml+1−1
@@ -5,7 +5,7 @@ allow-newer: true
5extra-deps:
5extra-deps:
6 - SDL-0.6.6.0
6 - SDL-0.6.6.0
7 - gauge-0.2.3
7 - gauge-0.2.3
8− - bench-graph-0.1.1
8+ - bench-graph-0.1.3
9 - Chart-1.9
9 - Chart-1.9
10 - Chart-diagrams-1.9
10 - Chart-diagrams-1.9
11 - SVGFonts-1.6.0.3
11 - SVGFonts-1.6.0.3
streamly.cabal+23−4
@@ -173,6 +173,7 @@ library
173 -- concurrency
173 -- concurrency
174 , atomic-primops >= 0.8 && < 0.9
174 , atomic-primops >= 0.8 && < 0.9
175 , lockfree-queue >= 0.2.3 && < 0.3
175 , lockfree-queue >= 0.2.3 && < 0.3
176+ , clock >= 0.7.1 && < 0.8
176
177
177 -- transfomers
178 -- transfomers
178 , exceptions >= 0.8 && < 0.11
179 , exceptions >= 0.8 && < 0.11
@@ -193,7 +194,7 @@ test-suite test
193 type: exitcode-stdio-1.0
194 type: exitcode-stdio-1.0
194 main-is: Main.hs
195 main-is: Main.hs
195 hs-source-dirs: test
196 hs-source-dirs: test
196− ghc-options: -O0 -Wall -threaded -with-rtsopts=-N
197+ ghc-options: -O2 -Wall -threaded -with-rtsopts=-N
197 if flag(dev)
198 if flag(dev)
198 ghc-options: -Wmissed-specialisations
199 ghc-options: -Wmissed-specialisations
199 -Wall-missed-specialisations
200 -Wall-missed-specialisations
@@ -220,7 +221,7 @@ test-suite properties
220 type: exitcode-stdio-1.0
221 type: exitcode-stdio-1.0
221 main-is: Prop.hs
222 main-is: Prop.hs
222 hs-source-dirs: test
223 hs-source-dirs: test
223− ghc-options: -O0 -Wall -threaded -with-rtsopts=-N4
224+ ghc-options: -O2 -Wall -threaded -with-rtsopts=-N
224 if flag(dev)
225 if flag(dev)
225 cpp-options: -DDEVBUILD
226 cpp-options: -DDEVBUILD
226 ghc-options: -Wmissed-specialisations
227 ghc-options: -Wmissed-specialisations
@@ -241,6 +242,23 @@ test-suite properties
241 , hspec >= 2.0 && < 3
242 , hspec >= 2.0 && < 3
242 default-language: Haskell2010
243 default-language: Haskell2010
243
244
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+
244test-suite loops
262test-suite loops
245 type: exitcode-stdio-1.0
263 type: exitcode-stdio-1.0
246 default-language: Haskell2010
264 default-language: Haskell2010
@@ -376,6 +394,7 @@ benchmark base
376 -- concurrency
394 -- concurrency
377 , atomic-primops >= 0.8 && < 0.9
395 , atomic-primops >= 0.8 && < 0.9
378 , lockfree-queue >= 0.2.3 && < 0.3
396 , lockfree-queue >= 0.2.3 && < 0.3
397+ , clock >= 0.7.1 && < 0.8
379
398
380 , exceptions >= 0.8 && < 0.11
399 , exceptions >= 0.8 && < 0.11
381 , monad-control >= 1.0 && < 2
400 , monad-control >= 1.0 && < 2
@@ -395,7 +414,7 @@ executable chart-linear
395 buildable: True
414 buildable: True
396 build-Depends:
415 build-Depends:
397 base >= 4.8 && < 5
416 base >= 4.8 && < 5
398− , bench-graph
417+ , bench-graph >= 0.1 && < 0.2
399 , split
418 , split
400 else
419 else
401 buildable: False
420 buildable: False
@@ -408,7 +427,7 @@ executable chart-nested
408 buildable: True
427 buildable: True
409 build-Depends:
428 build-Depends:
410 base >= 4.8 && < 5
429 base >= 4.8 && < 5
411− , bench-graph
430+ , bench-graph >= 0.1 && < 0.2
412 , split
431 , split
413 else
432 else
414 buildable: False
433 buildable: False
test/Main.hs+15
@@ -363,6 +363,14 @@ main = hspec $ do
363 describe "take on infinite concurrent stream" $ takeInfinite wAsyncly
363 describe "take on infinite concurrent stream" $ takeInfinite wAsyncly
364 describe "take on infinite concurrent stream" $ takeInfinite aheadly
364 describe "take on infinite concurrent stream" $ takeInfinite aheadly
365
365
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+
366 ---------------------------------------------------------------------------
374 ---------------------------------------------------------------------------
367 -- Folds are strict enough
375 -- Folds are strict enough
368 ---------------------------------------------------------------------------
376 ---------------------------------------------------------------------------
@@ -417,6 +425,13 @@ main = hspec $ do
417 replicate 4000 $ S.yieldM $ threadDelay 1000000)
425 replicate 4000 $ S.yieldM $ threadDelay 1000000)
418 `shouldReturn` ()
426 `shouldReturn` ()
419
427
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` []
420
435
421checkFoldxStrictness :: IO ()
436checkFoldxStrictness :: IO ()
422checkFoldxStrictness = do
437checkFoldxStrictness = 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
4
4
5import Control.Exception (BlockedIndefinitelyOnMVar(..), catches,
5import Control.Exception (BlockedIndefinitelyOnMVar(..), catches,
6 BlockedIndefinitelyOnSTM(..), Handler(..))
6 BlockedIndefinitelyOnSTM(..), Handler(..))
7−import Control.Monad (when)
7+import Control.Monad (when, forM_)
8import Control.Applicative (ZipList(..))
8import Control.Applicative (ZipList(..))
9import Control.Concurrent (MVar, takeMVar, putMVar, newEmptyMVar)
9import Control.Concurrent (MVar, takeMVar, putMVar, newEmptyMVar)
10import Control.Monad (replicateM, replicateM_)
10import Control.Monad (replicateM, replicateM_)
@@ -52,15 +52,12 @@ equals eq stream list = do
52constructWithReplicateM
52constructWithReplicateM
53 :: IsStream t
53 :: IsStream t
54 => (t IO Int -> SerialT IO Int)
54 => (t IO Int -> SerialT IO Int)
55− -> Int
56− -> Int
57 -> Word8
55 -> Word8
58 -> Property
56 -> Property
59−constructWithReplicateM op thr buf len = withMaxSuccess maxTestCount $
57+constructWithReplicateM op len = withMaxSuccess maxTestCount $
60 monadicIO $ do
58 monadicIO $ do
61 let x = return (1 :: Int)
59 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)
64 list <- run $ replicateM (fromIntegral len) x
61 list <- run $ replicateM (fromIntegral len) x
65 equals (==) stream list
62 equals (==) stream list
66
63
@@ -183,8 +180,12 @@ concurrentUnfoldrM eq op n =
183 return x
180 return x
184 equals eq stream list
181 equals eq stream list
185
182
186−concurrentApplication :: Word8 -> Property
183+concurrentApplication :: IsStream t
184+ => ([Word8] -> [Word8] -> Bool)
185+ -> (t IO Word8 -> SerialT IO Word8)
186+ -> Word8
187+ -> Property
187−concurrentApplication n =
188+concurrentApplication eq t n = withMaxSuccess maxTestCount $
188 monadicIO $ do
189 monadicIO $ do
189 -- XXX we should test empty list case as well
190 -- XXX we should test empty list case as well
190 let list = [0..n]
191 let list = [0..n]
@@ -194,7 +195,7 @@ concurrentApplication n =
194 -- since unfoldr happens in parallel with the stream processing we
195 -- since unfoldr happens in parallel with the stream processing we
195 -- can do two takeMVar in one iteration. If it is not parallel then
196 -- can do two takeMVar in one iteration. If it is not parallel then
196 -- this will not work and the test will fail.
197 -- this will not work and the test will fail.
197− S.toList $ do
198+ (S.toList . t) $ do
198 sourceUnfoldrM mv n |&
199 sourceUnfoldrM mv n |&
199 (S.mapM $ \x -> do
200 (S.mapM $ \x -> do
200 let msg = show x ++ "/" ++ show n
201 let msg = show x ++ "/" ++ show n
@@ -208,7 +209,7 @@ concurrentApplication n =
208 else return ()
209 else return ()
209 else return ()
210 else return ()
210 return x)
211 return x)
211− equals (==) stream list
212+ equals eq stream list
212
213
213sourceUnfoldrM1 :: IsStream t => Word8 -> t IO Word8
214sourceUnfoldrM1 :: IsStream t => Word8 -> t IO Word8
214sourceUnfoldrM1 n = S.unfoldrM step 0
215sourceUnfoldrM1 n = S.unfoldrM step 0
@@ -295,10 +296,10 @@ functorOps
295 :: Functor (t IO)
296 :: Functor (t IO)
296 => ([Int] -> t IO Int)
297 => ([Int] -> t IO Int)
297 -> String
298 -> String
298− -> (t IO Int -> SerialT IO Int)
299 -> ([Int] -> [Int] -> Bool)
299 -> ([Int] -> [Int] -> Bool)
300+ -> (t IO Int -> SerialT IO Int)
300 -> Spec
301 -> Spec
301−functorOps constr desc t eq = do
302+functorOps constr desc eq t = do
302 prop (desc ++ " id") $ transformFromList constr eq id $ t
303 prop (desc ++ " id") $ transformFromList constr eq id $ t
303 prop (desc ++ " fmap (+1)") $ transformFromList constr eq (fmap (+1)) $ t . (fmap (+1))
304 prop (desc ++ " fmap (+1)") $ transformFromList constr eq (fmap (+1)) $ t . (fmap (+1))
304
305
@@ -306,10 +307,10 @@ transformOps
306 :: IsStream t
307 :: IsStream t
307 => ([Int] -> t IO Int)
308 => ([Int] -> t IO Int)
308 -> String
309 -> String
309− -> (t IO Int -> SerialT IO Int)
310 -> ([Int] -> [Int] -> Bool)
310 -> ([Int] -> [Int] -> Bool)
311+ -> (t IO Int -> SerialT IO Int)
311 -> Spec
312 -> Spec
312−transformOps constr desc t eq = do
313+transformOps constr desc eq t = do
313 let transform = transformFromList constr eq
314 let transform = transformFromList constr eq
314 -- Filtering
315 -- Filtering
315 prop (desc ++ " filter False") $
316 prop (desc ++ " filter False") $
@@ -360,10 +361,10 @@ concurrentOps
360 :: IsStream t
361 :: IsStream t
361 => ([Word8] -> t IO Word8)
362 => ([Word8] -> t IO Word8)
362 -> String
363 -> String
363− -> (t IO Word8 -> SerialT IO Word8)
364 -> ([Word8] -> [Word8] -> Bool)
364 -> ([Word8] -> [Word8] -> Bool)
365+ -> (t IO Word8 -> SerialT IO Word8)
365 -> Spec
366 -> Spec
366−concurrentOps constr desc t eq = do
367+concurrentOps constr desc eq t = do
367 let prop1 d p = prop d $ withMaxSuccess maxTestCount p
368 let prop1 d p = prop d $ withMaxSuccess maxTestCount p
368
369
369 prop1 (desc ++ " fromFoldableM") $ concurrentFromFoldable eq t
370 prop1 (desc ++ " fromFoldableM") $ concurrentFromFoldable eq t
@@ -384,10 +385,10 @@ transformCombineOpsCommon
384 :: (IsStream t, Semigroup (t IO Int))
385 :: (IsStream t, Semigroup (t IO Int))
385 => ([Int] -> t IO Int)
386 => ([Int] -> t IO Int)
386 -> String
387 -> String
387− -> (t IO Int -> SerialT IO Int)
388 -> ([Int] -> [Int] -> Bool)
388 -> ([Int] -> [Int] -> Bool)
389+ -> (t IO Int -> SerialT IO Int)
389 -> Spec
390 -> Spec
390−transformCombineOpsCommon constr desc t eq = do
391+transformCombineOpsCommon constr desc eq t = do
391 let transform = transformCombineFromList constr eq
392 let transform = transformCombineFromList constr eq
392 -- Filtering
393 -- Filtering
393 prop (desc ++ " filter False") $
394 prop (desc ++ " filter False") $
@@ -448,13 +449,19 @@ transformCombineOpsOrdered
448 :: (IsStream t, Semigroup (t IO Int))
449 :: (IsStream t, Semigroup (t IO Int))
449 => ([Int] -> t IO Int)
450 => ([Int] -> t IO Int)
450 -> String
451 -> String
451− -> (t IO Int -> SerialT IO Int)
452 -> ([Int] -> [Int] -> Bool)
452 -> ([Int] -> [Int] -> Bool)
453+ -> (t IO Int -> SerialT IO Int)
453 -> Spec
454 -> Spec
454−transformCombineOpsOrdered constr desc t eq = do
455+transformCombineOpsOrdered constr desc eq t = do
455 let transform = transformCombineFromList constr eq
456 let transform = transformCombineFromList constr eq
456 -- Filtering
457 -- Filtering
457 prop (desc ++ " take 1") $ transform (take 1) t (S.take 1)
458 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
458 prop (desc ++ " take 10") $ transform (take 10) t (S.take 10)
465 prop (desc ++ " take 10") $ transform (take 10) t (S.take 10)
459
466
460 prop (desc ++ " takeWhile > 0") $
467 prop (desc ++ " takeWhile > 0") $
@@ -557,21 +564,21 @@ semigroupOps
557#endif
564#endif
558 , Monoid (t IO Int))
565 , Monoid (t IO Int))
559 => String
566 => String
560− -> (t IO Int -> SerialT IO Int)
561 -> ([Int] -> [Int] -> Bool)
567 -> ([Int] -> [Int] -> Bool)
568+ -> (t IO Int -> SerialT IO Int)
562 -> Spec
569 -> Spec
563−semigroupOps desc t eq = do
570+semigroupOps desc eq t = do
564 prop (desc ++ " <>") $ foldFromList (foldMapWith (<>) singleton) t eq
571 prop (desc ++ " <>") $ foldFromList (foldMapWith (<>) singleton) t eq
565 prop (desc ++ " mappend") $ foldFromList (foldMapWith mappend singleton) t eq
572 prop (desc ++ " mappend") $ foldFromList (foldMapWith mappend singleton) t eq
566
573
567applicativeOps
574applicativeOps
568 :: Applicative (t IO)
575 :: Applicative (t IO)
569 => ([Int] -> t IO Int)
576 => ([Int] -> t IO Int)
570− -> (t IO (Int, Int) -> SerialT IO (Int, Int))
571 -> ([(Int, Int)] -> [(Int, Int)] -> Bool)
577 -> ([(Int, Int)] -> [(Int, Int)] -> Bool)
578+ -> (t IO (Int, Int) -> SerialT IO (Int, Int))
572 -> ([Int], [Int])
579 -> ([Int], [Int])
573 -> Property
580 -> Property
574−applicativeOps constr t eq (a, b) = withMaxSuccess maxTestCount $
581+applicativeOps constr eq t (a, b) = withMaxSuccess maxTestCount $
575 monadicIO $ do
582 monadicIO $ do
576 stream <- run ((S.toList . t) ((,) <$> (constr a) <*> (constr b)))
583 stream <- run ((S.toList . t) ((,) <$> (constr a) <*> (constr b)))
577 let list = (,) <$> a <*> b
584 let list = (,) <$> a <*> b
@@ -580,11 +587,11 @@ applicativeOps constr t eq (a, b) = withMaxSuccess maxTestCount $
580zipApplicative
587zipApplicative
581 :: (IsStream t, Applicative (t IO))
588 :: (IsStream t, Applicative (t IO))
582 => ([Int] -> t IO Int)
589 => ([Int] -> t IO Int)
583− -> (t IO (Int, Int) -> SerialT IO (Int, Int))
584 -> ([(Int, Int)] -> [(Int, Int)] -> Bool)
590 -> ([(Int, Int)] -> [(Int, Int)] -> Bool)
591+ -> (t IO (Int, Int) -> SerialT IO (Int, Int))
585 -> ([Int], [Int])
592 -> ([Int], [Int])
586 -> Property
593 -> Property
587−zipApplicative constr t eq (a, b) = withMaxSuccess maxTestCount $
594+zipApplicative constr eq t (a, b) = withMaxSuccess maxTestCount $
588 monadicIO $ do
595 monadicIO $ do
589 stream1 <- run ((S.toList . t) ((,) <$> (constr a) <*> (constr b)))
596 stream1 <- run ((S.toList . t) ((,) <$> (constr a) <*> (constr b)))
590 stream2 <- run ((S.toList . t) (pure (,) <*> (constr a) <*> (constr b)))
597 stream2 <- run ((S.toList . t) (pure (,) <*> (constr a) <*> (constr b)))
@@ -597,11 +604,27 @@ zipApplicative constr t eq (a, b) = withMaxSuccess maxTestCount $
597zipMonadic
604zipMonadic
598 :: IsStream t
605 :: IsStream t
599 => ([Int] -> t IO Int)
606 => ([Int] -> t IO Int)
607+ -> ([(Int, Int)] -> [(Int, Int)] -> Bool)
600 -> (t IO (Int, Int) -> SerialT IO (Int, Int))
608 -> (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)
601 -> ([(Int, Int)] -> [(Int, Int)] -> Bool)
623 -> ([(Int, Int)] -> [(Int, Int)] -> Bool)
624+ -> (t IO (Int, Int) -> SerialT IO (Int, Int))
602 -> ([Int], [Int])
625 -> ([Int], [Int])
603 -> Property
626 -> Property
604−zipMonadic constr t eq (a, b) = withMaxSuccess maxTestCount $
627+zipAsyncMonadic constr eq t (a, b) = withMaxSuccess maxTestCount $
605 monadicIO $ do
628 monadicIO $ do
606 stream1 <-
629 stream1 <-
607 run
630 run
@@ -618,11 +641,11 @@ zipMonadic constr t eq (a, b) = withMaxSuccess maxTestCount $
618monadThen
641monadThen
619 :: Monad (t IO)
642 :: Monad (t IO)
620 => ([Int] -> t IO Int)
643 => ([Int] -> t IO Int)
621− -> (t IO Int -> SerialT IO Int)
622 -> ([Int] -> [Int] -> Bool)
644 -> ([Int] -> [Int] -> Bool)
645+ -> (t IO Int -> SerialT IO Int)
623 -> ([Int], [Int])
646 -> ([Int], [Int])
624 -> Property
647 -> Property
625−monadThen constr t eq (a, b) = withMaxSuccess maxTestCount $ monadicIO $ do
648+monadThen constr eq t (a, b) = withMaxSuccess maxTestCount $ monadicIO $ do
626 stream <- run ((S.toList . t) ((constr a) >> (constr b)))
649 stream <- run ((S.toList . t) ((constr a) >> (constr b)))
627 let list = a >> b
650 let list = a >> b
628 equals eq stream list
651 equals eq stream list
@@ -630,11 +653,11 @@ monadThen constr t eq (a, b) = withMaxSuccess maxTestCount $ monadicIO $ do
630monadBind
653monadBind
631 :: Monad (t IO)
654 :: Monad (t IO)
632 => ([Int] -> t IO Int)
655 => ([Int] -> t IO Int)
633− -> (t IO Int -> SerialT IO Int)
634 -> ([Int] -> [Int] -> Bool)
656 -> ([Int] -> [Int] -> Bool)
657+ -> (t IO Int -> SerialT IO Int)
635 -> ([Int], [Int])
658 -> ([Int], [Int])
636 -> Property
659 -> Property
637−monadBind constr t eq (a, b) = withMaxSuccess maxTestCount $
660+monadBind constr eq t (a, b) = withMaxSuccess maxTestCount $
638 monadicIO $ do
661 monadicIO $ do
639 stream <-
662 stream <-
640 run
663 run
@@ -643,22 +666,15 @@ monadBind constr t eq (a, b) = withMaxSuccess maxTestCount $
643 let list = a >>= \x -> b >>= return . (+ x)
666 let list = a >>= \x -> b >>= return . (+ x)
644 equals eq stream list
667 equals eq stream list
645
668
646−constructionConcurrent :: Int -> Int -> Spec
669+constructWithIterate :: IsStream t => (t IO Int -> SerialT IO Int) -> Spec
647−constructionConcurrent thr buf = do
670+constructWithIterate t = do
648− describe (" threads = " ++ show thr ++ "buffer = " ++ show buf) $ do
671+ it "iterate" $
649− prop "asyncly replicateM" $ constructWithReplicateM asyncly thr buf
672+ (S.toList . t . (S.take 100) $ (S.iterate (+ 1) (0 :: Int)))
650− prop "wAsyncly replicateM" $ constructWithReplicateM wAsyncly thr buf
673+ `shouldReturn` (take 100 $ iterate (+ 1) 0)
651− prop "parallely replicateM" $ constructWithReplicateM parallely thr buf
674+ it "iterateM" $ do
652− prop "aheadly replicateM" $ constructWithReplicateM aheadly thr buf
675+ let addM = (\ y -> return (y + 1))
653−
676+ S.toList . t . (S.take 100) $ S.iterateM addM (0 :: Int)
654−-- XXX test all concurrent ops for all these combinations
677+ `shouldReturn` (take 100 $ iterate (+ 1) 0)
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
662
678
663main :: IO ()
679main :: IO ()
664main = hspec $ do
680main = hspec $ do
@@ -668,153 +684,222 @@ main = hspec $ do
668 [x] -> return x -- singleton stream case
684 [x] -> return x -- singleton stream case
669 _ -> foldMapWith (<>) return xs
685 _ -> foldMapWith (<>) return xs
670 )
686 )
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+
671 describe "Construction" $ do
733 describe "Construction" $ do
672− prop "serially replicateM" $ constructWithReplicateM serially 0 0
734+ serialOps $ prop "serially replicateM" . constructWithReplicateM
673− it "iterate" $
735+ wSerialOps $ prop "wSerially replicateM" . constructWithReplicateM
674− (S.toList . serially . (S.take 100) $ (S.iterate (+ 1) (0 :: Int)))
736+ aheadOps $ prop "aheadly replicateM" . constructWithReplicateM
675− `shouldReturn` (take 100 $ iterate (+ 1) 0)
737+ asyncOps $ prop "asyncly replicateM" . constructWithReplicateM
738+ wAsyncOps $ prop "wAsyncly replicateM" . constructWithReplicateM
739+ parallelOps $ prop "parallely replicateM" . constructWithReplicateM
676 -- XXX test for all types of streams
740 -- XXX test for all types of streams
677− it "iterateM" $ do
741+ constructWithIterate serially
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
682
742
683 describe "Functor operations" $ do
743 describe "Functor operations" $ do
684− functorOps S.fromFoldable "serially" serially (==)
744+ serialOps $ functorOps S.fromFoldable "serially" (==)
685− functorOps folded "serially folded" serially (==)
745+ serialOps $ functorOps folded "serially folded" (==)
686− functorOps S.fromFoldable "wSerially" wSerially (==)
746+ wSerialOps $ functorOps S.fromFoldable "wSerially" (==)
687− functorOps folded "wSerially folded" wSerially (==)
747+ wSerialOps $ functorOps folded "wSerially folded" (==)
688− functorOps S.fromFoldable "aheadly" aheadly (==)
748+ aheadOps $ functorOps S.fromFoldable "aheadly" (==)
689− functorOps folded "aheadly folded" aheadly (==)
749+ aheadOps $ functorOps folded "aheadly folded" (==)
690− functorOps S.fromFoldable "asyncly" asyncly sortEq
750+ asyncOps $ functorOps S.fromFoldable "asyncly" sortEq
691− functorOps folded "asyncly folded" asyncly sortEq
751+ asyncOps $ functorOps folded "asyncly folded" sortEq
692− functorOps S.fromFoldable "wAsyncly" wAsyncly sortEq
752+ wAsyncOps $ functorOps S.fromFoldable "wAsyncly" sortEq
693− functorOps folded "wAsyncly folded" wAsyncly sortEq
753+ wAsyncOps $ functorOps folded "wAsyncly folded" sortEq
694− functorOps S.fromFoldable "parallely" parallely sortEq
754+ parallelOps $ functorOps S.fromFoldable "parallely" sortEq
695− functorOps folded "parallely folded" parallely sortEq
755+ parallelOps $ functorOps folded "parallely folded" sortEq
696− functorOps S.fromFoldable "zipSerially" zipSerially (==)
756+ zipSerialOps $ functorOps S.fromFoldable "zipSerially" (==)
697− functorOps folded "zipSerially folded" zipSerially (==)
757+ zipSerialOps $ functorOps folded "zipSerially folded" (==)
698− functorOps S.fromFoldable "zipAsyncly" zipAsyncly (==)
758+ zipAsyncOps $ functorOps S.fromFoldable "zipAsyncly" (==)
699− functorOps folded "zipAsyncly folded" zipAsyncly (==)
759+ zipAsyncOps $ functorOps folded "zipAsyncly folded" (==)
700
760
701 describe "Semigroup operations" $ do
761 describe "Semigroup operations" $ do
702− semigroupOps "serially" serially (==)
762+ serialOps $ semigroupOps "serially" (==)
703− semigroupOps "wSerially" wSerially (==)
763+ wSerialOps $ semigroupOps "wSerially" (==)
704− semigroupOps "aheadly" aheadly (==)
764+ aheadOps $ semigroupOps "aheadly" (==)
705− semigroupOps "asyncly" asyncly sortEq
765+ asyncOps $ semigroupOps "asyncly" sortEq
706− semigroupOps "wAsyncly" wAsyncly sortEq
766+ wAsyncOps $ semigroupOps "wAsyncly" sortEq
707− semigroupOps "parallely" parallely sortEq
767+ parallelOps $ semigroupOps "parallely" sortEq
708− semigroupOps "zipSerially" zipSerially (==)
768+ zipSerialOps $ semigroupOps "zipSerially" (==)
709− semigroupOps "zipAsyncly" zipAsyncly (==)
769+ zipAsyncOps $ semigroupOps "zipAsyncly" (==)
710
770
711 describe "Applicative operations" $ do
771 describe "Applicative operations" $ do
712 -- The tests using sorted equality are weaker tests
772 -- The tests using sorted equality are weaker tests
713 -- We need to have stronger unit tests for all those
773 -- We need to have stronger unit tests for all those
714 -- XXX applicative with three arguments
774 -- XXX applicative with three arguments
715− prop "serially applicative" $ applicativeOps S.fromFoldable serially (==)
775+ serialOps $ prop "serially applicative" . applicativeOps S.fromFoldable (==)
716− prop "serially applicative folded" $ applicativeOps folded serially (==)
776+ serialOps $ prop "serially applicative folded" . applicativeOps folded (==)
717− prop "aheadly applicative" $ applicativeOps S.fromFoldable aheadly (==)
777+ wSerialOps $ prop "wSerially applicative" . applicativeOps S.fromFoldable sortEq
718− prop "aheadly applicative folded" $ applicativeOps folded aheadly (==)
778+ wSerialOps $ prop "wSerially applicative folded" . applicativeOps folded sortEq
719− prop "wSerially applicative" $ applicativeOps S.fromFoldable wSerially sortEq
779+ aheadOps $ prop "aheadly applicative" . applicativeOps S.fromFoldable (==)
720− prop "wSerially applicative folded" $ applicativeOps folded wSerially sortEq
780+ aheadOps $ prop "aheadly applicative folded" . applicativeOps folded (==)
721− prop "asyncly applicative" $ applicativeOps S.fromFoldable asyncly sortEq
781+ asyncOps $ prop "asyncly applicative" . applicativeOps S.fromFoldable sortEq
722− prop "asyncly applicative folded" $ applicativeOps folded asyncly sortEq
782+ asyncOps $ prop "asyncly applicative folded" . applicativeOps folded sortEq
783+ wAsyncOps $ prop "wAsyncly applicative" . applicativeOps S.fromFoldable sortEq
723− prop "wAsyncly applicative folded" $ applicativeOps folded wAsyncly sortEq
784+ wAsyncOps $ prop "wAsyncly applicative folded" . applicativeOps folded sortEq
724− prop "parallely applicative folded" $ applicativeOps folded parallely sortEq
785+ parallelOps $ prop "parallely applicative folded" . applicativeOps folded sortEq
725
786
726 describe "Zip operations" $ do
787 describe "Zip operations" $ do
727− prop "zipSerially applicative" $ zipApplicative S.fromFoldable zipSerially (==)
788+ zipSerialOps $ prop "zipSerially applicative" . zipApplicative S.fromFoldable (==)
728− prop "zipSerially applicative folded" $ zipApplicative folded zipSerially (==)
789+ zipSerialOps $ prop "zipSerially applicative folded" . zipApplicative folded (==)
729− prop "zipAsyncly applicative" $ zipApplicative S.fromFoldable zipAsyncly (==)
790+ zipAsyncOps $ prop "zipAsyncly applicative" . zipApplicative S.fromFoldable (==)
730− prop "zipAsyncly applicative folded" $ zipApplicative folded zipAsyncly (==)
791+ zipAsyncOps $ prop "zipAsyncly applicative folded" . zipApplicative folded (==)
731−
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.
732− prop "zip monadic serially" $ zipMonadic S.fromFoldable serially (==)
796+ serialOps $ prop "zip monadic serially" . zipMonadic S.fromFoldable (==)
733− prop "zip monadic serially folded" $ zipMonadic folded serially (==)
797+ serialOps $ prop "zip monadic serially folded" . zipMonadic folded (==)
734− prop "zip monadic aheadly" $ zipMonadic S.fromFoldable aheadly (==)
798+ wSerialOps $ prop "zip monadic wSerially" . zipMonadic S.fromFoldable (==)
735− prop "zip monadic aheadly folded" $ zipMonadic folded aheadly (==)
799+ wSerialOps $ prop "zip monadic wSerially folded" . zipMonadic folded (==)
736− prop "zip monadic wSerially" $ zipMonadic S.fromFoldable wSerially (==)
800+ aheadOps $ prop "zip monadic aheadly" . zipAsyncMonadic S.fromFoldable (==)
737− prop "zip monadic wSerially folded" $ zipMonadic folded wSerially (==)
801+ aheadOps $ prop "zip monadic aheadly folded" . zipAsyncMonadic folded (==)
738− prop "zip monadic asyncly" $ zipMonadic S.fromFoldable asyncly (==)
802+ asyncOps $ prop "zip monadic asyncly" . zipAsyncMonadic S.fromFoldable (==)
739− prop "zip monadic asyncly folded" $ zipMonadic folded asyncly (==)
803+ asyncOps $ prop "zip monadic asyncly folded" . zipAsyncMonadic folded (==)
740− prop "zip monadic wAsyncly" $ zipMonadic S.fromFoldable wAsyncly (==)
804+ wAsyncOps $ prop "zip monadic wAsyncly" . zipAsyncMonadic S.fromFoldable (==)
741− prop "zip monadic wAsyncly folded" $ zipMonadic folded wAsyncly (==)
805+ wAsyncOps $ prop "zip monadic wAsyncly folded" . zipAsyncMonadic folded (==)
742− prop "zip monadic parallely" $ zipMonadic S.fromFoldable parallely (==)
806+ parallelOps $ prop "zip monadic parallely" . zipMonadic S.fromFoldable (==)
743− prop "zip monadic parallely folded" $ zipMonadic folded parallely (==)
807+ parallelOps $ prop "zip monadic parallely folded" . zipMonadic folded (==)
744
808
745 describe "Monad operations" $ do
809 describe "Monad operations" $ do
746− prop "serially monad then" $ monadThen S.fromFoldable serially (==)
810+ serialOps $ prop "serially monad then" . monadThen S.fromFoldable (==)
747− prop "aheadly monad then" $ monadThen S.fromFoldable aheadly (==)
811+ wSerialOps $ prop "wSerially monad then" . monadThen S.fromFoldable sortEq
748− prop "wSerially monad then" $ monadThen S.fromFoldable wSerially sortEq
812+ aheadOps $ prop "aheadly monad then" . monadThen S.fromFoldable (==)
749− prop "asyncly monad then" $ monadThen S.fromFoldable asyncly sortEq
813+ asyncOps $ prop "asyncly monad then" . monadThen S.fromFoldable sortEq
750− prop "wAsyncly monad then" $ monadThen S.fromFoldable wAsyncly sortEq
814+ wAsyncOps $ prop "wAsyncly monad then" . monadThen S.fromFoldable sortEq
751− prop "parallely monad then" $ monadThen S.fromFoldable parallely sortEq
815+ parallelOps $ prop "parallely monad then" . monadThen S.fromFoldable sortEq
752−
816+
753− prop "serially monad then folded" $ monadThen folded serially (==)
817+ serialOps $ prop "serially monad then folded" . monadThen folded (==)
754− prop "aheadly monad then folded" $ monadThen folded aheadly (==)
818+ wSerialOps $ prop "wSerially monad then folded" . monadThen folded sortEq
755− prop "wSerially monad then folded" $ monadThen folded wSerially sortEq
819+ aheadOps $ prop "aheadly monad then folded" . monadThen folded (==)
756− prop "asyncly monad then folded" $ monadThen folded asyncly sortEq
820+ asyncOps $ prop "asyncly monad then folded" . monadThen folded sortEq
757− prop "wAsyncly monad then folded" $ monadThen folded wAsyncly sortEq
821+ wAsyncOps $ prop "wAsyncly monad then folded" . monadThen folded sortEq
758− prop "parallely monad then folded" $ monadThen folded parallely sortEq
822+ parallelOps $ prop "parallely monad then folded" . monadThen folded sortEq
759−
823+
760− prop "serially monad bind" $ monadBind S.fromFoldable serially (==)
824+ serialOps $ prop "serially monad bind" . monadBind S.fromFoldable (==)
761− prop "aheadly monad bind" $ monadBind S.fromFoldable aheadly (==)
825+ wSerialOps $ prop "wSerially monad bind" . monadBind S.fromFoldable sortEq
826+ aheadOps $ prop "aheadly monad bind" . monadBind S.fromFoldable (==)
762− prop "wSerially monad bind" $ monadBind S.fromFoldable wSerially sortEq
827+ asyncOps $ prop "asyncly monad bind" . monadBind S.fromFoldable sortEq
763− prop "asyncly monad bind" $ monadBind S.fromFoldable asyncly sortEq
828+ wAsyncOps $ prop "wAsyncly monad bind" . monadBind S.fromFoldable sortEq
764− prop "wAsyncly monad bind" $ monadBind S.fromFoldable wAsyncly sortEq
829+ parallelOps $ prop "parallely monad bind" . monadBind S.fromFoldable sortEq
765− prop "parallely monad bind" $ monadBind S.fromFoldable parallely 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
766
837
767 describe "Stream transform operations" $ do
838 describe "Stream transform operations" $ do
768− transformOps S.fromFoldable "serially" serially (==)
839+ serialOps $ transformOps S.fromFoldable "serially" (==)
769− transformOps S.fromFoldable "aheadly" aheadly (==)
840+ wSerialOps $ transformOps S.fromFoldable "wSerially" (==)
770− transformOps S.fromFoldable "wSerially" wSerially (==)
841+ aheadOps $ transformOps S.fromFoldable "aheadly" (==)
771− transformOps S.fromFoldable "zipSerially" zipSerially (==)
842+ asyncOps $ transformOps S.fromFoldable "asyncly" sortEq
772− transformOps S.fromFoldable "zipAsyncly" zipAsyncly (==)
843+ wAsyncOps $ transformOps S.fromFoldable "wAsyncly" sortEq
773− transformOps S.fromFoldable "asyncly" asyncly sortEq
844+ parallelOps $ transformOps S.fromFoldable "parallely" sortEq
774− transformOps S.fromFoldable "wAsyncly" wAsyncly sortEq
845+ zipSerialOps $ transformOps S.fromFoldable "zipSerially" (==)
775− transformOps S.fromFoldable "parallely" parallely sortEq
846+ zipAsyncOps $ transformOps S.fromFoldable "zipAsyncly" (==)
776−
847+
777− transformOps folded "serially folded" serially (==)
848+ serialOps $ transformOps folded "serially folded" (==)
778− transformOps folded "aheadly folded" aheadly (==)
849+ wSerialOps $ transformOps folded "wSerially folded" (==)
779− transformOps folded "wSerially folded" wSerially (==)
850+ aheadOps $ transformOps folded "aheadly folded" (==)
780− transformOps folded "zipSerially folded" zipSerially (==)
851+ asyncOps $ transformOps folded "asyncly folded" sortEq
781− transformOps folded "zipAsyncly folded" zipAsyncly (==)
852+ wAsyncOps $ transformOps folded "wAsyncly folded" sortEq
782− transformOps folded "asyncly folded" asyncly sortEq
853+ parallelOps $ transformOps folded "parallely folded" sortEq
783− transformOps folded "wAsyncly folded" wAsyncly sortEq
854+ zipSerialOps $ transformOps folded "zipSerially folded" (==)
784− transformOps folded "parallely folded" parallely sortEq
855+ zipAsyncOps $ transformOps folded "zipAsyncly folded" (==)
785−
856+
786− transformOpsWord8 S.fromFoldable "serially" serially
857+ serialOps $ transformOpsWord8 S.fromFoldable "serially"
787− transformOpsWord8 S.fromFoldable "aheadly" aheadly
858+ wSerialOps $ transformOpsWord8 S.fromFoldable "wSerially"
788− transformOpsWord8 S.fromFoldable "wSerially" wSerially
859+ aheadOps $ transformOpsWord8 S.fromFoldable "aheadly"
789− transformOpsWord8 S.fromFoldable "zipSerially" zipSerially
860+ asyncOps $ transformOpsWord8 S.fromFoldable "asyncly"
790− transformOpsWord8 S.fromFoldable "zipAsyncly" zipAsyncly
861+ wAsyncOps $ transformOpsWord8 S.fromFoldable "wAsyncly"
791− transformOpsWord8 S.fromFoldable "asyncly" asyncly
862+ parallelOps $ transformOpsWord8 S.fromFoldable "parallely"
792− transformOpsWord8 S.fromFoldable "wAsyncly" wAsyncly
863+ zipSerialOps $ transformOpsWord8 S.fromFoldable "zipSerially"
793− transformOpsWord8 S.fromFoldable "parallely" parallely
864+ zipAsyncOps $ transformOpsWord8 S.fromFoldable "zipAsyncly"
794−
865+
795− transformOpsWord8 folded "serially folded" serially
866+ serialOps $ transformOpsWord8 folded "serially folded"
796− transformOpsWord8 folded "aheadly folded" aheadly
867+ wSerialOps $ transformOpsWord8 folded "wSerially folded"
797− transformOpsWord8 folded "wSerially folded" wSerially
868+ aheadOps $ transformOpsWord8 folded "aheadly folded"
798− transformOpsWord8 folded "zipSerially folded" zipSerially
869+ asyncOps $ transformOpsWord8 folded "asyncly folded"
799− transformOpsWord8 folded "zipAsyncly folded" zipAsyncly
870+ wAsyncOps $ transformOpsWord8 folded "wAsyncly folded"
800− transformOpsWord8 folded "asyncly folded" asyncly
871+ parallelOps $ transformOpsWord8 folded "parallely folded"
801− transformOpsWord8 folded "wAsyncly folded" wAsyncly
872+ zipSerialOps $ transformOpsWord8 folded "zipSerially folded"
802− transformOpsWord8 folded "parallely folded" parallely
873+ zipAsyncOps $ transformOpsWord8 folded "zipAsyncly folded"
803−
874+
804− -- XXX add tests with outputQueue size set to 1
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+
805 describe "Stream concurrent operations" $ do
886 describe "Stream concurrent operations" $ do
806− concurrentOps S.fromFoldable "aheadly" aheadly (==)
887+ forM_ (mkOps aheadly) $ concurrentOps S.fromFoldable "aheadly" (==)
807− concurrentOps S.fromFoldable "asyncly" asyncly sortEq
888+ forM_ (mkOps asyncly) $ concurrentOps S.fromFoldable "asyncly" sortEq
808− concurrentOps S.fromFoldable "wAsyncly" wAsyncly sortEq
889+ forM_ (mkOps wAsyncly) $ concurrentOps S.fromFoldable "wAsyncly" sortEq
809− concurrentOps S.fromFoldable "parallely" parallely sortEq
890+ forM_ (mkOps parallely) $ concurrentOps S.fromFoldable "parallely" sortEq
810−
891+
811− concurrentOps folded "aheadly folded" aheadly (==)
892+ forM_ (mkOps aheadly) $ concurrentOps folded "aheadly folded" (==)
812− concurrentOps folded "asyncly folded" asyncly sortEq
893+ forM_ (mkOps asyncly) $ concurrentOps folded "asyncly folded" sortEq
813− concurrentOps folded "wAsyncly folded" wAsyncly sortEq
894+ forM_ (mkOps wAsyncly) $ concurrentOps folded "wAsyncly folded" sortEq
814− concurrentOps folded "parallely folded" parallely sortEq
895+ forM_ (mkOps parallely) $ concurrentOps folded "parallely folded" sortEq
815−
896+
897+ describe "Concurrent application" $ do
816− prop "concurrent application" $ withMaxSuccess maxTestCount $
898+ serialOps $ prop "concurrent application" . concurrentApplication (==)
899+ asyncOps $ prop "concurrent application" . concurrentApplication sortEq
900+ aheadOps $ prop "concurrent application" . concurrentApplication (==)
817− concurrentApplication
901+ parallelOps $ prop "concurrent application" . concurrentApplication sortEq
902+
818 prop "concurrent foldr application" $ withMaxSuccess maxTestCount $
903 prop "concurrent foldr application" $ withMaxSuccess maxTestCount $
819 concurrentFoldrApplication
904 concurrentFoldrApplication
820 prop "concurrent foldl application" $ withMaxSuccess maxTestCount $
905 prop "concurrent foldl application" $ withMaxSuccess maxTestCount $
@@ -823,59 +908,59 @@ main = hspec $ do
823 -- These tests are specifically targeted towards detecting illegal sharing
908 -- These tests are specifically targeted towards detecting illegal sharing
824 -- of SVar across conurrent streams. All transform ops must be added here.
909 -- of SVar across conurrent streams. All transform ops must be added here.
825 describe "Stream transform and combine operations" $ do
910 describe "Stream transform and combine operations" $ do
826− transformCombineOpsCommon S.fromFoldable "serially" serially (==)
911+ serialOps $ transformCombineOpsCommon S.fromFoldable "serially" (==)
827− transformCombineOpsCommon S.fromFoldable "aheadly" aheadly (==)
828− transformCombineOpsCommon S.fromFoldable "wSerially" wSerially sortEq
912+ wSerialOps $ transformCombineOpsCommon S.fromFoldable "wSerially" sortEq
829− transformCombineOpsCommon S.fromFoldable "zipSerially" zipSerially (==)
913+ aheadOps $ transformCombineOpsCommon S.fromFoldable "aheadly" (==)
830− transformCombineOpsCommon S.fromFoldable "zipAsyncly" zipAsyncly (==)
914+ asyncOps $ transformCombineOpsCommon S.fromFoldable "asyncly" sortEq
831− transformCombineOpsCommon S.fromFoldable "asyncly" asyncly sortEq
915+ wAsyncOps $ transformCombineOpsCommon S.fromFoldable "wAsyncly" sortEq
832− transformCombineOpsCommon S.fromFoldable "wAsyncly" wAsyncly sortEq
916+ parallelOps $ transformCombineOpsCommon S.fromFoldable "parallely" sortEq
833− transformCombineOpsCommon S.fromFoldable "parallely" parallely sortEq
917+ zipSerialOps $ transformCombineOpsCommon S.fromFoldable "zipSerially" (==)
834−
835− transformCombineOpsCommon folded "serially" serially (==)
918+ zipAsyncOps $ transformCombineOpsCommon S.fromFoldable "zipAsyncly" (==)
919+
836− transformCombineOpsCommon folded "aheadly" aheadly (==)
920+ serialOps $ transformCombineOpsCommon folded "serially" (==)
837− transformCombineOpsCommon folded "wSerially" wSerially sortEq
921+ wSerialOps $ transformCombineOpsCommon folded "wSerially" sortEq
838− transformCombineOpsCommon folded "zipSerially" zipSerially (==)
922+ aheadOps $ transformCombineOpsCommon folded "aheadly" (==)
839− transformCombineOpsCommon folded "zipAsyncly" zipAsyncly (==)
923+ asyncOps $ transformCombineOpsCommon folded "asyncly" sortEq
840− transformCombineOpsCommon folded "asyncly" asyncly sortEq
924+ wAsyncOps $ transformCombineOpsCommon folded "wAsyncly" sortEq
841− transformCombineOpsCommon folded "wAsyncly" wAsyncly sortEq
925+ parallelOps $ transformCombineOpsCommon folded "parallely" sortEq
842− transformCombineOpsCommon folded "parallely" parallely sortEq
926+ zipSerialOps $ transformCombineOpsCommon folded "zipSerially" (==)
843−
927+ zipAsyncOps $ transformCombineOpsCommon folded "zipAsyncly" (==)
928+
844− transformCombineOpsOrdered S.fromFoldable "serially" serially (==)
929+ serialOps $ transformCombineOpsOrdered S.fromFoldable "serially" (==)
845− transformCombineOpsOrdered S.fromFoldable "serially" aheadly (==)
930+ aheadOps $ transformCombineOpsOrdered S.fromFoldable "aheadly" (==)
846− transformCombineOpsOrdered S.fromFoldable "zipSerially" zipSerially (==)
931+ zipSerialOps $ transformCombineOpsOrdered S.fromFoldable "zipSerially" (==)
847− transformCombineOpsOrdered S.fromFoldable "zipAsyncly" zipAsyncly (==)
932+ zipAsyncOps $ transformCombineOpsOrdered S.fromFoldable "zipAsyncly" (==)
848
933
849 describe "Stream elimination operations" $ do
934 describe "Stream elimination operations" $ do
850− eliminationOps S.fromFoldable "serially" serially
935+ serialOps $ eliminationOps S.fromFoldable "serially"
851− eliminationOps S.fromFoldable "aheadly" aheadly
936+ wSerialOps $ eliminationOps S.fromFoldable "wSerially"
852− eliminationOps S.fromFoldable "wSerially" wSerially
937+ aheadOps $ eliminationOps S.fromFoldable "aheadly"
853− eliminationOps S.fromFoldable "zipSerially" zipSerially
938+ asyncOps $ eliminationOps S.fromFoldable "asyncly"
854− eliminationOps S.fromFoldable "zipAsyncly" zipAsyncly
939+ wAsyncOps $ eliminationOps S.fromFoldable "wAsyncly"
855− eliminationOps S.fromFoldable "asyncly" asyncly
940+ parallelOps $ eliminationOps S.fromFoldable "parallely"
856− eliminationOps S.fromFoldable "wAsyncly" wAsyncly
941+ zipSerialOps $ eliminationOps S.fromFoldable "zipSerially"
857− eliminationOps S.fromFoldable "parallely" parallely
942+ zipAsyncOps $ eliminationOps S.fromFoldable "zipAsyncly"
858−
943+
859− eliminationOps folded "serially folded" serially
944+ serialOps $ eliminationOps folded "serially folded"
860− eliminationOps folded "aheadly folded" aheadly
945+ wSerialOps $ eliminationOps folded "wSerially folded"
861− eliminationOps folded "wSerially folded" wSerially
946+ aheadOps $ eliminationOps folded "aheadly folded"
862− eliminationOps folded "zipSerially folded" zipSerially
947+ asyncOps $ eliminationOps folded "asyncly folded"
863− eliminationOps folded "zipAsyncly folded" zipAsyncly
948+ wAsyncOps $ eliminationOps folded "wAsyncly folded"
864− eliminationOps folded "asyncly folded" asyncly
949+ parallelOps $ eliminationOps folded "parallely folded"
865− eliminationOps folded "wAsyncly folded" wAsyncly
950+ zipSerialOps $ eliminationOps folded "zipSerially folded"
866− eliminationOps folded "parallely folded" parallely
951+ zipAsyncOps $ eliminationOps folded "zipAsyncly folded"
867
952
868 -- XXX Add a test where we chain all transformation APIs and make sure that
953 -- XXX Add a test where we chain all transformation APIs and make sure that
869 -- the state is being passed through all of them.
954 -- the state is being passed through all of them.
870 describe "Stream serial elimination operations" $ do
955 describe "Stream serial elimination operations" $ do
871− serialEliminationOps S.fromFoldable "serially" serially
956+ serialOps $ serialEliminationOps S.fromFoldable "serially"
872− serialEliminationOps S.fromFoldable "aheadly" aheadly
957+ wSerialOps $ serialEliminationOps S.fromFoldable "wSerially"
873− serialEliminationOps S.fromFoldable "wSerially" wSerially
958+ aheadOps $ serialEliminationOps S.fromFoldable "aheadly"
874− serialEliminationOps S.fromFoldable "zipSerially" zipSerially
959+ zipSerialOps $ serialEliminationOps S.fromFoldable "zipSerially"
875− serialEliminationOps S.fromFoldable "zipAsyncly" zipAsyncly
960+ zipAsyncOps $ serialEliminationOps S.fromFoldable "zipAsyncly"
876−
961+
877− serialEliminationOps folded "serially folded" serially
962+ serialOps $ serialEliminationOps folded "serially folded"
878− serialEliminationOps folded "aheadly folded" aheadly
963+ wSerialOps $ serialEliminationOps folded "wSerially folded"
879− serialEliminationOps folded "wSerially folded" wSerially
964+ aheadOps $ serialEliminationOps folded "aheadly folded"
880− serialEliminationOps folded "zipSerially folded" zipSerially
965+ zipSerialOps $ serialEliminationOps folded "zipSerially folded"
881− serialEliminationOps folded "zipAsyncly folded" zipAsyncly
966+ zipAsyncOps $ serialEliminationOps folded "zipAsyncly folded"