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