on 1305145 help sign in
2396 lines 98 kB history raw
1{-# LANGUAGE CPP #-}
2{-# LANGUAGE KindSignatures #-}
3{-# LANGUAGE ConstraintKinds #-}
4{-# LANGUAGE ExistentialQuantification #-}
5{-# LANGUAGE FlexibleContexts #-}
6{-# LANGUAGE FlexibleInstances #-}
7{-# LANGUAGE GeneralizedNewtypeDeriving #-}
8{-# LANGUAGE LambdaCase #-}
9{-# LANGUAGE MagicHash #-}
10{-# LANGUAGE MultiParamTypeClasses #-}
11{-# LANGUAGE RankNTypes #-}
12{-# LANGUAGE ScopedTypeVariables #-}
13{-# LANGUAGE UnboxedTuples #-}
15-- |
16-- Module : Streamly.Internal.Data.SVar
17-- Copyright : (c) 2017 Harendra Kumar
18--
19-- License : BSD3
20-- Maintainer : harendra.kumar@gmail.com
21-- Stability : experimental
22-- Portability : GHC
24module Streamly.Internal.Data.SVar
25 (
26 MonadAsync
27 , SVarStyle (..)
28 , SVarStopStyle (..)
29 , SVar (..)
31 -- State threaded around the stream
32 , Limit (..)
33 , State (streamVar)
34 , defState
35 , adaptState
36 , getMaxThreads
37 , setMaxThreads
38 , getMaxBuffer
39 , setMaxBuffer
40 , getStreamRate
41 , setStreamRate
42 , setStreamLatency
43 , getYieldLimit
44 , setYieldLimit
45 , getInspectMode
46 , setInspectMode
48 , cleanupSVar
49 , cleanupSVarFromWorker
51 -- SVar related
52 , newAheadVar
53 , newParallelVar
54 , captureMonadState
55 , RunInIO (..)
57 , WorkerInfo (..)
58 , YieldRateInfo (..)
59 , ThreadAbort (..)
60 , ChildEvent (..)
61 , AheadHeapEntry (..)
62 , send
63 , sendYield
64 , sendStop
65 , enqueueLIFO
66 , enqueueFIFO
67 , enqueueAhead
68 , reEnqueueAhead
69 , pushWorkerPar
71 , queueEmptyAhead
72 , dequeueAhead
74 , HeapDequeueResult(..)
75 , dequeueFromHeap
76 , dequeueFromHeapSeq
77 , requeueOnHeapTop
78 , updateHeapSeq
79 , withIORef
80 , heapIsSane
82 , Rate (..)
83 , getYieldRateInfo
84 , newSVarStats
85 , collectLatency
86 , workerUpdateLatency
87 , isBeyondMaxRate
88 , workerRateControl
89 , updateYieldCount
90 , decrementYieldLimit
91 , incrementYieldLimit
92 , decrementBufferLimit
93 , incrementBufferLimit
94 , postProcessBounded
95 , postProcessPaced
96 , readOutputQBounded
97 , readOutputQPaced
98 , dispatchWorkerPaced
99 , sendFirstWorker
100 , delThread
101 , modifyThread
102 , doFork
104 , toStreamVar
105 , SVarStats (..)
106 , dumpSVar
107 )
108where
110import Control.Concurrent
111 (ThreadId, myThreadId, threadDelay, throwTo)
112import Control.Concurrent.MVar
113 (MVar, newEmptyMVar, tryPutMVar, takeMVar, tryTakeMVar, newMVar, tryReadMVar)
114import Control.Exception
115 (SomeException(..), catch, mask, assert, Exception, catches,
116 throwIO, Handler(..), BlockedIndefinitelyOnMVar(..),
117 BlockedIndefinitelyOnSTM(..))
118import Control.Monad (when)
119import Control.Monad.Catch (MonadThrow)
120import Control.Monad.IO.Class (MonadIO(..))
121import Control.Monad.Trans.Control (MonadBaseControl, control, StM)
122import Streamly.Internal.Data.Atomics
123 (atomicModifyIORefCAS, atomicModifyIORefCAS_, writeBarrier,
124 storeLoadBarrier)
125import Data.Concurrent.Queue.MichaelScott (LinkedQueue, pushL)
126import Data.Functor (void)
127import Data.Heap (Heap, Entry(..))
128import Data.Int (Int64)
129#if __GLASGOW_HASKELL__ >= 800
130import Data.Kind (Type)
131#endif
132import Data.IORef
133 (IORef, modifyIORef, newIORef, readIORef, writeIORef, atomicModifyIORef)
134import Data.Maybe (fromJust)
135#if __GLASGOW_HASKELL__ < 808
136import Data.Semigroup ((<>))
137#endif
138import Data.Set (Set)
139import GHC.Conc (ThreadId(..))
140import GHC.Exts
141import GHC.IO (IO(..))
142import Streamly.Internal.Data.Time.Clock (Clock(..), getTime)
143import Streamly.Internal.Data.Time.Units
144 (AbsTime, NanoSecond64(..), MicroSecond64(..), diffAbsTime64,
145 fromRelTime64, toRelTime64, showNanoSecond64, showRelTime64)
146import System.IO (hPutStrLn, stderr)
148import qualified Data.Heap as H
149import qualified Data.Set as S
151newtype Count = Count Int64
152 deriving ( Eq
153 , Read
154 , Show
155 , Enum
156 , Bounded
157 , Num
158 , Real
159 , Integral
160 , Ord
161 )
163------------------------------------------------------------------------------
164-- Parent child thread communication type
165------------------------------------------------------------------------------
167data ThreadAbort = ThreadAbort deriving Show
169instance Exception ThreadAbort
171-- | Events that a child thread may send to a parent thread.
172data ChildEvent a =
173 ChildYield a
174 | ChildStop ThreadId (Maybe SomeException)
176#if __GLASGOW_HASKELL__ < 800
177#define Type *
178#endif
179-- | Sorting out-of-turn outputs in a heap for Ahead style streams
180data AheadHeapEntry (t :: (Type -> Type) -> Type -> Type) m a =
181 AheadEntryNull
182 | AheadEntryPure a
183 | AheadEntryStream (t m a)
184#undef Type
186------------------------------------------------------------------------------
187-- State threaded around the monad for thread management
188------------------------------------------------------------------------------
190-- | Identify the type of the SVar. Two computations using the same style can
191-- be scheduled on the same SVar.
192data SVarStyle =
193 AsyncVar -- depth first concurrent
194 | WAsyncVar -- breadth first concurrent
195 | ParallelVar -- all parallel
196 | AheadVar -- Concurrent look ahead
197 deriving (Eq, Show)
199-- | An SVar or a Stream Var is a conduit to the output from multiple streams
200-- running concurrently and asynchronously. An SVar can be thought of as an
201-- asynchronous IO handle. We can write any number of streams to an SVar in a
202-- non-blocking manner and then read them back at any time at any pace. The
203-- SVar would run the streams asynchronously and accumulate results. An SVar
204-- may not really execute the stream completely and accumulate all the results.
205-- However, it ensures that the reader can read the results at whatever paces
206-- it wants to read. The SVar monitors and adapts to the consumer's pace.
207--
208-- An SVar is a mini scheduler, it has an associated workLoop that holds the
209-- stream tasks to be picked and run by a pool of worker threads. It has an
210-- associated output queue where the output stream elements are placed by the
211-- worker threads. A outputDoorBell is used by the worker threads to intimate the
212-- consumer thread about availability of new results in the output queue. More
213-- workers are added to the SVar by 'fromStreamVar' on demand if the output
214-- produced is not keeping pace with the consumer. On bounded SVars, workers
215-- block on the output queue to provide throttling of the producer when the
216-- consumer is not pulling fast enough. The number of workers may even get
217-- reduced depending on the consuming pace.
218--
219-- New work is enqueued either at the time of creation of the SVar or as a
220-- result of executing the parallel combinators i.e. '<|' and '<|>' when the
221-- already enqueued computations get evaluated. See 'joinStreamVarAsync'.
223-- We measure the individual worker latencies to estimate the number of workers
224-- needed or the amount of time we have to sleep between dispatches to achieve
225-- a particular rate when controlled pace mode it used.
226data WorkerInfo = WorkerInfo
227 { workerYieldMax :: Count -- 0 means unlimited
228 -- total number of yields by the worker till now
229 , workerYieldCount :: IORef Count
230 -- yieldCount at start, timestamp
231 , workerLatencyStart :: IORef (Count, AbsTime)
232 }
235-- | Specifies the stream yield rate in yields per second (@Hertz@).
236-- We keep accumulating yield credits at 'rateGoal'. At any point of time we
237-- allow only as many yields as we have accumulated as per 'rateGoal' since the
238-- start of time. If the consumer or the producer is slower or faster, the
239-- actual rate may fall behind or exceed 'rateGoal'. We try to recover the gap
240-- between the two by increasing or decreasing the pull rate from the producer.
241-- However, if the gap becomes more than 'rateBuffer' we try to recover only as
242-- much as 'rateBuffer'.
243--
244-- 'rateLow' puts a bound on how low the instantaneous rate can go when
245-- recovering the rate gap. In other words, it determines the maximum yield
246-- latency. Similarly, 'rateHigh' puts a bound on how high the instantaneous
247-- rate can go when recovering the rate gap. In other words, it determines the
248-- minimum yield latency. We reduce the latency by increasing concurrency,
249-- therefore we can say that it puts an upper bound on concurrency.
250--
251-- If the 'rateGoal' is 0 or negative the stream never yields a value.
252-- If the 'rateBuffer' is 0 or negative we do not attempt to recover.
253--
254-- @since 0.5.0
255data Rate = Rate
256 { rateLow :: Double -- ^ The lower rate limit
257 , rateGoal :: Double -- ^ The target rate we want to achieve
258 , rateHigh :: Double -- ^ The upper rate limit
259 , rateBuffer :: Int -- ^ Maximum slack from the goal
260 }
262data LatencyRange = LatencyRange
263 { minLatency :: NanoSecond64
264 , maxLatency :: NanoSecond64
265 } deriving Show
267-- Rate control.
268data YieldRateInfo = YieldRateInfo
269 { svarLatencyTarget :: NanoSecond64
270 , svarLatencyRange :: LatencyRange
271 , svarRateBuffer :: Int
273 -- [LOCKING] Unlocked access. Modified by the consumer thread and unsafely
274 -- read by the worker threads
275 , svarGainedLostYields :: IORef Count
277 -- Actual latency/througput as seen from the consumer side, we count the
278 -- yields and the time it took to generates those yields. This is used to
279 -- increase or decrease the number of workers needed to achieve the desired
280 -- rate. The idle time of workers is adjusted in this, so that we only
281 -- account for the rate when the consumer actually demands data.
282 -- XXX interval latency is enough, we can move this under diagnostics build
283 -- [LOCKING] Unlocked access. Modified by the consumer thread and unsafely
284 -- read by the worker threads
285 , svarAllTimeLatency :: IORef (Count, AbsTime)
287 -- XXX Worker latency specified by the user to be used before the first
288 -- actual measurement arrives. Not yet implemented
289 , workerBootstrapLatency :: Maybe NanoSecond64
291 -- After how many yields the worker should update the latency information.
292 -- If the latency is high, this count is kept lower and vice-versa. XXX If
293 -- the latency suddenly becomes too high this count may remain too high for
294 -- long time, in such cases the consumer can change it.
295 -- 0 means no latency computation
296 -- XXX this is derivable from workerMeasuredLatency, can be removed.
297 -- [LOCKING] Unlocked access. Modified by the consumer thread and unsafely
298 -- read by the worker threads
299 , workerPollingInterval :: IORef Count
301 -- This is in progress latency stats maintained by the workers which we
302 -- empty into workerCollectedLatency stats at certain intervals - whenever
303 -- we process the stream elements yielded in this period. The first count
304 -- is all yields, the second count is only those yields for which the
305 -- latency was measured to be non-zero (note that if the timer resolution
306 -- is low the measured latency may be zero e.g. on JS platform).
307 -- [LOCKING] Locked access. Modified by the consumer thread as well as
308 -- worker threads. Workers modify it periodically based on
309 -- workerPollingInterval and not on every yield to reduce the locking
310 -- overhead.
311 -- (allYieldCount, yieldCount, timeTaken)
312 , workerPendingLatency :: IORef (Count, Count, NanoSecond64)
314 -- This is the second level stat which is an accmulation from
315 -- workerPendingLatency stats. We keep accumulating latencies in this
316 -- bucket until we have stats for a sufficient period and then we reset it
317 -- to start collecting for the next period and retain the computed average
318 -- latency for the last period in workerMeasuredLatency.
319 -- [LOCKING] Unlocked access. Modified by the consumer thread and unsafely
320 -- read by the worker threads
321 -- (allYieldCount, yieldCount, timeTaken)
322 , workerCollectedLatency :: IORef (Count, Count, NanoSecond64)
324 -- Latency as measured by workers, aggregated for the last period.
325 -- [LOCKING] Unlocked access. Modified by the consumer thread and unsafely
326 -- read by the worker threads
327 , workerMeasuredLatency :: IORef NanoSecond64
328 }
330data SVarStats = SVarStats {
331 totalDispatches :: IORef Int
332 , maxWorkers :: IORef Int
333 , maxOutQSize :: IORef Int
334 , maxHeapSize :: IORef Int
335 , maxWorkQSize :: IORef Int
336 , avgWorkerLatency :: IORef (Count, NanoSecond64)
337 , minWorkerLatency :: IORef NanoSecond64
338 , maxWorkerLatency :: IORef NanoSecond64
339 , svarStopTime :: IORef (Maybe AbsTime)
342-- This is essentially a 'Maybe Word' type
343data Limit = Unlimited | Limited Word deriving Show
345instance Eq Limit where
346 Unlimited == Unlimited = True
347 Unlimited == Limited _ = False
348 Limited _ == Unlimited = False
349 Limited x == Limited y = x == y
351instance Ord Limit where
352 Unlimited <= Unlimited = True
353 Unlimited <= Limited _ = False
354 Limited _ <= Unlimited = True
355 Limited x <= Limited y = x <= y
357-- When to stop the composed stream.
358data SVarStopStyle =
359 StopNone -- stops only when all streams are finished
360 | StopAny -- stop when any stream finishes
361 | StopBy -- stop when a specific stream finishes
362 deriving (Eq, Show)
364-- | Buffering policy for persistent push workers (in ParallelT). In a pull
365-- style SVar (in AsyncT, AheadT etc.), the consumer side dispatches workers on
366-- demand, workers terminate if the buffer is full or if the consumer is not
367-- cosuming fast enough. In a push style SVar, a worker is dispatched only
368-- once, workers are persistent and keep pushing work to the consumer via a
369-- bounded buffer. If the buffer becomes full the worker either blocks, or it
370-- can drop an item from the buffer to make space.
371--
372-- Pull style SVars are useful in lazy stream evaluation whereas push style
373-- SVars are useful in strict left Folds.
374--
375-- XXX Maybe we can separate the implementation in two different types instead
376-- of using a common SVar type.
377--
378data PushBufferPolicy =
379 PushBufferDropNew -- drop the latest element and continue
380 | PushBufferDropOld -- drop the oldest element and continue
381 | PushBufferBlock -- block the thread until space
382 -- becomes available
384-- IMPORTANT NOTE: we cannot update the SVar after generating it as we have
385-- references to the original SVar stored in several functions which will keep
386-- pointing to the original data and the new updates won't reflect there.
387-- Any updateable parts must be kept in mutable references (IORef).
388--
389data SVar t m a = SVar
390 {
391 -- Read only state
392 svarStyle :: SVarStyle
393 , svarMrun :: RunInIO m
394 , svarStopStyle :: SVarStopStyle
395 , svarStopBy :: IORef ThreadId
397 -- Shared output queue (events, length)
398 -- XXX For better efficiency we can try a preallocated array type (perhaps
399 -- something like a vector) that allows an O(1) append. That way we will
400 -- avoid constructing and reversing the list. Possibly we can also avoid
401 -- the GC copying overhead. When the size increases we should be able to
402 -- allocate the array in chunks.
403 --
404 -- [LOCKING] Frequent locked access. This is updated by workers on each
405 -- yield and once in a while read by the consumer thread. This could have
406 -- big locking overhead if the number of workers is high.
407 --
408 -- XXX We can use a per-CPU data structure to reduce the locking overhead.
409 -- However, a per-cpu structure cannot guarantee the exact sequence in
410 -- which the elements were added, though that may not be important.
411 , outputQueue :: IORef ([ChildEvent a], Int)
413 -- [LOCKING] Infrequent MVar. Used when the outputQ transitions from empty
414 -- to non-empty, or a work item is queued by a worker to the work queue and
415 -- needDoorBell is set by the consumer.
416 , outputDoorBell :: MVar () -- signal the consumer about output
417 , readOutputQ :: m [ChildEvent a]
418 , postProcess :: m Bool
420 -- Combined/aggregate parameters
421 -- This is truncated to maxBufferLimit if set to more than that. Otherwise
422 -- potentially each worker may yield one value to the buffer in the worst
423 -- case exceeding the requested buffer size.
424 , maxWorkerLimit :: Limit
425 , maxBufferLimit :: Limit
426 -- These two are valid and used only when maxBufferLimit is Limited.
427 , pushBufferSpace :: IORef Count
428 , pushBufferPolicy :: PushBufferPolicy
429 -- [LOCKING] The consumer puts this MVar after emptying the buffer, workers
430 -- block on it when the buffer becomes full. No overhead unless the buffer
431 -- becomes full.
432 , pushBufferMVar :: MVar ()
434 -- [LOCKING] Read only access by consumer when dispatching a worker.
435 -- Decremented by workers when picking work and undo decrement if the
436 -- worker does not yield a value.
437 , remainingWork :: Maybe (IORef Count)
438 , yieldRateInfo :: Maybe YieldRateInfo
440 -- Used only by bounded SVar types
441 , enqueue :: t m a -> IO ()
442 , isWorkDone :: IO Bool
443 , isQueueDone :: IO Bool
444 , needDoorBell :: IORef Bool
445 , workLoop :: Maybe WorkerInfo -> m ()
447 -- Shared, thread tracking
448 -- [LOCKING] Updated unlocked only by consumer thread in case of
449 -- Async/Ahead style SVars. Updated locked by worker threads in case of
450 -- Parallel style.
451 , workerThreads :: IORef (Set ThreadId)
452 -- [LOCKING] Updated locked by consumer thread when dispatching a worker
453 -- and by the worker threads when the thread stops. This is read unsafely
454 -- at several places where we want to rely on an approximate value.
455 , workerCount :: IORef Int
456 , accountThread :: ThreadId -> m ()
457 , workerStopMVar :: MVar ()
459 , svarStats :: SVarStats
460 -- to track garbage collection of SVar
461 , svarRef :: Maybe (IORef ())
463 -- Only for diagnostics
464 , svarInspectMode :: Bool
465 , svarCreator :: ThreadId
466 , outputHeap :: IORef ( Heap (Entry Int (AheadHeapEntry t m a))
467 , Maybe Int)
468 -- Shared work queue (stream, seqNo)
469 , aheadWorkQueue :: IORef ([t m a], Int)
470 }
472-------------------------------------------------------------------------------
473-- State for concurrency control
474-------------------------------------------------------------------------------
476-- XXX we can put the resettable fields in a oneShotConfig field and others in
477-- a persistentConfig field. That way reset would be fast and scalable
478-- irrespective of the number of fields.
479--
480-- XXX make all these Limited types and use phantom types to distinguish them
481data State t m a = State
482 { -- one shot configuration, automatically reset for each API call
483 streamVar :: Maybe (SVar t m a)
484 , _yieldLimit :: Maybe Count
486 -- persistent configuration, state that remains valid until changed by
487 -- an explicit setting via a combinator.
488 , _threadsHigh :: Limit
489 , _bufferHigh :: Limit
490 -- XXX these two can be collapsed into a single type
491 , _streamLatency :: Maybe NanoSecond64 -- bootstrap latency
492 , _maxStreamRate :: Maybe Rate
493 , _inspectMode :: Bool
494 }
496-------------------------------------------------------------------------------
497-- State defaults and reset
498-------------------------------------------------------------------------------
500-- A magical value for the buffer size arrived at by running the smallest
501-- possible task and measuring the optimal value of the buffer for that. This
502-- is obviously dependent on hardware, this figure is based on a 2.2GHz intel
503-- core-i7 processor.
504magicMaxBuffer :: Word
505magicMaxBuffer = 1500
507defaultMaxThreads, defaultMaxBuffer :: Limit
508defaultMaxThreads = Limited magicMaxBuffer
509defaultMaxBuffer = Limited magicMaxBuffer
511-- The fields prefixed by an _ are not to be accessed or updated directly but
512-- via smart accessor APIs.
513defState :: State t m a
514defState = State
515 { streamVar = Nothing
516 , _yieldLimit = Nothing
517 , _threadsHigh = defaultMaxThreads
518 , _bufferHigh = defaultMaxBuffer
519 , _maxStreamRate = Nothing
520 , _streamLatency = Nothing
521 , _inspectMode = False
522 }
524-- XXX if perf gets affected we can have all the Nothing params in a single
525-- structure so that we reset is fast. We can also use rewrite rules such that
526-- reset occurs only in concurrent streams to reduce the impact on serial
527-- streams.
528-- We can optimize this so that we clear it only if it is a Just value, it
529-- results in slightly better perf for zip/zipM but the performance of scan
530-- worsens a lot, it does not fuse.
531--
532-- XXX This has a side effect of clearing the SVar and yieldLimit, therefore it
533-- should not be used to convert from the same type to the same type, unless
534-- you want to clear the SVar. For clearing the SVar you should be using the
535-- appropriate unStream functions instead.
536--
537-- | Adapt the stream state from one type to another.
538adaptState :: State t m a -> State t m b
539adaptState st = st
540 { streamVar = Nothing
541 , _yieldLimit = Nothing
542 }
544-------------------------------------------------------------------------------
545-- Smart get/set routines for State
546-------------------------------------------------------------------------------
548-- Use get/set routines instead of directly accessing the State fields
549setYieldLimit :: Maybe Int64 -> State t m a -> State t m a
550setYieldLimit lim st =
551 st { _yieldLimit =
552 case lim of
553 Nothing -> Nothing
554 Just n ->
555 if n <= 0
556 then Just 0
557 else Just (fromIntegral n)
558 }
560getYieldLimit :: State t m a -> Maybe Count
561getYieldLimit = _yieldLimit
563setMaxThreads :: Int -> State t m a -> State t m a
564setMaxThreads n st =
565 st { _threadsHigh =
566 if n < 0
567 then Unlimited
568 else if n == 0
569 then defaultMaxThreads
570 else Limited (fromIntegral n)
571 }
573getMaxThreads :: State t m a -> Limit
574getMaxThreads = _threadsHigh
576setMaxBuffer :: Int -> State t m a -> State t m a
577setMaxBuffer n st =
578 st { _bufferHigh =
579 if n < 0
580 then Unlimited
581 else if n == 0
582 then defaultMaxBuffer
583 else Limited (fromIntegral n)
584 }
586getMaxBuffer :: State t m a -> Limit
587getMaxBuffer = _bufferHigh
589setStreamRate :: Maybe Rate -> State t m a -> State t m a
590setStreamRate r st = st { _maxStreamRate = r }
592getStreamRate :: State t m a -> Maybe Rate
593getStreamRate = _maxStreamRate
595setStreamLatency :: Int -> State t m a -> State t m a
596setStreamLatency n st =
597 st { _streamLatency =
598 if n <= 0
599 then Nothing
600 else Just (fromIntegral n)
601 }
603getStreamLatency :: State t m a -> Maybe NanoSecond64
604getStreamLatency = _streamLatency
606setInspectMode :: State t m a -> State t m a
607setInspectMode st = st { _inspectMode = True }
609getInspectMode :: State t m a -> Bool
610getInspectMode = _inspectMode
612-------------------------------------------------------------------------------
613-- Cleanup
614-------------------------------------------------------------------------------
616cleanupSVar :: SVar t m a -> IO ()
617cleanupSVar sv = do
618 workers <- readIORef (workerThreads sv)
619 Prelude.mapM_ (`throwTo` ThreadAbort)
620 workers
622cleanupSVarFromWorker :: SVar t m a -> IO ()
623cleanupSVarFromWorker sv = do
624 workers <- readIORef (workerThreads sv)
625 self <- myThreadId
626 Prelude.mapM_ (`throwTo` ThreadAbort)
627 (Prelude.filter (/= self) $ S.toList workers)
629-------------------------------------------------------------------------------
630-- Worker latency data collection
631-------------------------------------------------------------------------------
633-- Every once in a while workers update the latencies and check the yield rate.
634-- They return if we are above the expected yield rate. If we check too often
635-- it may impact performance, if we check less often we may have a stale
636-- picture. We update every minThreadDelay but we translate that into a yield
637-- count based on latency so that the checking overhead is little.
638--
639-- XXX use a generation count to indicate that the value is updated. If the
640-- value is updated an existing worker must check it again on the next yield.
641-- Otherwise it is possible that we may keep updating it and because of the mod
642-- worker keeps skipping it.
643updateWorkerPollingInterval :: YieldRateInfo -> NanoSecond64 -> IO ()
644updateWorkerPollingInterval yinfo latency = do
645 let periodRef = workerPollingInterval yinfo
646 cnt = max 1 $ minThreadDelay `div` latency
647 period = min cnt (fromIntegral magicMaxBuffer)
649 writeIORef periodRef (fromIntegral period)
651{-# INLINE recordMinMaxLatency #-}
652recordMinMaxLatency :: SVar t m a -> NanoSecond64 -> IO ()
653recordMinMaxLatency sv new = do
654 let ss = svarStats sv
655 minLat <- readIORef (minWorkerLatency ss)
656 when (new < minLat || minLat == 0) $
657 writeIORef (minWorkerLatency ss) new
659 maxLat <- readIORef (maxWorkerLatency ss)
660 when (new > maxLat) $ writeIORef (maxWorkerLatency ss) new
662recordAvgLatency :: SVar t m a -> (Count, NanoSecond64) -> IO ()
663recordAvgLatency sv (count, time) = do
664 let ss = svarStats sv
665 modifyIORef (avgWorkerLatency ss) $
666 \(cnt, t) -> (cnt + count, t + time)
668-- Pour the pending latency stats into a collection bucket
669{-# INLINE collectWorkerPendingLatency #-}
670collectWorkerPendingLatency
671 :: IORef (Count, Count, NanoSecond64)
672 -> IORef (Count, Count, NanoSecond64)
673 -> IO (Count, Maybe (Count, NanoSecond64))
674collectWorkerPendingLatency cur col = do
675 (fcount, count, time) <- atomicModifyIORefCAS cur $ \v -> ((0,0,0), v)
677 (fcnt, cnt, t) <- readIORef col
678 let totalCount = fcnt + fcount
679 latCount = cnt + count
680 latTime = t + time
681 writeIORef col (totalCount, latCount, latTime)
683 assert (latCount == 0 || latTime /= 0) (return ())
684 let latPair =
685 if latCount > 0 && latTime > 0
686 then Just $ (latCount, latTime)
687 else Nothing
688 return (totalCount, latPair)
690{-# INLINE shouldUseCollectedBatch #-}
691shouldUseCollectedBatch
692 :: Count
693 -> NanoSecond64
694 -> NanoSecond64
695 -> NanoSecond64
696 -> Bool
697shouldUseCollectedBatch collectedYields collectedTime newLat prevLat =
698 let r = fromIntegral newLat / fromIntegral prevLat :: Double
699 in (collectedYields > fromIntegral magicMaxBuffer)
700 || (collectedTime > minThreadDelay)
701 || (prevLat > 0 && (r > 2 || r < 0.5))
702 || (prevLat == 0)
704-- Returns a triple, (1) yield count since last collection, (2) the base time
705-- when we started counting, (3) average latency in the last measurement
706-- period. The former two are used for accurate measurement of the going rate
707-- whereas the average is used for future estimates e.g. how many workers
708-- should be maintained to maintain the rate.
709-- CAUTION! keep it in sync with getWorkerLatency
710collectLatency :: SVar t m a
711 -> YieldRateInfo
712 -> Bool
713 -> IO (Count, AbsTime, NanoSecond64)
714collectLatency sv yinfo drain = do
715 let cur = workerPendingLatency yinfo
716 col = workerCollectedLatency yinfo
717 longTerm = svarAllTimeLatency yinfo
718 measured = workerMeasuredLatency yinfo
720 (newCount, newLatPair) <- collectWorkerPendingLatency cur col
721 (lcount, ltime) <- readIORef longTerm
722 prevLat <- readIORef measured
724 let newLcount = lcount + newCount
725 retWith lat = return (newLcount, ltime, lat)
727 case newLatPair of
728 Nothing -> retWith prevLat
729 Just (count, time) -> do
730 let newLat = time `div` (fromIntegral count)
731 when (svarInspectMode sv) $ recordMinMaxLatency sv newLat
732 -- When we have collected a significant sized batch we compute the
733 -- new latency using that batch and return the new latency,
734 -- otherwise we return the previous latency derived from the
735 -- previous batch.
736 if shouldUseCollectedBatch newCount time newLat prevLat || drain
737 then do
738 -- XXX make this NOINLINE?
739 updateWorkerPollingInterval yinfo (max newLat prevLat)
740 when (svarInspectMode sv) $ recordAvgLatency sv (count, time)
741 writeIORef col (0, 0, 0)
742 writeIORef measured ((prevLat + newLat) `div` 2)
743 modifyIORef longTerm $ \(_, t) -> (newLcount, t)
744 retWith newLat
745 else retWith prevLat
747-------------------------------------------------------------------------------
748-- Dumping the SVar for debug/diag
749-------------------------------------------------------------------------------
751dumpSVarStats :: SVar t m a -> SVarStats -> SVarStyle -> IO String
752dumpSVarStats sv ss style = do
753 case yieldRateInfo sv of
754 Nothing -> return ()
755 Just yinfo -> do
756 _ <- liftIO $ collectLatency sv yinfo True
757 return ()
759 dispatches <- readIORef $ totalDispatches ss
760 maxWrk <- readIORef $ maxWorkers ss
761 maxOq <- readIORef $ maxOutQSize ss
762 maxHp <- readIORef $ maxHeapSize ss
763 minLat <- readIORef $ minWorkerLatency ss
764 maxLat <- readIORef $ maxWorkerLatency ss
765 (avgCnt, avgTime) <- readIORef $ avgWorkerLatency ss
766 (svarCnt, svarGainLossCnt, svarLat) <- case yieldRateInfo sv of
767 Nothing -> return (0, 0, 0)
768 Just yinfo -> do
769 (cnt, startTime) <- readIORef $ svarAllTimeLatency yinfo
770 if cnt > 0
771 then do
772 t <- readIORef (svarStopTime ss)
773 gl <- readIORef (svarGainedLostYields yinfo)
774 case t of
775 Nothing -> do
776 now <- getTime Monotonic
777 let interval = diffAbsTime64 now startTime
778 return (cnt, gl, interval `div` fromIntegral cnt)
779 Just stopTime -> do
780 let interval = diffAbsTime64 stopTime startTime
781 return (cnt, gl, interval `div` fromIntegral cnt)
782 else return (0, 0, 0)
784 return $ unlines
785 [ "total dispatches = " <> show dispatches
786 , "max workers = " <> show maxWrk
787 , "max outQSize = " <> show maxOq
788 <> (if style == AheadVar
789 then "\nheap max size = " <> show maxHp
790 else "")
791 <> (if minLat > 0
792 then "\nmin worker latency = " <> showNanoSecond64 minLat
793 else "")
794 <> (if maxLat > 0
795 then "\nmax worker latency = " <> showNanoSecond64 maxLat
796 else "")
797 <> (if avgCnt > 0
798 then let lat = avgTime `div` fromIntegral avgCnt
799 in "\navg worker latency = " <> showNanoSecond64 lat
800 else "")
801 <> (if svarLat > 0
802 then "\nSVar latency = " <> showRelTime64 svarLat
803 else "")
804 <> (if svarCnt > 0
805 then "\nSVar yield count = " <> show svarCnt
806 else "")
807 <> (if svarGainLossCnt > 0
808 then "\nSVar gain/loss yield count = " <> show svarGainLossCnt
809 else "")
810 ]
812{-# NOINLINE dumpSVar #-}
813dumpSVar :: SVar t m a -> IO String
814dumpSVar sv = do
815 (oqList, oqLen) <- readIORef $ outputQueue sv
816 db <- tryReadMVar $ outputDoorBell sv
817 aheadDump <-
818 if svarStyle sv == AheadVar
819 then do
820 (oheap, oheapSeq) <- readIORef $ outputHeap sv
821 (wq, wqSeq) <- readIORef $ aheadWorkQueue sv
822 return $ unlines
823 [ "heap length = " <> show (H.size oheap)
824 , "heap seqeunce = " <> show oheapSeq
825 , "work queue length = " <> show (length wq)
826 , "work queue sequence = " <> show wqSeq
827 ]
828 else return []
830 let style = svarStyle sv
831 waiting <-
832 if style /= ParallelVar
833 then readIORef $ needDoorBell sv
834 else return False
835 rthread <- readIORef $ workerThreads sv
836 workers <- readIORef $ workerCount sv
837 stats <- dumpSVarStats sv (svarStats sv) (svarStyle sv)
839 return $ unlines
840 [
841 "Creator tid = " <> show (svarCreator sv),
842 "style = " <> show (svarStyle sv)
843 , "---------CURRENT STATE-----------"
844 , "outputQueue length computed = " <> show (length oqList)
845 , "outputQueue length maintained = " <> show oqLen
846 -- XXX print the types of events in the outputQueue, first 5
847 , "outputDoorBell = " <> show db
848 ]
849 <> aheadDump
850 <> unlines
851 [ "needDoorBell = " <> show waiting
852 , "running threads = " <> show rthread
853 -- XXX print the status of first 5 threads
854 , "running thread count = " <> show workers
855 ]
856 <> "---------STATS-----------\n"
857 <> stats
859-- MVar diagnostics has some overhead - around 5% on asyncly null benchmark, we
860-- can keep it on in production to debug problems quickly if and when they
861-- happen, but it may result in unexpected output when threads are left hanging
862-- until they are GCed because the consumer went away.
864{-# NOINLINE mvarExcHandler #-}
865mvarExcHandler :: SVar t m a -> String -> BlockedIndefinitelyOnMVar -> IO ()
866mvarExcHandler sv label e@BlockedIndefinitelyOnMVar = do
867 svInfo <- dumpSVar sv
868 hPutStrLn stderr $ label <> " " <> "BlockedIndefinitelyOnMVar\n" <> svInfo
869 throwIO e
871{-# NOINLINE stmExcHandler #-}
872stmExcHandler :: SVar t m a -> String -> BlockedIndefinitelyOnSTM -> IO ()
873stmExcHandler sv label e@BlockedIndefinitelyOnSTM = do
874 svInfo <- dumpSVar sv
875 hPutStrLn stderr $ label <> " " <> "BlockedIndefinitelyOnSTM\n" <> svInfo
876 throwIO e
878withDiagMVar :: SVar t m a -> String -> IO () -> IO ()
879withDiagMVar sv label action =
880 if svarInspectMode sv
881 then
882 action `catches` [ Handler (mvarExcHandler sv label)
883 , Handler (stmExcHandler sv label)
884 ]
885 else action
887------------------------------------------------------------------------------
888-- Spawning threads
889------------------------------------------------------------------------------
891-- | A monad that can perform concurrent or parallel IO operations. Streams
892-- that can be composed concurrently require the underlying monad to be
893-- 'MonadAsync'.
894--
895-- @since 0.1.0
896type MonadAsync m = (MonadIO m, MonadBaseControl IO m, MonadThrow m)
898-- When we run computations concurrently, we completely isolate the state of
899-- the concurrent computations from the parent computation. The invariant is
900-- that we should never be running two concurrent computations in the same
901-- thread without using the runInIO function. Also, we should never be running
902-- a concurrent computation in the parent thread, otherwise it may affect the
903-- state of the parent which is against the defined semantics of concurrent
904-- execution.
905newtype RunInIO m = RunInIO { runInIO :: forall b. m b -> IO (StM m b) }
907captureMonadState :: MonadBaseControl IO m => m (RunInIO m)
908captureMonadState = control $ \run -> run (return $ RunInIO run)
910-- Stolen from the async package. The perf improvement is modest, 2% on a
911-- thread heavy benchmark (parallel composition using noop computations).
912-- A version of forkIO that does not include the outer exception
913-- handler: saves a bit of time when we will be installing our own
914-- exception handler.
915{-# INLINE rawForkIO #-}
916rawForkIO :: IO () -> IO ThreadId
917rawForkIO action = IO $ \ s ->
918 case fork# action s of (# s1, tid #) -> (# s1, ThreadId tid #)
920{-# INLINE doFork #-}
921doFork :: MonadBaseControl IO m
922 => m ()
923 -> RunInIO m
924 -> (SomeException -> IO ())
925 -> m ThreadId
926doFork action (RunInIO mrun) exHandler =
927 control $ \run ->
928 mask $ \restore -> do
929 tid <- rawForkIO $ catch (restore $ void $ mrun action)
930 exHandler
931 run (return tid)
933------------------------------------------------------------------------------
934-- Collecting results from child workers in a streamed fashion
935------------------------------------------------------------------------------
937-- XXX Can we make access to remainingWork and yieldRateInfo fields in sv
938-- faster, along with the fields in sv required by send?
939-- XXX make it noinline
940--
941-- XXX we may want to employ an increment and decrement in batches when the
942-- througput is high or when the cost of synchronization is high. For example
943-- if the application is distributed then inc/dec of a shared variable may be
944-- very costly.
945--
946-- A worker decrements the yield limit before it executes an action. However,
947-- the action may not result in an element being yielded, in that case we have
948-- to increment the yield limit.
949--
950-- Note that we need it to be an Int type so that we have the ability to undo a
951-- decrement that takes it below zero.
952{-# INLINE decrementYieldLimit #-}
953decrementYieldLimit :: SVar t m a -> IO Bool
954decrementYieldLimit sv =
955 case remainingWork sv of
956 Nothing -> return True
957 Just ref -> do
958 r <- atomicModifyIORefCAS ref $ \x -> (x - 1, x)
959 return $ r >= 1
961{-# INLINE incrementYieldLimit #-}
962incrementYieldLimit :: SVar t m a -> IO ()
963incrementYieldLimit sv =
964 case remainingWork sv of
965 Nothing -> return ()
966 Just ref -> atomicModifyIORefCAS_ ref (+ 1)
968-- XXX exception safety of all atomic/MVar operations
970-- TBD Each worker can have their own queue and the consumer can empty one
971-- queue at a time, that way contention can be reduced.
973-- XXX Only yields should be counted in the buffer limit and not the Stop
974-- events.
976{-# INLINE decrementBufferLimit #-}
977decrementBufferLimit :: SVar t m a -> IO ()
978decrementBufferLimit sv =
979 case maxBufferLimit sv of
980 Unlimited -> return ()
981 Limited _ -> do
982 let ref = pushBufferSpace sv
983 old <- atomicModifyIORefCAS ref $ \x ->
984 (if x >= 1 then x - 1 else x, x)
985 when (old <= 0) $
986 case pushBufferPolicy sv of
987 PushBufferBlock -> blockAndRetry
988 PushBufferDropNew -> do
989 -- We just drop one item and proceed. It is possible
990 -- that by the time we drop the item the consumer
991 -- thread might have run and created space in the
992 -- buffer, but we do not care about that condition.
993 -- This is not pedantically correct but it should be
994 -- fine in practice.
995 -- XXX we may want to drop only if n == maxBuf
996 -- otherwise we must have space in the buffer and a
997 -- decrement should be possible.
998 block <- atomicModifyIORefCAS (outputQueue sv) $
999 \(es, n) ->
1000 case es of
1001 [] -> (([],n), True)
1002 _ : xs -> ((xs, n - 1), False)
1003 when block blockAndRetry
1004 -- XXX need a dequeue or ring buffer for this
1005 PushBufferDropOld -> undefined
1007 where
1009 blockAndRetry = do
1010 let ref = pushBufferSpace sv
1011 liftIO $ takeMVar (pushBufferMVar sv)
1012 old <- atomicModifyIORefCAS ref $ \x ->
1013 (if x >= 1 then x - 1 else x, x)
1014 -- When multiple threads sleep on takeMVar, the first thread would
1015 -- wakeup due to a putMVar by the consumer, but the rest of the threads
1016 -- would have to put back the MVar after taking it and decrementing the
1017 -- buffer count, otherwise all other threads will remain asleep.
1018 if old >= 1
1019 then void $ liftIO $ tryPutMVar (pushBufferMVar sv) ()
1020 -- We do not put the MVar back in this case, instead we
1021 -- wait for the consumer to put it.
1022 else blockAndRetry
1024{-# INLINE incrementBufferLimit #-}
1025incrementBufferLimit :: SVar t m a -> IO ()
1026incrementBufferLimit sv =
1027 case maxBufferLimit sv of
1028 Unlimited -> return ()
1029 Limited _ -> do
1030 atomicModifyIORefCAS_ (pushBufferSpace sv) (+ 1)
1031 writeBarrier
1032 void $ liftIO $ tryPutMVar (pushBufferMVar sv) ()
1034{-# INLINE resetBufferLimit #-}
1035resetBufferLimit :: SVar t m a -> IO ()
1036resetBufferLimit sv =
1037 case maxBufferLimit sv of
1038 Unlimited -> return ()
1039 Limited n -> atomicModifyIORefCAS_ (pushBufferSpace sv)
1040 (const (fromIntegral n))
1042-- | This function is used by the producer threads to queue output for the
1043-- consumer thread to consume. Returns whether the queue has more space.
1044send :: SVar t m a -> ChildEvent a -> IO Int
1045send sv msg = do
1046 -- XXX can the access to outputQueue and maxBufferLimit be made faster
1047 -- somehow?
1048 oldlen <- atomicModifyIORefCAS (outputQueue sv) $ \(es, n) ->
1049 ((msg : es, n + 1), n)
1050 when (oldlen <= 0) $ do
1051 -- The wake up must happen only after the store has finished otherwise
1052 -- we can have lost wakeup problems.
1053 writeBarrier
1054 -- Since multiple workers can try this at the same time, it is possible
1055 -- that we may put a spurious MVar after the consumer has already seen
1056 -- the output. But that's harmless, at worst it may cause the consumer
1057 -- to read the queue again and find it empty.
1058 -- The important point is that the consumer is guaranteed to receive a
1059 -- doorbell if something was added to the queue after it empties it.
1060 void $ tryPutMVar (outputDoorBell sv) ()
1061 return oldlen
1063workerCollectLatency :: WorkerInfo -> IO (Maybe (Count, NanoSecond64))
1064workerCollectLatency winfo = do
1065 (cnt0, t0) <- readIORef (workerLatencyStart winfo)
1066 cnt1 <- readIORef (workerYieldCount winfo)
1067 let cnt = cnt1 - cnt0
1069 if cnt > 0
1070 then do
1071 t1 <- getTime Monotonic
1072 let period = fromRelTime64 $ diffAbsTime64 t1 t0
1073 writeIORef (workerLatencyStart winfo) (cnt1, t1)
1074 return $ Just (cnt, period)
1075 else return Nothing
1077-- XXX There are a number of gotchas in measuring latencies.
1078-- 1) We measure latencies only when a worker yields a value
1079-- 2) It is possible that a stream calls the stop continuation, in which case
1080-- the worker would not yield a value and we would not account that worker in
1081-- latencies. Even though this case should ideally be accounted we do not
1082-- account it because we cannot or do not distinguish it from the case
1083-- described next.
1084-- 3) It is possible that a worker returns without yielding anything because it
1085-- never got a chance to pick up work.
1086-- 4) If the system timer resolution is lower than the latency, the latency
1087-- computation turns out to be zero.
1089-- We can fix this if we measure the latencies by counting the work items
1090-- picked rather than based on the outputs yielded.
1091workerUpdateLatency :: YieldRateInfo -> WorkerInfo -> IO ()
1092workerUpdateLatency yinfo winfo = do
1093 r <- workerCollectLatency winfo
1094 case r of
1095 Just (cnt, period) -> do
1096 -- NOTE: On JS platform the timer resolution could be pretty low. When
1097 -- the timer resolution is low, measurement of latencies could be
1098 -- tricky. All the worker latencies will turn out to be zero if they
1099 -- are lower than the resolution. We only take into account those
1100 -- measurements which are more than the timer resolution.
1102 let ref = workerPendingLatency yinfo
1103 (cnt1, t1) = if period > 0 then (cnt, period) else (0, 0)
1104 atomicModifyIORefCAS_ ref $
1105 \(fc, n, t) -> (fc + cnt, n + cnt1, t + t1)
1106 Nothing -> return ()
1108updateYieldCount :: WorkerInfo -> IO Count
1109updateYieldCount winfo = do
1110 cnt <- readIORef (workerYieldCount winfo)
1111 let cnt1 = cnt + 1
1112 writeIORef (workerYieldCount winfo) cnt1
1113 return cnt1
1115isBeyondMaxYield :: Count -> WorkerInfo -> Bool
1116isBeyondMaxYield cnt winfo =
1117 let ymax = workerYieldMax winfo
1118 in ymax /= 0 && cnt >= ymax
1120-- XXX we should do rate control periodically based on the total yields rather
1121-- than based on the worker local yields as other workers may have yielded more
1122-- and we should stop based on the aggregate yields. However, latency update
1123-- period can be based on individual worker yields.
1124{-# NOINLINE checkRatePeriodic #-}
1125checkRatePeriodic :: SVar t m a
1126 -> YieldRateInfo
1127 -> WorkerInfo
1128 -> Count
1129 -> IO Bool
1130checkRatePeriodic sv yinfo winfo ycnt = do
1131 i <- readIORef (workerPollingInterval yinfo)
1132 -- XXX use generation count to check if the interval has been updated
1133 if i /= 0 && (ycnt `mod` i) == 0
1134 then do
1135 workerUpdateLatency yinfo winfo
1136 -- XXX not required for parallel streams
1137 isBeyondMaxRate sv yinfo
1138 else return False
1140-- CAUTION! this also updates the yield count and therefore should be called
1141-- only when we are actually yielding an element.
1142{-# NOINLINE workerRateControl #-}
1143workerRateControl :: SVar t m a -> YieldRateInfo -> WorkerInfo -> IO Bool
1144workerRateControl sv yinfo winfo = do
1145 cnt <- updateYieldCount winfo
1146 beyondMaxRate <- checkRatePeriodic sv yinfo winfo cnt
1147 return $ not (isBeyondMaxYield cnt winfo || beyondMaxRate)
1149-- XXX we should do rate control here but not latency update in case of ahead
1150-- streams. latency update must be done when we yield directly to outputQueue
1151-- or when we yield to heap.
1153-- returns whether the worker should continue (True) or stop (False).
1154{-# INLINE sendYield #-}
1155sendYield :: SVar t m a -> Maybe WorkerInfo -> ChildEvent a -> IO Bool
1156sendYield sv mwinfo msg = do
1157 oldlen <- send sv msg
1158 let limit = maxBufferLimit sv
1159 bufferSpaceOk <- case limit of
1160 Unlimited -> return True
1161 Limited lim -> do
1162 active <- readIORef (workerCount sv)
1163 return $ (oldlen + 1) < (fromIntegral lim - active)
1164 rateLimitOk <-
1165 case mwinfo of
1166 Just winfo ->
1167 case yieldRateInfo sv of
1168 Nothing -> return True
1169 Just yinfo -> workerRateControl sv yinfo winfo
1170 Nothing -> return True
1171 return $ bufferSpaceOk && rateLimitOk
1173{-# INLINE workerStopUpdate #-}
1174workerStopUpdate :: WorkerInfo -> YieldRateInfo -> IO ()
1175workerStopUpdate winfo info = do
1176 i <- readIORef (workerPollingInterval info)
1177 when (i /= 0) $ workerUpdateLatency info winfo
1179{-# INLINABLE sendStop #-}
1180sendStop :: SVar t m a -> Maybe WorkerInfo -> IO ()
1181sendStop sv mwinfo = do
1182 atomicModifyIORefCAS_ (workerCount sv) $ \n -> n - 1
1183 case (mwinfo, yieldRateInfo sv) of
1184 (Just winfo, Just info) ->
1185 workerStopUpdate winfo info
1186 _ ->
1187 return ()
1188 myThreadId >>= \tid -> void $ send sv (ChildStop tid Nothing)
1190-------------------------------------------------------------------------------
1191-- Doorbell
1192-------------------------------------------------------------------------------
1194{-# INLINE ringDoorBell #-}
1195ringDoorBell :: SVar t m a -> IO ()
1196ringDoorBell sv = do
1197 storeLoadBarrier
1198 w <- readIORef $ needDoorBell sv
1199 when w $ do
1200 -- Note: the sequence of operations is important for correctness here.
1201 -- We need to set the flag to false strictly before sending the
1202 -- outputDoorBell, otherwise the outputDoorBell may get processed too
1203 -- early and then we may set the flag to False to later making the
1204 -- consumer lose the flag, even without receiving a outputDoorBell.
1205 atomicModifyIORefCAS_ (needDoorBell sv) (const False)
1206 void $ tryPutMVar (outputDoorBell sv) ()
1208-------------------------------------------------------------------------------
1209-- Async
1210-------------------------------------------------------------------------------
1212-- Note: For purely right associated expressions this queue should have at most
1213-- one element. It grows to more than one when we have left associcated
1214-- expressions. Large left associated compositions can grow this to a
1215-- large size
1216{-# INLINE enqueueLIFO #-}
1217enqueueLIFO :: SVar t m a -> IORef [t m a] -> t m a -> IO ()
1218enqueueLIFO sv q m = do
1219 atomicModifyIORefCAS_ q $ \ms -> m : ms
1220 ringDoorBell sv
1222-------------------------------------------------------------------------------
1223-- WAsync
1224-------------------------------------------------------------------------------
1226-- XXX we can use the Ahead style sequence/heap mechanism to make the best
1227-- effort to always try to finish the streams on the left side of an expression
1228-- first as long as possible.
1230{-# INLINE enqueueFIFO #-}
1231enqueueFIFO :: SVar t m a -> LinkedQueue (t m a) -> t m a -> IO ()
1232enqueueFIFO sv q m = do
1233 pushL q m
1234 ringDoorBell sv
1236-------------------------------------------------------------------------------
1237-- Ahead
1238-------------------------------------------------------------------------------
1240-- Lookahead streams can execute multiple tasks concurrently, ahead of time,
1241-- but always serve them in the same order as they appear in the stream. To
1242-- implement lookahead streams efficiently we assign a sequence number to each
1243-- task when the task is picked up for execution. When the task finishes, the
1244-- output is tagged with the same sequence number and we rearrange the outputs
1245-- in sequence based on that number.
1247-- To explain the mechanism imagine that the current task at the head of the
1248-- stream has a "token" to yield to the outputQueue. The ownership of the token
1249-- is determined by the current sequence number is maintained in outputHeap.
1250-- Sequence number is assigned when a task is queued. When a thread dequeues a
1251-- task it picks up the sequence number as well and when the output is ready it
1252-- uses the sequence number to queue the output to the outputQueue.
1254-- The thread with current sequence number sends the output directly to the
1255-- outputQueue. Other threads push the output to the outputHeap. When the task
1256-- being queued on the heap is a stream of many elements we evaluate only the
1257-- first element and keep the rest of the unevaluated computation in the heap.
1258-- When such a task gets the "token" for outputQueue it evaluates and directly
1259-- yields all the elements to the outputQueue without checking for the
1260-- "token".
1262-- Note that no two outputs in the heap can have the same sequence numbers and
1263-- therefore we do not need a stable heap. We have also separated the buffer
1264-- for the current task (outputQueue) and the pending tasks (outputHeap) so
1265-- that the pending tasks cannot interfere with the current task. Note that for
1266-- a single task just the outputQueue is enough and for the case of many
1267-- threads just a heap is good enough. However we balance between these two
1268-- cases, so that both are efficient.
1270-- For bigger streams it may make sense to have separate buffers for each
1271-- stream. However, for singleton streams this may become inefficient. However,
1272-- if we do not have separate buffers, then the streams that come later in
1273-- sequence may hog the buffer, hindering the streams that are ahead. For this
1274-- reason we have a single element buffer limitation for the streams being
1275-- executed in advance.
1277-- This scheme works pretty efficiently with less than 40% extra overhead
1278-- compared to the Async streams where we do not have any kind of sequencing of
1279-- the outputs. It is especially devised so that we are most efficient when we
1280-- have short tasks and need just a single thread. Also when a thread yields
1281-- many items it can hold lockfree access to the outputQueue and do it
1282-- efficiently.
1284-- XXX Maybe we can start the ahead threads at a lower cpu and IO priority so
1285-- that they do not hog the resources and hinder the progress of the threads in
1286-- front of them.
1288-- XXX Left associated ahead expressions are expensive. We start a new SVar for
1289-- each left associative expression. The queue is used only for right
1290-- associated expression, we queue the right expression and execute the left.
1291-- Thererefore the queue never has more than one item in it.
1293-- XXX we can fix this. When we queue more than one item on the queue we can
1294-- mark the previously queued item as not-runnable. The not-runnable item is
1295-- not dequeued until the already running one has finished and at that time we
1296-- would also know the exact sequence number of the already queued item.
1298-- we can even run the already queued items but they will have to be sorted in
1299-- layers in the heap. We can use a list of heaps for that.
1300{-# INLINE enqueueAhead #-}
1301enqueueAhead :: SVar t m a -> IORef ([t m a], Int) -> t m a -> IO ()
1302enqueueAhead sv q m = do
1303 atomicModifyIORefCAS_ q $ \ case
1304 ([], n) -> ([m], n + 1) -- increment sequence
1305 _ -> error "not empty"
1306 ringDoorBell sv
1308-- enqueue without incrementing the sequence number
1309{-# INLINE reEnqueueAhead #-}
1310reEnqueueAhead :: SVar t m a -> IORef ([t m a], Int) -> t m a -> IO ()
1311reEnqueueAhead sv q m = do
1312 atomicModifyIORefCAS_ q $ \ case
1313 ([], n) -> ([m], n) -- DO NOT increment sequence
1314 _ -> error "not empty"
1315 ringDoorBell sv
1317-- Normally the thread that has the token should never go away. The token gets
1318-- handed over to another thread, but someone or the other has the token at any
1319-- point of time. But if the task that has the token finds that the outputQueue
1320-- is full, in that case it can go away without even handing over the token to
1321-- another thread. In that case it sets the nextSequence number in the heap its
1322-- own sequence number before going away. To handle this case, any task that
1323-- does not have the token tries to dequeue from the heap first before
1324-- dequeuing from the work queue. If it finds that the task at the top of the
1325-- heap is the one that owns the current sequence number then it grabs the
1326-- token and starts with that.
1328-- XXX instead of queueing just the head element and the remaining computation
1329-- on the heap, evaluate as many as we can and place them on the heap. But we
1330-- need to give higher priority to the lower sequence numbers so that lower
1331-- priority tasks do not fill up the heap making higher priority tasks block
1332-- due to full heap. Maybe we can have a weighted space for them in the heap.
1333-- The weight is inversely proportional to the sequence number.
1335-- XXX review for livelock
1337{-# INLINE queueEmptyAhead #-}
1338queueEmptyAhead :: MonadIO m => IORef ([t m a], Int) -> m Bool
1339queueEmptyAhead q = liftIO $ do
1340 (xs, _) <- readIORef q
1341 return $ null xs
1343{-# INLINE dequeueAhead #-}
1344dequeueAhead :: MonadIO m
1345 => IORef ([t m a], Int) -> m (Maybe (t m a, Int))
1346dequeueAhead q = liftIO $
1347 atomicModifyIORefCAS q $ \case
1348 ([], n) -> (([], n), Nothing)
1349 (x : [], n) -> (([], n), Just (x, n))
1350 _ -> error "more than one item on queue"
1352-------------------------------------------------------------------------------
1353-- Heap manipulation
1354-------------------------------------------------------------------------------
1356withIORef :: IORef a -> (a -> IO b) -> IO b
1357withIORef ref f = readIORef ref >>= f
1359atomicModifyIORef_ :: IORef a -> (a -> a) -> IO ()
1360atomicModifyIORef_ ref f =
1361 atomicModifyIORef ref $ \x -> (f x, ())
1363data HeapDequeueResult t m a =
1364 Clearing
1365 | Waiting Int
1366 | Ready (Entry Int (AheadHeapEntry t m a))
1368{-# INLINE dequeueFromHeap #-}
1369dequeueFromHeap
1370 :: IORef (Heap (Entry Int (AheadHeapEntry t m a)), Maybe Int)
1371 -> IO (HeapDequeueResult t m a)
1372dequeueFromHeap hpVar =
1373 atomicModifyIORef hpVar $ \pair@(hp, snum) ->
1374 case snum of
1375 Nothing -> (pair, Clearing)
1376 Just n -> do
1377 let r = H.uncons hp
1378 case r of
1379 Just (ent@(Entry seqNo _ev), hp') ->
1380 if seqNo == n
1381 then ((hp', Nothing), Ready ent)
1382 else assert (seqNo >= n) (pair, Waiting n)
1383 Nothing -> (pair, Waiting n)
1385{-# INLINE dequeueFromHeapSeq #-}
1386dequeueFromHeapSeq
1387 :: IORef (Heap (Entry Int (AheadHeapEntry t m a)), Maybe Int)
1388 -> Int
1389 -> IO (HeapDequeueResult t m a)
1390dequeueFromHeapSeq hpVar i =
1391 atomicModifyIORef hpVar $ \(hp, snum) ->
1392 case snum of
1393 Nothing -> do
1394 let r = H.uncons hp
1395 case r of
1396 Just (ent@(Entry seqNo _ev), hp') ->
1397 if seqNo == i
1398 then ((hp', Nothing), Ready ent)
1399 else assert (seqNo >= i) ((hp, Just i), Waiting i)
1400 Nothing -> ((hp, Just i), Waiting i)
1401 Just _ -> error "dequeueFromHeapSeq: unreachable"
1403heapIsSane :: Maybe Int -> Int -> Bool
1404heapIsSane snum seqNo =
1405 case snum of
1406 Nothing -> True
1407 Just n -> seqNo >= n
1409{-# INLINE requeueOnHeapTop #-}
1410requeueOnHeapTop
1411 :: IORef (Heap (Entry Int (AheadHeapEntry t m a)), Maybe Int)
1412 -> Entry Int (AheadHeapEntry t m a)
1413 -> Int
1414 -> IO ()
1415requeueOnHeapTop hpVar ent seqNo =
1416 atomicModifyIORef_ hpVar $ \(hp, snum) ->
1417 assert (heapIsSane snum seqNo) (H.insert ent hp, Just seqNo)
1419{-# INLINE updateHeapSeq #-}
1420updateHeapSeq
1421 :: IORef (Heap (Entry Int (AheadHeapEntry t m a)), Maybe Int)
1422 -> Int
1423 -> IO ()
1424updateHeapSeq hpVar seqNo =
1425 atomicModifyIORef_ hpVar $ \(hp, snum) ->
1426 assert (heapIsSane snum seqNo) (hp, Just seqNo)
1428-------------------------------------------------------------------------------
1429-- WAhead
1430-------------------------------------------------------------------------------
1432-- XXX To be implemented. Use a linked queue like WAsync and put back the
1433-- remaining computation at the back of the queue instead of the heap, and
1434-- increment the sequence number.
1436-------------------------------------------------------------------------------
1437-- Dispatching workers and tracking them
1438-------------------------------------------------------------------------------
1440-- Thread tracking is needed for two reasons:
1442-- 1) Killing threads on exceptions. Threads may not be left to go away by
1443-- themselves because they may run for significant times before going away or
1444-- worse they may be stuck in IO and never go away.
1446-- 2) To know when all threads are done and the stream has ended.
1448{-# NOINLINE addThread #-}
1449addThread :: MonadIO m => SVar t m a -> ThreadId -> m ()
1450addThread sv tid =
1451 liftIO $ modifyIORef (workerThreads sv) (S.insert tid)
1453-- This is cheaper than modifyThread because we do not have to send a
1454-- outputDoorBell This can make a difference when more workers are being
1455-- dispatched.
1456{-# INLINE delThread #-}
1457delThread :: MonadIO m => SVar t m a -> ThreadId -> m ()
1458delThread sv tid =
1459 liftIO $ modifyIORef (workerThreads sv) (S.delete tid)
1461-- If present then delete else add. This takes care of out of order add and
1462-- delete i.e. a delete arriving before we even added a thread.
1463-- This occurs when the forked thread is done even before the 'addThread' right
1464-- after the fork gets a chance to run.
1465{-# INLINE modifyThread #-}
1466modifyThread :: MonadIO m => SVar t m a -> ThreadId -> m ()
1467modifyThread sv tid = do
1468 changed <- liftIO $ atomicModifyIORefCAS (workerThreads sv) $ \old ->
1469 if S.member tid old
1470 then let new = S.delete tid old in (new, new)
1471 else let new = S.insert tid old in (new, old)
1472 when (null changed) $
1473 liftIO $ do
1474 writeBarrier
1475 void $ tryPutMVar (outputDoorBell sv) ()
1477-- | This is safe even if we are adding more threads concurrently because if
1478-- a child thread is adding another thread then anyway 'workerThreads' will
1479-- not be empty.
1480{-# INLINE allThreadsDone #-}
1481allThreadsDone :: MonadIO m => SVar t m a -> m Bool
1482allThreadsDone sv = liftIO $ S.null <$> readIORef (workerThreads sv)
1484{-# NOINLINE handleChildException #-}
1485handleChildException :: SVar t m a -> SomeException -> IO ()
1486handleChildException sv e = do
1487 tid <- myThreadId
1488 void $ send sv (ChildStop tid (Just e))
1490{-# NOINLINE recordMaxWorkers #-}
1491recordMaxWorkers :: MonadIO m => SVar t m a -> m ()
1492recordMaxWorkers sv = liftIO $ do
1493 active <- readIORef (workerCount sv)
1494 maxWrk <- readIORef (maxWorkers $ svarStats sv)
1495 when (active > maxWrk) $ writeIORef (maxWorkers $ svarStats sv) active
1496 modifyIORef (totalDispatches $ svarStats sv) (+1)
1498{-# NOINLINE pushWorker #-}
1499pushWorker :: MonadAsync m => Count -> SVar t m a -> m ()
1500pushWorker yieldMax sv = do
1501 liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n + 1
1502 when (svarInspectMode sv) $ recordMaxWorkers sv
1503 -- This allocation matters when significant number of workers are being
1504 -- sent. We allocate it only when needed.
1505 winfo <-
1506 case yieldRateInfo sv of
1507 Nothing -> return Nothing
1508 Just _ -> liftIO $ do
1509 cntRef <- newIORef 0
1510 t <- getTime Monotonic
1511 lat <- newIORef (0, t)
1512 return $ Just WorkerInfo
1513 { workerYieldMax = yieldMax
1514 , workerYieldCount = cntRef
1515 , workerLatencyStart = lat
1517 doFork (workLoop sv winfo) (svarMrun sv) (handleChildException sv)
1518 >>= addThread sv
1520-- XXX we can push the workerCount modification in accountThread and use the
1521-- same pushWorker for Parallel case as well.
1523-- | In contrast to pushWorker which always happens only from the consumer
1524-- thread, a pushWorkerPar can happen concurrently from multiple threads on the
1525-- producer side. So we need to use a thread safe modification of
1526-- workerThreads. Alternatively, we can use a CreateThread event to avoid
1527-- using a CAS based modification.
1528{-# INLINE pushWorkerPar #-}
1529pushWorkerPar
1530 :: MonadAsync m
1531 => SVar t m a -> (Maybe WorkerInfo -> m ()) -> m ()
1532pushWorkerPar sv wloop =
1533 if svarInspectMode sv
1534 then forkWithDiag
1535 else doFork (wloop Nothing) (svarMrun sv) (handleChildException sv)
1536 >>= modifyThread sv
1538 where
1540 {-# NOINLINE forkWithDiag #-}
1541 forkWithDiag = do
1542 -- We do not use workerCount in case of ParallelVar but still there is
1543 -- no harm in maintaining it correctly.
1544 liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n + 1
1545 recordMaxWorkers sv
1546 -- This allocation matters when significant number of workers are being
1547 -- sent. We allocate it only when needed. The overhead increases by 4x.
1548 winfo <-
1549 case yieldRateInfo sv of
1550 Nothing -> return Nothing
1551 Just _ -> liftIO $ do
1552 cntRef <- newIORef 0
1553 t <- getTime Monotonic
1554 lat <- newIORef (0, t)
1555 return $ Just WorkerInfo
1556 { workerYieldMax = 0
1557 , workerYieldCount = cntRef
1558 , workerLatencyStart = lat
1561 doFork (wloop winfo) (svarMrun sv) (handleChildException sv)
1562 >>= modifyThread sv
1564-- Returns:
1565-- True: can dispatch more
1566-- False: cannot dispatch any more
1567dispatchWorker :: MonadAsync m => Count -> SVar t m a -> m Bool
1568dispatchWorker yieldCount sv = do
1569 let workerLimit = maxWorkerLimit sv
1570 -- XXX in case of Ahead streams we should not send more than one worker
1571 -- when the work queue is done but heap is not done.
1572 done <- liftIO $ isWorkDone sv
1573 -- Note, "done" may not mean that the work is actually finished if there
1574 -- are workers active, because there may be a worker which has not yet
1575 -- queued the leftover work.
1576 if not done
1577 then do
1578 qDone <- liftIO $ isQueueDone sv
1579 -- This count may not be accurate as it is decremented by the workers
1580 -- and we have no synchronization with that decrement.
1581 active <- liftIO $ readIORef $ workerCount sv
1582 if not qDone
1583 then do
1584 -- Note that we may deadlock if the previous workers (tasks in the
1585 -- stream) wait/depend on the future workers (tasks in the stream)
1586 -- executing. In that case we should either configure the maxWorker
1587 -- count to higher or use parallel style instead of ahead or async
1588 -- style.
1589 limit <- case remainingWork sv of
1590 Nothing -> return workerLimit
1591 Just ref -> do
1592 n <- liftIO $ readIORef ref
1593 case yieldRateInfo sv of
1594 Just _ -> return workerLimit
1595 Nothing ->
1596 return $
1597 case workerLimit of
1598 Unlimited -> Limited (fromIntegral n)
1599 Limited lim -> Limited $ min lim (fromIntegral n)
1601 -- XXX for ahead streams shall we take the heap yields into account
1602 -- for controlling the dispatch? We should not dispatch if the heap
1603 -- has already got the limit covered.
1604 let dispatch = pushWorker yieldCount sv >> return True
1605 in case limit of
1606 Unlimited -> dispatch
1607 -- Note that the use of remainingWork and workerCount is not
1608 -- atomic and the counts may even have changed between reading
1609 -- and using them here, so this is just approximate logic and
1610 -- we cannot rely on it for correctness. We may actually
1611 -- dispatch more workers than required.
1612 Limited lim | lim > fromIntegral active -> dispatch
1613 _ -> return False
1614 else do
1615 when (active <= 0) $ pushWorker 0 sv
1616 return False
1617 else return False
1619-------------------------------------------------------------------------------
1620-- Dispatch workers with rate control
1621-------------------------------------------------------------------------------
1623-- | This is a magic number and it is overloaded, and used at several places to
1624-- achieve batching:
1626-- 1. If we have to sleep to slowdown this is the minimum period that we
1627-- accumulate before we sleep. Also, workers do not stop until this much
1628-- sleep time is accumulated.
1629-- 3. Collected latencies are computed and transferred to measured latency
1630-- after a minimum of this period.
1631minThreadDelay :: NanoSecond64
1632minThreadDelay = 1000000
1634-- | Another magic number! When we have to start more workers to cover up a
1635-- number of yields that we are lagging by then we cannot start one worker for
1636-- each yield because that may be a very big number and if the latency of the
1637-- workers is low these number of yields could be very high. We assume that we
1638-- run each extra worker for at least this much time.
1639rateRecoveryTime :: NanoSecond64
1640rateRecoveryTime = 1000000
1642-- We either block, or send one worker with limited yield count or one or more
1643-- workers with unlimited yield count.
1644data Work
1645 = BlockWait NanoSecond64
1646 | PartialWorker Count
1647 | ManyWorkers Int Count
1648 deriving Show
1650-- XXX we can use phantom types to distinguish the duration/latency/expectedLat
1651estimateWorkers
1652 :: Limit
1653 -> Count
1654 -> Count
1655 -> NanoSecond64
1656 -> NanoSecond64
1657 -> NanoSecond64
1658 -> LatencyRange
1659 -> Work
1660estimateWorkers workerLimit svarYields gainLossYields
1661 svarElapsed wLatency targetLat range =
1662 -- XXX we can have a maxEfficiency combinator as well which runs the
1663 -- producer at the maximal efficiency i.e. the number of workers are chosen
1664 -- such that the latency is minimum or within a range. Or we can call it
1665 -- maxWorkerLatency.
1666 --
1667 let
1668 -- How many workers do we need to acheive the required rate?
1669 --
1670 -- When the workers are IO bound we can increase the throughput by
1671 -- increasing the number of workers as long as the IO device has enough
1672 -- capacity to process all the requests concurrently. If the IO
1673 -- bandwidth is saturated increasing the workers won't help. Also, if
1674 -- the CPU utilization in processing all these requests exceeds the CPU
1675 -- bandwidth, then increasing the number of workers won't help.
1676 --
1677 -- When the workers are purely CPU bound, increasing the workers beyond
1678 -- the number of CPUs won't help.
1679 --
1680 -- TODO - measure the CPU and IO requirements of the workers. Have a
1681 -- way to specify the max bandwidth of the underlying IO mechanism and
1682 -- use that to determine the max rate of workers, and also take the CPU
1683 -- bandwidth into account. We can also discover the IO bandwidth if we
1684 -- know that we are not CPU bound, then how much steady state rate are
1685 -- we able to acheive. Design tests for CPU bound and IO bound cases.
1687 -- Calculate how many yields are we ahead or behind to match the exact
1688 -- required rate. Based on that we increase or decrease the effective
1689 -- workers.
1690 --
1691 -- When the worker latency is lower than required latency we begin with
1692 -- a yield and then wait rather than first waiting and then yielding.
1693 targetYields = (svarElapsed + wLatency + targetLat - 1) `div` targetLat
1694 effectiveYields = svarYields + gainLossYields
1695 deltaYields = fromIntegral targetYields - effectiveYields
1697 -- We recover the deficit by running at a higher/lower rate for a
1698 -- certain amount of time. To keep the effective rate in reasonable
1699 -- limits we use rateRecoveryTime, minLatency and maxLatency.
1700 in if deltaYields > 0
1701 then
1702 let deltaYieldsFreq :: Double
1703 deltaYieldsFreq =
1704 fromIntegral deltaYields /
1705 fromIntegral rateRecoveryTime
1706 yieldsFreq = 1.0 / fromIntegral targetLat
1707 totalYieldsFreq = yieldsFreq + deltaYieldsFreq
1708 requiredLat = NanoSecond64 $ round $ 1.0 / totalYieldsFreq
1709 adjustedLat = min (max requiredLat (minLatency range))
1710 (maxLatency range)
1711 in assert (adjustedLat > 0) $
1712 if wLatency <= adjustedLat
1713 then PartialWorker deltaYields
1714 else let workers = withLimit $ wLatency `div` adjustedLat
1715 limited = min workers (fromIntegral deltaYields)
1716 in ManyWorkers (fromIntegral limited) deltaYields
1717 else
1718 let expectedDuration = fromIntegral effectiveYields * targetLat
1719 sleepTime = expectedDuration - svarElapsed
1720 maxSleepTime = maxLatency range - wLatency
1721 s = min sleepTime maxSleepTime
1722 in assert (sleepTime >= 0) $
1723 -- if s is less than 0 it means our maxSleepTime is less
1724 -- than the worker latency.
1725 if s > 0 then BlockWait s else ManyWorkers 1 (Count 0)
1726 where
1727 withLimit n =
1728 case workerLimit of
1729 Unlimited -> n
1730 Limited x -> min n (fromIntegral x)
1732-- | Get the worker latency without resetting workerPendingLatency
1733-- Returns (total yield count, base time, measured latency)
1734-- CAUTION! keep it in sync with collectLatency
1735getWorkerLatency :: YieldRateInfo -> IO (Count, AbsTime, NanoSecond64)
1736getWorkerLatency yinfo = do
1737 let cur = workerPendingLatency yinfo
1738 col = workerCollectedLatency yinfo
1739 longTerm = svarAllTimeLatency yinfo
1740 measured = workerMeasuredLatency yinfo
1742 (curTotalCount, curCount, curTime) <- readIORef cur
1743 (colTotalCount, colCount, colTime) <- readIORef col
1744 (lcount, ltime) <- readIORef longTerm
1745 prevLat <- readIORef measured
1747 let latCount = colCount + curCount
1748 latTime = colTime + curTime
1749 totalCount = colTotalCount + curTotalCount
1750 newLat =
1751 if latCount > 0 && latTime > 0
1752 then let lat = latTime `div` fromIntegral latCount
1753 -- XXX Give more weight to new?
1754 in (lat + prevLat) `div` 2
1755 else prevLat
1756 return (lcount + totalCount, ltime, newLat)
1758isBeyondMaxRate :: SVar t m a -> YieldRateInfo -> IO Bool
1759isBeyondMaxRate sv yinfo = do
1760 (count, tstamp, wLatency) <- getWorkerLatency yinfo
1761 now <- getTime Monotonic
1762 let duration = fromRelTime64 $ diffAbsTime64 now tstamp
1763 let targetLat = svarLatencyTarget yinfo
1764 gainLoss <- readIORef (svarGainedLostYields yinfo)
1765 let work = estimateWorkers (maxWorkerLimit sv) count gainLoss duration
1766 wLatency targetLat (svarLatencyRange yinfo)
1767 cnt <- readIORef $ workerCount sv
1768 return $ case work of
1769 -- XXX set the worker's maxYields or polling interval based on yields
1770 PartialWorker _yields -> cnt > 1
1771 ManyWorkers n _ -> cnt > n
1772 BlockWait _ -> True
1774-- XXX in case of ahead style stream we need to take the heap size into account
1775-- because we return the workers on the basis of that which causes a condition
1776-- where we keep dispatching and they keep returning. So we must have exactly
1777-- the same logic for not dispatching and for returning.
1779-- Returns:
1780-- True: can dispatch more
1781-- False: full, no more dispatches
1782dispatchWorkerPaced :: MonadAsync m => SVar t m a -> m Bool
1783dispatchWorkerPaced sv = do
1784 let yinfo = fromJust $ yieldRateInfo sv
1785 (svarYields, svarElapsed, wLatency) <- do
1786 now <- liftIO $ getTime Monotonic
1787 (yieldCount, baseTime, lat) <-
1788 liftIO $ collectLatency sv yinfo False
1789 let elapsed = fromRelTime64 $ diffAbsTime64 now baseTime
1790 let latency =
1791 if lat == 0
1792 then
1793 case workerBootstrapLatency yinfo of
1794 Nothing -> lat
1795 Just t -> t
1796 else lat
1798 return (yieldCount, elapsed, latency)
1800 if wLatency == 0
1801 -- Need to measure the latency with a single worker before we can perform
1802 -- any computation.
1803 then return False
1804 else do
1805 let workerLimit = maxWorkerLimit sv
1806 let targetLat = svarLatencyTarget yinfo
1807 let range = svarLatencyRange yinfo
1808 gainLoss <- liftIO $ readIORef (svarGainedLostYields yinfo)
1809 let work = estimateWorkers workerLimit svarYields gainLoss svarElapsed
1810 wLatency targetLat range
1812 -- XXX we need to take yieldLimit into account here. If we are at the
1813 -- end of the limit as well as the time, we should not be sleeping.
1814 -- If we are not actually planning to dispatch any more workers we need
1815 -- to take that in account.
1816 case work of
1817 BlockWait s -> do
1818 assert (s >= 0) (return ())
1819 -- XXX note that when we return from here we will block waiting
1820 -- for the result from the existing worker. If that takes too
1821 -- long we won't be able to send another worker until the
1822 -- result arrives.
1823 --
1824 -- Sleep only if there are no active workers, otherwise we will
1825 -- defer the output of those. Note we cannot use workerCount
1826 -- here as it is not a reliable way to ensure there are
1827 -- definitely no active workers. When workerCount is 0 we may
1828 -- still have a Stop event waiting in the outputQueue.
1829 done <- allThreadsDone sv
1830 when done $ void $ do
1831 let us = fromRelTime64 (toRelTime64 s) :: MicroSecond64
1832 liftIO $ threadDelay (fromIntegral us)
1833 dispatchWorker 1 sv
1834 return False
1835 PartialWorker yields -> do
1836 assert (yields > 0) (return ())
1837 updateGainedLostYields yinfo yields
1839 done <- allThreadsDone sv
1840 when done $ void $ dispatchWorker yields sv
1841 return False
1842 ManyWorkers netWorkers yields -> do
1843 assert (netWorkers >= 1) (return ())
1844 assert (yields >= 0) (return ())
1845 updateGainedLostYields yinfo yields
1847 let periodRef = workerPollingInterval yinfo
1848 ycnt = max 1 $ yields `div` fromIntegral netWorkers
1849 period = min ycnt (fromIntegral magicMaxBuffer)
1851 old <- liftIO $ readIORef periodRef
1852 when (period < old) $
1853 liftIO $ writeIORef periodRef period
1855 cnt <- liftIO $ readIORef $ workerCount sv
1856 if cnt < netWorkers
1857 then do
1858 let total = netWorkers - cnt
1859 batch = max 1 $ fromIntegral $
1860 minThreadDelay `div` targetLat
1861 -- XXX stagger the workers over a period?
1862 -- XXX cannot sleep, as that would mean we cannot process
1863 -- the outputs. need to try a different mechanism to
1864 -- stagger.
1865 -- when (total > batch) $
1866 -- liftIO $ threadDelay $ nanoToMicroSecs minThreadDelay
1867 dispatchN (min total batch)
1868 else return False
1870 where
1872 updateGainedLostYields yinfo yields = do
1873 let buf = fromIntegral $ svarRateBuffer yinfo
1874 when (yields /= 0 && abs yields > buf) $ do
1875 let delta =
1876 if yields > 0
1877 then yields - buf
1878 else yields + buf
1879 liftIO $ modifyIORef (svarGainedLostYields yinfo) (+ delta)
1881 dispatchN n =
1882 if n == 0
1883 then return True
1884 else do
1885 r <- dispatchWorker 0 sv
1886 if r
1887 then dispatchN (n - 1)
1888 else return False
1890-------------------------------------------------------------------------------
1891-- Worker dispatch and wait loop
1892-------------------------------------------------------------------------------
1894sendWorkerDelayPaced :: SVar t m a -> IO ()
1895sendWorkerDelayPaced _ = return ()
1897sendWorkerDelay :: SVar t m a -> IO ()
1898sendWorkerDelay _sv =
1899 -- XXX we need a better way to handle this than hardcoded delays. The
1900 -- delays may be different for different systems.
1901 -- If there is a usecase where this is required we can create a combinator
1902 -- to set it as a config in the state.
1903 {-
1904 do
1905 ncpu <- getNumCapabilities
1906 if ncpu <= 1
1907 then
1908 if (svarStyle sv == AheadVar)
1909 then threadDelay 100
1910 else threadDelay 25
1911 else
1912 if (svarStyle sv == AheadVar)
1913 then threadDelay 100
1914 else threadDelay 10
1915 -}
1916 return ()
1918{-# NOINLINE sendWorkerWait #-}
1919sendWorkerWait
1920 :: MonadAsync m
1921 => (SVar t m a -> IO ())
1922 -> (SVar t m a -> m Bool)
1923 -> SVar t m a
1924 -> m ()
1925sendWorkerWait delay dispatch sv = do
1926 -- Note that we are guaranteed to have at least one outstanding worker when
1927 -- we enter this function. So if we sleep we are guaranteed to be woken up
1928 -- by an outputDoorBell, when the worker exits.
1930 liftIO $ delay sv
1931 (_, n) <- liftIO $ readIORef (outputQueue sv)
1932 when (n <= 0) $ do
1933 -- The queue may be empty temporarily if the worker has dequeued the
1934 -- work item but has not enqueued the remaining part yet. For the same
1935 -- reason, a worker may come back if it tries to dequeue and finds the
1936 -- queue empty, even though the whole work has not finished yet.
1938 -- If we find that the queue is empty, but it may be empty
1939 -- temporarily, when we checked it. If that's the case we might
1940 -- sleep indefinitely unless the active workers produce some
1941 -- output. We may deadlock specially if the otuput from the active
1942 -- workers depends on the future workers that we may never send.
1943 -- So in case the queue was temporarily empty set a flag to inform
1944 -- the enqueue to send us a doorbell.
1946 -- Note that this is just a best effort mechanism to avoid a
1947 -- deadlock. Deadlocks may still happen if for some weird reason
1948 -- the consuming computation shares an MVar or some other resource
1949 -- with the producing computation and gets blocked on that resource
1950 -- and therefore cannot do any pushworker to add more threads to
1951 -- the producer. In such cases the programmer should use a parallel
1952 -- style so that all the producers are scheduled immediately and
1953 -- unconditionally. We can also use a separate monitor thread to
1954 -- push workers instead of pushing them from the consumer, but then
1955 -- we are no longer using pull based concurrency rate adaptation.
1956 --
1957 -- XXX update this in the tutorial.
1958 --
1959 -- Having pending active workers does not mean that we are guaranteed
1960 -- to be woken up if we sleep. In case of Ahead streams, there may be
1961 -- queued items in the heap even though the outputQueue is empty, and
1962 -- we may have active workers which are deadlocked on those items to be
1963 -- processed by the consumer. We should either guarantee that any
1964 -- worker, before returning, clears the heap or we send a worker to
1965 -- clear it. Normally we always send a worker if no output is seen, but
1966 -- if the thread limit is reached or we are using pacing then we may
1967 -- not send a worker. See the concurrentApplication test in the tests,
1968 -- that test case requires at least one yield from the producer to not
1969 -- deadlock, if the last workers output is stuck in the heap then this
1970 -- test fails. This problem can be extended to n threads when the
1971 -- consumer may depend on the evaluation of next n items in the
1972 -- producer stream.
1974 -- register for the outputDoorBell before we check the queue so that if
1975 -- we sleep because the queue was empty we are guaranteed to get a
1976 -- doorbell on the next enqueue.
1978 liftIO $ atomicModifyIORefCAS_ (needDoorBell sv) $ const True
1979 liftIO storeLoadBarrier
1980 canDoMore <- dispatch sv
1982 -- XXX test for the case when we miss sending a worker when the worker
1983 -- count is more than 1500.
1984 --
1985 -- XXX Assert here that if the heap is not empty then there is at
1986 -- least one outstanding worker. Otherwise we could be sleeping
1987 -- forever.
1989 if canDoMore
1990 then sendWorkerWait delay dispatch sv
1991 else do
1992 liftIO $ withDiagMVar sv "sendWorkerWait: nothing to do"
1993 $ takeMVar (outputDoorBell sv)
1994 (_, len) <- liftIO $ readIORef (outputQueue sv)
1995 when (len <= 0) $ sendWorkerWait delay dispatch sv
1997-------------------------------------------------------------------------------
1998-- Reading from the workers' output queue/buffer
1999-------------------------------------------------------------------------------
2001{-# INLINE readOutputQRaw #-}
2002readOutputQRaw :: SVar t m a -> IO ([ChildEvent a], Int)
2003readOutputQRaw sv = do
2004 (list, len) <- atomicModifyIORefCAS (outputQueue sv) $ \x -> (([],0), x)
2005 when (svarInspectMode sv) $ do
2006 let ref = maxOutQSize $ svarStats sv
2007 oqLen <- readIORef ref
2008 when (len > oqLen) $ writeIORef ref len
2009 return (list, len)
2011readOutputQBounded :: MonadAsync m => SVar t m a -> m [ChildEvent a]
2012readOutputQBounded sv = do
2013 (list, len) <- liftIO $ readOutputQRaw sv
2014 -- When there is no output seen we dispatch more workers to help
2015 -- out if there is work pending in the work queue.
2016 if len <= 0
2017 then blockingRead
2018 else do
2019 -- send a worker proactively, if needed, even before we start
2020 -- processing the output. This may degrade single processor
2021 -- perf but improves multi-processor, because of more
2022 -- parallelism
2023 sendOneWorker
2024 return list
2026 where
2028 sendOneWorker = do
2029 cnt <- liftIO $ readIORef $ workerCount sv
2030 when (cnt <= 0) $ do
2031 done <- liftIO $ isWorkDone sv
2032 when (not done) (pushWorker 0 sv)
2034 {-# INLINE blockingRead #-}
2035 blockingRead = do
2036 sendWorkerWait sendWorkerDelay (dispatchWorker 0) sv
2037 liftIO (fst `fmap` readOutputQRaw sv)
2039readOutputQPaced :: MonadAsync m => SVar t m a -> m [ChildEvent a]
2040readOutputQPaced sv = do
2041 (list, len) <- liftIO $ readOutputQRaw sv
2042 if len <= 0
2043 then blockingRead
2044 else do
2045 -- XXX send a worker proactively, if needed, even before we start
2046 -- processing the output.
2047 void $ dispatchWorkerPaced sv
2048 return list
2050 where
2052 {-# INLINE blockingRead #-}
2053 blockingRead = do
2054 sendWorkerWait sendWorkerDelayPaced dispatchWorkerPaced sv
2055 liftIO (fst `fmap` readOutputQRaw sv)
2057postProcessBounded :: MonadAsync m => SVar t m a -> m Bool
2058postProcessBounded sv = do
2059 workersDone <- allThreadsDone sv
2060 -- There may still be work pending even if there are no workers pending
2061 -- because all the workers may return if the outputQueue becomes full. In
2062 -- that case send off a worker to kickstart the work again.
2063 --
2064 -- Note that isWorkDone can only be safely checked if all workers are done.
2065 -- When some workers are in progress they may have decremented the yield
2066 -- Limit and later ending up incrementing it again. If we look at the yield
2067 -- limit in that window we may falsely say that it is 0 and therefore we
2068 -- are done.
2069 if workersDone
2070 then do
2071 r <- liftIO $ isWorkDone sv
2072 -- Note that we need to guarantee a worker, therefore we cannot just
2073 -- use dispatchWorker which may or may not send a worker.
2074 when (not r) (pushWorker 0 sv)
2075 -- XXX do we need to dispatch many here?
2076 -- void $ dispatchWorker sv
2077 return r
2078 else return False
2080postProcessPaced :: MonadAsync m => SVar t m a -> m Bool
2081postProcessPaced sv = do
2082 workersDone <- allThreadsDone sv
2083 -- XXX If during consumption we figure out we are getting delayed then we
2084 -- should trigger dispatch there as well. We should try to check on the
2085 -- workers after consuming every n item from the buffer?
2086 if workersDone
2087 then do
2088 r <- liftIO $ isWorkDone sv
2089 when (not r) $ do
2090 void $ dispatchWorkerPaced sv
2091 -- Note that we need to guarantee a worker since the work is not
2092 -- finished, therefore we cannot just rely on dispatchWorkerPaced
2093 -- which may or may not send a worker.
2094 noWorker <- allThreadsDone sv
2095 when noWorker $ pushWorker 0 sv
2096 return r
2097 else return False
2099-------------------------------------------------------------------------------
2100-- Creating an SVar
2101-------------------------------------------------------------------------------
2103getYieldRateInfo :: State t m a -> IO (Maybe YieldRateInfo)
2104getYieldRateInfo st = do
2105 -- convert rate in Hertz to latency in Nanoseconds
2106 let rateToLatency r = if r <= 0 then maxBound else round $ 1.0e9 / r
2107 case getStreamRate st of
2108 Just (Rate low goal high buf) ->
2109 let l = rateToLatency goal
2110 minl = rateToLatency high
2111 maxl = rateToLatency low
2112 in mkYieldRateInfo l (LatencyRange minl maxl) buf
2113 Nothing -> return Nothing
2115 where
2117 mkYieldRateInfo latency latRange buf = do
2118 measured <- newIORef 0
2119 wcur <- newIORef (0,0,0)
2120 wcol <- newIORef (0,0,0)
2121 now <- getTime Monotonic
2122 wlong <- newIORef (0,now)
2123 period <- newIORef 1
2124 gainLoss <- newIORef (Count 0)
2126 return $ Just YieldRateInfo
2127 { svarLatencyTarget = latency
2128 , svarLatencyRange = latRange
2129 , svarRateBuffer = buf
2130 , svarGainedLostYields = gainLoss
2131 , workerBootstrapLatency = getStreamLatency st
2132 , workerPollingInterval = period
2133 , workerMeasuredLatency = measured
2134 , workerPendingLatency = wcur
2135 , workerCollectedLatency = wcol
2136 , svarAllTimeLatency = wlong
2139newSVarStats :: IO SVarStats
2140newSVarStats = do
2141 disp <- newIORef 0
2142 maxWrk <- newIORef 0
2143 maxOq <- newIORef 0
2144 maxHs <- newIORef 0
2145 maxWq <- newIORef 0
2146 avgLat <- newIORef (0, NanoSecond64 0)
2147 maxLat <- newIORef (NanoSecond64 0)
2148 minLat <- newIORef (NanoSecond64 0)
2149 stpTime <- newIORef Nothing
2151 return SVarStats
2152 { totalDispatches = disp
2153 , maxWorkers = maxWrk
2154 , maxOutQSize = maxOq
2155 , maxHeapSize = maxHs
2156 , maxWorkQSize = maxWq
2157 , avgWorkerLatency = avgLat
2158 , minWorkerLatency = minLat
2159 , maxWorkerLatency = maxLat
2160 , svarStopTime = stpTime
2163-- XXX remove polymorphism in t, inline f
2164getAheadSVar :: MonadAsync m
2165 => State t m a
2166 -> ( IORef ([t m a], Int)
2167 -> IORef (Heap (Entry Int (AheadHeapEntry t m a)), Maybe Int)
2168 -> State t m a
2169 -> SVar t m a
2170 -> Maybe WorkerInfo
2171 -> m ())
2172 -> RunInIO m
2173 -> IO (SVar t m a)
2174getAheadSVar st f mrun = do
2175 outQ <- newIORef ([], 0)
2176 -- the second component of the tuple is "Nothing" when heap is being
2177 -- cleared, "Just n" when we are expecting sequence number n to arrive
2178 -- before we can start clearing the heap.
2179 outH <- newIORef (H.empty, Just 0)
2180 outQMv <- newEmptyMVar
2181 active <- newIORef 0
2182 wfw <- newIORef False
2183 running <- newIORef S.empty
2184 -- Sequence number is incremented whenever something is queued, therefore,
2185 -- first sequence number would be 0
2186 q <- newIORef ([], -1)
2187 stopMVar <- newMVar ()
2188 yl <- case getYieldLimit st of
2189 Nothing -> return Nothing
2190 Just x -> Just <$> newIORef x
2191 rateInfo <- getYieldRateInfo st
2193 stats <- newSVarStats
2194 tid <- myThreadId
2196 let getSVar sv readOutput postProc = SVar
2197 { outputQueue = outQ
2198 , remainingWork = yl
2199 , maxBufferLimit = getMaxBuffer st
2200 , pushBufferSpace = undefined
2201 , pushBufferPolicy = undefined
2202 , pushBufferMVar = undefined
2203 , maxWorkerLimit = min (getMaxThreads st) (getMaxBuffer st)
2204 , yieldRateInfo = rateInfo
2205 , outputDoorBell = outQMv
2206 , readOutputQ = readOutput sv
2207 , postProcess = postProc sv
2208 , workerThreads = running
2209 , workLoop = f q outH st{streamVar = Just sv} sv
2210 , enqueue = enqueueAhead sv q
2211 , isWorkDone = isWorkDoneAhead sv q outH
2212 , isQueueDone = isQueueDoneAhead sv q
2213 , needDoorBell = wfw
2214 , svarStyle = AheadVar
2215 , svarStopStyle = StopNone
2216 , svarStopBy = undefined
2217 , svarMrun = mrun
2218 , workerCount = active
2219 , accountThread = delThread sv
2220 , workerStopMVar = stopMVar
2221 , svarRef = Nothing
2222 , svarInspectMode = getInspectMode st
2223 , svarCreator = tid
2224 , aheadWorkQueue = q
2225 , outputHeap = outH
2226 , svarStats = stats
2229 let sv =
2230 case getStreamRate st of
2231 Nothing -> getSVar sv readOutputQBounded postProcessBounded
2232 Just _ -> getSVar sv readOutputQPaced postProcessPaced
2233 in return sv
2235 where
2237 {-# INLINE isQueueDoneAhead #-}
2238 isQueueDoneAhead sv q = do
2239 queueDone <- checkEmpty q
2240 yieldsDone <-
2241 case remainingWork sv of
2242 Just yref -> do
2243 n <- readIORef yref
2244 return (n <= 0)
2245 Nothing -> return False
2246 -- XXX note that yieldsDone can only be authoritative only when there
2247 -- are no workers running. If there are active workers they can
2248 -- later increment the yield count and therefore change the result.
2249 return $ yieldsDone || queueDone
2251 {-# INLINE isWorkDoneAhead #-}
2252 isWorkDoneAhead sv q ref = do
2253 heapDone <- do
2254 (hp, _) <- readIORef ref
2255 return (H.size hp <= 0)
2256 queueDone <- isQueueDoneAhead sv q
2257 return $ heapDone && queueDone
2259 checkEmpty q = do
2260 (xs, _) <- readIORef q
2261 return $ null xs
2263getParallelSVar :: MonadIO m
2264 => SVarStopStyle -> State t m a -> RunInIO m -> IO (SVar t m a)
2265getParallelSVar ss st mrun = do
2266 outQ <- newIORef ([], 0)
2267 outQMv <- newEmptyMVar
2268 active <- newIORef 0
2269 running <- newIORef S.empty
2270 yl <- case getYieldLimit st of
2271 Nothing -> return Nothing
2272 Just x -> Just <$> newIORef x
2273 rateInfo <- getYieldRateInfo st
2274 let bufLim =
2275 case getMaxBuffer st of
2276 Unlimited -> undefined
2277 Limited x -> (fromIntegral x)
2278 remBuf <- newIORef bufLim
2279 pbMVar <- newMVar ()
2281 stats <- newSVarStats
2282 tid <- myThreadId
2284 stopBy <-
2285 case ss of
2286 StopBy -> liftIO $ newIORef undefined
2287 _ -> return undefined
2289 let sv =
2290 SVar { outputQueue = outQ
2291 , remainingWork = yl
2292 , maxBufferLimit = getMaxBuffer st
2293 , pushBufferSpace = remBuf
2294 , pushBufferPolicy = PushBufferBlock
2295 , pushBufferMVar = pbMVar
2296 , maxWorkerLimit = Unlimited
2297 -- Used only for diagnostics
2298 , yieldRateInfo = rateInfo
2299 , outputDoorBell = outQMv
2300 , readOutputQ = readOutputQPar sv
2301 , postProcess = allThreadsDone sv
2302 , workerThreads = running
2303 , workLoop = undefined
2304 , enqueue = undefined
2305 , isWorkDone = undefined
2306 , isQueueDone = undefined
2307 , needDoorBell = undefined
2308 , svarStyle = ParallelVar
2309 , svarStopStyle = ss
2310 , svarStopBy = stopBy
2311 , svarMrun = mrun
2312 , workerCount = active
2313 , accountThread = modifyThread sv
2314 , workerStopMVar = undefined
2315 , svarRef = Nothing
2316 , svarInspectMode = getInspectMode st
2317 , svarCreator = tid
2318 , aheadWorkQueue = undefined
2319 , outputHeap = undefined
2320 , svarStats = stats
2322 in return sv
2324 where
2326 readOutputQPar sv = liftIO $ do
2327 withDiagMVar sv "readOutputQPar: doorbell"
2328 $ takeMVar (outputDoorBell sv)
2329 case yieldRateInfo sv of
2330 Nothing -> return ()
2331 Just yinfo -> void $ collectLatency sv yinfo False
2332 r <- fst `fmap` readOutputQRaw sv
2333 liftIO $ do
2334 void $ tryTakeMVar (pushBufferMVar sv)
2335 resetBufferLimit sv
2336 writeBarrier
2337 void $ tryPutMVar (pushBufferMVar sv) ()
2338 return r
2340sendFirstWorker :: MonadAsync m => SVar t m a -> t m a -> m (SVar t m a)
2341sendFirstWorker sv m = do
2342 -- Note: We must have all the work on the queue before sending the
2343 -- pushworker, otherwise the pushworker may exit before we even get a
2344 -- chance to push.
2345 liftIO $ enqueue sv m
2346 case yieldRateInfo sv of
2347 Nothing -> pushWorker 0 sv
2348 Just yinfo ->
2349 if svarLatencyTarget yinfo == maxBound
2350 then liftIO $ threadDelay maxBound
2351 else pushWorker 1 sv
2352 return sv
2354{-# INLINABLE newAheadVar #-}
2355newAheadVar :: MonadAsync m
2356 => State t m a
2357 -> t m a
2358 -> ( IORef ([t m a], Int)
2359 -> IORef (Heap (Entry Int (AheadHeapEntry t m a)), Maybe Int)
2360 -> State t m a
2361 -> SVar t m a
2362 -> Maybe WorkerInfo
2363 -> m ())
2364 -> m (SVar t m a)
2365newAheadVar st m wloop = do
2366 mrun <- captureMonadState
2367 sv <- liftIO $ getAheadSVar st wloop mrun
2368 sendFirstWorker sv m
2370{-# INLINABLE newParallelVar #-}
2371newParallelVar :: MonadAsync m
2372 => SVarStopStyle -> State t m a -> m (SVar t m a)
2373newParallelVar ss st = do
2374 mrun <- captureMonadState
2375 liftIO $ getParallelSVar ss st mrun
2377-------------------------------------------------------------------------------
2378-- Write a stream to an SVar
2379-------------------------------------------------------------------------------
2381-- XXX this errors out for Parallel/Ahead SVars
2382-- | Write a stream to an 'SVar' in a non-blocking manner. The stream can then
2383-- be read back from the SVar using 'fromSVar'.
2384toStreamVar :: MonadAsync m => SVar t m a -> t m a -> m ()
2385toStreamVar sv m = do
2386 liftIO $ enqueue sv m
2387 done <- allThreadsDone sv
2388 -- XXX This is safe only when called from the consumer thread or when no
2389 -- consumer is present. There may be a race if we are not running in the
2390 -- consumer thread.
2391 -- XXX do this only if the work queue is not empty. The work may have been
2392 -- carried out by existing workers.
2393 when done $
2394 case yieldRateInfo sv of
2395 Nothing -> pushWorker 0 sv
2396 Just _ -> pushWorker 1 sv