on master help sign in
Refactor SVar module
a296facHarendra Kumar
benchmark/Streamly/Benchmark/Data/Stream/StreamK.hs+1−1
@@ -31,8 +31,8 @@ import Prelude hiding
31import qualified Prelude as P
31import qualified Prelude as P
32import qualified Data.List as List
32import qualified Data.List as List
33
33
34+import qualified Streamly.Internal.Control.Concurrent as S
34import qualified Streamly.Internal.Data.Stream.StreamK as S
35import qualified Streamly.Internal.Data.Stream.StreamK as S
35−import qualified Streamly.Internal.Data.SVar as S
36
36
37import Gauge (bench, nfIO, bgroup, Benchmark, defaultMain)
37import Gauge (bench, nfIO, bgroup, Benchmark, defaultMain)
38
38
src/Streamly.hs+2−1
@@ -210,7 +210,8 @@ module Streamly {-# DEPRECATED "Please use \"Streamly.Prelude\" instead." #-}
210where
210where
211
211
212import Data.Semigroup (Semigroup(..))
212import Data.Semigroup (Semigroup(..))
213−import Streamly.Internal.Data.SVar (MonadAsync, Rate(..))
213+import Streamly.Internal.Control.Concurrent (MonadAsync)
214+import Streamly.Internal.Data.SVar (Rate(..))
214import Streamly.Internal.Data.Stream.Ahead
215import Streamly.Internal.Data.Stream.Ahead
215import Streamly.Internal.Data.Stream.Async hiding (mkAsync)
216import Streamly.Internal.Data.Stream.Async hiding (mkAsync)
216import Streamly.Internal.Data.Stream.IsStream.Combinators
217import Streamly.Internal.Data.Stream.IsStream.Combinators
src/Streamly/Internal/Control/Concurrent.hs+11
@@ -12,6 +12,7 @@ module Streamly.Internal.Control.Concurrent
12 (
12 (
13 MonadAsync
13 MonadAsync
14 , RunInIO(..)
14 , RunInIO(..)
15+ , captureMonadState
15 , doFork
16 , doFork
16 , fork
17 , fork
17 , forkManaged
18 , forkManaged
@@ -43,6 +44,16 @@ type MonadAsync m = (MonadIO m, MonadBaseControl IO m, MonadThrow m)
43
44
44newtype RunInIO m = RunInIO { runInIO :: forall b. m b -> IO (StM m b) }
45newtype RunInIO m = RunInIO { runInIO :: forall b. m b -> IO (StM m b) }
45
46
47+-- | When we run computations concurrently, we completely isolate the state of
48+-- the concurrent computations from the parent computation. The invariant is
49+-- that we should never be running two concurrent computations in the same
50+-- thread without using the runInIO function. Also, we should never be running
51+-- a concurrent computation in the parent thread, otherwise it may affect the
52+-- state of the parent which is against the defined semantics of concurrent
53+-- execution.
54+captureMonadState :: MonadBaseControl IO m => m (RunInIO m)
55+captureMonadState = control $ \run -> run (return $ RunInIO run)
56+
46-- Stolen from the async package. The perf improvement is modest, 2% on a
57-- Stolen from the async package. The perf improvement is modest, 2% on a
47-- thread heavy benchmark (parallel composition using noop computations).
58-- thread heavy benchmark (parallel composition using noop computations).
48-- A version of forkIO that does not include the outer exception
59-- A version of forkIO that does not include the outer exception
src/Streamly/Internal/Data/Array/Foreign/Mut/Type.hs+1−1
@@ -119,7 +119,7 @@ import GHC.Ptr (Ptr(..))
119import Streamly.Internal.BaseCompat
119import Streamly.Internal.BaseCompat
120import Streamly.Internal.Data.Fold.Type (Fold(..))
120import Streamly.Internal.Data.Fold.Type (Fold(..))
121import Streamly.Internal.Data.Producer.Type (Producer (..))
121import Streamly.Internal.Data.Producer.Type (Producer (..))
122−import Streamly.Internal.Data.SVar (adaptState)
122+import Streamly.Internal.Data.SVar.Type (adaptState)
123import Streamly.Internal.Data.Unfold.Type (Unfold(..))
123import Streamly.Internal.Data.Unfold.Type (Unfold(..))
124
124
125import Streamly.Internal.System.IO
125import Streamly.Internal.System.IO
src/Streamly/Internal/Data/Fold/SVar.hsnew file+78
@@ -0,0 +1,78 @@
1+-- |
2+-- Module : Streamly.Internal.Data.Fold.SVar
3+-- Copyright : (c) 2017 Composewell Technologies
4+-- License : BSD-3-Clause
5+-- Maintainer : streamly@composewell.com
6+-- Stability : experimental
7+-- Portability : GHC
8+--
9+--
10+module Streamly.Internal.Data.Fold.SVar
11+ (
12+ write
13+ , writeLimited
14+ )
15+where
16+
17+#include "inline.hs"
18+
19+import Control.Monad (void)
20+import Control.Monad.IO.Class (MonadIO(liftIO))
21+import Streamly.Internal.Data.Fold.Type (Fold(..))
22+
23+import qualified Streamly.Internal.Data.Fold.Type as FL
24+
25+import Streamly.Internal.Data.SVar
26+
27+-- | A fold to write a stream to an SVar. Unlike 'toSVar' this does not allow
28+-- for concurrent evaluation of the stream, as the fold receives the input one
29+-- element at a time, it just forwards the elements to the SVar. However, we
30+-- can safely execute the fold in an independent thread, the SVar can act as a
31+-- buffer decoupling the sender from the receiver. Also, we can have multiple
32+-- folds running concurrently pusing the streams to the SVar.
33+--
34+{-# INLINE write #-}
35+write :: MonadIO m => SVar t m a -> Maybe WorkerInfo -> Fold m a ()
36+write svar winfo = Fold step initial extract
37+
38+ where
39+
40+ initial = return $ FL.Partial ()
41+
42+ -- XXX we can have a separate fold for unlimited buffer case to avoid a
43+ -- branch in the step here.
44+ step () x =
45+ liftIO $ do
46+ decrementBufferLimit svar
47+ void $ send svar (ChildYield x)
48+ return $ FL.Partial ()
49+
50+ extract () = liftIO $ sendStop svar winfo
51+
52+-- | Like write, but applies a yield limit.
53+--
54+{-# INLINE writeLimited #-}
55+writeLimited :: MonadIO m
56+ => SVar t m a -> Maybe WorkerInfo -> Fold m a ()
57+writeLimited svar winfo = Fold step initial extract
58+
59+ where
60+
61+ initial = return $ FL.Partial True
62+
63+ step True x =
64+ liftIO $ do
65+ yieldLimitOk <- decrementYieldLimit svar
66+ if yieldLimitOk
67+ then do
68+ decrementBufferLimit svar
69+ void $ send svar (ChildYield x)
70+ return $ FL.Partial True
71+ else do
72+ cleanupSVarFromWorker svar
73+ sendStop svar winfo
74+ return $ FL.Done ()
75+ step False _ = return $ FL.Done ()
76+
77+ extract True = liftIO $ sendStop svar winfo
78+ extract False = return ()
src/Streamly/Internal/Data/Fold/Type.hs+1−1
@@ -278,7 +278,7 @@ import Data.Maybe (isJust, fromJust)
278import Fusion.Plugin.Types (Fuse(..))
278import Fusion.Plugin.Types (Fuse(..))
279import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)
279import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)
280import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3'(..))
280import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3'(..))
281−import Streamly.Internal.Data.SVar (MonadAsync)
281+import Streamly.Internal.Control.Concurrent (MonadAsync)
282
282
283import Prelude hiding (concatMap, filter, foldr, map, take)
283import Prelude hiding (concatMap, filter, foldr, map, take)
284
284
src/Streamly/Internal/Data/IOFinalizer.hs+1−1
@@ -23,7 +23,7 @@ import Control.Monad (void)
23import Control.Monad.IO.Class (MonadIO(..))
23import Control.Monad.IO.Class (MonadIO(..))
24import Control.Monad.Trans.Control (MonadBaseControl, control)
24import Control.Monad.Trans.Control (MonadBaseControl, control)
25import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef, IORef)
25import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef, IORef)
26−import Streamly.Internal.Data.SVar (captureMonadState, runInIO)
26+import Streamly.Internal.Control.Concurrent (captureMonadState, runInIO)
27
27
28-- | An 'IOFinalizer' has an associated IO action that is automatically called
28-- | An 'IOFinalizer' has an associated IO action that is automatically called
29-- whenever the finalizer is garbage collected. The action can be run and
29-- whenever the finalizer is garbage collected. The action can be run and
src/Streamly/Internal/Data/Producer.hs+1−1
@@ -46,7 +46,7 @@ where
46
46
47import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))
47import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))
48import Streamly.Internal.Data.Stream.StreamD.Type (Stream(..))
48import Streamly.Internal.Data.Stream.StreamD.Type (Stream(..))
49−import Streamly.Internal.Data.SVar (defState)
49+import Streamly.Internal.Data.SVar.Type (defState)
50import Streamly.Internal.Data.Unfold.Type (Unfold(..))
50import Streamly.Internal.Data.Unfold.Type (Unfold(..))
51
51
52import Streamly.Internal.Data.Producer.Type
52import Streamly.Internal.Data.Producer.Type
src/Streamly/Internal/Data/SVar.hs+114−1987
@@ -1,69 +1,31 @@
1−{-# LANGUAGE UnboxedTuples #-}
2−
3-- |
1-- |
4-- Module : Streamly.Internal.Data.SVar
2-- Module : Streamly.Internal.Data.SVar
5-- Copyright : (c) 2017 Composewell Technologies
3-- Copyright : (c) 2017 Composewell Technologies
6−--
7−-- License : BSD3
4+-- License : BSD-3-Clause
8-- Maintainer : streamly@composewell.com
5-- Maintainer : streamly@composewell.com
9-- Stability : experimental
6-- Stability : experimental
10-- Portability : GHC
7-- Portability : GHC
11−
8+--
9+--
12module Streamly.Internal.Data.SVar
10module Streamly.Internal.Data.SVar
13 (
11 (
14− MonadAsync
12+ module Streamly.Internal.Data.SVar.Type
15− , SVarStyle (..)
13+ , module Streamly.Internal.Data.SVar.Worker
16− , SVarStopStyle (..)
14+ , module Streamly.Internal.Data.SVar.Dispatch
17− , SVar (..)
15+ , module Streamly.Internal.Data.SVar.Pull
18−
19− -- State threaded around the stream
20− , Limit (..)
21− , State (streamVar)
22− , defState
23− , adaptState
24− , getMaxThreads
25− , setMaxThreads
26− , getMaxBuffer
27− , setMaxBuffer
28− , getStreamRate
29− , setStreamRate
30− , setStreamLatency
31− , getYieldLimit
32− , setYieldLimit
33− , getInspectMode
34− , setInspectMode
35− , recordMaxWorkers
36
16
37− , cleanupSVar
17+ -- * New SVar
38− , cleanupSVarFromWorker
18+ , getYieldRateInfo
19+ , newSVarStats
39
20
40− -- SVar related
21+ -- ** Parallel
41− , newAheadVar
42 , newParallelVar
22 , newParallelVar
43− , captureMonadState
44− , RunInIO (..)
45
23
46− , WorkerInfo (..)
24+ -- ** Ahead
47− , YieldRateInfo (..)
48− , ThreadAbort (..)
49− , ChildEvent (..)
50− , AheadHeapEntry (..)
51− , send
52− , sendToProducer
53− , sendYield
54− , sendStop
55− , sendStopToProducer
56− , enqueueLIFO
57− , enqueueFIFO
58 , enqueueAhead
25 , enqueueAhead
59 , reEnqueueAhead
26 , reEnqueueAhead
60− , pushWorkerPar
61− , handleChildException
62− , handleFoldException
63−
64 , queueEmptyAhead
27 , queueEmptyAhead
65 , dequeueAhead
28 , dequeueAhead
66−
67 , HeapDequeueResult(..)
29 , HeapDequeueResult(..)
68 , dequeueFromHeap
30 , dequeueFromHeap
69 , dequeueFromHeapSeq
31 , dequeueFromHeapSeq
@@ -71,1166 +33,98 @@ module Streamly.Internal.Data.SVar
71 , updateHeapSeq
33 , updateHeapSeq
72 , withIORef
34 , withIORef
73 , heapIsSane
35 , heapIsSane
74−
36+ , newAheadVar
75− , Rate (..)
76− , getYieldRateInfo
77− , newSVarStats
78− , collectLatency
79− , workerUpdateLatency
80− , isBeyondMaxRate
81− , workerRateControl
82− , updateYieldCount
83− , decrementYieldLimit
84− , incrementYieldLimit
85− , decrementBufferLimit
86− , incrementBufferLimit
87− , postProcessBounded
88− , postProcessPaced
89− , readOutputQBounded
90− , readOutputQPaced
91− , readOutputQBasic
92− , dispatchWorkerPaced
93− , sendFirstWorker
94− , delThread
95− , modifyThread
96− , doFork
97− , fork
98− , forkManaged
99−
100− , toStreamVar
101− , SVarStats (..)
102− , dumpSVar
103− , printSVar
104− , withDiagMVar
105 )
37 )
106where
38where
107
39
108−import Control.Concurrent (ThreadId, myThreadId, threadDelay, throwTo)
40+#include "inline.hs"
109−import Control.Concurrent.MVar
110− (MVar, newEmptyMVar, tryPutMVar, takeMVar, tryTakeMVar, newMVar,
111− tryReadMVar)
112−import Control.Exception
113− (SomeException(..), assert, Exception, catches,
114− throwIO, Handler(..), BlockedIndefinitelyOnMVar(..),
115− BlockedIndefinitelyOnSTM(..))
116−import Control.Monad (when)
117−import Control.Monad.IO.Class (MonadIO(..))
118−import Control.Monad.Trans.Control (MonadBaseControl, control)
119−import Streamly.Internal.Data.Atomics
120− (atomicModifyIORefCAS, atomicModifyIORefCAS_, writeBarrier,
121− storeLoadBarrier)
122−import Data.Concurrent.Queue.MichaelScott (LinkedQueue, pushL)
123−import Data.Functor (void)
124−import Data.Heap (Heap, Entry(..))
125−import Data.Int (Int64)
126−import Data.IORef
127− (IORef, modifyIORef, newIORef, readIORef, writeIORef, atomicModifyIORef)
128−import Data.Kind (Type)
129−import Data.Maybe (fromJust, fromMaybe)
130−#if __GLASGOW_HASKELL__ < 808
131−import Data.Semigroup ((<>))
132−#endif
133−import Data.Set (Set)
134−import System.IO (hPutStrLn, stderr)
135
41
42+import Control.Concurrent (myThreadId, takeMVar)
43+import Control.Concurrent.MVar (newEmptyMVar, tryPutMVar, tryTakeMVar, newMVar)
44+import Control.Exception (assert)
45+import Control.Monad (void)
46+import Control.Monad.IO.Class (MonadIO(liftIO))
47+import Data.Heap (Heap, Entry(..))
48+import Data.IORef (newIORef, readIORef)
49+import Data.IORef (IORef, atomicModifyIORef)
136import Streamly.Internal.Control.Concurrent
50import Streamly.Internal.Control.Concurrent
137− (MonadAsync, RunInIO(..), doFork, fork, forkManaged)
51+ (MonadAsync, captureMonadState, RunInIO(..))
52+import Streamly.Internal.Data.Atomics
53+ (atomicModifyIORefCAS, atomicModifyIORefCAS_, writeBarrier)
138−import Streamly.Internal.Data.Time.Clock.Type (Clock(..), getTime)
54+import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
139−import Streamly.Internal.Data.Time.Units
55+import Streamly.Internal.Data.Time.Units (NanoSecond64(..))
140− (AbsTime, NanoSecond64(..), MicroSecond64(..), diffAbsTime64,
141− fromRelTime64, toRelTime64, showNanoSecond64, showRelTime64)
142
56
143import qualified Data.Heap as H
57import qualified Data.Heap as H
144−import qualified Data.Set as S
58+import qualified Data.Set as S
145−
146−newtype Count = Count Int64
147− deriving ( Eq
148− , Read
149− , Show
150− , Enum
151− , Bounded
152− , Num
153− , Real
154− , Integral
155− , Ord
156− )
157
59
158−------------------------------------------------------------------------------
60+import Streamly.Internal.Data.SVar.Dispatch
159−-- Parent child thread communication type
61+import Streamly.Internal.Data.SVar.Pull
160−------------------------------------------------------------------------------
62+import Streamly.Internal.Data.SVar.Type
63+import Streamly.Internal.Data.SVar.Worker
161
64
162−data ThreadAbort = ThreadAbort deriving Show
163−
164−instance Exception ThreadAbort
165−
166−-- | Events that a child thread may send to a parent thread.
167−data ChildEvent a =
168− ChildYield a
169− | ChildStop ThreadId (Maybe SomeException)
170−
171−-- | Sorting out-of-turn outputs in a heap for Ahead style streams
172−data AheadHeapEntry (t :: (Type -> Type) -> Type -> Type) m a =
173− AheadEntryNull
174− | AheadEntryPure a
175− | AheadEntryStream (RunInIO m, t m a)
176−#undef Type
177−
178−------------------------------------------------------------------------------
179−-- State threaded around the monad for thread management
180−------------------------------------------------------------------------------
181−
182−-- | Identify the type of the SVar. Two computations using the same style can
183−-- be scheduled on the same SVar.
184−data SVarStyle =
185− AsyncVar -- depth first concurrent
186− | WAsyncVar -- breadth first concurrent
187− | ParallelVar -- all parallel
188− | AheadVar -- Concurrent look ahead
189− deriving (Eq, Show)
190−
191−-- | An SVar or a Stream Var is a conduit to the output from multiple streams
192−-- running concurrently and asynchronously. An SVar can be thought of as an
193−-- asynchronous IO handle. We can write any number of streams to an SVar in a
194−-- non-blocking manner and then read them back at any time at any pace. The
195−-- SVar would run the streams asynchronously and accumulate results. An SVar
196−-- may not really execute the stream completely and accumulate all the results.
197−-- However, it ensures that the reader can read the results at whatever paces
198−-- it wants to read. The SVar monitors and adapts to the consumer's pace.
199−--
200−-- An SVar is a mini scheduler, it has an associated workLoop that holds the
201−-- stream tasks to be picked and run by a pool of worker threads. It has an
202−-- associated output queue where the output stream elements are placed by the
203−-- worker threads. A outputDoorBell is used by the worker threads to intimate the
204−-- consumer thread about availability of new results in the output queue. More
205−-- workers are added to the SVar by 'fromStreamVar' on demand if the output
206−-- produced is not keeping pace with the consumer. On bounded SVars, workers
207−-- block on the output queue to provide throttling of the producer when the
208−-- consumer is not pulling fast enough. The number of workers may even get
209−-- reduced depending on the consuming pace.
210−--
211−-- New work is enqueued either at the time of creation of the SVar or as a
212−-- result of executing the parallel combinators i.e. '<|' and '<|>' when the
213−-- already enqueued computations get evaluated. See 'joinStreamVarAsync'.
214−
215−-- We measure the individual worker latencies to estimate the number of workers
216−-- needed or the amount of time we have to sleep between dispatches to achieve
217−-- a particular rate when controlled pace mode it used.
218−data WorkerInfo = WorkerInfo
219− { workerYieldMax :: Count -- 0 means unlimited
220− -- total number of yields by the worker till now
221− , workerYieldCount :: IORef Count
222− -- yieldCount at start, timestamp
223− , workerLatencyStart :: IORef (Count, AbsTime)
224− }
225−
226−-- | Specifies the stream yield rate in yields per second (@Hertz@).
227−-- We keep accumulating yield credits at 'rateGoal'. At any point of time we
228−-- allow only as many yields as we have accumulated as per 'rateGoal' since the
229−-- start of time. If the consumer or the producer is slower or faster, the
230−-- actual rate may fall behind or exceed 'rateGoal'. We try to recover the gap
231−-- between the two by increasing or decreasing the pull rate from the producer.
232−-- However, if the gap becomes more than 'rateBuffer' we try to recover only as
233−-- much as 'rateBuffer'.
234−--
235−-- 'rateLow' puts a bound on how low the instantaneous rate can go when
236−-- recovering the rate gap. In other words, it determines the maximum yield
237−-- latency. Similarly, 'rateHigh' puts a bound on how high the instantaneous
238−-- rate can go when recovering the rate gap. In other words, it determines the
239−-- minimum yield latency. We reduce the latency by increasing concurrency,
240−-- therefore we can say that it puts an upper bound on concurrency.
241−--
242−-- If the 'rateGoal' is 0 or negative the stream never yields a value.
243−-- If the 'rateBuffer' is 0 or negative we do not attempt to recover.
244−--
245−-- /Since: 0.5.0 ("Streamly")/
246−--
247−-- @since 0.8.0
248−data Rate = Rate
249− { rateLow :: Double -- ^ The lower rate limit
250− , rateGoal :: Double -- ^ The target rate we want to achieve
251− , rateHigh :: Double -- ^ The upper rate limit
252− , rateBuffer :: Int -- ^ Maximum slack from the goal
253− }
254−
255−data LatencyRange = LatencyRange
256− { minLatency :: NanoSecond64
257− , maxLatency :: NanoSecond64
258− } deriving Show
259−
260−-- Rate control.
261−data YieldRateInfo = YieldRateInfo
262− { svarLatencyTarget :: NanoSecond64
263− , svarLatencyRange :: LatencyRange
264− , svarRateBuffer :: Int
265−
266− -- [LOCKING] Unlocked access. Modified by the consumer thread and unsafely
267− -- read by the worker threads
268− , svarGainedLostYields :: IORef Count
269−
270− -- Actual latency/througput as seen from the consumer side, we count the
271− -- yields and the time it took to generates those yields. This is used to
272− -- increase or decrease the number of workers needed to achieve the desired
273− -- rate. The idle time of workers is adjusted in this, so that we only
274− -- account for the rate when the consumer actually demands data.
275− -- XXX interval latency is enough, we can move this under diagnostics build
276− -- [LOCKING] Unlocked access. Modified by the consumer thread and unsafely
277− -- read by the worker threads
278− , svarAllTimeLatency :: IORef (Count, AbsTime)
279−
280− -- XXX Worker latency specified by the user to be used before the first
281− -- actual measurement arrives. Not yet implemented
282− , workerBootstrapLatency :: Maybe NanoSecond64
283−
284− -- After how many yields the worker should update the latency information.
285− -- If the latency is high, this count is kept lower and vice-versa. XXX If
286− -- the latency suddenly becomes too high this count may remain too high for
287− -- long time, in such cases the consumer can change it.
288− -- 0 means no latency computation
289− -- XXX this is derivable from workerMeasuredLatency, can be removed.
290− -- [LOCKING] Unlocked access. Modified by the consumer thread and unsafely
291− -- read by the worker threads
292− , workerPollingInterval :: IORef Count
293−
294− -- This is in progress latency stats maintained by the workers which we
295− -- empty into workerCollectedLatency stats at certain intervals - whenever
296− -- we process the stream elements yielded in this period. The first count
297− -- is all yields, the second count is only those yields for which the
298− -- latency was measured to be non-zero (note that if the timer resolution
299− -- is low the measured latency may be zero e.g. on JS platform).
300− -- [LOCKING] Locked access. Modified by the consumer thread as well as
301− -- worker threads. Workers modify it periodically based on
302− -- workerPollingInterval and not on every yield to reduce the locking
303− -- overhead.
304− -- (allYieldCount, yieldCount, timeTaken)
305− , workerPendingLatency :: IORef (Count, Count, NanoSecond64)
306−
307− -- This is the second level stat which is an accmulation from
308− -- workerPendingLatency stats. We keep accumulating latencies in this
309− -- bucket until we have stats for a sufficient period and then we reset it
310− -- to start collecting for the next period and retain the computed average
311− -- latency for the last period in workerMeasuredLatency.
312− -- [LOCKING] Unlocked access. Modified by the consumer thread and unsafely
313− -- read by the worker threads
314− -- (allYieldCount, yieldCount, timeTaken)
315− , workerCollectedLatency :: IORef (Count, Count, NanoSecond64)
316−
317− -- Latency as measured by workers, aggregated for the last period.
318− -- [LOCKING] Unlocked access. Modified by the consumer thread and unsafely
319− -- read by the worker threads
320− , workerMeasuredLatency :: IORef NanoSecond64
321− }
322−
323−data SVarStats = SVarStats {
324− totalDispatches :: IORef Int
325− , maxWorkers :: IORef Int
326− , maxOutQSize :: IORef Int
327− , maxHeapSize :: IORef Int
328− , maxWorkQSize :: IORef Int
329− , avgWorkerLatency :: IORef (Count, NanoSecond64)
330− , minWorkerLatency :: IORef NanoSecond64
331− , maxWorkerLatency :: IORef NanoSecond64
332− , svarStopTime :: IORef (Maybe AbsTime)
333−}
334−
335−-- This is essentially a 'Maybe Word' type
336−data Limit = Unlimited | Limited Word deriving Show
337−
338−instance Eq Limit where
339− Unlimited == Unlimited = True
340− Unlimited == Limited _ = False
341− Limited _ == Unlimited = False
342− Limited x == Limited y = x == y
343−
344−instance Ord Limit where
345− Unlimited <= Unlimited = True
346− Unlimited <= Limited _ = False
347− Limited _ <= Unlimited = True
348− Limited x <= Limited y = x <= y
349−
350−-- When to stop the composed stream.
351−data SVarStopStyle =
352− StopNone -- stops only when all streams are finished
353− | StopAny -- stop when any stream finishes
354− | StopBy -- stop when a specific stream finishes
355− deriving (Eq, Show)
356−
357−-- | Buffering policy for persistent push workers (in ParallelT). In a pull
358−-- style SVar (in AsyncT, AheadT etc.), the consumer side dispatches workers on
359−-- demand, workers terminate if the buffer is full or if the consumer is not
360−-- cosuming fast enough. In a push style SVar, a worker is dispatched only
361−-- once, workers are persistent and keep pushing work to the consumer via a
362−-- bounded buffer. If the buffer becomes full the worker either blocks, or it
363−-- can drop an item from the buffer to make space.
364−--
365−-- Pull style SVars are useful in lazy stream evaluation whereas push style
366−-- SVars are useful in strict left Folds.
367−--
368−-- XXX Maybe we can separate the implementation in two different types instead
369−-- of using a common SVar type.
370−--
371−data PushBufferPolicy =
372− PushBufferDropNew -- drop the latest element and continue
373− | PushBufferDropOld -- drop the oldest element and continue
374− | PushBufferBlock -- block the thread until space
375− -- becomes available
376−
377−-- IMPORTANT NOTE: we cannot update the SVar after generating it as we have
378−-- references to the original SVar stored in several functions which will keep
379−-- pointing to the original data and the new updates won't reflect there.
380−-- Any updateable parts must be kept in mutable references (IORef).
381−--
382−data SVar t m a = SVar
383− {
384− -- Read only state
385− svarStyle :: SVarStyle
386− , svarMrun :: RunInIO m
387− , svarStopStyle :: SVarStopStyle
388− , svarStopBy :: IORef ThreadId
389−
390− -- Shared output queue (events, length)
391− -- XXX For better efficiency we can try a preallocated array type (perhaps
392− -- something like a vector) that allows an O(1) append. That way we will
393− -- avoid constructing and reversing the list. Possibly we can also avoid
394− -- the GC copying overhead. When the size increases we should be able to
395− -- allocate the array in chunks.
396− --
397− -- [LOCKING] Frequent locked access. This is updated by workers on each
398− -- yield and once in a while read by the consumer thread. This could have
399− -- big locking overhead if the number of workers is high.
400− --
401− -- XXX We can use a per-CPU data structure to reduce the locking overhead.
402− -- However, a per-cpu structure cannot guarantee the exact sequence in
403− -- which the elements were added, though that may not be important.
404− , outputQueue :: IORef ([ChildEvent a], Int)
405−
406− -- [LOCKING] Infrequent MVar. Used when the outputQ transitions from empty
407− -- to non-empty, or a work item is queued by a worker to the work queue and
408− -- needDoorBell is set by the consumer.
409− , outputDoorBell :: MVar () -- signal the consumer about output
410− , readOutputQ :: m [ChildEvent a]
411− , postProcess :: m Bool
412−
413− -- channel to send events from the consumer to the worker. Used to send
414− -- exceptions from a fold driver to the fold computation running as a
415− -- consumer thread in the concurrent fold cases. Currently only one event
416− -- is sent by the fold so we do not really need a queue for it.
417− , outputQueueFromConsumer :: IORef ([ChildEvent a], Int)
418− , outputDoorBellFromConsumer :: MVar ()
419−
420− -- Combined/aggregate parameters
421− -- This is truncated to maxBufferLimit if set to more than that. Otherwise
422− -- potentially each worker may yield one value to the buffer in the worst
423− -- case exceeding the requested buffer size.
424− , maxWorkerLimit :: Limit
425− , maxBufferLimit :: Limit
426− -- These two are valid and used only when maxBufferLimit is Limited.
427− , pushBufferSpace :: IORef Count
428− , pushBufferPolicy :: PushBufferPolicy
429− -- [LOCKING] The consumer puts this MVar after emptying the buffer, workers
430− -- block on it when the buffer becomes full. No overhead unless the buffer
431− -- becomes full.
432− , pushBufferMVar :: MVar ()
433−
434− -- [LOCKING] Read only access by consumer when dispatching a worker.
435− -- Decremented by workers when picking work and undo decrement if the
436− -- worker does not yield a value.
437− , remainingWork :: Maybe (IORef Count)
438− , yieldRateInfo :: Maybe YieldRateInfo
439−
440− -- Used only by bounded SVar types
441− , enqueue :: (RunInIO m, t m a) -> IO ()
442− , isWorkDone :: IO Bool
443− , isQueueDone :: IO Bool
444− , needDoorBell :: IORef Bool
445− , workLoop :: Maybe WorkerInfo -> m ()
446−
447− -- Shared, thread tracking
448− -- [LOCKING] Updated unlocked only by consumer thread in case of
449− -- Async/Ahead style SVars. Updated locked by worker threads in case of
450− -- Parallel style.
451− , workerThreads :: IORef (Set ThreadId)
452− -- [LOCKING] Updated locked by consumer thread when dispatching a worker
453− -- and by the worker threads when the thread stops. This is read unsafely
454− -- at several places where we want to rely on an approximate value.
455− , workerCount :: IORef Int
456− , accountThread :: ThreadId -> m ()
457− , workerStopMVar :: MVar ()
458−
459− , svarStats :: SVarStats
460− -- to track garbage collection of SVar
461− , svarRef :: Maybe (IORef ())
462−
463− -- Only for diagnostics
464− , svarInspectMode :: Bool
465− , svarCreator :: ThreadId
466− , outputHeap :: IORef ( Heap (Entry Int (AheadHeapEntry t m a))
467− , Maybe Int)
468− -- Shared work queue (stream, seqNo)
469− , aheadWorkQueue :: IORef ([t m a], Int)
470− }
471−
472−-------------------------------------------------------------------------------
473−-- State for concurrency control
474−-------------------------------------------------------------------------------
475−
476−-- XXX we can put the resettable fields in a oneShotConfig field and others in
477−-- a persistentConfig field. That way reset would be fast and scalable
478−-- irrespective of the number of fields.
479−--
480−-- XXX make all these Limited types and use phantom types to distinguish them
481−data State t m a = State
482− { -- one shot configuration, automatically reset for each API call
483− streamVar :: Maybe (SVar t m a)
484− , _yieldLimit :: Maybe Count
485−
486− -- persistent configuration, state that remains valid until changed by
487− -- an explicit setting via a combinator.
488− , _threadsHigh :: Limit
489− , _bufferHigh :: Limit
490− -- XXX these two can be collapsed into a single type
491− , _streamLatency :: Maybe NanoSecond64 -- bootstrap latency
492− , _maxStreamRate :: Maybe Rate
493− , _inspectMode :: Bool
494− }
495−
496−-------------------------------------------------------------------------------
497−-- State defaults and reset
498−-------------------------------------------------------------------------------
499−
500−-- A magical value for the buffer size arrived at by running the smallest
501−-- possible task and measuring the optimal value of the buffer for that. This
502−-- is obviously dependent on hardware, this figure is based on a 2.2GHz intel
503−-- core-i7 processor.
504−magicMaxBuffer :: Word
505−magicMaxBuffer = 1500
506−
507−defaultMaxThreads, defaultMaxBuffer :: Limit
508−defaultMaxThreads = Limited magicMaxBuffer
509−defaultMaxBuffer = Limited magicMaxBuffer
510−
511−-- The fields prefixed by an _ are not to be accessed or updated directly but
512−-- via smart accessor APIs.
513−defState :: State t m a
514−defState = State
515− { streamVar = Nothing
516− , _yieldLimit = Nothing
517− , _threadsHigh = defaultMaxThreads
518− , _bufferHigh = defaultMaxBuffer
519− , _maxStreamRate = Nothing
520− , _streamLatency = Nothing
521− , _inspectMode = False
522− }
523−
524−-- XXX if perf gets affected we can have all the Nothing params in a single
525−-- structure so that we reset is fast. We can also use rewrite rules such that
526−-- reset occurs only in concurrent streams to reduce the impact on serial
527−-- streams.
528−-- We can optimize this so that we clear it only if it is a Just value, it
529−-- results in slightly better perf for zip/zipM but the performance of scan
530−-- worsens a lot, it does not fuse.
531−--
532−-- XXX This has a side effect of clearing the SVar and yieldLimit, therefore it
533−-- should not be used to convert from the same type to the same type, unless
534−-- you want to clear the SVar. For clearing the SVar you should be using the
535−-- appropriate unStream functions instead.
536−--
537−-- | Adapt the stream state from one type to another.
538−adaptState :: State t m a -> State t n b
539−adaptState st = st
540− { streamVar = Nothing
541− , _yieldLimit = Nothing
542− }
543−
544−-------------------------------------------------------------------------------
545−-- Smart get/set routines for State
546−-------------------------------------------------------------------------------
547−
548−-- Use get/set routines instead of directly accessing the State fields
549−setYieldLimit :: Maybe Int64 -> State t m a -> State t m a
550−setYieldLimit lim st =
551− st { _yieldLimit =
552− case lim of
553− Nothing -> Nothing
554− Just n ->
555− if n <= 0
556− then Just 0
557− else Just (fromIntegral n)
558− }
559−
560−getYieldLimit :: State t m a -> Maybe Count
561−getYieldLimit = _yieldLimit
562−
563−setMaxThreads :: Int -> State t m a -> State t m a
564−setMaxThreads n st =
565− st { _threadsHigh =
566− if n < 0
567− then Unlimited
568− else if n == 0
569− then defaultMaxThreads
570− else Limited (fromIntegral n)
571− }
572−
573−getMaxThreads :: State t m a -> Limit
574−getMaxThreads = _threadsHigh
575−
576−setMaxBuffer :: Int -> State t m a -> State t m a
577−setMaxBuffer n st =
578− st { _bufferHigh =
579− if n < 0
580− then Unlimited
581− else if n == 0
582− then defaultMaxBuffer
583− else Limited (fromIntegral n)
584− }
585−
586−getMaxBuffer :: State t m a -> Limit
587−getMaxBuffer = _bufferHigh
588−
589−setStreamRate :: Maybe Rate -> State t m a -> State t m a
590−setStreamRate r st = st { _maxStreamRate = r }
591−
592−getStreamRate :: State t m a -> Maybe Rate
593−getStreamRate = _maxStreamRate
594−
595−setStreamLatency :: Int -> State t m a -> State t m a
596−setStreamLatency n st =
597− st { _streamLatency =
598− if n <= 0
599− then Nothing
600− else Just (fromIntegral n)
601− }
602−
603−getStreamLatency :: State t m a -> Maybe NanoSecond64
604−getStreamLatency = _streamLatency
605−
606−setInspectMode :: State t m a -> State t m a
607−setInspectMode st = st { _inspectMode = True }
608−
609−getInspectMode :: State t m a -> Bool
610−getInspectMode = _inspectMode
611−
612−-------------------------------------------------------------------------------
613−-- Cleanup
614-------------------------------------------------------------------------------
65-------------------------------------------------------------------------------
615−
66+-- Creating an SVar
616−cleanupSVar :: SVar t m a -> IO ()
617−cleanupSVar sv = do
618− workers <- readIORef (workerThreads sv)
619− Prelude.mapM_ (`throwTo` ThreadAbort)
620− workers
621−
622−cleanupSVarFromWorker :: SVar t m a -> IO ()
623−cleanupSVarFromWorker sv = do
624− workers <- readIORef (workerThreads sv)
625− self <- myThreadId
626− Prelude.mapM_ (`throwTo` ThreadAbort)
627− (Prelude.filter (/= self) $ S.toList workers)
628−
629−-------------------------------------------------------------------------------
630−-- Worker latency data collection
631−-------------------------------------------------------------------------------
632−
633−-- Every once in a while workers update the latencies and check the yield rate.
634−-- They return if we are above the expected yield rate. If we check too often
635−-- it may impact performance, if we check less often we may have a stale
636−-- picture. We update every minThreadDelay but we translate that into a yield
637−-- count based on latency so that the checking overhead is little.
638−--
639−-- XXX use a generation count to indicate that the value is updated. If the
640−-- value is updated an existing worker must check it again on the next yield.
641−-- Otherwise it is possible that we may keep updating it and because of the mod
642−-- worker keeps skipping it.
643−updateWorkerPollingInterval :: YieldRateInfo -> NanoSecond64 -> IO ()
644−updateWorkerPollingInterval yinfo latency = do
645− let periodRef = workerPollingInterval yinfo
646− cnt = max 1 $ minThreadDelay `div` latency
647− period = min cnt (fromIntegral magicMaxBuffer)
648−
649− writeIORef periodRef (fromIntegral period)
650−
651−{-# INLINE recordMinMaxLatency #-}
652−recordMinMaxLatency :: SVar t m a -> NanoSecond64 -> IO ()
653−recordMinMaxLatency sv new = do
654− let ss = svarStats sv
655− minLat <- readIORef (minWorkerLatency ss)
656− when (new < minLat || minLat == 0) $
657− writeIORef (minWorkerLatency ss) new
658−
659− maxLat <- readIORef (maxWorkerLatency ss)
660− when (new > maxLat) $ writeIORef (maxWorkerLatency ss) new
661−
662−recordAvgLatency :: SVar t m a -> (Count, NanoSecond64) -> IO ()
663−recordAvgLatency sv (count, time) = do
664− let ss = svarStats sv
665− modifyIORef (avgWorkerLatency ss) $
666− \(cnt, t) -> (cnt + count, t + time)
667−
668−-- Pour the pending latency stats into a collection bucket
669−{-# INLINE collectWorkerPendingLatency #-}
670−collectWorkerPendingLatency
671− :: IORef (Count, Count, NanoSecond64)
672− -> IORef (Count, Count, NanoSecond64)
673− -> IO (Count, Maybe (Count, NanoSecond64))
674−collectWorkerPendingLatency cur col = do
675− (fcount, count, time) <- atomicModifyIORefCAS cur $ \v -> ((0,0,0), v)
676−
677− (fcnt, cnt, t) <- readIORef col
678− let totalCount = fcnt + fcount
679− latCount = cnt + count
680− latTime = t + time
681− writeIORef col (totalCount, latCount, latTime)
682−
683− assert (latCount == 0 || latTime /= 0) (return ())
684− let latPair =
685− if latCount > 0 && latTime > 0
686− then Just (latCount, latTime)
687− else Nothing
688− return (totalCount, latPair)
689−
690−{-# INLINE shouldUseCollectedBatch #-}
691−shouldUseCollectedBatch
692− :: Count
693− -> NanoSecond64
694− -> NanoSecond64
695− -> NanoSecond64
696− -> Bool
697−shouldUseCollectedBatch collectedYields collectedTime newLat prevLat =
698− let r = fromIntegral newLat / fromIntegral prevLat :: Double
699− in (collectedYields > fromIntegral magicMaxBuffer)
700− || (collectedTime > minThreadDelay)
701− || (prevLat > 0 && (r > 2 || r < 0.5))
702− || (prevLat == 0)
703−
704−-- Returns a triple, (1) yield count since last collection, (2) the base time
705−-- when we started counting, (3) average latency in the last measurement
706−-- period. The former two are used for accurate measurement of the going rate
707−-- whereas the average is used for future estimates e.g. how many workers
708−-- should be maintained to maintain the rate.
709−-- CAUTION! keep it in sync with getWorkerLatency
710−collectLatency :: SVar t m a
711− -> YieldRateInfo
712− -> Bool
713− -> IO (Count, AbsTime, NanoSecond64)
714−collectLatency sv yinfo drain = do
715− let cur = workerPendingLatency yinfo
716− col = workerCollectedLatency yinfo
717− longTerm = svarAllTimeLatency yinfo
718− measured = workerMeasuredLatency yinfo
719−
720− (newCount, newLatPair) <- collectWorkerPendingLatency cur col
721− (lcount, ltime) <- readIORef longTerm
722− prevLat <- readIORef measured
723−
724− let newLcount = lcount + newCount
725− retWith lat = return (newLcount, ltime, lat)
726−
727− case newLatPair of
728− Nothing -> retWith prevLat
729− Just (count, time) -> do
730− let newLat = time `div` fromIntegral count
731− when (svarInspectMode sv) $ recordMinMaxLatency sv newLat
732− -- When we have collected a significant sized batch we compute the
733− -- new latency using that batch and return the new latency,
734− -- otherwise we return the previous latency derived from the
735− -- previous batch.
736− if shouldUseCollectedBatch newCount time newLat prevLat || drain
737− then do
738− -- XXX make this NOINLINE?
739− updateWorkerPollingInterval yinfo (max newLat prevLat)
740− when (svarInspectMode sv) $ recordAvgLatency sv (count, time)
741− writeIORef col (0, 0, 0)
742− writeIORef measured ((prevLat + newLat) `div` 2)
743− modifyIORef longTerm $ \(_, t) -> (newLcount, t)
744− retWith newLat
745− else retWith prevLat
746−
747−-------------------------------------------------------------------------------
748−-- Dumping the SVar for debug/diag
749-------------------------------------------------------------------------------
67-------------------------------------------------------------------------------
750
68
751−dumpSVarStats :: SVar t m a -> SVarStats -> SVarStyle -> IO String
69+getYieldRateInfo :: State t m a -> IO (Maybe YieldRateInfo)
752−dumpSVarStats sv ss style = do
70+getYieldRateInfo st = do
753− case yieldRateInfo sv of
71+ -- convert rate in Hertz to latency in Nanoseconds
754− Nothing -> return ()
72+ let rateToLatency r = if r <= 0 then maxBound else round $ 1.0e9 / r
755− Just yinfo -> do
73+ case getStreamRate st of
756− _ <- liftIO $ collectLatency sv yinfo True
74+ Just (Rate low goal high buf) ->
757− return ()
75+ let l = rateToLatency goal
758−
76+ minl = rateToLatency high
759− dispatches <- readIORef $ totalDispatches ss
77+ maxl = rateToLatency low
760− maxWrk <- readIORef $ maxWorkers ss
78+ in mkYieldRateInfo l (LatencyRange minl maxl) buf
761− maxOq <- readIORef $ maxOutQSize ss
79+ Nothing -> return Nothing
762− maxHp <- readIORef $ maxHeapSize ss
763− minLat <- readIORef $ minWorkerLatency ss
764− maxLat <- readIORef $ maxWorkerLatency ss
765− (avgCnt, avgTime) <- readIORef $ avgWorkerLatency ss
766− (svarCnt, svarGainLossCnt, svarLat) <- case yieldRateInfo sv of
767− Nothing -> return (0, 0, 0)
768− Just yinfo -> do
769− (cnt, startTime) <- readIORef $ svarAllTimeLatency yinfo
770− if cnt > 0
771− then do
772− t <- readIORef (svarStopTime ss)
773− gl <- readIORef (svarGainedLostYields yinfo)
774− case t of
775− Nothing -> do
776− now <- getTime Monotonic
777− let interval = diffAbsTime64 now startTime
778− return (cnt, gl, interval `div` fromIntegral cnt)
779− Just stopTime -> do
780− let interval = diffAbsTime64 stopTime startTime
781− return (cnt, gl, interval `div` fromIntegral cnt)
782− else return (0, 0, 0)
783−
784− return $ unlines
785− [ "total dispatches = " <> show dispatches
786− , "max workers = " <> show maxWrk
787− , "max outQSize = " <> show maxOq
788− <> (if style == AheadVar
789− then "\nheap max size = " <> show maxHp
790− else "")
791− <> (if minLat > 0
792− then "\nmin worker latency = " <> showNanoSecond64 minLat
793− else "")
794− <> (if maxLat > 0
795− then "\nmax worker latency = " <> showNanoSecond64 maxLat
796− else "")
797− <> (if avgCnt > 0
798− then let lat = avgTime `div` fromIntegral avgCnt
799− in "\navg worker latency = " <> showNanoSecond64 lat
800− else "")
801− <> (if svarLat > 0
802− then "\nSVar latency = " <> showRelTime64 svarLat
803− else "")
804− <> (if svarCnt > 0
805− then "\nSVar yield count = " <> show svarCnt
806− else "")
807− <> (if svarGainLossCnt > 0
808− then "\nSVar gain/loss yield count = " <> show svarGainLossCnt
809− else "")
810− ]
811−
812−{-# NOINLINE dumpSVar #-}
813−dumpSVar :: SVar t m a -> IO String
814−dumpSVar sv = do
815− (oqList, oqLen) <- readIORef $ outputQueue sv
816− db <- tryReadMVar $ outputDoorBell sv
817− aheadDump <-
818− if svarStyle sv == AheadVar
819− then do
820− (oheap, oheapSeq) <- readIORef $ outputHeap sv
821− (wq, wqSeq) <- readIORef $ aheadWorkQueue sv
822− return $ unlines
823− [ "heap length = " <> show (H.size oheap)
824− , "heap seqeunce = " <> show oheapSeq
825− , "work queue length = " <> show (length wq)
826− , "work queue sequence = " <> show wqSeq
827− ]
828− else return []
829−
830− let style = svarStyle sv
831− waiting <-
832− if style /= ParallelVar
833− then readIORef $ needDoorBell sv
834− else return False
835− rthread <- readIORef $ workerThreads sv
836− workers <- readIORef $ workerCount sv
837− stats <- dumpSVarStats sv (svarStats sv) (svarStyle sv)
838−
839− return $ unlines
840− [
841− "Creator tid = " <> show (svarCreator sv),
842− "style = " <> show (svarStyle sv)
843− , "---------CURRENT STATE-----------"
844− , "outputQueue length computed = " <> show (length oqList)
845− , "outputQueue length maintained = " <> show oqLen
846− -- XXX print the types of events in the outputQueue, first 5
847− , "outputDoorBell = " <> show db
848− ]
849− <> aheadDump
850− <> unlines
851− [ "needDoorBell = " <> show waiting
852− , "running threads = " <> show rthread
853− -- XXX print the status of first 5 threads
854− , "running thread count = " <> show workers
855− ]
856− <> "---------STATS-----------\n"
857− <> stats
858−
859−printSVar :: SVar t m a -> String -> IO ()
860−printSVar sv how = do
861− svInfo <- dumpSVar sv
862− hPutStrLn stderr $ "\n" <> how <> "\n" <> svInfo
863−
864−-- MVar diagnostics has some overhead - around 5% on AsyncT null benchmark, we
865−-- can keep it on in production to debug problems quickly if and when they
866−-- happen, but it may result in unexpected output when threads are left hanging
867−-- until they are GCed because the consumer went away.
868−
869−{-# NOINLINE mvarExcHandler #-}
870−mvarExcHandler :: SVar t m a -> String -> BlockedIndefinitelyOnMVar -> IO ()
871−mvarExcHandler sv label e@BlockedIndefinitelyOnMVar = do
872− svInfo <- dumpSVar sv
873− hPutStrLn stderr $ label <> " " <> "BlockedIndefinitelyOnMVar\n" <> svInfo
874− throwIO e
875−
876−{-# NOINLINE stmExcHandler #-}
877−stmExcHandler :: SVar t m a -> String -> BlockedIndefinitelyOnSTM -> IO ()
878−stmExcHandler sv label e@BlockedIndefinitelyOnSTM = do
879− svInfo <- dumpSVar sv
880− hPutStrLn stderr $ label <> " " <> "BlockedIndefinitelyOnSTM\n" <> svInfo
881− throwIO e
882−
883−withDiagMVar :: SVar t m a -> String -> IO () -> IO ()
884−withDiagMVar sv label action =
885− if svarInspectMode sv
886− then
887− action `catches` [ Handler (mvarExcHandler sv label)
888− , Handler (stmExcHandler sv label)
889− ]
890− else action
891−
892−------------------------------------------------------------------------------
893−-- Spawning threads
894−------------------------------------------------------------------------------
895−
896−-- | When we run computations concurrently, we completely isolate the state of
897−-- the concurrent computations from the parent computation. The invariant is
898−-- that we should never be running two concurrent computations in the same
899−-- thread without using the runInIO function. Also, we should never be running
900−-- a concurrent computation in the parent thread, otherwise it may affect the
901−-- state of the parent which is against the defined semantics of concurrent
902−-- execution.
903−captureMonadState :: MonadBaseControl IO m => m (RunInIO m)
904−captureMonadState = control $ \run -> run (return $ RunInIO run)
905−
906−------------------------------------------------------------------------------
907−-- Collecting results from child workers in a streamed fashion
908−------------------------------------------------------------------------------
909−
910−-- XXX Can we make access to remainingWork and yieldRateInfo fields in sv
911−-- faster, along with the fields in sv required by send?
912−-- XXX make it noinline
913−--
914−-- XXX we may want to employ an increment and decrement in batches when the
915−-- througput is high or when the cost of synchronization is high. For example
916−-- if the application is distributed then inc/dec of a shared variable may be
917−-- very costly.
918−--
919−-- A worker decrements the yield limit before it executes an action. However,
920−-- the action may not result in an element being yielded, in that case we have
921−-- to increment the yield limit.
922−--
923−-- Note that we need it to be an Int type so that we have the ability to undo a
924−-- decrement that takes it below zero.
925−{-# INLINE decrementYieldLimit #-}
926−decrementYieldLimit :: SVar t m a -> IO Bool
927−decrementYieldLimit sv =
928− case remainingWork sv of
929− Nothing -> return True
930− Just ref -> do
931− r <- atomicModifyIORefCAS ref $ \x -> (x - 1, x)
932− return $ r >= 1
933−
934−{-# INLINE incrementYieldLimit #-}
935−incrementYieldLimit :: SVar t m a -> IO ()
936−incrementYieldLimit sv =
937− case remainingWork sv of
938− Nothing -> return ()
939− Just ref -> atomicModifyIORefCAS_ ref (+ 1)
940−
941−-- XXX exception safety of all atomic/MVar operations
942−
943−-- TBD Each worker can have their own queue and the consumer can empty one
944−-- queue at a time, that way contention can be reduced.
945−
946−-- XXX Only yields should be counted in the buffer limit and not the Stop
947−-- events.
948−
949−{-# INLINE decrementBufferLimit #-}
950−decrementBufferLimit :: SVar t m a -> IO ()
951−decrementBufferLimit sv =
952− case maxBufferLimit sv of
953− Unlimited -> return ()
954− Limited _ -> do
955− let ref = pushBufferSpace sv
956− old <- atomicModifyIORefCAS ref $ \x ->
957− (if x >= 1 then x - 1 else x, x)
958− when (old <= 0) $
959− case pushBufferPolicy sv of
960− PushBufferBlock -> blockAndRetry
961− PushBufferDropNew -> do
962− -- We just drop one item and proceed. It is possible
963− -- that by the time we drop the item the consumer
964− -- thread might have run and created space in the
965− -- buffer, but we do not care about that condition.
966− -- This is not pedantically correct but it should be
967− -- fine in practice.
968− -- XXX we may want to drop only if n == maxBuf
969− -- otherwise we must have space in the buffer and a
970− -- decrement should be possible.
971− block <- atomicModifyIORefCAS (outputQueue sv) $
972− \(es, n) ->
973− case es of
974− [] -> (([],n), True)
975− _ : xs -> ((xs, n - 1), False)
976− when block blockAndRetry
977− -- XXX need a dequeue or ring buffer for this
978− PushBufferDropOld -> undefined
979
80
980 where
81 where
981
82
982− blockAndRetry = do
83+ mkYieldRateInfo latency latRange buf = do
983− let ref = pushBufferSpace sv
84+ measured <- newIORef 0
984− liftIO $ takeMVar (pushBufferMVar sv)
85+ wcur <- newIORef (0,0,0)
985− old <- atomicModifyIORefCAS ref $ \x ->
86+ wcol <- newIORef (0,0,0)
986− (if x >= 1 then x - 1 else x, x)
987− -- When multiple threads sleep on takeMVar, the first thread would
988− -- wakeup due to a putMVar by the consumer, but the rest of the threads
989− -- would have to put back the MVar after taking it and decrementing the
990− -- buffer count, otherwise all other threads will remain asleep.
991− if old >= 1
992− then void $ liftIO $ tryPutMVar (pushBufferMVar sv) ()
993− -- We do not put the MVar back in this case, instead we
994− -- wait for the consumer to put it.
995− else blockAndRetry
996−
997−{-# INLINE incrementBufferLimit #-}
998−incrementBufferLimit :: SVar t m a -> IO ()
999−incrementBufferLimit sv =
1000− case maxBufferLimit sv of
1001− Unlimited -> return ()
1002− Limited _ -> do
1003− atomicModifyIORefCAS_ (pushBufferSpace sv) (+ 1)
1004− writeBarrier
1005− void $ liftIO $ tryPutMVar (pushBufferMVar sv) ()
1006−
1007−{-# INLINE resetBufferLimit #-}
1008−resetBufferLimit :: SVar t m a -> IO ()
1009−resetBufferLimit sv =
1010− case maxBufferLimit sv of
1011− Unlimited -> return ()
1012− Limited n -> atomicModifyIORefCAS_ (pushBufferSpace sv)
1013− (const (fromIntegral n))
1014−
1015−{-# INLINE sendWithDoorBell #-}
1016−sendWithDoorBell ::
1017− IORef ([ChildEvent a], Int) -> MVar () -> ChildEvent a -> IO Int
1018−sendWithDoorBell q bell msg = do
1019− -- XXX can the access to outputQueue be made faster somehow?
1020− oldlen <- atomicModifyIORefCAS q $ \(es, n) ->
1021− ((msg : es, n + 1), n)
1022− when (oldlen <= 0) $ do
1023− -- The wake up must happen only after the store has finished otherwise
1024− -- we can have lost wakeup problems.
1025− writeBarrier
1026− -- Since multiple workers can try this at the same time, it is possible
1027− -- that we may put a spurious MVar after the consumer has already seen
1028− -- the output. But that's harmless, at worst it may cause the consumer
1029− -- to read the queue again and find it empty.
1030− -- The important point is that the consumer is guaranteed to receive a
1031− -- doorbell if something was added to the queue after it empties it.
1032− void $ tryPutMVar bell ()
1033− return oldlen
1034−
1035−-- | This function is used by the producer threads to queue output for the
1036−-- consumer thread to consume. Returns whether the queue has more space.
1037−send :: SVar t m a -> ChildEvent a -> IO Int
1038−send sv = sendWithDoorBell (outputQueue sv) (outputDoorBell sv)
1039−
1040−-- There is no bound implemented on the buffer, this is assumed to be low
1041−-- traffic.
1042−sendToProducer :: SVar t m a -> ChildEvent a -> IO Int
1043−sendToProducer sv msg = do
1044− -- In case the producer stream is blocked on pushing to the fold buffer
1045− -- then wake it up so that it can check for the stop event or exception
1046− -- being sent to it otherwise we will be deadlocked.
1047− void $ tryPutMVar (pushBufferMVar sv) ()
1048− sendWithDoorBell (outputQueueFromConsumer sv)
1049− (outputDoorBellFromConsumer sv) msg
1050−
1051−-- {-# NOINLINE sendStopToProducer #-}
1052−sendStopToProducer :: MonadIO m => SVar t m a -> m ()
1053−sendStopToProducer sv = liftIO $ do
1054− tid <- myThreadId
1055− void $ sendToProducer sv (ChildStop tid Nothing)
1056−
1057−workerCollectLatency :: WorkerInfo -> IO (Maybe (Count, NanoSecond64))
1058−workerCollectLatency winfo = do
1059− (cnt0, t0) <- readIORef (workerLatencyStart winfo)
1060− cnt1 <- readIORef (workerYieldCount winfo)
1061− let cnt = cnt1 - cnt0
1062−
1063− if cnt > 0
1064− then do
1065− t1 <- getTime Monotonic
87+ now <- getTime Monotonic
1066− let period = fromRelTime64 $ diffAbsTime64 t1 t0
88+ wlong <- newIORef (0,now)
1067− writeIORef (workerLatencyStart winfo) (cnt1, t1)
89+ period <- newIORef 1
1068− return $ Just (cnt, period)
90+ gainLoss <- newIORef (Count 0)
1069− else return Nothing
1070−
1071−-- XXX There are a number of gotchas in measuring latencies.
1072−-- 1) We measure latencies only when a worker yields a value
1073−-- 2) It is possible that a stream calls the stop continuation, in which case
1074−-- the worker would not yield a value and we would not account that worker in
1075−-- latencies. Even though this case should ideally be accounted we do not
1076−-- account it because we cannot or do not distinguish it from the case
1077−-- described next.
1078−-- 3) It is possible that a worker returns without yielding anything because it
1079−-- never got a chance to pick up work.
1080−-- 4) If the system timer resolution is lower than the latency, the latency
1081−-- computation turns out to be zero.
1082−--
1083−-- We can fix this if we measure the latencies by counting the work items
1084−-- picked rather than based on the outputs yielded.
1085−workerUpdateLatency :: YieldRateInfo -> WorkerInfo -> IO ()
1086−workerUpdateLatency yinfo winfo = do
1087− r <- workerCollectLatency winfo
1088− case r of
1089− Just (cnt, period) -> do
1090− -- NOTE: On JS platform the timer resolution could be pretty low. When
1091− -- the timer resolution is low, measurement of latencies could be
1092− -- tricky. All the worker latencies will turn out to be zero if they
1093− -- are lower than the resolution. We only take into account those
1094− -- measurements which are more than the timer resolution.
1095−
1096− let ref = workerPendingLatency yinfo
1097− (cnt1, t1) = if period > 0 then (cnt, period) else (0, 0)
1098− atomicModifyIORefCAS_ ref $
1099− \(fc, n, t) -> (fc + cnt, n + cnt1, t + t1)
1100− Nothing -> return ()
1101−
1102−updateYieldCount :: WorkerInfo -> IO Count
1103−updateYieldCount winfo = do
1104− cnt <- readIORef (workerYieldCount winfo)
1105− let cnt1 = cnt + 1
1106− writeIORef (workerYieldCount winfo) cnt1
1107− return cnt1
1108−
1109−isBeyondMaxYield :: Count -> WorkerInfo -> Bool
1110−isBeyondMaxYield cnt winfo =
1111− let ymax = workerYieldMax winfo
1112− in ymax /= 0 && cnt >= ymax
1113−
1114−-- XXX we should do rate control periodically based on the total yields rather
1115−-- than based on the worker local yields as other workers may have yielded more
1116−-- and we should stop based on the aggregate yields. However, latency update
1117−-- period can be based on individual worker yields.
1118−{-# NOINLINE checkRatePeriodic #-}
1119−checkRatePeriodic :: SVar t m a
1120− -> YieldRateInfo
1121− -> WorkerInfo
1122− -> Count
1123− -> IO Bool
1124−checkRatePeriodic sv yinfo winfo ycnt = do
1125− i <- readIORef (workerPollingInterval yinfo)
1126− -- XXX use generation count to check if the interval has been updated
1127− if i /= 0 && (ycnt `mod` i) == 0
1128− then do
1129− workerUpdateLatency yinfo winfo
1130− -- XXX not required for parallel streams
1131− isBeyondMaxRate sv yinfo
1132− else return False
1133−
1134−-- CAUTION! this also updates the yield count and therefore should be called
1135−-- only when we are actually yielding an element.
1136−{-# NOINLINE workerRateControl #-}
1137−workerRateControl :: SVar t m a -> YieldRateInfo -> WorkerInfo -> IO Bool
1138−workerRateControl sv yinfo winfo = do
1139− cnt <- updateYieldCount winfo
1140− beyondMaxRate <- checkRatePeriodic sv yinfo winfo cnt
1141− return $ not (isBeyondMaxYield cnt winfo || beyondMaxRate)
1142−
1143−-- XXX we should do rate control here but not latency update in case of ahead
1144−-- streams. latency update must be done when we yield directly to outputQueue
1145−-- or when we yield to heap.
1146−--
1147−-- returns whether the worker should continue (True) or stop (False).
1148−{-# INLINE sendYield #-}
1149−sendYield :: SVar t m a -> Maybe WorkerInfo -> ChildEvent a -> IO Bool
1150−sendYield sv mwinfo msg = do
1151− oldlen <- send sv msg
1152− let limit = maxBufferLimit sv
1153− bufferSpaceOk <- case limit of
1154− Unlimited -> return True
1155− Limited lim -> do
1156− active <- readIORef (workerCount sv)
1157− return $ (oldlen + 1) < (fromIntegral lim - active)
1158− rateLimitOk <-
1159− case mwinfo of
1160− Just winfo ->
1161− case yieldRateInfo sv of
1162− Nothing -> return True
1163− Just yinfo -> workerRateControl sv yinfo winfo
1164− Nothing -> return True
1165− return $ bufferSpaceOk && rateLimitOk
1166−
1167−{-# INLINE workerStopUpdate #-}
1168−workerStopUpdate :: WorkerInfo -> YieldRateInfo -> IO ()
1169−workerStopUpdate winfo info = do
1170− i <- readIORef (workerPollingInterval info)
1171− when (i /= 0) $ workerUpdateLatency info winfo
1172−
1173−{-# INLINABLE sendStop #-}
1174−sendStop :: SVar t m a -> Maybe WorkerInfo -> IO ()
1175−sendStop sv mwinfo = do
1176− atomicModifyIORefCAS_ (workerCount sv) $ \n -> n - 1
1177− case (mwinfo, yieldRateInfo sv) of
1178− (Just winfo, Just info) ->
1179− workerStopUpdate winfo info
1180− _ ->
1181− return ()
1182− myThreadId >>= \tid -> void $ send sv (ChildStop tid Nothing)
1183−
1184−-------------------------------------------------------------------------------
1185−-- Doorbell
1186−-------------------------------------------------------------------------------
1187−
1188−{-# INLINE ringDoorBell #-}
1189−ringDoorBell :: SVar t m a -> IO ()
1190−ringDoorBell sv = do
1191− storeLoadBarrier
1192− w <- readIORef $ needDoorBell sv
1193− when w $ do
1194− -- Note: the sequence of operations is important for correctness here.
1195− -- We need to set the flag to false strictly before sending the
1196− -- outputDoorBell, otherwise the outputDoorBell may get processed too
1197− -- early and then we may set the flag to False to later making the
1198− -- consumer lose the flag, even without receiving a outputDoorBell.
1199− atomicModifyIORefCAS_ (needDoorBell sv) (const False)
1200− void $ tryPutMVar (outputDoorBell sv) ()
1201−
1202−-------------------------------------------------------------------------------
1203−-- Async
1204−-------------------------------------------------------------------------------
1205−
1206−-- Note: For purely right associated expressions this queue should have at most
1207−-- one element. It grows to more than one when we have left associcated
1208−-- expressions. Large left associated compositions can grow this to a
1209−-- large size
1210−{-# INLINE enqueueLIFO #-}
1211−enqueueLIFO ::
1212− SVar t m a -> IORef [(RunInIO m, t m a)] -> (RunInIO m, t m a) -> IO ()
1213−enqueueLIFO sv q m = do
1214− atomicModifyIORefCAS_ q $ \ms -> m : ms
1215− ringDoorBell sv
1216
91
1217−-------------------------------------------------------------------------------
92+ return $ Just YieldRateInfo
1218−-- WAsync
93+ { svarLatencyTarget = latency
1219−-------------------------------------------------------------------------------
94+ , svarLatencyRange = latRange
95+ , svarRateBuffer = buf
96+ , svarGainedLostYields = gainLoss
97+ , workerBootstrapLatency = getStreamLatency st
98+ , workerPollingInterval = period
99+ , workerMeasuredLatency = measured
100+ , workerPendingLatency = wcur
101+ , workerCollectedLatency = wcol
102+ , svarAllTimeLatency = wlong
103+ }
1220
104
1221−-- XXX we can use the Ahead style sequence/heap mechanism to make the best
105+newSVarStats :: IO SVarStats
1222−-- effort to always try to finish the streams on the left side of an expression
106+newSVarStats = do
1223−-- first as long as possible.
107+ disp <- newIORef 0
108+ maxWrk <- newIORef 0
109+ maxOq <- newIORef 0
110+ maxHs <- newIORef 0
111+ maxWq <- newIORef 0
112+ avgLat <- newIORef (0, NanoSecond64 0)
113+ maxLat <- newIORef (NanoSecond64 0)
114+ minLat <- newIORef (NanoSecond64 0)
115+ stpTime <- newIORef Nothing
1224
116
1225−{-# INLINE enqueueFIFO #-}
117+ return SVarStats
1226−enqueueFIFO ::
118+ { totalDispatches = disp
1227− SVar t m a
119+ , maxWorkers = maxWrk
1228− -> LinkedQueue (RunInIO m, t m a)
120+ , maxOutQSize = maxOq
1229− -> (RunInIO m, t m a)
121+ , maxHeapSize = maxHs
1230− -> IO ()
122+ , maxWorkQSize = maxWq
1231−enqueueFIFO sv q m = do
123+ , avgWorkerLatency = avgLat
1232− pushL q m
124+ , minWorkerLatency = minLat
1233− ringDoorBell sv
125+ , maxWorkerLatency = maxLat
126+ , svarStopTime = stpTime
127+ }
1234
128
1235-------------------------------------------------------------------------------
129-------------------------------------------------------------------------------
1236-- Ahead
130-- Ahead
@@ -1424,748 +318,6 @@ updateHeapSeq hpVar seqNo =
1424 atomicModifyIORef_ hpVar $ \(hp, snum) ->
318 atomicModifyIORef_ hpVar $ \(hp, snum) ->
1425 assert (heapIsSane snum seqNo) (hp, Just seqNo)
319 assert (heapIsSane snum seqNo) (hp, Just seqNo)
1426
320
1427−-------------------------------------------------------------------------------
1428−-- WAhead
1429−-------------------------------------------------------------------------------
1430−
1431−-- XXX To be implemented. Use a linked queue like WAsync and put back the
1432−-- remaining computation at the back of the queue instead of the heap, and
1433−-- increment the sequence number.
1434−
1435−-------------------------------------------------------------------------------
1436−-- Dispatching workers and tracking them
1437−-------------------------------------------------------------------------------
1438−
1439−-- Thread tracking is needed for two reasons:
1440−--
1441−-- 1) Killing threads on exceptions. Threads may not be left to go away by
1442−-- themselves because they may run for significant times before going away or
1443−-- worse they may be stuck in IO and never go away.
1444−--
1445−-- 2) To know when all threads are done and the stream has ended.
1446−
1447−{-# NOINLINE addThread #-}
1448−addThread :: MonadIO m => SVar t m a -> ThreadId -> m ()
1449−addThread sv tid =
1450− liftIO $ modifyIORef (workerThreads sv) (S.insert tid)
1451−
1452−-- This is cheaper than modifyThread because we do not have to send a
1453−-- outputDoorBell This can make a difference when more workers are being
1454−-- dispatched.
1455−{-# INLINE delThread #-}
1456−delThread :: MonadIO m => SVar t m a -> ThreadId -> m ()
1457−delThread sv tid =
1458− liftIO $ modifyIORef (workerThreads sv) (S.delete tid)
1459−
1460−-- If present then delete else add. This takes care of out of order add and
1461−-- delete i.e. a delete arriving before we even added a thread.
1462−-- This occurs when the forked thread is done even before the 'addThread' right
1463−-- after the fork gets a chance to run.
1464−{-# INLINE modifyThread #-}
1465−modifyThread :: MonadIO m => SVar t m a -> ThreadId -> m ()
1466−modifyThread sv tid = do
1467− changed <- liftIO $ atomicModifyIORefCAS (workerThreads sv) $ \old ->
1468− if S.member tid old
1469− then let new = S.delete tid old in (new, new)
1470− else let new = S.insert tid old in (new, old)
1471− when (null changed) $
1472− liftIO $ do
1473− writeBarrier
1474− void $ tryPutMVar (outputDoorBell sv) ()
1475−
1476−-- | This is safe even if we are adding more threads concurrently because if
1477−-- a child thread is adding another thread then anyway 'workerThreads' will
1478−-- not be empty.
1479−{-# INLINE allThreadsDone #-}
1480−allThreadsDone :: MonadIO m => SVar t m a -> m Bool
1481−allThreadsDone sv = liftIO $ S.null <$> readIORef (workerThreads sv)
1482−
1483−{-# NOINLINE handleChildException #-}
1484−handleChildException :: SVar t m a -> SomeException -> IO ()
1485−handleChildException sv e = do
1486− tid <- myThreadId
1487− void $ send sv (ChildStop tid (Just e))
1488−
1489−{-# NOINLINE handleFoldException #-}
1490−handleFoldException :: SVar t m a -> SomeException -> IO ()
1491−handleFoldException sv e = do
1492− tid <- myThreadId
1493− void $ sendToProducer sv (ChildStop tid (Just e))
1494−
1495−{-# NOINLINE recordMaxWorkers #-}
1496−recordMaxWorkers :: MonadIO m => SVar t m a -> m ()
1497−recordMaxWorkers sv = liftIO $ do
1498− active <- readIORef (workerCount sv)
1499− maxWrk <- readIORef (maxWorkers $ svarStats sv)
1500− when (active > maxWrk) $ writeIORef (maxWorkers $ svarStats sv) active
1501− modifyIORef (totalDispatches $ svarStats sv) (+1)
1502−
1503−{-# NOINLINE pushWorker #-}
1504−pushWorker :: MonadAsync m => Count -> SVar t m a -> m ()
1505−pushWorker yieldMax sv = do
1506− liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n + 1
1507− when (svarInspectMode sv) $ recordMaxWorkers sv
1508− -- This allocation matters when significant number of workers are being
1509− -- sent. We allocate it only when needed.
1510− winfo <-
1511− case yieldRateInfo sv of
1512− Nothing -> return Nothing
1513− Just _ -> liftIO $ do
1514− cntRef <- newIORef 0
1515− t <- getTime Monotonic
1516− lat <- newIORef (0, t)
1517− return $ Just WorkerInfo
1518− { workerYieldMax = yieldMax
1519− , workerYieldCount = cntRef
1520− , workerLatencyStart = lat
1521− }
1522− doFork (workLoop sv winfo) (svarMrun sv) (handleChildException sv)
1523− >>= addThread sv
1524−
1525−-- XXX we can push the workerCount modification in accountThread and use the
1526−-- same pushWorker for Parallel case as well.
1527−--
1528−-- | In contrast to pushWorker which always happens only from the consumer
1529−-- thread, a pushWorkerPar can happen concurrently from multiple threads on the
1530−-- producer side. So we need to use a thread safe modification of
1531−-- workerThreads. Alternatively, we can use a CreateThread event to avoid
1532−-- using a CAS based modification.
1533−{-# INLINE pushWorkerPar #-}
1534−pushWorkerPar
1535− :: MonadAsync m
1536− => SVar t m a -> (Maybe WorkerInfo -> m ()) -> m ()
1537−pushWorkerPar sv wloop =
1538− if svarInspectMode sv
1539− then forkWithDiag
1540− else doFork (wloop Nothing) (svarMrun sv) (handleChildException sv)
1541− >>= modifyThread sv
1542−
1543− where
1544−
1545− {-# NOINLINE forkWithDiag #-}
1546− forkWithDiag = do
1547− -- We do not use workerCount in case of ParallelVar but still there is
1548− -- no harm in maintaining it correctly.
1549− liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n + 1
1550− recordMaxWorkers sv
1551− -- This allocation matters when significant number of workers are being
1552− -- sent. We allocate it only when needed. The overhead increases by 4x.
1553− winfo <-
1554− case yieldRateInfo sv of
1555− Nothing -> return Nothing
1556− Just _ -> liftIO $ do
1557− cntRef <- newIORef 0
1558− t <- getTime Monotonic
1559− lat <- newIORef (0, t)
1560− return $ Just WorkerInfo
1561− { workerYieldMax = 0
1562− , workerYieldCount = cntRef
1563− , workerLatencyStart = lat
1564− }
1565−
1566− doFork (wloop winfo) (svarMrun sv) (handleChildException sv)
1567− >>= modifyThread sv
1568−
1569−-- Returns:
1570−-- True: can dispatch more
1571−-- False: cannot dispatch any more
1572−dispatchWorker :: MonadAsync m => Count -> SVar t m a -> m Bool
1573−dispatchWorker yieldCount sv = do
1574− let workerLimit = maxWorkerLimit sv
1575− -- XXX in case of Ahead streams we should not send more than one worker
1576− -- when the work queue is done but heap is not done.
1577− done <- liftIO $ isWorkDone sv
1578− -- Note, "done" may not mean that the work is actually finished if there
1579− -- are workers active, because there may be a worker which has not yet
1580− -- queued the leftover work.
1581− if not done
1582− then do
1583− qDone <- liftIO $ isQueueDone sv
1584− -- This count may not be accurate as it is decremented by the workers
1585− -- and we have no synchronization with that decrement.
1586− active <- liftIO $ readIORef $ workerCount sv
1587− if not qDone
1588− then do
1589− -- Note that we may deadlock if the previous workers (tasks in the
1590− -- stream) wait/depend on the future workers (tasks in the stream)
1591− -- executing. In that case we should either configure the maxWorker
1592− -- count to higher or use parallel style instead of ahead or async
1593− -- style.
1594− limit <- case remainingWork sv of
1595− Nothing -> return workerLimit
1596− Just ref -> do
1597− n <- liftIO $ readIORef ref
1598− case yieldRateInfo sv of
1599− Just _ -> return workerLimit
1600− Nothing ->
1601− return $
1602− case workerLimit of
1603− Unlimited -> Limited (fromIntegral n)
1604− Limited lim -> Limited $ min lim (fromIntegral n)
1605−
1606− -- XXX for ahead streams shall we take the heap yields into account
1607− -- for controlling the dispatch? We should not dispatch if the heap
1608− -- has already got the limit covered.
1609− let dispatch = pushWorker yieldCount sv >> return True
1610− in case limit of
1611− Unlimited -> dispatch
1612− -- Note that the use of remainingWork and workerCount is not
1613− -- atomic and the counts may even have changed between reading
1614− -- and using them here, so this is just approximate logic and
1615− -- we cannot rely on it for correctness. We may actually
1616− -- dispatch more workers than required.
1617− Limited lim | lim > fromIntegral active -> dispatch
1618− _ -> return False
1619− else do
1620− when (active <= 0) $ pushWorker 0 sv
1621− return False
1622− else return False
1623−
1624−-------------------------------------------------------------------------------
1625−-- Dispatch workers with rate control
1626−-------------------------------------------------------------------------------
1627−
1628−-- | This is a magic number and it is overloaded, and used at several places to
1629−-- achieve batching:
1630−--
1631−-- 1. If we have to sleep to slowdown this is the minimum period that we
1632−-- accumulate before we sleep. Also, workers do not stop until this much
1633−-- sleep time is accumulated.
1634−-- 3. Collected latencies are computed and transferred to measured latency
1635−-- after a minimum of this period.
1636−minThreadDelay :: NanoSecond64
1637−minThreadDelay = 1000000
1638−
1639−-- | Another magic number! When we have to start more workers to cover up a
1640−-- number of yields that we are lagging by then we cannot start one worker for
1641−-- each yield because that may be a very big number and if the latency of the
1642−-- workers is low these number of yields could be very high. We assume that we
1643−-- run each extra worker for at least this much time.
1644−rateRecoveryTime :: NanoSecond64
1645−rateRecoveryTime = 1000000
1646−
1647−-- We either block, or send one worker with limited yield count or one or more
1648−-- workers with unlimited yield count.
1649−data Work
1650− = BlockWait NanoSecond64
1651− | PartialWorker Count
1652− | ManyWorkers Int Count
1653− deriving Show
1654−
1655−-- XXX we can use phantom types to distinguish the duration/latency/expectedLat
1656−estimateWorkers
1657− :: Limit
1658− -> Count
1659− -> Count
1660− -> NanoSecond64
1661− -> NanoSecond64
1662− -> NanoSecond64
1663− -> LatencyRange
1664− -> Work
1665−estimateWorkers workerLimit svarYields gainLossYields
1666− svarElapsed wLatency targetLat range =
1667− -- XXX we can have a maxEfficiency combinator as well which runs the
1668− -- producer at the maximal efficiency i.e. the number of workers are chosen
1669− -- such that the latency is minimum or within a range. Or we can call it
1670− -- maxWorkerLatency.
1671− --
1672− let
1673− -- How many workers do we need to achieve the required rate?
1674− --
1675− -- When the workers are IO bound we can increase the throughput by
1676− -- increasing the number of workers as long as the IO device has enough
1677− -- capacity to process all the requests concurrently. If the IO
1678− -- bandwidth is saturated increasing the workers won't help. Also, if
1679− -- the CPU utilization in processing all these requests exceeds the CPU
1680− -- bandwidth, then increasing the number of workers won't help.
1681− --
1682− -- When the workers are purely CPU bound, increasing the workers beyond
1683− -- the number of CPUs won't help.
1684− --
1685− -- TODO - measure the CPU and IO requirements of the workers. Have a
1686− -- way to specify the max bandwidth of the underlying IO mechanism and
1687− -- use that to determine the max rate of workers, and also take the CPU
1688− -- bandwidth into account. We can also discover the IO bandwidth if we
1689− -- know that we are not CPU bound, then how much steady state rate are
1690− -- we able to achieve. Design tests for CPU bound and IO bound cases.
1691−
1692− -- Calculate how many yields are we ahead or behind to match the exact
1693− -- required rate. Based on that we increase or decrease the effective
1694− -- workers.
1695− --
1696− -- When the worker latency is lower than required latency we begin with
1697− -- a yield and then wait rather than first waiting and then yielding.
1698− targetYields = (svarElapsed + wLatency + targetLat - 1) `div` targetLat
1699− effectiveYields = svarYields + gainLossYields
1700− deltaYields = fromIntegral targetYields - effectiveYields
1701−
1702− -- We recover the deficit by running at a higher/lower rate for a
1703− -- certain amount of time. To keep the effective rate in reasonable
1704− -- limits we use rateRecoveryTime, minLatency and maxLatency.
1705− in if deltaYields > 0
1706− then
1707− let deltaYieldsFreq :: Double
1708− deltaYieldsFreq =
1709− fromIntegral deltaYields /
1710− fromIntegral rateRecoveryTime
1711− yieldsFreq = 1.0 / fromIntegral targetLat
1712− totalYieldsFreq = yieldsFreq + deltaYieldsFreq
1713− requiredLat = NanoSecond64 $ round $ 1.0 / totalYieldsFreq
1714− adjustedLat = min (max requiredLat (minLatency range))
1715− (maxLatency range)
1716− in assert (adjustedLat > 0) $
1717− if wLatency <= adjustedLat
1718− then PartialWorker deltaYields
1719− else let workers = withLimit $ wLatency `div` adjustedLat
1720− limited = min workers (fromIntegral deltaYields)
1721− in ManyWorkers (fromIntegral limited) deltaYields
1722− else
1723− let expectedDuration = fromIntegral effectiveYields * targetLat
1724− sleepTime = expectedDuration - svarElapsed
1725− maxSleepTime = maxLatency range - wLatency
1726− s = min sleepTime maxSleepTime
1727− in assert (sleepTime >= 0) $
1728− -- if s is less than 0 it means our maxSleepTime is less
1729− -- than the worker latency.
1730− if s > 0 then BlockWait s else ManyWorkers 1 (Count 0)
1731− where
1732− withLimit n =
1733− case workerLimit of
1734− Unlimited -> n
1735− Limited x -> min n (fromIntegral x)
1736−
1737−-- | Get the worker latency without resetting workerPendingLatency
1738−-- Returns (total yield count, base time, measured latency)
1739−-- CAUTION! keep it in sync with collectLatency
1740−getWorkerLatency :: YieldRateInfo -> IO (Count, AbsTime, NanoSecond64)
1741−getWorkerLatency yinfo = do
1742− let cur = workerPendingLatency yinfo
1743− col = workerCollectedLatency yinfo
1744− longTerm = svarAllTimeLatency yinfo
1745− measured = workerMeasuredLatency yinfo
1746−
1747− (curTotalCount, curCount, curTime) <- readIORef cur
1748− (colTotalCount, colCount, colTime) <- readIORef col
1749− (lcount, ltime) <- readIORef longTerm
1750− prevLat <- readIORef measured
1751−
1752− let latCount = colCount + curCount
1753− latTime = colTime + curTime
1754− totalCount = colTotalCount + curTotalCount
1755− newLat =
1756− if latCount > 0 && latTime > 0
1757− then let lat = latTime `div` fromIntegral latCount
1758− -- XXX Give more weight to new?
1759− in (lat + prevLat) `div` 2
1760− else prevLat
1761− return (lcount + totalCount, ltime, newLat)
1762−
1763−isBeyondMaxRate :: SVar t m a -> YieldRateInfo -> IO Bool
1764−isBeyondMaxRate sv yinfo = do
1765− (count, tstamp, wLatency) <- getWorkerLatency yinfo
1766− now <- getTime Monotonic
1767− let duration = fromRelTime64 $ diffAbsTime64 now tstamp
1768− let targetLat = svarLatencyTarget yinfo
1769− gainLoss <- readIORef (svarGainedLostYields yinfo)
1770− let work = estimateWorkers (maxWorkerLimit sv) count gainLoss duration
1771− wLatency targetLat (svarLatencyRange yinfo)
1772− cnt <- readIORef $ workerCount sv
1773− return $ case work of
1774− -- XXX set the worker's maxYields or polling interval based on yields
1775− PartialWorker _yields -> cnt > 1
1776− ManyWorkers n _ -> cnt > n
1777− BlockWait _ -> True
1778−
1779−-- XXX in case of ahead style stream we need to take the heap size into account
1780−-- because we return the workers on the basis of that which causes a condition
1781−-- where we keep dispatching and they keep returning. So we must have exactly
1782−-- the same logic for not dispatching and for returning.
1783−--
1784−-- Returns:
1785−-- True: can dispatch more
1786−-- False: full, no more dispatches
1787−dispatchWorkerPaced :: MonadAsync m => SVar t m a -> m Bool
1788−dispatchWorkerPaced sv = do
1789− let yinfo = fromJust $ yieldRateInfo sv
1790− (svarYields, svarElapsed, wLatency) <- do
1791− now <- liftIO $ getTime Monotonic
1792− (yieldCount, baseTime, lat) <-
1793− liftIO $ collectLatency sv yinfo False
1794− let elapsed = fromRelTime64 $ diffAbsTime64 now baseTime
1795− let latency =
1796− if lat == 0
1797− then fromMaybe lat (workerBootstrapLatency yinfo)
1798− else lat
1799−
1800− return (yieldCount, elapsed, latency)
1801−
1802− if wLatency == 0
1803− -- Need to measure the latency with a single worker before we can perform
1804− -- any computation.
1805− then return False
1806− else do
1807− let workerLimit = maxWorkerLimit sv
1808− let targetLat = svarLatencyTarget yinfo
1809− let range = svarLatencyRange yinfo
1810− gainLoss <- liftIO $ readIORef (svarGainedLostYields yinfo)
1811− let work = estimateWorkers workerLimit svarYields gainLoss svarElapsed
1812− wLatency targetLat range
1813−
1814− -- XXX we need to take yieldLimit into account here. If we are at the
1815− -- end of the limit as well as the time, we should not be sleeping.
1816− -- If we are not actually planning to dispatch any more workers we need
1817− -- to take that in account.
1818− case work of
1819− BlockWait s -> do
1820− assert (s >= 0) (return ())
1821− -- XXX note that when we return from here we will block waiting
1822− -- for the result from the existing worker. If that takes too
1823− -- long we won't be able to send another worker until the
1824− -- result arrives.
1825− --
1826− -- Sleep only if there are no active workers, otherwise we will
1827− -- defer the output of those. Note we cannot use workerCount
1828− -- here as it is not a reliable way to ensure there are
1829− -- definitely no active workers. When workerCount is 0 we may
1830− -- still have a Stop event waiting in the outputQueue.
1831− done <- allThreadsDone sv
1832− when done $ void $ do
1833− let us = fromRelTime64 (toRelTime64 s) :: MicroSecond64
1834− liftIO $ threadDelay (fromIntegral us)
1835− dispatchWorker 1 sv
1836− return False
1837− PartialWorker yields -> do
1838− assert (yields > 0) (return ())
1839− updateGainedLostYields yinfo yields
1840−
1841− done <- allThreadsDone sv
1842− when done $ void $ dispatchWorker yields sv
1843− return False
1844− ManyWorkers netWorkers yields -> do
1845− assert (netWorkers >= 1) (return ())
1846− assert (yields >= 0) (return ())
1847− updateGainedLostYields yinfo yields
1848−
1849− let periodRef = workerPollingInterval yinfo
1850− ycnt = max 1 $ yields `div` fromIntegral netWorkers
1851− period = min ycnt (fromIntegral magicMaxBuffer)
1852−
1853− old <- liftIO $ readIORef periodRef
1854− when (period < old) $
1855− liftIO $ writeIORef periodRef period
1856−
1857− cnt <- liftIO $ readIORef $ workerCount sv
1858− if cnt < netWorkers
1859− then do
1860− let total = netWorkers - cnt
1861− batch = max 1 $ fromIntegral $
1862− minThreadDelay `div` targetLat
1863− -- XXX stagger the workers over a period?
1864− -- XXX cannot sleep, as that would mean we cannot process
1865− -- the outputs. need to try a different mechanism to
1866− -- stagger.
1867− -- when (total > batch) $
1868− -- liftIO $ threadDelay $ nanoToMicroSecs minThreadDelay
1869− dispatchN (min total batch)
1870− else return False
1871−
1872− where
1873−
1874− updateGainedLostYields yinfo yields = do
1875− let buf = fromIntegral $ svarRateBuffer yinfo
1876− when (yields /= 0 && abs yields > buf) $ do
1877− let delta =
1878− if yields > 0
1879− then yields - buf
1880− else yields + buf
1881− liftIO $ modifyIORef (svarGainedLostYields yinfo) (+ delta)
1882−
1883− dispatchN n =
1884− if n == 0
1885− then return True
1886− else do
1887− r <- dispatchWorker 0 sv
1888− if r
1889− then dispatchN (n - 1)
1890− else return False
1891−
1892−-------------------------------------------------------------------------------
1893−-- Worker dispatch and wait loop
1894−-------------------------------------------------------------------------------
1895−
1896−sendWorkerDelayPaced :: SVar t m a -> IO ()
1897−sendWorkerDelayPaced _ = return ()
1898−
1899−sendWorkerDelay :: SVar t m a -> IO ()
1900−sendWorkerDelay _sv =
1901− -- XXX we need a better way to handle this than hardcoded delays. The
1902− -- delays may be different for different systems.
1903− -- If there is a usecase where this is required we can create a combinator
1904− -- to set it as a config in the state.
1905− {-
1906− do
1907− ncpu <- getNumCapabilities
1908− if ncpu <= 1
1909− then
1910− if (svarStyle sv == AheadVar)
1911− then threadDelay 100
1912− else threadDelay 25
1913− else
1914− if (svarStyle sv == AheadVar)
1915− then threadDelay 100
1916− else threadDelay 10
1917− -}
1918− return ()
1919−
1920−{-# NOINLINE sendWorkerWait #-}
1921−sendWorkerWait
1922− :: MonadAsync m
1923− => (SVar t m a -> IO ())
1924− -> (SVar t m a -> m Bool)
1925− -> SVar t m a
1926− -> m ()
1927−sendWorkerWait delay dispatch sv = do
1928− -- Note that we are guaranteed to have at least one outstanding worker when
1929− -- we enter this function. So if we sleep we are guaranteed to be woken up
1930− -- by an outputDoorBell, when the worker exits.
1931−
1932− liftIO $ delay sv
1933− (_, n) <- liftIO $ readIORef (outputQueue sv)
1934− when (n <= 0) $ do
1935− -- The queue may be empty temporarily if the worker has dequeued the
1936− -- work item but has not enqueued the remaining part yet. For the same
1937− -- reason, a worker may come back if it tries to dequeue and finds the
1938− -- queue empty, even though the whole work has not finished yet.
1939−
1940− -- If we find that the queue is empty, but it may be empty
1941− -- temporarily, when we checked it. If that's the case we might
1942− -- sleep indefinitely unless the active workers produce some
1943− -- output. We may deadlock specially if the otuput from the active
1944− -- workers depends on the future workers that we may never send.
1945− -- So in case the queue was temporarily empty set a flag to inform
1946− -- the enqueue to send us a doorbell.
1947−
1948− -- Note that this is just a best effort mechanism to avoid a
1949− -- deadlock. Deadlocks may still happen if for some weird reason
1950− -- the consuming computation shares an MVar or some other resource
1951− -- with the producing computation and gets blocked on that resource
1952− -- and therefore cannot do any pushworker to add more threads to
1953− -- the producer. In such cases the programmer should use a parallel
1954− -- style so that all the producers are scheduled immediately and
1955− -- unconditionally. We can also use a separate monitor thread to
1956− -- push workers instead of pushing them from the consumer, but then
1957− -- we are no longer using pull based concurrency rate adaptation.
1958− --
1959− -- XXX update this in the tutorial.
1960− --
1961− -- Having pending active workers does not mean that we are guaranteed
1962− -- to be woken up if we sleep. In case of Ahead streams, there may be
1963− -- queued items in the heap even though the outputQueue is empty, and
1964− -- we may have active workers which are deadlocked on those items to be
1965− -- processed by the consumer. We should either guarantee that any
1966− -- worker, before returning, clears the heap or we send a worker to
1967− -- clear it. Normally we always send a worker if no output is seen, but
1968− -- if the thread limit is reached or we are using pacing then we may
1969− -- not send a worker. See the concurrentApplication test in the tests,
1970− -- that test case requires at least one yield from the producer to not
1971− -- deadlock, if the last workers output is stuck in the heap then this
1972− -- test fails. This problem can be extended to n threads when the
1973− -- consumer may depend on the evaluation of next n items in the
1974− -- producer stream.
1975−
1976− -- register for the outputDoorBell before we check the queue so that if
1977− -- we sleep because the queue was empty we are guaranteed to get a
1978− -- doorbell on the next enqueue.
1979−
1980− liftIO $ atomicModifyIORefCAS_ (needDoorBell sv) $ const True
1981− liftIO storeLoadBarrier
1982− canDoMore <- dispatch sv
1983−
1984− -- XXX test for the case when we miss sending a worker when the worker
1985− -- count is more than 1500.
1986− --
1987− -- XXX Assert here that if the heap is not empty then there is at
1988− -- least one outstanding worker. Otherwise we could be sleeping
1989− -- forever.
1990−
1991− if canDoMore
1992− then sendWorkerWait delay dispatch sv
1993− else do
1994− liftIO $ withDiagMVar sv "sendWorkerWait: nothing to do"
1995− $ takeMVar (outputDoorBell sv)
1996− (_, len) <- liftIO $ readIORef (outputQueue sv)
1997− when (len <= 0) $ sendWorkerWait delay dispatch sv
1998−
1999−-------------------------------------------------------------------------------
2000−-- Reading from the workers' output queue/buffer
2001−-------------------------------------------------------------------------------
2002−
2003−{-# INLINE readOutputQBasic #-}
2004−readOutputQBasic :: IORef ([ChildEvent a], Int) -> IO ([ChildEvent a], Int)
2005−readOutputQBasic q = atomicModifyIORefCAS q $ \x -> (([],0), x)
2006−
2007−{-# INLINE readOutputQRaw #-}
2008−readOutputQRaw :: SVar t m a -> IO ([ChildEvent a], Int)
2009−readOutputQRaw sv = do
2010− (list, len) <- readOutputQBasic (outputQueue sv)
2011− when (svarInspectMode sv) $ do
2012− let ref = maxOutQSize $ svarStats sv
2013− oqLen <- readIORef ref
2014− when (len > oqLen) $ writeIORef ref len
2015− return (list, len)
2016−
2017−readOutputQBounded :: MonadAsync m => SVar t m a -> m [ChildEvent a]
2018−readOutputQBounded sv = do
2019− (list, len) <- liftIO $ readOutputQRaw sv
2020− -- When there is no output seen we dispatch more workers to help
2021− -- out if there is work pending in the work queue.
2022− if len <= 0
2023− then blockingRead
2024− else do
2025− -- send a worker proactively, if needed, even before we start
2026− -- processing the output. This may degrade single processor
2027− -- perf but improves multi-processor, because of more
2028− -- parallelism
2029− sendOneWorker
2030− return list
2031−
2032− where
2033−
2034− sendOneWorker = do
2035− cnt <- liftIO $ readIORef $ workerCount sv
2036− when (cnt <= 0) $ do
2037− done <- liftIO $ isWorkDone sv
2038− when (not done) (pushWorker 0 sv)
2039−
2040− {-# INLINE blockingRead #-}
2041− blockingRead = do
2042− sendWorkerWait sendWorkerDelay (dispatchWorker 0) sv
2043− liftIO (fst `fmap` readOutputQRaw sv)
2044−
2045−readOutputQPaced :: MonadAsync m => SVar t m a -> m [ChildEvent a]
2046−readOutputQPaced sv = do
2047− (list, len) <- liftIO $ readOutputQRaw sv
2048− if len <= 0
2049− then blockingRead
2050− else do
2051− -- XXX send a worker proactively, if needed, even before we start
2052− -- processing the output.
2053− void $ dispatchWorkerPaced sv
2054− return list
2055−
2056− where
2057−
2058− {-# INLINE blockingRead #-}
2059− blockingRead = do
2060− sendWorkerWait sendWorkerDelayPaced dispatchWorkerPaced sv
2061− liftIO (fst `fmap` readOutputQRaw sv)
2062−
2063−postProcessBounded :: MonadAsync m => SVar t m a -> m Bool
2064−postProcessBounded sv = do
2065− workersDone <- allThreadsDone sv
2066− -- There may still be work pending even if there are no workers pending
2067− -- because all the workers may return if the outputQueue becomes full. In
2068− -- that case send off a worker to kickstart the work again.
2069− --
2070− -- Note that isWorkDone can only be safely checked if all workers are done.
2071− -- When some workers are in progress they may have decremented the yield
2072− -- Limit and later ending up incrementing it again. If we look at the yield
2073− -- limit in that window we may falsely say that it is 0 and therefore we
2074− -- are done.
2075− if workersDone
2076− then do
2077− r <- liftIO $ isWorkDone sv
2078− -- Note that we need to guarantee a worker, therefore we cannot just
2079− -- use dispatchWorker which may or may not send a worker.
2080− when (not r) (pushWorker 0 sv)
2081− -- XXX do we need to dispatch many here?
2082− -- void $ dispatchWorker sv
2083− return r
2084− else return False
2085−
2086−postProcessPaced :: MonadAsync m => SVar t m a -> m Bool
2087−postProcessPaced sv = do
2088− workersDone <- allThreadsDone sv
2089− -- XXX If during consumption we figure out we are getting delayed then we
2090− -- should trigger dispatch there as well. We should try to check on the
2091− -- workers after consuming every n item from the buffer?
2092− if workersDone
2093− then do
2094− r <- liftIO $ isWorkDone sv
2095− when (not r) $ do
2096− void $ dispatchWorkerPaced sv
2097− -- Note that we need to guarantee a worker since the work is not
2098− -- finished, therefore we cannot just rely on dispatchWorkerPaced
2099− -- which may or may not send a worker.
2100− noWorker <- allThreadsDone sv
2101− when noWorker $ pushWorker 0 sv
2102− return r
2103− else return False
2104−
2105−-------------------------------------------------------------------------------
2106−-- Creating an SVar
2107−-------------------------------------------------------------------------------
2108−
2109−getYieldRateInfo :: State t m a -> IO (Maybe YieldRateInfo)
2110−getYieldRateInfo st = do
2111− -- convert rate in Hertz to latency in Nanoseconds
2112− let rateToLatency r = if r <= 0 then maxBound else round $ 1.0e9 / r
2113− case getStreamRate st of
2114− Just (Rate low goal high buf) ->
2115− let l = rateToLatency goal
2116− minl = rateToLatency high
2117− maxl = rateToLatency low
2118− in mkYieldRateInfo l (LatencyRange minl maxl) buf
2119− Nothing -> return Nothing
2120−
2121− where
2122−
2123− mkYieldRateInfo latency latRange buf = do
2124− measured <- newIORef 0
2125− wcur <- newIORef (0,0,0)
2126− wcol <- newIORef (0,0,0)
2127− now <- getTime Monotonic
2128− wlong <- newIORef (0,now)
2129− period <- newIORef 1
2130− gainLoss <- newIORef (Count 0)
2131−
2132− return $ Just YieldRateInfo
2133− { svarLatencyTarget = latency
2134− , svarLatencyRange = latRange
2135− , svarRateBuffer = buf
2136− , svarGainedLostYields = gainLoss
2137− , workerBootstrapLatency = getStreamLatency st
2138− , workerPollingInterval = period
2139− , workerMeasuredLatency = measured
2140− , workerPendingLatency = wcur
2141− , workerCollectedLatency = wcol
2142− , svarAllTimeLatency = wlong
2143− }
2144−
2145−newSVarStats :: IO SVarStats
2146−newSVarStats = do
2147− disp <- newIORef 0
2148− maxWrk <- newIORef 0
2149− maxOq <- newIORef 0
2150− maxHs <- newIORef 0
2151− maxWq <- newIORef 0
2152− avgLat <- newIORef (0, NanoSecond64 0)
2153− maxLat <- newIORef (NanoSecond64 0)
2154− minLat <- newIORef (NanoSecond64 0)
2155− stpTime <- newIORef Nothing
2156−
2157− return SVarStats
2158− { totalDispatches = disp
2159− , maxWorkers = maxWrk
2160− , maxOutQSize = maxOq
2161− , maxHeapSize = maxHs
2162− , maxWorkQSize = maxWq
2163− , avgWorkerLatency = avgLat
2164− , minWorkerLatency = minLat
2165− , maxWorkerLatency = maxLat
2166− , svarStopTime = stpTime
2167− }
2168−
2169-- XXX remove polymorphism in t, inline f
321-- XXX remove polymorphism in t, inline f
2170getAheadSVar :: MonadAsync m
322getAheadSVar :: MonadAsync m
2171 => State t m a
323 => State t m a
@@ -2268,6 +420,34 @@ getAheadSVar st f mrun = do
2268 (xs, _) <- readIORef q
420 (xs, _) <- readIORef q
2269 return $ null xs
421 return $ null xs
2270
422
423+{-# INLINABLE newAheadVar #-}
424+newAheadVar :: MonadAsync m
425+ => State t m a
426+ -> t m a
427+ -> ( IORef ([t m a], Int)
428+ -> IORef (Heap (Entry Int (AheadHeapEntry t m a)), Maybe Int)
429+ -> State t m a
430+ -> SVar t m a
431+ -> Maybe WorkerInfo
432+ -> m ())
433+ -> m (SVar t m a)
434+newAheadVar st m wloop = do
435+ mrun <- captureMonadState
436+ sv <- liftIO $ getAheadSVar st wloop mrun
437+ sendFirstWorker sv m
438+
439+-------------------------------------------------------------------------------
440+-- WAhead
441+-------------------------------------------------------------------------------
442+
443+-- XXX To be implemented. Use a linked queue like WAsync and put back the
444+-- remaining computation at the back of the queue instead of the heap, and
445+-- increment the sequence number.
446+
447+-------------------------------------------------------------------------------
448+-- Parallel
449+-------------------------------------------------------------------------------
450+
2271getParallelSVar :: MonadIO m
451getParallelSVar :: MonadIO m
2272 => SVarStopStyle -> State t m a -> RunInIO m -> IO (SVar t m a)
452 => SVarStopStyle -> State t m a -> RunInIO m -> IO (SVar t m a)
2273getParallelSVar ss st mrun = do
453getParallelSVar ss st mrun = do
@@ -2349,62 +529,9 @@ getParallelSVar ss st mrun = do
2349 void $ tryPutMVar (pushBufferMVar sv) ()
529 void $ tryPutMVar (pushBufferMVar sv) ()
2350 return r
530 return r
2351
531
2352−sendFirstWorker :: MonadAsync m => SVar t m a -> t m a -> m (SVar t m a)
2353−sendFirstWorker sv m = do
2354− -- Note: We must have all the work on the queue before sending the
2355− -- pushworker, otherwise the pushworker may exit before we even get a
2356− -- chance to push.
2357− runIn <- captureMonadState
2358− liftIO $ enqueue sv (runIn, m)
2359− case yieldRateInfo sv of
2360− Nothing -> pushWorker 0 sv
2361− Just yinfo ->
2362− if svarLatencyTarget yinfo == maxBound
2363− then liftIO $ threadDelay maxBound
2364− else pushWorker 1 sv
2365− return sv
2366−
2367−{-# INLINABLE newAheadVar #-}
2368−newAheadVar :: MonadAsync m
2369− => State t m a
2370− -> t m a
2371− -> ( IORef ([t m a], Int)
2372− -> IORef (Heap (Entry Int (AheadHeapEntry t m a)), Maybe Int)
2373− -> State t m a
2374− -> SVar t m a
2375− -> Maybe WorkerInfo
2376− -> m ())
2377− -> m (SVar t m a)
2378−newAheadVar st m wloop = do
2379− mrun <- captureMonadState
2380− sv <- liftIO $ getAheadSVar st wloop mrun
2381− sendFirstWorker sv m
2382−
2383{-# INLINABLE newParallelVar #-}
532{-# INLINABLE newParallelVar #-}
2384newParallelVar :: MonadAsync m
533newParallelVar :: MonadAsync m
2385 => SVarStopStyle -> State t m a -> m (SVar t m a)
534 => SVarStopStyle -> State t m a -> m (SVar t m a)
2386newParallelVar ss st = do
535newParallelVar ss st = do
2387 mrun <- captureMonadState
536 mrun <- captureMonadState
2388 liftIO $ getParallelSVar ss st mrun
537 liftIO $ getParallelSVar ss st mrun
2389−
2390−-------------------------------------------------------------------------------
2391−-- Write a stream to an SVar
2392−-------------------------------------------------------------------------------
2393−
2394−-- XXX this errors out for Parallel/Ahead SVars
2395−-- | Write a stream to an 'SVar' in a non-blocking manner. The stream can then
2396−-- be read back from the SVar using 'fromSVar'.
2397−toStreamVar :: MonadAsync m => SVar t m a -> t m a -> m ()
2398−toStreamVar sv m = do
2399− runIn <- captureMonadState
2400− liftIO $ enqueue sv (runIn, m)
2401− done <- allThreadsDone sv
2402− -- XXX This is safe only when called from the consumer thread or when no
2403− -- consumer is present. There may be a race if we are not running in the
2404− -- consumer thread.
2405− -- XXX do this only if the work queue is not empty. The work may have been
2406− -- carried out by existing workers.
2407− when done $
2408− case yieldRateInfo sv of
2409− Nothing -> pushWorker 0 sv
2410− Just _ -> pushWorker 1 sv
src/Streamly/Internal/Data/SVar/Dispatch.hsnew file+742
@@ -0,0 +1,742 @@
1+-- |
2+-- Module : Streamly.Internal.Data.SVar.Dispatch
3+-- Copyright : (c) 2017 Composewell Technologies
4+-- License : BSD-3-Clause
5+-- Maintainer : streamly@composewell.com
6+-- Stability : experimental
7+-- Portability : GHC
8+--
9+--
10+module Streamly.Internal.Data.SVar.Dispatch
11+ (
12+ -- * Latency collection
13+ collectLatency
14+
15+ -- * Diagnostics
16+ , withDiagMVar
17+ , dumpSVar
18+ , printSVar
19+
20+ -- * Thread accounting
21+ , delThread
22+ , modifyThread
23+ , allThreadsDone
24+
25+ -- * Dispatching
26+ , recordMaxWorkers
27+ , pushWorker
28+ , pushWorkerPar
29+ , dispatchWorker
30+ , dispatchWorkerPaced
31+ , sendWorkerWait
32+ , sendFirstWorker
33+ , sendWorkerDelay
34+ , sendWorkerDelayPaced
35+ )
36+where
37+
38+#include "inline.hs"
39+
40+import Control.Concurrent (takeMVar, tryReadMVar, ThreadId, threadDelay)
41+import Control.Concurrent.MVar (tryPutMVar)
42+import Control.Exception
43+ (assert, catches, throwIO, Handler(..), BlockedIndefinitelyOnMVar(..),
44+ BlockedIndefinitelyOnSTM(..))
45+import Control.Monad (when, void)
46+import Control.Monad.IO.Class (MonadIO(liftIO))
47+import Data.Maybe (fromJust, fromMaybe)
48+import Data.IORef (IORef, modifyIORef, newIORef, readIORef, writeIORef)
49+#if __GLASGOW_HASKELL__ < 804
50+import Data.Semigroup ((<>))
51+#endif
52+import Streamly.Internal.Control.Concurrent
53+ (MonadAsync, captureMonadState, doFork)
54+import Streamly.Internal.Data.Atomics
55+ (atomicModifyIORefCAS, atomicModifyIORefCAS_, writeBarrier,
56+ storeLoadBarrier)
57+import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
58+import Streamly.Internal.Data.Time.Units
59+ (AbsTime, NanoSecond64(..), MicroSecond64(..), diffAbsTime64,
60+ fromRelTime64, toRelTime64, showNanoSecond64, showRelTime64)
61+import System.IO (hPutStrLn, stderr)
62+
63+import qualified Data.Heap as H
64+import qualified Data.Set as S
65+
66+import Streamly.Internal.Data.SVar.Type
67+import Streamly.Internal.Data.SVar.Worker
68+
69+-------------------------------------------------------------------------------
70+-- Worker latency data processing
71+-------------------------------------------------------------------------------
72+
73+-- Every once in a while workers update the latencies and check the yield rate.
74+-- They return if we are above the expected yield rate. If we check too often
75+-- it may impact performance, if we check less often we may have a stale
76+-- picture. We update every minThreadDelay but we translate that into a yield
77+-- count based on latency so that the checking overhead is little.
78+--
79+-- XXX use a generation count to indicate that the value is updated. If the
80+-- value is updated an existing worker must check it again on the next yield.
81+-- Otherwise it is possible that we may keep updating it and because of the mod
82+-- worker keeps skipping it.
83+updateWorkerPollingInterval :: YieldRateInfo -> NanoSecond64 -> IO ()
84+updateWorkerPollingInterval yinfo latency = do
85+ let periodRef = workerPollingInterval yinfo
86+ cnt = max 1 $ minThreadDelay `div` latency
87+ period = min cnt (fromIntegral magicMaxBuffer)
88+
89+ writeIORef periodRef (fromIntegral period)
90+
91+{-# INLINE recordMinMaxLatency #-}
92+recordMinMaxLatency :: SVar t m a -> NanoSecond64 -> IO ()
93+recordMinMaxLatency sv new = do
94+ let ss = svarStats sv
95+ minLat <- readIORef (minWorkerLatency ss)
96+ when (new < minLat || minLat == 0) $
97+ writeIORef (minWorkerLatency ss) new
98+
99+ maxLat <- readIORef (maxWorkerLatency ss)
100+ when (new > maxLat) $ writeIORef (maxWorkerLatency ss) new
101+
102+recordAvgLatency :: SVar t m a -> (Count, NanoSecond64) -> IO ()
103+recordAvgLatency sv (count, time) = do
104+ let ss = svarStats sv
105+ modifyIORef (avgWorkerLatency ss) $
106+ \(cnt, t) -> (cnt + count, t + time)
107+
108+-- Pour the pending latency stats into a collection bucket
109+{-# INLINE collectWorkerPendingLatency #-}
110+collectWorkerPendingLatency
111+ :: IORef (Count, Count, NanoSecond64)
112+ -> IORef (Count, Count, NanoSecond64)
113+ -> IO (Count, Maybe (Count, NanoSecond64))
114+collectWorkerPendingLatency cur col = do
115+ (fcount, count, time) <- atomicModifyIORefCAS cur $ \v -> ((0,0,0), v)
116+
117+ (fcnt, cnt, t) <- readIORef col
118+ let totalCount = fcnt + fcount
119+ latCount = cnt + count
120+ latTime = t + time
121+ writeIORef col (totalCount, latCount, latTime)
122+
123+ assert (latCount == 0 || latTime /= 0) (return ())
124+ let latPair =
125+ if latCount > 0 && latTime > 0
126+ then Just (latCount, latTime)
127+ else Nothing
128+ return (totalCount, latPair)
129+
130+{-# INLINE shouldUseCollectedBatch #-}
131+shouldUseCollectedBatch
132+ :: Count
133+ -> NanoSecond64
134+ -> NanoSecond64
135+ -> NanoSecond64
136+ -> Bool
137+shouldUseCollectedBatch collectedYields collectedTime newLat prevLat =
138+ let r = fromIntegral newLat / fromIntegral prevLat :: Double
139+ in (collectedYields > fromIntegral magicMaxBuffer)
140+ || (collectedTime > minThreadDelay)
141+ || (prevLat > 0 && (r > 2 || r < 0.5))
142+ || (prevLat == 0)
143+
144+-- Returns a triple, (1) yield count since last collection, (2) the base time
145+-- when we started counting, (3) average latency in the last measurement
146+-- period. The former two are used for accurate measurement of the going rate
147+-- whereas the average is used for future estimates e.g. how many workers
148+-- should be maintained to maintain the rate.
149+-- CAUTION! keep it in sync with getWorkerLatency
150+collectLatency :: SVar t m a
151+ -> YieldRateInfo
152+ -> Bool
153+ -> IO (Count, AbsTime, NanoSecond64)
154+collectLatency sv yinfo drain = do
155+ let cur = workerPendingLatency yinfo
156+ col = workerCollectedLatency yinfo
157+ longTerm = svarAllTimeLatency yinfo
158+ measured = workerMeasuredLatency yinfo
159+
160+ (newCount, newLatPair) <- collectWorkerPendingLatency cur col
161+ (lcount, ltime) <- readIORef longTerm
162+ prevLat <- readIORef measured
163+
164+ let newLcount = lcount + newCount
165+ retWith lat = return (newLcount, ltime, lat)
166+
167+ case newLatPair of
168+ Nothing -> retWith prevLat
169+ Just (count, time) -> do
170+ let newLat = time `div` fromIntegral count
171+ when (svarInspectMode sv) $ recordMinMaxLatency sv newLat
172+ -- When we have collected a significant sized batch we compute the
173+ -- new latency using that batch and return the new latency,
174+ -- otherwise we return the previous latency derived from the
175+ -- previous batch.
176+ if shouldUseCollectedBatch newCount time newLat prevLat || drain
177+ then do
178+ -- XXX make this NOINLINE?
179+ updateWorkerPollingInterval yinfo (max newLat prevLat)
180+ when (svarInspectMode sv) $ recordAvgLatency sv (count, time)
181+ writeIORef col (0, 0, 0)
182+ writeIORef measured ((prevLat + newLat) `div` 2)
183+ modifyIORef longTerm $ \(_, t) -> (newLcount, t)
184+ retWith newLat
185+ else retWith prevLat
186+
187+-------------------------------------------------------------------------------
188+-- Dumping the SVar for debug/diag
189+-------------------------------------------------------------------------------
190+
191+dumpSVarStats :: SVar t m a -> SVarStats -> SVarStyle -> IO String
192+dumpSVarStats sv ss style = do
193+ case yieldRateInfo sv of
194+ Nothing -> return ()
195+ Just yinfo -> do
196+ _ <- liftIO $ collectLatency sv yinfo True
197+ return ()
198+
199+ dispatches <- readIORef $ totalDispatches ss
200+ maxWrk <- readIORef $ maxWorkers ss
201+ maxOq <- readIORef $ maxOutQSize ss
202+ maxHp <- readIORef $ maxHeapSize ss
203+ minLat <- readIORef $ minWorkerLatency ss
204+ maxLat <- readIORef $ maxWorkerLatency ss
205+ (avgCnt, avgTime) <- readIORef $ avgWorkerLatency ss
206+ (svarCnt, svarGainLossCnt, svarLat) <- case yieldRateInfo sv of
207+ Nothing -> return (0, 0, 0)
208+ Just yinfo -> do
209+ (cnt, startTime) <- readIORef $ svarAllTimeLatency yinfo
210+ if cnt > 0
211+ then do
212+ t <- readIORef (svarStopTime ss)
213+ gl <- readIORef (svarGainedLostYields yinfo)
214+ case t of
215+ Nothing -> do
216+ now <- getTime Monotonic
217+ let interval = diffAbsTime64 now startTime
218+ return (cnt, gl, interval `div` fromIntegral cnt)
219+ Just stopTime -> do
220+ let interval = diffAbsTime64 stopTime startTime
221+ return (cnt, gl, interval `div` fromIntegral cnt)
222+ else return (0, 0, 0)
223+
224+ return $ unlines
225+ [ "total dispatches = " <> show dispatches
226+ , "max workers = " <> show maxWrk
227+ , "max outQSize = " <> show maxOq
228+ <> (if style == AheadVar
229+ then "\nheap max size = " <> show maxHp
230+ else "")
231+ <> (if minLat > 0
232+ then "\nmin worker latency = " <> showNanoSecond64 minLat
233+ else "")
234+ <> (if maxLat > 0
235+ then "\nmax worker latency = " <> showNanoSecond64 maxLat
236+ else "")
237+ <> (if avgCnt > 0
238+ then let lat = avgTime `div` fromIntegral avgCnt
239+ in "\navg worker latency = " <> showNanoSecond64 lat
240+ else "")
241+ <> (if svarLat > 0
242+ then "\nSVar latency = " <> showRelTime64 svarLat
243+ else "")
244+ <> (if svarCnt > 0
245+ then "\nSVar yield count = " <> show svarCnt
246+ else "")
247+ <> (if svarGainLossCnt > 0
248+ then "\nSVar gain/loss yield count = " <> show svarGainLossCnt
249+ else "")
250+ ]
251+
252+{-# NOINLINE dumpSVar #-}
253+dumpSVar :: SVar t m a -> IO String
254+dumpSVar sv = do
255+ (oqList, oqLen) <- readIORef $ outputQueue sv
256+ db <- tryReadMVar $ outputDoorBell sv
257+ aheadDump <-
258+ if svarStyle sv == AheadVar
259+ then do
260+ (oheap, oheapSeq) <- readIORef $ outputHeap sv
261+ (wq, wqSeq) <- readIORef $ aheadWorkQueue sv
262+ return $ unlines
263+ [ "heap length = " <> show (H.size oheap)
264+ , "heap seqeunce = " <> show oheapSeq
265+ , "work queue length = " <> show (length wq)
266+ , "work queue sequence = " <> show wqSeq
267+ ]
268+ else return []
269+
270+ let style = svarStyle sv
271+ waiting <-
272+ if style /= ParallelVar
273+ then readIORef $ needDoorBell sv
274+ else return False
275+ rthread <- readIORef $ workerThreads sv
276+ workers <- readIORef $ workerCount sv
277+ stats <- dumpSVarStats sv (svarStats sv) (svarStyle sv)
278+
279+ return $ unlines
280+ [
281+ "Creator tid = " <> show (svarCreator sv),
282+ "style = " <> show (svarStyle sv)
283+ , "---------CURRENT STATE-----------"
284+ , "outputQueue length computed = " <> show (length oqList)
285+ , "outputQueue length maintained = " <> show oqLen
286+ -- XXX print the types of events in the outputQueue, first 5
287+ , "outputDoorBell = " <> show db
288+ ]
289+ <> aheadDump
290+ <> unlines
291+ [ "needDoorBell = " <> show waiting
292+ , "running threads = " <> show rthread
293+ -- XXX print the status of first 5 threads
294+ , "running thread count = " <> show workers
295+ ]
296+ <> "---------STATS-----------\n"
297+ <> stats
298+
299+-- MVar diagnostics has some overhead - around 5% on AsyncT null benchmark, we
300+-- can keep it on in production to debug problems quickly if and when they
301+-- happen, but it may result in unexpected output when threads are left hanging
302+-- until they are GCed because the consumer went away.
303+
304+{-# NOINLINE mvarExcHandler #-}
305+mvarExcHandler :: SVar t m a -> String -> BlockedIndefinitelyOnMVar -> IO ()
306+mvarExcHandler sv label e@BlockedIndefinitelyOnMVar = do
307+ svInfo <- dumpSVar sv
308+ hPutStrLn stderr $ label <> " " <> "BlockedIndefinitelyOnMVar\n" <> svInfo
309+ throwIO e
310+
311+{-# NOINLINE stmExcHandler #-}
312+stmExcHandler :: SVar t m a -> String -> BlockedIndefinitelyOnSTM -> IO ()
313+stmExcHandler sv label e@BlockedIndefinitelyOnSTM = do
314+ svInfo <- dumpSVar sv
315+ hPutStrLn stderr $ label <> " " <> "BlockedIndefinitelyOnSTM\n" <> svInfo
316+ throwIO e
317+
318+withDiagMVar :: SVar t m a -> String -> IO () -> IO ()
319+withDiagMVar sv label action =
320+ if svarInspectMode sv
321+ then
322+ action `catches` [ Handler (mvarExcHandler sv label)
323+ , Handler (stmExcHandler sv label)
324+ ]
325+ else action
326+
327+printSVar :: SVar t m a -> String -> IO ()
328+printSVar sv how = do
329+ svInfo <- dumpSVar sv
330+ hPutStrLn stderr $ "\n" <> how <> "\n" <> svInfo
331+
332+-------------------------------------------------------------------------------
333+-- Thread accounting
334+-------------------------------------------------------------------------------
335+
336+-- Thread tracking is needed for two reasons:
337+--
338+-- 1) Killing threads on exceptions. Threads may not be left to go away by
339+-- themselves because they may run for significant times before going away or
340+-- worse they may be stuck in IO and never go away.
341+--
342+-- 2) To know when all threads are done and the stream has ended.
343+
344+{-# NOINLINE addThread #-}
345+addThread :: MonadIO m => SVar t m a -> ThreadId -> m ()
346+addThread sv tid =
347+ liftIO $ modifyIORef (workerThreads sv) (S.insert tid)
348+
349+-- This is cheaper than modifyThread because we do not have to send a
350+-- outputDoorBell This can make a difference when more workers are being
351+-- dispatched.
352+{-# INLINE delThread #-}
353+delThread :: MonadIO m => SVar t m a -> ThreadId -> m ()
354+delThread sv tid =
355+ liftIO $ modifyIORef (workerThreads sv) (S.delete tid)
356+
357+-- If present then delete else add. This takes care of out of order add and
358+-- delete i.e. a delete arriving before we even added a thread.
359+-- This occurs when the forked thread is done even before the 'addThread' right
360+-- after the fork gets a chance to run.
361+{-# INLINE modifyThread #-}
362+modifyThread :: MonadIO m => SVar t m a -> ThreadId -> m ()
363+modifyThread sv tid = do
364+ changed <- liftIO $ atomicModifyIORefCAS (workerThreads sv) $ \old ->
365+ if S.member tid old
366+ then let new = S.delete tid old in (new, new)
367+ else let new = S.insert tid old in (new, old)
368+ when (null changed) $
369+ liftIO $ do
370+ writeBarrier
371+ void $ tryPutMVar (outputDoorBell sv) ()
372+
373+-- | This is safe even if we are adding more threads concurrently because if
374+-- a child thread is adding another thread then anyway 'workerThreads' will
375+-- not be empty.
376+{-# INLINE allThreadsDone #-}
377+allThreadsDone :: MonadIO m => SVar t m a -> m Bool
378+allThreadsDone sv = liftIO $ S.null <$> readIORef (workerThreads sv)
379+
380+-------------------------------------------------------------------------------
381+-- Dispatching workers
382+-------------------------------------------------------------------------------
383+
384+{-# NOINLINE recordMaxWorkers #-}
385+recordMaxWorkers :: MonadIO m => SVar t m a -> m ()
386+recordMaxWorkers sv = liftIO $ do
387+ active <- readIORef (workerCount sv)
388+ maxWrk <- readIORef (maxWorkers $ svarStats sv)
389+ when (active > maxWrk) $ writeIORef (maxWorkers $ svarStats sv) active
390+ modifyIORef (totalDispatches $ svarStats sv) (+1)
391+
392+{-# NOINLINE pushWorker #-}
393+pushWorker :: MonadAsync m => Count -> SVar t m a -> m ()
394+pushWorker yieldMax sv = do
395+ liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n + 1
396+ when (svarInspectMode sv) $ recordMaxWorkers sv
397+ -- This allocation matters when significant number of workers are being
398+ -- sent. We allocate it only when needed.
399+ winfo <-
400+ case yieldRateInfo sv of
401+ Nothing -> return Nothing
402+ Just _ -> liftIO $ do
403+ cntRef <- newIORef 0
404+ t <- getTime Monotonic
405+ lat <- newIORef (0, t)
406+ return $ Just WorkerInfo
407+ { workerYieldMax = yieldMax
408+ , workerYieldCount = cntRef
409+ , workerLatencyStart = lat
410+ }
411+ doFork (workLoop sv winfo) (svarMrun sv) (handleChildException sv)
412+ >>= addThread sv
413+
414+-- XXX we can push the workerCount modification in accountThread and use the
415+-- same pushWorker for Parallel case as well.
416+--
417+-- | In contrast to pushWorker which always happens only from the consumer
418+-- thread, a pushWorkerPar can happen concurrently from multiple threads on the
419+-- producer side. So we need to use a thread safe modification of
420+-- workerThreads. Alternatively, we can use a CreateThread event to avoid
421+-- using a CAS based modification.
422+{-# INLINE pushWorkerPar #-}
423+pushWorkerPar
424+ :: MonadAsync m
425+ => SVar t m a -> (Maybe WorkerInfo -> m ()) -> m ()
426+pushWorkerPar sv wloop =
427+ if svarInspectMode sv
428+ then forkWithDiag
429+ else doFork (wloop Nothing) (svarMrun sv) (handleChildException sv)
430+ >>= modifyThread sv
431+
432+ where
433+
434+ {-# NOINLINE forkWithDiag #-}
435+ forkWithDiag = do
436+ -- We do not use workerCount in case of ParallelVar but still there is
437+ -- no harm in maintaining it correctly.
438+ liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n + 1
439+ recordMaxWorkers sv
440+ -- This allocation matters when significant number of workers are being
441+ -- sent. We allocate it only when needed. The overhead increases by 4x.
442+ winfo <-
443+ case yieldRateInfo sv of
444+ Nothing -> return Nothing
445+ Just _ -> liftIO $ do
446+ cntRef <- newIORef 0
447+ t <- getTime Monotonic
448+ lat <- newIORef (0, t)
449+ return $ Just WorkerInfo
450+ { workerYieldMax = 0
451+ , workerYieldCount = cntRef
452+ , workerLatencyStart = lat
453+ }
454+
455+ doFork (wloop winfo) (svarMrun sv) (handleChildException sv)
456+ >>= modifyThread sv
457+
458+-- Returns:
459+-- True: can dispatch more
460+-- False: cannot dispatch any more
461+dispatchWorker :: MonadAsync m => Count -> SVar t m a -> m Bool
462+dispatchWorker yieldCount sv = do
463+ let workerLimit = maxWorkerLimit sv
464+ -- XXX in case of Ahead streams we should not send more than one worker
465+ -- when the work queue is done but heap is not done.
466+ done <- liftIO $ isWorkDone sv
467+ -- Note, "done" may not mean that the work is actually finished if there
468+ -- are workers active, because there may be a worker which has not yet
469+ -- queued the leftover work.
470+ if not done
471+ then do
472+ qDone <- liftIO $ isQueueDone sv
473+ -- This count may not be accurate as it is decremented by the workers
474+ -- and we have no synchronization with that decrement.
475+ active <- liftIO $ readIORef $ workerCount sv
476+ if not qDone
477+ then do
478+ -- Note that we may deadlock if the previous workers (tasks in the
479+ -- stream) wait/depend on the future workers (tasks in the stream)
480+ -- executing. In that case we should either configure the maxWorker
481+ -- count to higher or use parallel style instead of ahead or async
482+ -- style.
483+ limit <- case remainingWork sv of
484+ Nothing -> return workerLimit
485+ Just ref -> do
486+ n <- liftIO $ readIORef ref
487+ case yieldRateInfo sv of
488+ Just _ -> return workerLimit
489+ Nothing ->
490+ return $
491+ case workerLimit of
492+ Unlimited -> Limited (fromIntegral n)
493+ Limited lim -> Limited $ min lim (fromIntegral n)
494+
495+ -- XXX for ahead streams shall we take the heap yields into account
496+ -- for controlling the dispatch? We should not dispatch if the heap
497+ -- has already got the limit covered.
498+ let dispatch = pushWorker yieldCount sv >> return True
499+ in case limit of
500+ Unlimited -> dispatch
501+ -- Note that the use of remainingWork and workerCount is not
502+ -- atomic and the counts may even have changed between reading
503+ -- and using them here, so this is just approximate logic and
504+ -- we cannot rely on it for correctness. We may actually
505+ -- dispatch more workers than required.
506+ Limited lim | lim > fromIntegral active -> dispatch
507+ _ -> return False
508+ else do
509+ when (active <= 0) $ pushWorker 0 sv
510+ return False
511+ else return False
512+
513+-- XXX in case of ahead style stream we need to take the heap size into account
514+-- because we return the workers on the basis of that which causes a condition
515+-- where we keep dispatching and they keep returning. So we must have exactly
516+-- the same logic for not dispatching and for returning.
517+--
518+-- Returns:
519+-- True: can dispatch more
520+-- False: full, no more dispatches
521+dispatchWorkerPaced :: MonadAsync m => SVar t m a -> m Bool
522+dispatchWorkerPaced sv = do
523+ let yinfo = fromJust $ yieldRateInfo sv
524+ (svarYields, svarElapsed, wLatency) <- do
525+ now <- liftIO $ getTime Monotonic
526+ (yieldCount, baseTime, lat) <-
527+ liftIO $ collectLatency sv yinfo False
528+ let elapsed = fromRelTime64 $ diffAbsTime64 now baseTime
529+ let latency =
530+ if lat == 0
531+ then fromMaybe lat (workerBootstrapLatency yinfo)
532+ else lat
533+
534+ return (yieldCount, elapsed, latency)
535+
536+ if wLatency == 0
537+ -- Need to measure the latency with a single worker before we can perform
538+ -- any computation.
539+ then return False
540+ else do
541+ let workerLimit = maxWorkerLimit sv
542+ let targetLat = svarLatencyTarget yinfo
543+ let range = svarLatencyRange yinfo
544+ gainLoss <- liftIO $ readIORef (svarGainedLostYields yinfo)
545+ let work = estimateWorkers workerLimit svarYields gainLoss svarElapsed
546+ wLatency targetLat range
547+
548+ -- XXX we need to take yieldLimit into account here. If we are at the
549+ -- end of the limit as well as the time, we should not be sleeping.
550+ -- If we are not actually planning to dispatch any more workers we need
551+ -- to take that in account.
552+ case work of
553+ BlockWait s -> do
554+ assert (s >= 0) (return ())
555+ -- XXX note that when we return from here we will block waiting
556+ -- for the result from the existing worker. If that takes too
557+ -- long we won't be able to send another worker until the
558+ -- result arrives.
559+ --
560+ -- Sleep only if there are no active workers, otherwise we will
561+ -- defer the output of those. Note we cannot use workerCount
562+ -- here as it is not a reliable way to ensure there are
563+ -- definitely no active workers. When workerCount is 0 we may
564+ -- still have a Stop event waiting in the outputQueue.
565+ done <- allThreadsDone sv
566+ when done $ void $ do
567+ let us = fromRelTime64 (toRelTime64 s) :: MicroSecond64
568+ liftIO $ threadDelay (fromIntegral us)
569+ dispatchWorker 1 sv
570+ return False
571+ PartialWorker yields -> do
572+ assert (yields > 0) (return ())
573+ updateGainedLostYields yinfo yields
574+
575+ done <- allThreadsDone sv
576+ when done $ void $ dispatchWorker yields sv
577+ return False
578+ ManyWorkers netWorkers yields -> do
579+ assert (netWorkers >= 1) (return ())
580+ assert (yields >= 0) (return ())
581+ updateGainedLostYields yinfo yields
582+
583+ let periodRef = workerPollingInterval yinfo
584+ ycnt = max 1 $ yields `div` fromIntegral netWorkers
585+ period = min ycnt (fromIntegral magicMaxBuffer)
586+
587+ old <- liftIO $ readIORef periodRef
588+ when (period < old) $
589+ liftIO $ writeIORef periodRef period
590+
591+ cnt <- liftIO $ readIORef $ workerCount sv
592+ if cnt < netWorkers
593+ then do
594+ let total = netWorkers - cnt
595+ batch = max 1 $ fromIntegral $
596+ minThreadDelay `div` targetLat
597+ -- XXX stagger the workers over a period?
598+ -- XXX cannot sleep, as that would mean we cannot process
599+ -- the outputs. need to try a different mechanism to
600+ -- stagger.
601+ -- when (total > batch) $
602+ -- liftIO $ threadDelay $ nanoToMicroSecs minThreadDelay
603+ dispatchN (min total batch)
604+ else return False
605+
606+ where
607+
608+ updateGainedLostYields yinfo yields = do
609+ let buf = fromIntegral $ svarRateBuffer yinfo
610+ when (yields /= 0 && abs yields > buf) $ do
611+ let delta =
612+ if yields > 0
613+ then yields - buf
614+ else yields + buf
615+ liftIO $ modifyIORef (svarGainedLostYields yinfo) (+ delta)
616+
617+ dispatchN n =
618+ if n == 0
619+ then return True
620+ else do
621+ r <- dispatchWorker 0 sv
622+ if r
623+ then dispatchN (n - 1)
624+ else return False
625+
626+{-# NOINLINE sendWorkerWait #-}
627+sendWorkerWait
628+ :: MonadAsync m
629+ => (SVar t m a -> IO ())
630+ -> (SVar t m a -> m Bool)
631+ -> SVar t m a
632+ -> m ()
633+sendWorkerWait delay dispatch sv = do
634+ -- Note that we are guaranteed to have at least one outstanding worker when
635+ -- we enter this function. So if we sleep we are guaranteed to be woken up
636+ -- by an outputDoorBell, when the worker exits.
637+
638+ liftIO $ delay sv
639+ (_, n) <- liftIO $ readIORef (outputQueue sv)
640+ when (n <= 0) $ do
641+ -- The queue may be empty temporarily if the worker has dequeued the
642+ -- work item but has not enqueued the remaining part yet. For the same
643+ -- reason, a worker may come back if it tries to dequeue and finds the
644+ -- queue empty, even though the whole work has not finished yet.
645+
646+ -- If we find that the queue is empty, but it may be empty
647+ -- temporarily, when we checked it. If that's the case we might
648+ -- sleep indefinitely unless the active workers produce some
649+ -- output. We may deadlock specially if the otuput from the active
650+ -- workers depends on the future workers that we may never send.
651+ -- So in case the queue was temporarily empty set a flag to inform
652+ -- the enqueue to send us a doorbell.
653+
654+ -- Note that this is just a best effort mechanism to avoid a
655+ -- deadlock. Deadlocks may still happen if for some weird reason
656+ -- the consuming computation shares an MVar or some other resource
657+ -- with the producing computation and gets blocked on that resource
658+ -- and therefore cannot do any pushworker to add more threads to
659+ -- the producer. In such cases the programmer should use a parallel
660+ -- style so that all the producers are scheduled immediately and
661+ -- unconditionally. We can also use a separate monitor thread to
662+ -- push workers instead of pushing them from the consumer, but then
663+ -- we are no longer using pull based concurrency rate adaptation.
664+ --
665+ -- XXX update this in the tutorial.
666+ --
667+ -- Having pending active workers does not mean that we are guaranteed
668+ -- to be woken up if we sleep. In case of Ahead streams, there may be
669+ -- queued items in the heap even though the outputQueue is empty, and
670+ -- we may have active workers which are deadlocked on those items to be
671+ -- processed by the consumer. We should either guarantee that any
672+ -- worker, before returning, clears the heap or we send a worker to
673+ -- clear it. Normally we always send a worker if no output is seen, but
674+ -- if the thread limit is reached or we are using pacing then we may
675+ -- not send a worker. See the concurrentApplication test in the tests,
676+ -- that test case requires at least one yield from the producer to not
677+ -- deadlock, if the last workers output is stuck in the heap then this
678+ -- test fails. This problem can be extended to n threads when the
679+ -- consumer may depend on the evaluation of next n items in the
680+ -- producer stream.
681+
682+ -- register for the outputDoorBell before we check the queue so that if
683+ -- we sleep because the queue was empty we are guaranteed to get a
684+ -- doorbell on the next enqueue.
685+
686+ liftIO $ atomicModifyIORefCAS_ (needDoorBell sv) $ const True
687+ liftIO storeLoadBarrier
688+ canDoMore <- dispatch sv
689+
690+ -- XXX test for the case when we miss sending a worker when the worker
691+ -- count is more than 1500.
692+ --
693+ -- XXX Assert here that if the heap is not empty then there is at
694+ -- least one outstanding worker. Otherwise we could be sleeping
695+ -- forever.
696+
697+ if canDoMore
698+ then sendWorkerWait delay dispatch sv
699+ else do
700+ liftIO $ withDiagMVar sv "sendWorkerWait: nothing to do"
701+ $ takeMVar (outputDoorBell sv)
702+ (_, len) <- liftIO $ readIORef (outputQueue sv)
703+ when (len <= 0) $ sendWorkerWait delay dispatch sv
704+
705+sendFirstWorker :: MonadAsync m => SVar t m a -> t m a -> m (SVar t m a)
706+sendFirstWorker sv m = do
707+ -- Note: We must have all the work on the queue before sending the
708+ -- pushworker, otherwise the pushworker may exit before we even get a
709+ -- chance to push.
710+ runIn <- captureMonadState
711+ liftIO $ enqueue sv (runIn, m)
712+ case yieldRateInfo sv of
713+ Nothing -> pushWorker 0 sv
714+ Just yinfo ->
715+ if svarLatencyTarget yinfo == maxBound
716+ then liftIO $ threadDelay maxBound
717+ else pushWorker 1 sv
718+ return sv
719+
720+sendWorkerDelayPaced :: SVar t m a -> IO ()
721+sendWorkerDelayPaced _ = return ()
722+
723+sendWorkerDelay :: SVar t m a -> IO ()
724+sendWorkerDelay _sv =
725+ -- XXX we need a better way to handle this than hardcoded delays. The
726+ -- delays may be different for different systems.
727+ -- If there is a usecase where this is required we can create a combinator
728+ -- to set it as a config in the state.
729+ {-
730+ do
731+ ncpu <- getNumCapabilities
732+ if ncpu <= 1
733+ then
734+ if (svarStyle sv == AheadVar)
735+ then threadDelay 100
736+ else threadDelay 25
737+ else
738+ if (svarStyle sv == AheadVar)
739+ then threadDelay 100
740+ else threadDelay 10
741+ -}
742+ return ()
src/Streamly/Internal/Data/SVar/Pull.hsnew file+164
@@ -0,0 +1,164 @@
1+-- |
2+-- Module : Streamly.Internal.Data.SVar.Pull
3+-- Copyright : (c) 2017 Composewell Technologies
4+-- License : BSD-3-Clause
5+-- Maintainer : streamly@composewell.com
6+-- Stability : experimental
7+-- Portability : GHC
8+--
9+--
10+module Streamly.Internal.Data.SVar.Pull
11+ (
12+ -- * Read Output
13+ readOutputQBasic
14+ , readOutputQRaw
15+ , readOutputQPaced
16+ , readOutputQBounded
17+
18+ -- * Postprocess Hook After Reading
19+ , postProcessPaced
20+ , postProcessBounded
21+
22+ -- * Release Resources
23+ , cleanupSVar
24+ , cleanupSVarFromWorker
25+ )
26+where
27+
28+#include "inline.hs"
29+
30+import Control.Concurrent (myThreadId, throwTo)
31+import Control.Monad (when, void)
32+import Control.Monad.IO.Class (MonadIO(liftIO))
33+import Data.IORef (readIORef, writeIORef)
34+import Data.IORef (IORef)
35+import Streamly.Internal.Control.Concurrent (MonadAsync)
36+import Streamly.Internal.Data.Atomics (atomicModifyIORefCAS)
37+
38+import qualified Data.Set as S
39+
40+import Streamly.Internal.Data.SVar.Type
41+import Streamly.Internal.Data.SVar.Dispatch
42+
43+-------------------------------------------------------------------------------
44+-- Reading from the workers' output queue/buffer
45+-------------------------------------------------------------------------------
46+
47+{-# INLINE readOutputQBasic #-}
48+readOutputQBasic :: IORef ([ChildEvent a], Int) -> IO ([ChildEvent a], Int)
49+readOutputQBasic q = atomicModifyIORefCAS q $ \x -> (([],0), x)
50+
51+{-# INLINE readOutputQRaw #-}
52+readOutputQRaw :: SVar t m a -> IO ([ChildEvent a], Int)
53+readOutputQRaw sv = do
54+ (list, len) <- readOutputQBasic (outputQueue sv)
55+ when (svarInspectMode sv) $ do
56+ let ref = maxOutQSize $ svarStats sv
57+ oqLen <- readIORef ref
58+ when (len > oqLen) $ writeIORef ref len
59+ return (list, len)
60+
61+readOutputQBounded :: MonadAsync m => SVar t m a -> m [ChildEvent a]
62+readOutputQBounded sv = do
63+ (list, len) <- liftIO $ readOutputQRaw sv
64+ -- When there is no output seen we dispatch more workers to help
65+ -- out if there is work pending in the work queue.
66+ if len <= 0
67+ then blockingRead
68+ else do
69+ -- send a worker proactively, if needed, even before we start
70+ -- processing the output. This may degrade single processor
71+ -- perf but improves multi-processor, because of more
72+ -- parallelism
73+ sendOneWorker
74+ return list
75+
76+ where
77+
78+ sendOneWorker = do
79+ cnt <- liftIO $ readIORef $ workerCount sv
80+ when (cnt <= 0) $ do
81+ done <- liftIO $ isWorkDone sv
82+ when (not done) (pushWorker 0 sv)
83+
84+ {-# INLINE blockingRead #-}
85+ blockingRead = do
86+ sendWorkerWait sendWorkerDelay (dispatchWorker 0) sv
87+ liftIO (fst `fmap` readOutputQRaw sv)
88+
89+readOutputQPaced :: MonadAsync m => SVar t m a -> m [ChildEvent a]
90+readOutputQPaced sv = do
91+ (list, len) <- liftIO $ readOutputQRaw sv
92+ if len <= 0
93+ then blockingRead
94+ else do
95+ -- XXX send a worker proactively, if needed, even before we start
96+ -- processing the output.
97+ void $ dispatchWorkerPaced sv
98+ return list
99+
100+ where
101+
102+ {-# INLINE blockingRead #-}
103+ blockingRead = do
104+ sendWorkerWait sendWorkerDelayPaced dispatchWorkerPaced sv
105+ liftIO (fst `fmap` readOutputQRaw sv)
106+
107+postProcessPaced :: MonadAsync m => SVar t m a -> m Bool
108+postProcessPaced sv = do
109+ workersDone <- allThreadsDone sv
110+ -- XXX If during consumption we figure out we are getting delayed then we
111+ -- should trigger dispatch there as well. We should try to check on the
112+ -- workers after consuming every n item from the buffer?
113+ if workersDone
114+ then do
115+ r <- liftIO $ isWorkDone sv
116+ when (not r) $ do
117+ void $ dispatchWorkerPaced sv
118+ -- Note that we need to guarantee a worker since the work is not
119+ -- finished, therefore we cannot just rely on dispatchWorkerPaced
120+ -- which may or may not send a worker.
121+ noWorker <- allThreadsDone sv
122+ when noWorker $ pushWorker 0 sv
123+ return r
124+ else return False
125+
126+postProcessBounded :: MonadAsync m => SVar t m a -> m Bool
127+postProcessBounded sv = do
128+ workersDone <- allThreadsDone sv
129+ -- There may still be work pending even if there are no workers pending
130+ -- because all the workers may return if the outputQueue becomes full. In
131+ -- that case send off a worker to kickstart the work again.
132+ --
133+ -- Note that isWorkDone can only be safely checked if all workers are done.
134+ -- When some workers are in progress they may have decremented the yield
135+ -- Limit and later ending up incrementing it again. If we look at the yield
136+ -- limit in that window we may falsely say that it is 0 and therefore we
137+ -- are done.
138+ if workersDone
139+ then do
140+ r <- liftIO $ isWorkDone sv
141+ -- Note that we need to guarantee a worker, therefore we cannot just
142+ -- use dispatchWorker which may or may not send a worker.
143+ when (not r) (pushWorker 0 sv)
144+ -- XXX do we need to dispatch many here?
145+ -- void $ dispatchWorker sv
146+ return r
147+ else return False
148+
149+-------------------------------------------------------------------------------
150+-- Cleanup
151+-------------------------------------------------------------------------------
152+
153+cleanupSVar :: SVar t m a -> IO ()
154+cleanupSVar sv = do
155+ workers <- readIORef (workerThreads sv)
156+ Prelude.mapM_ (`throwTo` ThreadAbort)
157+ workers
158+
159+cleanupSVarFromWorker :: SVar t m a -> IO ()
160+cleanupSVarFromWorker sv = do
161+ workers <- readIORef (workerThreads sv)
162+ self <- myThreadId
163+ Prelude.mapM_ (`throwTo` ThreadAbort)
164+ (Prelude.filter (/= self) $ S.toList workers)
src/Streamly/Internal/Data/SVar/Type.hsnew file+531
@@ -0,0 +1,531 @@
1+-- |
2+-- Module : Streamly.Internal.Data.SVar.Type
3+-- Copyright : (c) 2017 Composewell Technologies
4+-- License : BSD-3-Clause
5+-- Maintainer : streamly@composewell.com
6+-- Stability : experimental
7+-- Portability : GHC
8+
9+module Streamly.Internal.Data.SVar.Type
10+ (
11+ -- * Parent child communication
12+ ThreadAbort (..)
13+ , ChildEvent (..)
14+ , AheadHeapEntry (..)
15+
16+ -- * SVar
17+ , Count (..)
18+ , Limit (..)
19+ , SVarStyle (..)
20+ , SVarStopStyle (..)
21+ , SVarStats (..)
22+ , WorkerInfo (..)
23+ , PushBufferPolicy(..)
24+ , LatencyRange (..)
25+ , YieldRateInfo (..)
26+ , SVar (..)
27+
28+ -- * State threaded around the stream
29+ , Rate (..)
30+ , State (streamVar)
31+
32+ -- ** Default State
33+ , magicMaxBuffer
34+ , defState
35+
36+ -- ** Type cast
37+ , adaptState
38+
39+ -- ** State accessors
40+ , getMaxThreads
41+ , setMaxThreads
42+ , getMaxBuffer
43+ , setMaxBuffer
44+ , getStreamRate
45+ , setStreamRate
46+ , getStreamLatency
47+ , setStreamLatency
48+ , getYieldLimit
49+ , setYieldLimit
50+ , getInspectMode
51+ , setInspectMode
52+ )
53+where
54+
55+import Control.Concurrent (ThreadId)
56+import Control.Concurrent.MVar (MVar)
57+import Control.Exception (SomeException(..), Exception)
58+import Data.Heap (Heap, Entry(..))
59+import Data.Int (Int64)
60+import Data.IORef (IORef)
61+import Data.Kind (Type)
62+import Data.Set (Set)
63+
64+import Streamly.Internal.Data.Time.Units (AbsTime, NanoSecond64(..))
65+import Streamly.Internal.Control.Concurrent (RunInIO(..))
66+
67+newtype Count = Count Int64
68+ deriving ( Eq
69+ , Read
70+ , Show
71+ , Enum
72+ , Bounded
73+ , Num
74+ , Real
75+ , Integral
76+ , Ord
77+ )
78+
79+------------------------------------------------------------------------------
80+-- Parent child thread communication type
81+------------------------------------------------------------------------------
82+
83+data ThreadAbort = ThreadAbort deriving Show
84+
85+instance Exception ThreadAbort
86+
87+-- | Events that a child thread may send to a parent thread.
88+data ChildEvent a =
89+ ChildYield a
90+ | ChildStop ThreadId (Maybe SomeException)
91+
92+-- | Sorting out-of-turn outputs in a heap for Ahead style streams
93+data AheadHeapEntry (t :: (Type -> Type) -> Type -> Type) m a =
94+ AheadEntryNull
95+ | AheadEntryPure a
96+ | AheadEntryStream (RunInIO m, t m a)
97+#undef Type
98+
99+------------------------------------------------------------------------------
100+-- SVar: the state for thread management
101+------------------------------------------------------------------------------
102+
103+-- | Identify the type of the SVar. Two computations using the same style can
104+-- be scheduled on the same SVar.
105+data SVarStyle =
106+ AsyncVar -- depth first concurrent
107+ | WAsyncVar -- breadth first concurrent
108+ | ParallelVar -- all parallel
109+ | AheadVar -- Concurrent look ahead
110+ deriving (Eq, Show)
111+
112+-- | An SVar or a Stream Var is a conduit to the output from multiple streams
113+-- running concurrently and asynchronously. An SVar can be thought of as an
114+-- asynchronous IO handle. We can write any number of streams to an SVar in a
115+-- non-blocking manner and then read them back at any time at any pace. The
116+-- SVar would run the streams asynchronously and accumulate results. An SVar
117+-- may not really execute the stream completely and accumulate all the results.
118+-- However, it ensures that the reader can read the results at whatever paces
119+-- it wants to read. The SVar monitors and adapts to the consumer's pace.
120+--
121+-- An SVar is a mini scheduler, it has an associated workLoop that holds the
122+-- stream tasks to be picked and run by a pool of worker threads. It has an
123+-- associated output queue where the output stream elements are placed by the
124+-- worker threads. A outputDoorBell is used by the worker threads to intimate the
125+-- consumer thread about availability of new results in the output queue. More
126+-- workers are added to the SVar by 'fromStreamVar' on demand if the output
127+-- produced is not keeping pace with the consumer. On bounded SVars, workers
128+-- block on the output queue to provide throttling of the producer when the
129+-- consumer is not pulling fast enough. The number of workers may even get
130+-- reduced depending on the consuming pace.
131+--
132+-- New work is enqueued either at the time of creation of the SVar or as a
133+-- result of executing the parallel combinators i.e. '<|' and '<|>' when the
134+-- already enqueued computations get evaluated. See 'joinStreamVarAsync'.
135+
136+-- We measure the individual worker latencies to estimate the number of workers
137+-- needed or the amount of time we have to sleep between dispatches to achieve
138+-- a particular rate when controlled pace mode it used.
139+data WorkerInfo = WorkerInfo
140+ { workerYieldMax :: Count -- 0 means unlimited
141+ -- total number of yields by the worker till now
142+ , workerYieldCount :: IORef Count
143+ -- yieldCount at start, timestamp
144+ , workerLatencyStart :: IORef (Count, AbsTime)
145+ }
146+
147+data LatencyRange = LatencyRange
148+ { minLatency :: NanoSecond64
149+ , maxLatency :: NanoSecond64
150+ } deriving Show
151+
152+-- Rate control.
153+data YieldRateInfo = YieldRateInfo
154+ { svarLatencyTarget :: NanoSecond64
155+ , svarLatencyRange :: LatencyRange
156+ , svarRateBuffer :: Int
157+
158+ -- [LOCKING] Unlocked access. Modified by the consumer thread and unsafely
159+ -- read by the worker threads
160+ , svarGainedLostYields :: IORef Count
161+
162+ -- Actual latency/througput as seen from the consumer side, we count the
163+ -- yields and the time it took to generates those yields. This is used to
164+ -- increase or decrease the number of workers needed to achieve the desired
165+ -- rate. The idle time of workers is adjusted in this, so that we only
166+ -- account for the rate when the consumer actually demands data.
167+ -- XXX interval latency is enough, we can move this under diagnostics build
168+ -- [LOCKING] Unlocked access. Modified by the consumer thread and unsafely
169+ -- read by the worker threads
170+ , svarAllTimeLatency :: IORef (Count, AbsTime)
171+
172+ -- XXX Worker latency specified by the user to be used before the first
173+ -- actual measurement arrives. Not yet implemented
174+ , workerBootstrapLatency :: Maybe NanoSecond64
175+
176+ -- After how many yields the worker should update the latency information.
177+ -- If the latency is high, this count is kept lower and vice-versa. XXX If
178+ -- the latency suddenly becomes too high this count may remain too high for
179+ -- long time, in such cases the consumer can change it.
180+ -- 0 means no latency computation
181+ -- XXX this is derivable from workerMeasuredLatency, can be removed.
182+ -- [LOCKING] Unlocked access. Modified by the consumer thread and unsafely
183+ -- read by the worker threads
184+ , workerPollingInterval :: IORef Count
185+
186+ -- This is in progress latency stats maintained by the workers which we
187+ -- empty into workerCollectedLatency stats at certain intervals - whenever
188+ -- we process the stream elements yielded in this period. The first count
189+ -- is all yields, the second count is only those yields for which the
190+ -- latency was measured to be non-zero (note that if the timer resolution
191+ -- is low the measured latency may be zero e.g. on JS platform).
192+ -- [LOCKING] Locked access. Modified by the consumer thread as well as
193+ -- worker threads. Workers modify it periodically based on
194+ -- workerPollingInterval and not on every yield to reduce the locking
195+ -- overhead.
196+ -- (allYieldCount, yieldCount, timeTaken)
197+ , workerPendingLatency :: IORef (Count, Count, NanoSecond64)
198+
199+ -- This is the second level stat which is an accmulation from
200+ -- workerPendingLatency stats. We keep accumulating latencies in this
201+ -- bucket until we have stats for a sufficient period and then we reset it
202+ -- to start collecting for the next period and retain the computed average
203+ -- latency for the last period in workerMeasuredLatency.
204+ -- [LOCKING] Unlocked access. Modified by the consumer thread and unsafely
205+ -- read by the worker threads
206+ -- (allYieldCount, yieldCount, timeTaken)
207+ , workerCollectedLatency :: IORef (Count, Count, NanoSecond64)
208+
209+ -- Latency as measured by workers, aggregated for the last period.
210+ -- [LOCKING] Unlocked access. Modified by the consumer thread and unsafely
211+ -- read by the worker threads
212+ , workerMeasuredLatency :: IORef NanoSecond64
213+ }
214+
215+data SVarStats = SVarStats {
216+ totalDispatches :: IORef Int
217+ , maxWorkers :: IORef Int
218+ , maxOutQSize :: IORef Int
219+ , maxHeapSize :: IORef Int
220+ , maxWorkQSize :: IORef Int
221+ , avgWorkerLatency :: IORef (Count, NanoSecond64)
222+ , minWorkerLatency :: IORef NanoSecond64
223+ , maxWorkerLatency :: IORef NanoSecond64
224+ , svarStopTime :: IORef (Maybe AbsTime)
225+}
226+
227+-- This is essentially a 'Maybe Word' type
228+data Limit = Unlimited | Limited Word deriving Show
229+
230+instance Eq Limit where
231+ Unlimited == Unlimited = True
232+ Unlimited == Limited _ = False
233+ Limited _ == Unlimited = False
234+ Limited x == Limited y = x == y
235+
236+instance Ord Limit where
237+ Unlimited <= Unlimited = True
238+ Unlimited <= Limited _ = False
239+ Limited _ <= Unlimited = True
240+ Limited x <= Limited y = x <= y
241+
242+-- When to stop the composed stream.
243+data SVarStopStyle =
244+ StopNone -- stops only when all streams are finished
245+ | StopAny -- stop when any stream finishes
246+ | StopBy -- stop when a specific stream finishes
247+ deriving (Eq, Show)
248+
249+-- | Buffering policy for persistent push workers (in ParallelT). In a pull
250+-- style SVar (in AsyncT, AheadT etc.), the consumer side dispatches workers on
251+-- demand, workers terminate if the buffer is full or if the consumer is not
252+-- cosuming fast enough. In a push style SVar, a worker is dispatched only
253+-- once, workers are persistent and keep pushing work to the consumer via a
254+-- bounded buffer. If the buffer becomes full the worker either blocks, or it
255+-- can drop an item from the buffer to make space.
256+--
257+-- Pull style SVars are useful in lazy stream evaluation whereas push style
258+-- SVars are useful in strict left Folds.
259+--
260+-- XXX Maybe we can separate the implementation in two different types instead
261+-- of using a common SVar type.
262+--
263+data PushBufferPolicy =
264+ PushBufferDropNew -- drop the latest element and continue
265+ | PushBufferDropOld -- drop the oldest element and continue
266+ | PushBufferBlock -- block the thread until space
267+ -- becomes available
268+
269+-- IMPORTANT NOTE: we cannot update the SVar after generating it as we have
270+-- references to the original SVar stored in several functions which will keep
271+-- pointing to the original data and the new updates won't reflect there.
272+-- Any updateable parts must be kept in mutable references (IORef).
273+--
274+data SVar t m a = SVar
275+ {
276+ -- Read only state
277+ svarStyle :: SVarStyle
278+ , svarMrun :: RunInIO m
279+ , svarStopStyle :: SVarStopStyle
280+ , svarStopBy :: IORef ThreadId
281+
282+ -- Shared output queue (events, length)
283+ -- XXX For better efficiency we can try a preallocated array type (perhaps
284+ -- something like a vector) that allows an O(1) append. That way we will
285+ -- avoid constructing and reversing the list. Possibly we can also avoid
286+ -- the GC copying overhead. When the size increases we should be able to
287+ -- allocate the array in chunks.
288+ --
289+ -- [LOCKING] Frequent locked access. This is updated by workers on each
290+ -- yield and once in a while read by the consumer thread. This could have
291+ -- big locking overhead if the number of workers is high.
292+ --
293+ -- XXX We can use a per-CPU data structure to reduce the locking overhead.
294+ -- However, a per-cpu structure cannot guarantee the exact sequence in
295+ -- which the elements were added, though that may not be important.
296+ , outputQueue :: IORef ([ChildEvent a], Int)
297+
298+ -- [LOCKING] Infrequent MVar. Used when the outputQ transitions from empty
299+ -- to non-empty, or a work item is queued by a worker to the work queue and
300+ -- needDoorBell is set by the consumer.
301+ , outputDoorBell :: MVar () -- signal the consumer about output
302+ , readOutputQ :: m [ChildEvent a]
303+ , postProcess :: m Bool
304+
305+ -- channel to send events from the consumer to the worker. Used to send
306+ -- exceptions from a fold driver to the fold computation running as a
307+ -- consumer thread in the concurrent fold cases. Currently only one event
308+ -- is sent by the fold so we do not really need a queue for it.
309+ , outputQueueFromConsumer :: IORef ([ChildEvent a], Int)
310+ , outputDoorBellFromConsumer :: MVar ()
311+
312+ -- Combined/aggregate parameters
313+ -- This is truncated to maxBufferLimit if set to more than that. Otherwise
314+ -- potentially each worker may yield one value to the buffer in the worst
315+ -- case exceeding the requested buffer size.
316+ , maxWorkerLimit :: Limit
317+ , maxBufferLimit :: Limit
318+ -- These two are valid and used only when maxBufferLimit is Limited.
319+ , pushBufferSpace :: IORef Count
320+ , pushBufferPolicy :: PushBufferPolicy
321+ -- [LOCKING] The consumer puts this MVar after emptying the buffer, workers
322+ -- block on it when the buffer becomes full. No overhead unless the buffer
323+ -- becomes full.
324+ , pushBufferMVar :: MVar ()
325+
326+ -- [LOCKING] Read only access by consumer when dispatching a worker.
327+ -- Decremented by workers when picking work and undo decrement if the
328+ -- worker does not yield a value.
329+ , remainingWork :: Maybe (IORef Count)
330+ , yieldRateInfo :: Maybe YieldRateInfo
331+
332+ -- Used only by bounded SVar types
333+ , enqueue :: (RunInIO m, t m a) -> IO ()
334+ , isWorkDone :: IO Bool
335+ , isQueueDone :: IO Bool
336+ , needDoorBell :: IORef Bool
337+ , workLoop :: Maybe WorkerInfo -> m ()
338+
339+ -- Shared, thread tracking
340+ -- [LOCKING] Updated unlocked only by consumer thread in case of
341+ -- Async/Ahead style SVars. Updated locked by worker threads in case of
342+ -- Parallel style.
343+ , workerThreads :: IORef (Set ThreadId)
344+ -- [LOCKING] Updated locked by consumer thread when dispatching a worker
345+ -- and by the worker threads when the thread stops. This is read unsafely
346+ -- at several places where we want to rely on an approximate value.
347+ , workerCount :: IORef Int
348+ , accountThread :: ThreadId -> m ()
349+ , workerStopMVar :: MVar ()
350+
351+ , svarStats :: SVarStats
352+ -- to track garbage collection of SVar
353+ , svarRef :: Maybe (IORef ())
354+
355+ -- Only for diagnostics
356+ , svarInspectMode :: Bool
357+ , svarCreator :: ThreadId
358+ , outputHeap :: IORef ( Heap (Entry Int (AheadHeapEntry t m a))
359+ , Maybe Int)
360+ -- Shared work queue (stream, seqNo)
361+ , aheadWorkQueue :: IORef ([t m a], Int)
362+ }
363+
364+-------------------------------------------------------------------------------
365+-- Overall state threaded around a stream
366+-------------------------------------------------------------------------------
367+
368+-- | Specifies the stream yield rate in yields per second (@Hertz@).
369+-- We keep accumulating yield credits at 'rateGoal'. At any point of time we
370+-- allow only as many yields as we have accumulated as per 'rateGoal' since the
371+-- start of time. If the consumer or the producer is slower or faster, the
372+-- actual rate may fall behind or exceed 'rateGoal'. We try to recover the gap
373+-- between the two by increasing or decreasing the pull rate from the producer.
374+-- However, if the gap becomes more than 'rateBuffer' we try to recover only as
375+-- much as 'rateBuffer'.
376+--
377+-- 'rateLow' puts a bound on how low the instantaneous rate can go when
378+-- recovering the rate gap. In other words, it determines the maximum yield
379+-- latency. Similarly, 'rateHigh' puts a bound on how high the instantaneous
380+-- rate can go when recovering the rate gap. In other words, it determines the
381+-- minimum yield latency. We reduce the latency by increasing concurrency,
382+-- therefore we can say that it puts an upper bound on concurrency.
383+--
384+-- If the 'rateGoal' is 0 or negative the stream never yields a value.
385+-- If the 'rateBuffer' is 0 or negative we do not attempt to recover.
386+--
387+-- /Since: 0.5.0 ("Streamly")/
388+--
389+-- @since 0.8.0
390+data Rate = Rate
391+ { rateLow :: Double -- ^ The lower rate limit
392+ , rateGoal :: Double -- ^ The target rate we want to achieve
393+ , rateHigh :: Double -- ^ The upper rate limit
394+ , rateBuffer :: Int -- ^ Maximum slack from the goal
395+ }
396+
397+-- XXX we can put the resettable fields in a oneShotConfig field and others in
398+-- a persistentConfig field. That way reset would be fast and scalable
399+-- irrespective of the number of fields.
400+--
401+-- XXX make all these Limited types and use phantom types to distinguish them
402+data State t m a = State
403+ { -- one shot configuration, automatically reset for each API call
404+ streamVar :: Maybe (SVar t m a)
405+ , _yieldLimit :: Maybe Count
406+
407+ -- persistent configuration, state that remains valid until changed by
408+ -- an explicit setting via a combinator.
409+ , _threadsHigh :: Limit
410+ , _bufferHigh :: Limit
411+ -- XXX these two can be collapsed into a single type
412+ , _streamLatency :: Maybe NanoSecond64 -- bootstrap latency
413+ , _maxStreamRate :: Maybe Rate
414+ , _inspectMode :: Bool
415+ }
416+
417+-------------------------------------------------------------------------------
418+-- State defaults and reset
419+-------------------------------------------------------------------------------
420+
421+-- A magical value for the buffer size arrived at by running the smallest
422+-- possible task and measuring the optimal value of the buffer for that. This
423+-- is obviously dependent on hardware, this figure is based on a 2.2GHz intel
424+-- core-i7 processor.
425+magicMaxBuffer :: Word
426+magicMaxBuffer = 1500
427+
428+defaultMaxThreads, defaultMaxBuffer :: Limit
429+defaultMaxThreads = Limited magicMaxBuffer
430+defaultMaxBuffer = Limited magicMaxBuffer
431+
432+-- The fields prefixed by an _ are not to be accessed or updated directly but
433+-- via smart accessor APIs.
434+defState :: State t m a
435+defState = State
436+ { streamVar = Nothing
437+ , _yieldLimit = Nothing
438+ , _threadsHigh = defaultMaxThreads
439+ , _bufferHigh = defaultMaxBuffer
440+ , _maxStreamRate = Nothing
441+ , _streamLatency = Nothing
442+ , _inspectMode = False
443+ }
444+
445+-- XXX if perf gets affected we can have all the Nothing params in a single
446+-- structure so that we reset is fast. We can also use rewrite rules such that
447+-- reset occurs only in concurrent streams to reduce the impact on serial
448+-- streams.
449+-- We can optimize this so that we clear it only if it is a Just value, it
450+-- results in slightly better perf for zip/zipM but the performance of scan
451+-- worsens a lot, it does not fuse.
452+--
453+-- XXX This has a side effect of clearing the SVar and yieldLimit, therefore it
454+-- should not be used to convert from the same type to the same type, unless
455+-- you want to clear the SVar. For clearing the SVar you should be using the
456+-- appropriate unStream functions instead.
457+--
458+-- | Adapt the stream state from one type to another.
459+adaptState :: State t m a -> State t n b
460+adaptState st = st
461+ { streamVar = Nothing
462+ , _yieldLimit = Nothing
463+ }
464+
465+-------------------------------------------------------------------------------
466+-- Smart get/set routines for State
467+-------------------------------------------------------------------------------
468+
469+-- Use get/set routines instead of directly accessing the State fields
470+setYieldLimit :: Maybe Int64 -> State t m a -> State t m a
471+setYieldLimit lim st =
472+ st { _yieldLimit =
473+ case lim of
474+ Nothing -> Nothing
475+ Just n ->
476+ if n <= 0
477+ then Just 0
478+ else Just (fromIntegral n)
479+ }
480+
481+getYieldLimit :: State t m a -> Maybe Count
482+getYieldLimit = _yieldLimit
483+
484+setMaxThreads :: Int -> State t m a -> State t m a
485+setMaxThreads n st =
486+ st { _threadsHigh =
487+ if n < 0
488+ then Unlimited
489+ else if n == 0
490+ then defaultMaxThreads
491+ else Limited (fromIntegral n)
492+ }
493+
494+getMaxThreads :: State t m a -> Limit
495+getMaxThreads = _threadsHigh
496+
497+setMaxBuffer :: Int -> State t m a -> State t m a
498+setMaxBuffer n st =
499+ st { _bufferHigh =
500+ if n < 0
501+ then Unlimited
502+ else if n == 0
503+ then defaultMaxBuffer
504+ else Limited (fromIntegral n)
505+ }
506+
507+getMaxBuffer :: State t m a -> Limit
508+getMaxBuffer = _bufferHigh
509+
510+setStreamRate :: Maybe Rate -> State t m a -> State t m a
511+setStreamRate r st = st { _maxStreamRate = r }
512+
513+getStreamRate :: State t m a -> Maybe Rate
514+getStreamRate = _maxStreamRate
515+
516+setStreamLatency :: Int -> State t m a -> State t m a
517+setStreamLatency n st =
518+ st { _streamLatency =
519+ if n <= 0
520+ then Nothing
521+ else Just (fromIntegral n)
522+ }
523+
524+getStreamLatency :: State t m a -> Maybe NanoSecond64
525+getStreamLatency = _streamLatency
526+
527+setInspectMode :: State t m a -> State t m a
528+setInspectMode st = st { _inspectMode = True }
529+
530+getInspectMode :: State t m a -> Bool
531+getInspectMode = _inspectMode
src/Streamly/Internal/Data/SVar/Worker.hsnew file+544
@@ -0,0 +1,544 @@
1+-- |
2+-- Module : Streamly.Internal.Data.SVar.Worker
3+-- Copyright : (c) 2017 Composewell Technologies
4+-- License : BSD-3-Clause
5+-- Maintainer : streamly@composewell.com
6+-- Stability : experimental
7+-- Portability : GHC
8+--
9+--
10+module Streamly.Internal.Data.SVar.Worker
11+ (
12+ -- * Adjusting Limits
13+ decrementYieldLimit
14+ , incrementYieldLimit
15+ , decrementBufferLimit
16+ , incrementBufferLimit
17+ , resetBufferLimit
18+
19+ -- * Rate Control
20+ , Work (..)
21+ , isBeyondMaxRate
22+ , estimateWorkers
23+ , updateYieldCount
24+ , minThreadDelay
25+ , workerRateControl
26+ , workerUpdateLatency
27+
28+ -- * Send Events
29+ , send
30+ , ringDoorBell
31+
32+ -- ** Yield
33+ , sendYield
34+ , sendToProducer
35+
36+ -- ** Stop
37+ , sendStop
38+ , sendStopToProducer
39+
40+ -- ** Exception
41+ , handleChildException
42+ , handleFoldException
43+ )
44+where
45+
46+#include "inline.hs"
47+
48+import Control.Concurrent (myThreadId, takeMVar)
49+import Control.Concurrent.MVar (MVar, tryPutMVar)
50+import Control.Exception (SomeException(..), assert)
51+import Control.Monad (when, void)
52+import Control.Monad.IO.Class (MonadIO(liftIO))
53+import Data.IORef (IORef, readIORef, writeIORef)
54+import Streamly.Internal.Data.Atomics
55+ (atomicModifyIORefCAS, atomicModifyIORefCAS_, writeBarrier,
56+ storeLoadBarrier)
57+import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
58+import Streamly.Internal.Data.Time.Units
59+ (AbsTime, NanoSecond64(..), diffAbsTime64, fromRelTime64)
60+
61+import Streamly.Internal.Data.SVar.Type
62+
63+------------------------------------------------------------------------------
64+-- Collecting results from child workers in a streamed fashion
65+------------------------------------------------------------------------------
66+
67+-- XXX Can we make access to remainingWork and yieldRateInfo fields in sv
68+-- faster, along with the fields in sv required by send?
69+-- XXX make it noinline
70+--
71+-- XXX we may want to employ an increment and decrement in batches when the
72+-- througput is high or when the cost of synchronization is high. For example
73+-- if the application is distributed then inc/dec of a shared variable may be
74+-- very costly.
75+--
76+-- A worker decrements the yield limit before it executes an action. However,
77+-- the action may not result in an element being yielded, in that case we have
78+-- to increment the yield limit.
79+--
80+-- Note that we need it to be an Int type so that we have the ability to undo a
81+-- decrement that takes it below zero.
82+{-# INLINE decrementYieldLimit #-}
83+decrementYieldLimit :: SVar t m a -> IO Bool
84+decrementYieldLimit sv =
85+ case remainingWork sv of
86+ Nothing -> return True
87+ Just ref -> do
88+ r <- atomicModifyIORefCAS ref $ \x -> (x - 1, x)
89+ return $ r >= 1
90+
91+{-# INLINE incrementYieldLimit #-}
92+incrementYieldLimit :: SVar t m a -> IO ()
93+incrementYieldLimit sv =
94+ case remainingWork sv of
95+ Nothing -> return ()
96+ Just ref -> atomicModifyIORefCAS_ ref (+ 1)
97+
98+-- XXX exception safety of all atomic/MVar operations
99+
100+-- TBD Each worker can have their own queue and the consumer can empty one
101+-- queue at a time, that way contention can be reduced.
102+
103+-- XXX Only yields should be counted in the buffer limit and not the Stop
104+-- events.
105+
106+{-# INLINE decrementBufferLimit #-}
107+decrementBufferLimit :: SVar t m a -> IO ()
108+decrementBufferLimit sv =
109+ case maxBufferLimit sv of
110+ Unlimited -> return ()
111+ Limited _ -> do
112+ let ref = pushBufferSpace sv
113+ old <- atomicModifyIORefCAS ref $ \x ->
114+ (if x >= 1 then x - 1 else x, x)
115+ when (old <= 0) $
116+ case pushBufferPolicy sv of
117+ PushBufferBlock -> blockAndRetry
118+ PushBufferDropNew -> do
119+ -- We just drop one item and proceed. It is possible
120+ -- that by the time we drop the item the consumer
121+ -- thread might have run and created space in the
122+ -- buffer, but we do not care about that condition.
123+ -- This is not pedantically correct but it should be
124+ -- fine in practice.
125+ -- XXX we may want to drop only if n == maxBuf
126+ -- otherwise we must have space in the buffer and a
127+ -- decrement should be possible.
128+ block <- atomicModifyIORefCAS (outputQueue sv) $
129+ \(es, n) ->
130+ case es of
131+ [] -> (([],n), True)
132+ _ : xs -> ((xs, n - 1), False)
133+ when block blockAndRetry
134+ -- XXX need a dequeue or ring buffer for this
135+ PushBufferDropOld -> undefined
136+
137+ where
138+
139+ blockAndRetry = do
140+ let ref = pushBufferSpace sv
141+ liftIO $ takeMVar (pushBufferMVar sv)
142+ old <- atomicModifyIORefCAS ref $ \x ->
143+ (if x >= 1 then x - 1 else x, x)
144+ -- When multiple threads sleep on takeMVar, the first thread would
145+ -- wakeup due to a putMVar by the consumer, but the rest of the threads
146+ -- would have to put back the MVar after taking it and decrementing the
147+ -- buffer count, otherwise all other threads will remain asleep.
148+ if old >= 1
149+ then void $ liftIO $ tryPutMVar (pushBufferMVar sv) ()
150+ -- We do not put the MVar back in this case, instead we
151+ -- wait for the consumer to put it.
152+ else blockAndRetry
153+
154+{-# INLINE incrementBufferLimit #-}
155+incrementBufferLimit :: SVar t m a -> IO ()
156+incrementBufferLimit sv =
157+ case maxBufferLimit sv of
158+ Unlimited -> return ()
159+ Limited _ -> do
160+ atomicModifyIORefCAS_ (pushBufferSpace sv) (+ 1)
161+ writeBarrier
162+ void $ liftIO $ tryPutMVar (pushBufferMVar sv) ()
163+
164+{-# INLINE resetBufferLimit #-}
165+resetBufferLimit :: SVar t m a -> IO ()
166+resetBufferLimit sv =
167+ case maxBufferLimit sv of
168+ Unlimited -> return ()
169+ Limited n -> atomicModifyIORefCAS_ (pushBufferSpace sv)
170+ (const (fromIntegral n))
171+
172+-------------------------------------------------------------------------------
173+-- Yield control
174+-------------------------------------------------------------------------------
175+
176+updateYieldCount :: WorkerInfo -> IO Count
177+updateYieldCount winfo = do
178+ cnt <- readIORef (workerYieldCount winfo)
179+ let cnt1 = cnt + 1
180+ writeIORef (workerYieldCount winfo) cnt1
181+ return cnt1
182+
183+isBeyondMaxYield :: Count -> WorkerInfo -> Bool
184+isBeyondMaxYield cnt winfo =
185+ let ymax = workerYieldMax winfo
186+ in ymax /= 0 && cnt >= ymax
187+
188+-------------------------------------------------------------------------------
189+-- Sending results from worker
190+-------------------------------------------------------------------------------
191+
192+{-# INLINE ringDoorBell #-}
193+ringDoorBell :: SVar t m a -> IO ()
194+ringDoorBell sv = do
195+ storeLoadBarrier
196+ w <- readIORef $ needDoorBell sv
197+ when w $ do
198+ -- Note: the sequence of operations is important for correctness here.
199+ -- We need to set the flag to false strictly before sending the
200+ -- outputDoorBell, otherwise the outputDoorBell may get processed too
201+ -- early and then we may set the flag to False to later making the
202+ -- consumer lose the flag, even without receiving a outputDoorBell.
203+ atomicModifyIORefCAS_ (needDoorBell sv) (const False)
204+ void $ tryPutMVar (outputDoorBell sv) ()
205+
206+{-# INLINE sendWithDoorBell #-}
207+sendWithDoorBell ::
208+ IORef ([ChildEvent a], Int) -> MVar () -> ChildEvent a -> IO Int
209+sendWithDoorBell q bell msg = do
210+ -- XXX can the access to outputQueue be made faster somehow?
211+ oldlen <- atomicModifyIORefCAS q $ \(es, n) ->
212+ ((msg : es, n + 1), n)
213+ when (oldlen <= 0) $ do
214+ -- The wake up must happen only after the store has finished otherwise
215+ -- we can have lost wakeup problems.
216+ writeBarrier
217+ -- Since multiple workers can try this at the same time, it is possible
218+ -- that we may put a spurious MVar after the consumer has already seen
219+ -- the output. But that's harmless, at worst it may cause the consumer
220+ -- to read the queue again and find it empty.
221+ -- The important point is that the consumer is guaranteed to receive a
222+ -- doorbell if something was added to the queue after it empties it.
223+ void $ tryPutMVar bell ()
224+ return oldlen
225+
226+-- | This function is used by the producer threads to queue output for the
227+-- consumer thread to consume. Returns whether the queue has more space.
228+send :: SVar t m a -> ChildEvent a -> IO Int
229+send sv = sendWithDoorBell (outputQueue sv) (outputDoorBell sv)
230+
231+-- There is no bound implemented on the buffer, this is assumed to be low
232+-- traffic.
233+sendToProducer :: SVar t m a -> ChildEvent a -> IO Int
234+sendToProducer sv msg = do
235+ -- In case the producer stream is blocked on pushing to the fold buffer
236+ -- then wake it up so that it can check for the stop event or exception
237+ -- being sent to it otherwise we will be deadlocked.
238+ void $ tryPutMVar (pushBufferMVar sv) ()
239+ sendWithDoorBell (outputQueueFromConsumer sv)
240+ (outputDoorBellFromConsumer sv) msg
241+
242+-------------------------------------------------------------------------------
243+-- Collect and update worker latency
244+-------------------------------------------------------------------------------
245+
246+workerCollectLatency :: WorkerInfo -> IO (Maybe (Count, NanoSecond64))
247+workerCollectLatency winfo = do
248+ (cnt0, t0) <- readIORef (workerLatencyStart winfo)
249+ cnt1 <- readIORef (workerYieldCount winfo)
250+ let cnt = cnt1 - cnt0
251+
252+ if cnt > 0
253+ then do
254+ t1 <- getTime Monotonic
255+ let period = fromRelTime64 $ diffAbsTime64 t1 t0
256+ writeIORef (workerLatencyStart winfo) (cnt1, t1)
257+ return $ Just (cnt, period)
258+ else return Nothing
259+
260+-- XXX There are a number of gotchas in measuring latencies.
261+-- 1) We measure latencies only when a worker yields a value
262+-- 2) It is possible that a stream calls the stop continuation, in which case
263+-- the worker would not yield a value and we would not account that worker in
264+-- latencies. Even though this case should ideally be accounted we do not
265+-- account it because we cannot or do not distinguish it from the case
266+-- described next.
267+-- 3) It is possible that a worker returns without yielding anything because it
268+-- never got a chance to pick up work.
269+-- 4) If the system timer resolution is lower than the latency, the latency
270+-- computation turns out to be zero.
271+--
272+-- We can fix this if we measure the latencies by counting the work items
273+-- picked rather than based on the outputs yielded.
274+workerUpdateLatency :: YieldRateInfo -> WorkerInfo -> IO ()
275+workerUpdateLatency yinfo winfo = do
276+ r <- workerCollectLatency winfo
277+ case r of
278+ Just (cnt, period) -> do
279+ -- NOTE: On JS platform the timer resolution could be pretty low. When
280+ -- the timer resolution is low, measurement of latencies could be
281+ -- tricky. All the worker latencies will turn out to be zero if they
282+ -- are lower than the resolution. We only take into account those
283+ -- measurements which are more than the timer resolution.
284+
285+ let ref = workerPendingLatency yinfo
286+ (cnt1, t1) = if period > 0 then (cnt, period) else (0, 0)
287+ atomicModifyIORefCAS_ ref $
288+ \(fc, n, t) -> (fc + cnt, n + cnt1, t + t1)
289+ Nothing -> return ()
290+
291+-------------------------------------------------------------------------------
292+-- Worker rate control
293+-------------------------------------------------------------------------------
294+
295+-- We either block, or send one worker with limited yield count or one or more
296+-- workers with unlimited yield count.
297+data Work
298+ = BlockWait NanoSecond64
299+ | PartialWorker Count
300+ | ManyWorkers Int Count
301+ deriving Show
302+
303+-- | This is a magic number and it is overloaded, and used at several places to
304+-- achieve batching:
305+--
306+-- 1. If we have to sleep to slowdown this is the minimum period that we
307+-- accumulate before we sleep. Also, workers do not stop until this much
308+-- sleep time is accumulated.
309+-- 3. Collected latencies are computed and transferred to measured latency
310+-- after a minimum of this period.
311+minThreadDelay :: NanoSecond64
312+minThreadDelay = 1000000
313+
314+-- | Another magic number! When we have to start more workers to cover up a
315+-- number of yields that we are lagging by then we cannot start one worker for
316+-- each yield because that may be a very big number and if the latency of the
317+-- workers is low these number of yields could be very high. We assume that we
318+-- run each extra worker for at least this much time.
319+rateRecoveryTime :: NanoSecond64
320+rateRecoveryTime = 1000000
321+
322+-- | Get the worker latency without resetting workerPendingLatency
323+-- Returns (total yield count, base time, measured latency)
324+-- CAUTION! keep it in sync with collectLatency
325+getWorkerLatency :: YieldRateInfo -> IO (Count, AbsTime, NanoSecond64)
326+getWorkerLatency yinfo = do
327+ let cur = workerPendingLatency yinfo
328+ col = workerCollectedLatency yinfo
329+ longTerm = svarAllTimeLatency yinfo
330+ measured = workerMeasuredLatency yinfo
331+
332+ (curTotalCount, curCount, curTime) <- readIORef cur
333+ (colTotalCount, colCount, colTime) <- readIORef col
334+ (lcount, ltime) <- readIORef longTerm
335+ prevLat <- readIORef measured
336+
337+ let latCount = colCount + curCount
338+ latTime = colTime + curTime
339+ totalCount = colTotalCount + curTotalCount
340+ newLat =
341+ if latCount > 0 && latTime > 0
342+ then let lat = latTime `div` fromIntegral latCount
343+ -- XXX Give more weight to new?
344+ in (lat + prevLat) `div` 2
345+ else prevLat
346+ return (lcount + totalCount, ltime, newLat)
347+
348+-- XXX we can use phantom types to distinguish the duration/latency/expectedLat
349+estimateWorkers
350+ :: Limit
351+ -> Count
352+ -> Count
353+ -> NanoSecond64
354+ -> NanoSecond64
355+ -> NanoSecond64
356+ -> LatencyRange
357+ -> Work
358+estimateWorkers workerLimit svarYields gainLossYields
359+ svarElapsed wLatency targetLat range =
360+ -- XXX we can have a maxEfficiency combinator as well which runs the
361+ -- producer at the maximal efficiency i.e. the number of workers are chosen
362+ -- such that the latency is minimum or within a range. Or we can call it
363+ -- maxWorkerLatency.
364+ --
365+ let
366+ -- How many workers do we need to achieve the required rate?
367+ --
368+ -- When the workers are IO bound we can increase the throughput by
369+ -- increasing the number of workers as long as the IO device has enough
370+ -- capacity to process all the requests concurrently. If the IO
371+ -- bandwidth is saturated increasing the workers won't help. Also, if
372+ -- the CPU utilization in processing all these requests exceeds the CPU
373+ -- bandwidth, then increasing the number of workers won't help.
374+ --
375+ -- When the workers are purely CPU bound, increasing the workers beyond
376+ -- the number of CPUs won't help.
377+ --
378+ -- TODO - measure the CPU and IO requirements of the workers. Have a
379+ -- way to specify the max bandwidth of the underlying IO mechanism and
380+ -- use that to determine the max rate of workers, and also take the CPU
381+ -- bandwidth into account. We can also discover the IO bandwidth if we
382+ -- know that we are not CPU bound, then how much steady state rate are
383+ -- we able to achieve. Design tests for CPU bound and IO bound cases.
384+
385+ -- Calculate how many yields are we ahead or behind to match the exact
386+ -- required rate. Based on that we increase or decrease the effective
387+ -- workers.
388+ --
389+ -- When the worker latency is lower than required latency we begin with
390+ -- a yield and then wait rather than first waiting and then yielding.
391+ targetYields = (svarElapsed + wLatency + targetLat - 1) `div` targetLat
392+ effectiveYields = svarYields + gainLossYields
393+ deltaYields = fromIntegral targetYields - effectiveYields
394+
395+ -- We recover the deficit by running at a higher/lower rate for a
396+ -- certain amount of time. To keep the effective rate in reasonable
397+ -- limits we use rateRecoveryTime, minLatency and maxLatency.
398+ in if deltaYields > 0
399+ then
400+ let deltaYieldsFreq :: Double
401+ deltaYieldsFreq =
402+ fromIntegral deltaYields /
403+ fromIntegral rateRecoveryTime
404+ yieldsFreq = 1.0 / fromIntegral targetLat
405+ totalYieldsFreq = yieldsFreq + deltaYieldsFreq
406+ requiredLat = NanoSecond64 $ round $ 1.0 / totalYieldsFreq
407+ adjustedLat = min (max requiredLat (minLatency range))
408+ (maxLatency range)
409+ in assert (adjustedLat > 0) $
410+ if wLatency <= adjustedLat
411+ then PartialWorker deltaYields
412+ else let workers = withLimit $ wLatency `div` adjustedLat
413+ limited = min workers (fromIntegral deltaYields)
414+ in ManyWorkers (fromIntegral limited) deltaYields
415+ else
416+ let expectedDuration = fromIntegral effectiveYields * targetLat
417+ sleepTime = expectedDuration - svarElapsed
418+ maxSleepTime = maxLatency range - wLatency
419+ s = min sleepTime maxSleepTime
420+ in assert (sleepTime >= 0) $
421+ -- if s is less than 0 it means our maxSleepTime is less
422+ -- than the worker latency.
423+ if s > 0 then BlockWait s else ManyWorkers 1 (Count 0)
424+ where
425+ withLimit n =
426+ case workerLimit of
427+ Unlimited -> n
428+ Limited x -> min n (fromIntegral x)
429+
430+isBeyondMaxRate :: SVar t m a -> YieldRateInfo -> IO Bool
431+isBeyondMaxRate sv yinfo = do
432+ (count, tstamp, wLatency) <- getWorkerLatency yinfo
433+ now <- getTime Monotonic
434+ let duration = fromRelTime64 $ diffAbsTime64 now tstamp
435+ let targetLat = svarLatencyTarget yinfo
436+ gainLoss <- readIORef (svarGainedLostYields yinfo)
437+ let work = estimateWorkers (maxWorkerLimit sv) count gainLoss duration
438+ wLatency targetLat (svarLatencyRange yinfo)
439+ cnt <- readIORef $ workerCount sv
440+ return $ case work of
441+ -- XXX set the worker's maxYields or polling interval based on yields
442+ PartialWorker _yields -> cnt > 1
443+ ManyWorkers n _ -> cnt > n
444+ BlockWait _ -> True
445+
446+-- XXX we should do rate control periodically based on the total yields rather
447+-- than based on the worker local yields as other workers may have yielded more
448+-- and we should stop based on the aggregate yields. However, latency update
449+-- period can be based on individual worker yields.
450+{-# NOINLINE checkRatePeriodic #-}
451+checkRatePeriodic :: SVar t m a
452+ -> YieldRateInfo
453+ -> WorkerInfo
454+ -> Count
455+ -> IO Bool
456+checkRatePeriodic sv yinfo winfo ycnt = do
457+ i <- readIORef (workerPollingInterval yinfo)
458+ -- XXX use generation count to check if the interval has been updated
459+ if i /= 0 && (ycnt `mod` i) == 0
460+ then do
461+ workerUpdateLatency yinfo winfo
462+ -- XXX not required for parallel streams
463+ isBeyondMaxRate sv yinfo
464+ else return False
465+
466+-- CAUTION! this also updates the yield count and therefore should be called
467+-- only when we are actually yielding an element.
468+{-# NOINLINE workerRateControl #-}
469+workerRateControl :: SVar t m a -> YieldRateInfo -> WorkerInfo -> IO Bool
470+workerRateControl sv yinfo winfo = do
471+ cnt <- updateYieldCount winfo
472+ beyondMaxRate <- checkRatePeriodic sv yinfo winfo cnt
473+ return $ not (isBeyondMaxYield cnt winfo || beyondMaxRate)
474+
475+-------------------------------------------------------------------------------
476+-- Send a yield event
477+-------------------------------------------------------------------------------
478+
479+-- XXX we should do rate control here but not latency update in case of ahead
480+-- streams. latency update must be done when we yield directly to outputQueue
481+-- or when we yield to heap.
482+--
483+-- returns whether the worker should continue (True) or stop (False).
484+{-# INLINE sendYield #-}
485+sendYield :: SVar t m a -> Maybe WorkerInfo -> ChildEvent a -> IO Bool
486+sendYield sv mwinfo msg = do
487+ oldlen <- send sv msg
488+ let limit = maxBufferLimit sv
489+ bufferSpaceOk <- case limit of
490+ Unlimited -> return True
491+ Limited lim -> do
492+ active <- readIORef (workerCount sv)
493+ return $ (oldlen + 1) < (fromIntegral lim - active)
494+ rateLimitOk <-
495+ case mwinfo of
496+ Just winfo ->
497+ case yieldRateInfo sv of
498+ Nothing -> return True
499+ Just yinfo -> workerRateControl sv yinfo winfo
500+ Nothing -> return True
501+ return $ bufferSpaceOk && rateLimitOk
502+
503+-------------------------------------------------------------------------------
504+-- Send a Stop event
505+-------------------------------------------------------------------------------
506+
507+{-# INLINE workerStopUpdate #-}
508+workerStopUpdate :: WorkerInfo -> YieldRateInfo -> IO ()
509+workerStopUpdate winfo info = do
510+ i <- readIORef (workerPollingInterval info)
511+ when (i /= 0) $ workerUpdateLatency info winfo
512+
513+{-# INLINABLE sendStop #-}
514+sendStop :: SVar t m a -> Maybe WorkerInfo -> IO ()
515+sendStop sv mwinfo = do
516+ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n - 1
517+ case (mwinfo, yieldRateInfo sv) of
518+ (Just winfo, Just info) ->
519+ workerStopUpdate winfo info
520+ _ ->
521+ return ()
522+ myThreadId >>= \tid -> void $ send sv (ChildStop tid Nothing)
523+
524+-- {-# NOINLINE sendStopToProducer #-}
525+sendStopToProducer :: MonadIO m => SVar t m a -> m ()
526+sendStopToProducer sv = liftIO $ do
527+ tid <- myThreadId
528+ void $ sendToProducer sv (ChildStop tid Nothing)
529+
530+-------------------------------------------------------------------------------
531+-- Send exceptions
532+-------------------------------------------------------------------------------
533+
534+{-# NOINLINE handleFoldException #-}
535+handleFoldException :: SVar t m a -> SomeException -> IO ()
536+handleFoldException sv e = do
537+ tid <- myThreadId
538+ void $ sendToProducer sv (ChildStop tid (Just e))
539+
540+{-# NOINLINE handleChildException #-}
541+handleChildException :: SVar t m a -> SomeException -> IO ()
542+handleChildException sv e = do
543+ tid <- myThreadId
544+ void $ send sv (ChildStop tid (Just e))
src/Streamly/Internal/Data/Stream/Ahead.hs+5−3
@@ -50,15 +50,17 @@ import GHC.Exts (inline)
50
50
51import qualified Data.Heap as H
51import qualified Data.Heap as H
52
52
53−import Streamly.Internal.Data.Stream.SVar (fromSVar)
53+import Streamly.Internal.Control.Concurrent
54−import Streamly.Internal.Data.SVar
54+ (MonadAsync, RunInIO(..), captureMonadState)
55import Streamly.Internal.Data.Stream.StreamK.Type
55import Streamly.Internal.Data.Stream.StreamK.Type
56− (IsStream(..), Stream, mkStream, foldStream, foldStreamShared)
56+ (IsStream(..), Stream, mkStream, foldStream, foldStreamShared)
57
57
58import qualified Streamly.Internal.Data.Stream.StreamK as K (withLocal)
58import qualified Streamly.Internal.Data.Stream.StreamK as K (withLocal)
59import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
59import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
60import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
60import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
61
61
62+import Streamly.Internal.Data.Stream.SVar.Generate
63+import Streamly.Internal.Data.SVar
62import Prelude hiding (map)
64import Prelude hiding (map)
63
65
64#include "Instances.hs"
66#include "Instances.hs"
src/Streamly/Internal/Data/Stream/Async.hs+34−4
@@ -49,7 +49,7 @@ import Control.Monad.IO.Class (MonadIO(..))
49import Control.Monad.Reader.Class (MonadReader(..))
49import Control.Monad.Reader.Class (MonadReader(..))
50import Control.Monad.State.Class (MonadState(..))
50import Control.Monad.State.Class (MonadState(..))
51import Control.Monad.Trans.Class (MonadTrans(lift))
51import Control.Monad.Trans.Class (MonadTrans(lift))
52−import Data.Concurrent.Queue.MichaelScott (LinkedQueue, newQ, nullQ, tryPopR)
52+import Data.Concurrent.Queue.MichaelScott (LinkedQueue, newQ, nullQ, tryPopR, pushL)
53import Data.IORef (IORef, newIORef, readIORef)
53import Data.IORef (IORef, newIORef, readIORef)
54import Data.Maybe (fromJust)
54import Data.Maybe (fromJust)
55#if __GLASGOW_HASKELL__ < 808
55#if __GLASGOW_HASKELL__ < 808
@@ -59,9 +59,11 @@ import Data.Semigroup (Semigroup(..))
59import Prelude hiding (map)
59import Prelude hiding (map)
60import qualified Data.Set as S
60import qualified Data.Set as S
61
61
62−import Streamly.Internal.Data.Atomics (atomicModifyIORefCAS)
62+import Streamly.Internal.Control.Concurrent
63+ (MonadAsync, RunInIO(..), captureMonadState)
64+import Streamly.Internal.Data.Atomics
65+ (atomicModifyIORefCAS, atomicModifyIORefCAS_)
63−import Streamly.Internal.Data.Stream.SVar (fromSVar, fromSVarD)
66+import Streamly.Internal.Data.Stream.SVar.Generate (fromSVar, fromSVarD)
64−import Streamly.Internal.Data.SVar
65import Streamly.Internal.Data.Stream.StreamK.Type
67import Streamly.Internal.Data.Stream.StreamK.Type
66 (IsStream(..), Stream, mkStream, foldStream, adapt, foldStreamShared)
68 (IsStream(..), Stream, mkStream, foldStream, adapt, foldStreamShared)
67
69
@@ -69,6 +71,9 @@ import qualified Streamly.Internal.Data.Stream.StreamK as K (withLocal)
69import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
71import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
70import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
72import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
71
73
74+import Streamly.Internal.Data.SVar.Type
75+import Streamly.Internal.Data.SVar
76+
72#include "Instances.hs"
77#include "Instances.hs"
73
78
74-- $setup
79-- $setup
@@ -86,6 +91,17 @@ import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
86-- Async
91-- Async
87-------------------------------------------------------------------------------
92-------------------------------------------------------------------------------
88
93
94+-- Note: For purely right associated expressions this queue should have at most
95+-- one element. It grows to more than one when we have left associcated
96+-- expressions. Large left associated compositions can grow this to a
97+-- large size
98+{-# INLINE enqueueLIFO #-}
99+enqueueLIFO ::
100+ SVar t m a -> IORef [(RunInIO m, t m a)] -> (RunInIO m, t m a) -> IO ()
101+enqueueLIFO sv q m = do
102+ atomicModifyIORefCAS_ q $ \ms -> m : ms
103+ ringDoorBell sv
104+
89data WorkerStatus = Continue | Suspend
105data WorkerStatus = Continue | Suspend
90
106
91{-# INLINE workLoopLIFO #-}
107{-# INLINE workLoopLIFO #-}
@@ -208,6 +224,20 @@ workLoopLIFOLimited q st sv winfo = run
208-- WAsync
224-- WAsync
209-------------------------------------------------------------------------------
225-------------------------------------------------------------------------------
210
226
227+-- XXX we can use the Ahead style sequence/heap mechanism to make the best
228+-- effort to always try to finish the streams on the left side of an expression
229+-- first as long as possible.
230+
231+{-# INLINE enqueueFIFO #-}
232+enqueueFIFO ::
233+ SVar t m a
234+ -> LinkedQueue (RunInIO m, t m a)
235+ -> (RunInIO m, t m a)
236+ -> IO ()
237+enqueueFIFO sv q m = do
238+ pushL q m
239+ ringDoorBell sv
240+
211-- XXX we can remove sv as it is derivable from st
241-- XXX we can remove sv as it is derivable from st
212
242
213{-# INLINE workLoopFIFO #-}
243{-# INLINE workLoopFIFO #-}
src/Streamly/Internal/Data/Stream/IsStream/Common.hs+1−1
@@ -56,6 +56,7 @@ where
56import Control.Concurrent (threadDelay)
56import Control.Concurrent (threadDelay)
57import Control.Monad.IO.Class (MonadIO(..))
57import Control.Monad.IO.Class (MonadIO(..))
58import Foreign.Storable (Storable)
58import Foreign.Storable (Storable)
59+import Streamly.Internal.Control.Concurrent (MonadAsync)
59import Streamly.Internal.Data.Array.Foreign.Type (Array)
60import Streamly.Internal.Data.Array.Foreign.Type (Array)
60import Streamly.Internal.Data.Fold.Type (Fold (..))
61import Streamly.Internal.Data.Fold.Type (Fold (..))
61import Streamly.Internal.Data.Stream.IsStream.Combinators (maxYields)
62import Streamly.Internal.Data.Stream.IsStream.Combinators (maxYields)
@@ -63,7 +64,6 @@ import Streamly.Internal.Data.Stream.Prelude (fromStreamS, toStreamS)
63import Streamly.Internal.Data.Stream.Serial (SerialT)
64import Streamly.Internal.Data.Stream.Serial (SerialT)
64import Streamly.Internal.Data.Stream.StreamD.Type (fromStreamD, toStreamD)
65import Streamly.Internal.Data.Stream.StreamD.Type (fromStreamD, toStreamD)
65import Streamly.Internal.Data.Stream.StreamK.Type (IsStream())
66import Streamly.Internal.Data.Stream.StreamK.Type (IsStream())
66−import Streamly.Internal.Data.SVar (MonadAsync)
67import Streamly.Internal.Data.Time.Units (AbsTime, RelTime64, addToAbsTime64)
67import Streamly.Internal.Data.Time.Units (AbsTime, RelTime64, addToAbsTime64)
68
68
69import qualified Streamly.Internal.Data.Stream.Parallel as Par
69import qualified Streamly.Internal.Data.Stream.Parallel as Par
src/Streamly/Internal/Data/Stream/IsStream/Eliminate.hs+2−1
@@ -152,8 +152,9 @@ where
152import Control.Monad.Catch (MonadThrow)
152import Control.Monad.Catch (MonadThrow)
153import Control.Monad.IO.Class (MonadIO(..))
153import Control.Monad.IO.Class (MonadIO(..))
154import Foreign.Storable (Storable)
154import Foreign.Storable (Storable)
155+import Streamly.Internal.Control.Concurrent (MonadAsync)
155import Streamly.Internal.Data.Parser (Parser (..))
156import Streamly.Internal.Data.Parser (Parser (..))
156−import Streamly.Internal.Data.SVar (MonadAsync, defState)
157+import Streamly.Internal.Data.SVar (defState)
157import Streamly.Internal.Data.Stream.IsStream.Common
158import Streamly.Internal.Data.Stream.IsStream.Common
158 ( fold, fold_, drop, findIndices, reverse, splitOnSeq, take
159 ( fold, fold_, drop, findIndices, reverse, splitOnSeq, take
159 , takeWhile)
160 , takeWhile)
src/Streamly/Internal/Data/Stream/IsStream/Exception.hs+1−1
@@ -28,9 +28,9 @@ import Control.Monad.Catch (MonadCatch)
28import Control.Monad.IO.Class (MonadIO(..))
28import Control.Monad.IO.Class (MonadIO(..))
29import Control.Monad.Trans.Control (MonadBaseControl)
29import Control.Monad.Trans.Control (MonadBaseControl)
30import Data.Map.Strict (Map)
30import Data.Map.Strict (Map)
31+import Streamly.Internal.Control.Concurrent (MonadAsync)
31import Streamly.Internal.Data.Stream.StreamD (toStreamD)
32import Streamly.Internal.Data.Stream.StreamD (toStreamD)
32import Streamly.Internal.Data.Stream.StreamK (IsStream)
33import Streamly.Internal.Data.Stream.StreamK (IsStream)
33−import Streamly.Internal.Data.SVar (MonadAsync)
34
34
35import qualified Streamly.Internal.Data.Stream.StreamD as D
35import qualified Streamly.Internal.Data.Stream.StreamD as D
36
36
src/Streamly/Internal/Data/Stream/IsStream/Expand.hs+1−1
@@ -158,6 +158,7 @@ where
158
158
159#include "inline.hs"
159#include "inline.hs"
160
160
161+import Streamly.Internal.Control.Concurrent (MonadAsync)
161import Streamly.Internal.Data.Stream.Ahead (ahead)
162import Streamly.Internal.Data.Stream.Ahead (ahead)
162import Streamly.Internal.Data.Stream.Async (async, wAsync)
163import Streamly.Internal.Data.Stream.Async (async, wAsync)
163import Streamly.Internal.Data.Stream.IsStream.Common
164import Streamly.Internal.Data.Stream.IsStream.Common
@@ -169,7 +170,6 @@ import Streamly.Internal.Data.Stream.Prelude
169import Streamly.Internal.Data.Stream.Serial (serial, wSerial)
170import Streamly.Internal.Data.Stream.Serial (serial, wSerial)
170import Streamly.Internal.Data.Stream.StreamD (fromStreamD, toStreamD)
171import Streamly.Internal.Data.Stream.StreamD (fromStreamD, toStreamD)
171import Streamly.Internal.Data.Stream.StreamK (IsStream)
172import Streamly.Internal.Data.Stream.StreamK (IsStream)
172−import Streamly.Internal.Data.SVar (MonadAsync)
173import Streamly.Internal.Data.Unfold.Type (Unfold)
173import Streamly.Internal.Data.Unfold.Type (Unfold)
174
174
175import qualified Streamly.Internal.Data.Stream.Parallel as Par
175import qualified Streamly.Internal.Data.Stream.Parallel as Par
src/Streamly/Internal/Data/Stream/IsStream/Generate.hs+2−1
@@ -91,8 +91,9 @@ where
91
91
92import Control.Monad.IO.Class (MonadIO(..))
92import Control.Monad.IO.Class (MonadIO(..))
93import Data.Void (Void)
93import Data.Void (Void)
94+import Streamly.Internal.Control.Concurrent (MonadAsync)
94import Streamly.Internal.Data.Unfold.Type (Unfold)
95import Streamly.Internal.Data.Unfold.Type (Unfold)
95−import Streamly.Internal.Data.SVar (MonadAsync, Rate (..))
96+import Streamly.Internal.Data.SVar (Rate (..))
96import Streamly.Internal.Data.Stream.IsStream.Enumeration
97import Streamly.Internal.Data.Stream.IsStream.Enumeration
97 (Enumerable(..), enumerate, enumerateTo)
98 (Enumerable(..), enumerate, enumerateTo)
98import Streamly.Internal.Data.Stream.IsStream.Common
99import Streamly.Internal.Data.Stream.IsStream.Common
src/Streamly/Internal/Data/Stream/IsStream/Reduce.hs+1−1
@@ -154,10 +154,10 @@ import Data.Heap (Entry(..))
154import Data.Kind (Type)
154import Data.Kind (Type)
155import Data.Maybe (isNothing)
155import Data.Maybe (isNothing)
156import Foreign.Storable (Storable)
156import Foreign.Storable (Storable)
157+import Streamly.Internal.Control.Concurrent (MonadAsync)
157import Streamly.Internal.Data.Fold.Type (Fold (..), Fold2 (..))
158import Streamly.Internal.Data.Fold.Type (Fold (..), Fold2 (..))
158import Streamly.Internal.Data.Parser (Parser (..))
159import Streamly.Internal.Data.Parser (Parser (..))
159import Streamly.Internal.Data.Array.Foreign.Type (Array)
160import Streamly.Internal.Data.Array.Foreign.Type (Array)
160−import Streamly.Internal.Data.SVar (MonadAsync)
161import Streamly.Internal.Data.Stream.IsStream.Common
161import Streamly.Internal.Data.Stream.IsStream.Common
162 ( concatMap
162 ( concatMap
163 , fold
163 , fold
src/Streamly/Internal/Data/Stream/IsStream/Top.hs+1−1
@@ -59,7 +59,7 @@ import Data.Kind (Type)
59#if !(MIN_VERSION_base(4,11,0))
59#if !(MIN_VERSION_base(4,11,0))
60import Data.Semigroup (Semigroup(..))
60import Data.Semigroup (Semigroup(..))
61#endif
61#endif
62−import Streamly.Internal.Data.SVar (MonadAsync)
62+import Streamly.Internal.Control.Concurrent (MonadAsync)
63import Streamly.Internal.Data.Stream.IsStream.Common (concatM)
63import Streamly.Internal.Data.Stream.IsStream.Common (concatM)
64import Streamly.Internal.Data.Stream.Prelude (foldl', fromList)
64import Streamly.Internal.Data.Stream.Prelude (foldl', fromList)
65import Streamly.Internal.Data.Stream.Serial (SerialT)
65import Streamly.Internal.Data.Stream.Serial (SerialT)
src/Streamly/Internal/Data/Stream/IsStream/Transform.hs+2−1
@@ -237,11 +237,12 @@ import Streamly.Internal.Data.Stream.IsStream.Common
237 , interjectSuffix
237 , interjectSuffix
238 , intersperseM
238 , intersperseM
239 )
239 )
240+import Streamly.Internal.Control.Concurrent (MonadAsync)
240import Streamly.Internal.Data.Stream.Prelude (fromStreamS, toStreamS)
241import Streamly.Internal.Data.Stream.Prelude (fromStreamS, toStreamS)
241import Streamly.Internal.Data.Stream.Serial (SerialT)
242import Streamly.Internal.Data.Stream.Serial (SerialT)
242import Streamly.Internal.Data.Stream.StreamD (fromStreamD, toStreamD)
243import Streamly.Internal.Data.Stream.StreamD (fromStreamD, toStreamD)
243import Streamly.Internal.Data.Stream.StreamK (IsStream)
244import Streamly.Internal.Data.Stream.StreamK (IsStream)
244−import Streamly.Internal.Data.SVar (MonadAsync, Rate(..))
245+import Streamly.Internal.Data.SVar (Rate(..))
245import Streamly.Internal.Data.Time.Units (TimeUnit64, AbsTime, RelTime64)
246import Streamly.Internal.Data.Time.Units (TimeUnit64, AbsTime, RelTime64)
246
247
247import qualified Streamly.Internal.Data.Fold as FL
248import qualified Streamly.Internal.Data.Fold as FL
src/Streamly/Internal/Data/Stream/IsStream/Types.hs+1−1
@@ -71,4 +71,4 @@ import Streamly.Internal.Data.Stream.Serial
71import Streamly.Internal.Data.Stream.StreamK (IsStream(), adapt)
71import Streamly.Internal.Data.Stream.StreamK (IsStream(), adapt)
72import Streamly.Internal.Data.Stream.Zip
72import Streamly.Internal.Data.Stream.Zip
73 (ZipSerialM, ZipSerial, ZipAsyncM, ZipAsync, fromZipSerial, fromZipAsync)
73 (ZipSerialM, ZipSerial, ZipAsyncM, ZipAsync, fromZipSerial, fromZipAsync)
74−import Streamly.Internal.Data.SVar (MonadAsync)
74+import Streamly.Internal.Control.Concurrent (MonadAsync)
src/Streamly/Internal/Data/Stream/Parallel.hs+5−3
@@ -68,17 +68,19 @@ import Prelude hiding (map)
68
68
69import qualified Data.Set as Set
69import qualified Data.Set as Set
70
70
71+import Streamly.Internal.Control.Concurrent (MonadAsync)
71import Streamly.Internal.Data.Fold.Type (Fold)
72import Streamly.Internal.Data.Fold.Type (Fold)
72import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))
73import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))
73import Streamly.Internal.Data.Stream.StreamK
74import Streamly.Internal.Data.Stream.StreamK
74 (IsStream(..), Stream, mkStream, foldStream, foldStreamShared, adapt)
75 (IsStream(..), Stream, mkStream, foldStream, foldStreamShared, adapt)
75
76
76−import Streamly.Internal.Data.SVar
77−
78import qualified Streamly.Internal.Data.Stream.StreamK as K (withLocal)
77import qualified Streamly.Internal.Data.Stream.StreamK as K (withLocal)
79import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
78import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
80import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
79import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
81−import qualified Streamly.Internal.Data.Stream.SVar as SVar
80+import qualified Streamly.Internal.Data.Stream.SVar.Generate as SVar
81+import qualified Streamly.Internal.Data.Stream.SVar.Eliminate as SVar
82+
83+import Streamly.Internal.Data.SVar
82
84
83#include "Instances.hs"
85#include "Instances.hs"
84
86
src/Streamly/Internal/Data/Stream/SVar.hs → src/Streamly/Internal/Data/Stream/SVar/Eliminate.hsrenamed+20−311
@@ -1,348 +1,51 @@
1−#ifdef __HADDOCK_VERSION__
2−#undef INSPECTION
3−#endif
4−
5−#ifdef INSPECTION
6−{-# LANGUAGE TemplateHaskell #-}
7−{-# OPTIONS_GHC -fplugin Test.Inspection.Plugin #-}
8−#endif
9−
10-- |
1-- |
11−-- Module : Streamly.Internal.Data.Stream.SVar
2+-- Module : Streamly.Internal.Data.Stream.SVar.Eliminate
12-- Copyright : (c) 2017 Composewell Technologies
3-- Copyright : (c) 2017 Composewell Technologies
13−--
14−-- License : BSD3
4+-- License : BSD-3-Clause
15-- Maintainer : streamly@composewell.com
5-- Maintainer : streamly@composewell.com
16-- Stability : experimental
6-- Stability : experimental
17-- Portability : GHC
7-- Portability : GHC
18--
8--
9+-- Eliminate a stream by distributing it to multiple SVars concurrently.
19--
10--
20−module Streamly.Internal.Data.Stream.SVar
11+module Streamly.Internal.Data.Stream.SVar.Eliminate
21 (
12 (
22− -- * Unfold streams from SVar
13+ -- * Concurrent Function Application
23− -- $concurrentEval
24− fromSVar
25− , fromSVarD
26− -- , fromStreamVar
27−
28− -- * Fold streams to SVar
29− , toSVar
30− , toSVarParallel
14+ toSVarParallel
31
15
32 -- * Concurrent folds
16 -- * Concurrent folds
33 -- $concurrentFolds
17 -- $concurrentFolds
18+ , newFoldSVar
19+ , newFoldSVarF
20+
34 , fromConsumer
21 , fromConsumer
35 , pushToFold
22 , pushToFold
36 , teeToSVar
23 , teeToSVar
37− , newFoldSVar
38− , newFoldSVarF
39 )
24 )
40where
25where
41
26
42#include "inline.hs"
27#include "inline.hs"
43
28
44import Control.Concurrent (myThreadId, takeMVar)
29import Control.Concurrent (myThreadId, takeMVar)
45−import Control.Exception (fromException)
46import Control.Monad (when, void)
30import Control.Monad (when, void)
47import Control.Monad.Catch (throwM)
31import Control.Monad.Catch (throwM)
48import Control.Monad.IO.Class (MonadIO(liftIO))
32import Control.Monad.IO.Class (MonadIO(liftIO))
49−import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef)
33+import Data.IORef (newIORef, readIORef, writeIORef)
50−import Data.Maybe (isNothing)
34+import Streamly.Internal.Control.Concurrent (MonadAsync, doFork)
51import Streamly.Internal.Data.Atomics (atomicModifyIORefCAS_)
35import Streamly.Internal.Data.Atomics (atomicModifyIORefCAS_)
36+import Streamly.Internal.Data.Fold.SVar (write, writeLimited)
52import Streamly.Internal.Data.Fold.Type (Fold(..))
37import Streamly.Internal.Data.Fold.Type (Fold(..))
53import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
38import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
54−import System.Mem (performMajorGC)
55
39
56−import qualified Streamly.Internal.Data.Fold.Type as FL
57import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
40import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
58import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
41import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
59
42
60import Streamly.Internal.Data.SVar
43import Streamly.Internal.Data.SVar
61
44
62−#if __GLASGOW_HASKELL__ < 810
63−#ifdef INSPECTION
64−import Control.Exception (Exception)
65−import Control.Monad.Catch (MonadThrow)
66−import Control.Monad.Trans.Control (MonadBaseControl)
67−import Data.Typeable (Typeable)
68−import Test.Inspection (inspect, hasNoTypeClassesExcept)
69−#endif
70−#endif
71−
72−------------------------------------------------------------------------------
73−-- Generating streams from SVar
74−------------------------------------------------------------------------------
75−
76−-- $concurrentEval
77−--
78−-- Usually the SVar is used to concurrently evaluate multiple actions in a
79−-- stream using many worker threads that push the results to the SVar and a
80−-- single puller that pulls them from SVar generating the evaluated stream.
81−--
82−-- @
83−-- input stream
84−-- |
85−-- <-----------------|<--------worker
86−-- | exceptions |
87−-- output stream <------SVar<------worker
88−-- |
89−-- |<--------worker
90−--
91−-- @
92−--
93−-- The puller itself schedules the worker threads based on demand.
94−-- Exceptions are propagated from the worker threads to the puller.
95−
96−-- | Pull a stream from an SVar.
97−{-# NOINLINE fromStreamVar #-}
98−fromStreamVar :: MonadAsync m => SVar K.Stream m a -> K.Stream m a
99−fromStreamVar sv = K.MkStream $ \st yld sng stp -> do
100− list <- readOutputQ sv
101− -- Reversing the output is important to guarantee that we process the
102− -- outputs in the same order as they were generated by the constituent
103− -- streams.
104− K.foldStream st yld sng stp $ processEvents $ reverse list
105−
106− where
107−
108− allDone stp = do
109− when (svarInspectMode sv) $ do
110− t <- liftIO $ getTime Monotonic
111− liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)
112− liftIO $ printSVar sv "SVar Done"
113− stp
114−
115− {-# INLINE processEvents #-}
116− processEvents [] = K.MkStream $ \st yld sng stp -> do
117− done <- postProcess sv
118− if done
119− then allDone stp
120− else K.foldStream st yld sng stp $ fromStreamVar sv
121−
122− processEvents (ev : es) = K.MkStream $ \st yld sng stp -> do
123− let rest = processEvents es
124− case ev of
125− ChildYield a -> yld a rest
126− ChildStop tid e -> do
127− accountThread sv tid
128− case e of
129− Nothing -> do
130− stop <- shouldStop tid
131− if stop
132− then liftIO (cleanupSVar sv) >> allDone stp
133− else K.foldStream st yld sng stp rest
134− Just ex ->
135− case fromException ex of
136− Just ThreadAbort ->
137− K.foldStream st yld sng stp rest
138− Nothing -> liftIO (cleanupSVar sv) >> throwM ex
139− shouldStop tid =
140− case svarStopStyle sv of
141− StopNone -> return False
142− StopAny -> return True
143− StopBy -> do
144− sid <- liftIO $ readIORef (svarStopBy sv)
145− return $ tid == sid
146−
147−#if __GLASGOW_HASKELL__ < 810
148−#ifdef INSPECTION
149−-- Use of GHC constraint tuple (GHC.Classes.(%,,%)) in fromStreamVar leads to
150−-- space leak because the tuple gets allocated in every recursive call and each
151−-- allocation holds on to the previous allocation. This test is to make sure
152−-- that we do not use the constraint tuple type class.
153−--
154−inspect $ hasNoTypeClassesExcept 'fromStreamVar
155− [ ''Monad
156− , ''Applicative
157− , ''MonadThrow
158− , ''Exception
159− , ''MonadIO
160− , ''MonadBaseControl
161− , ''Typeable
162− , ''Functor
163− ]
164−#endif
165−#endif
166−
167−-- | Generate a stream from an SVar. An unevaluated stream can be pushed to an
168−-- SVar using 'toSVar'. As we pull a stream from the SVar the input stream
169−-- gets evaluated concurrently. The evaluation depends on the SVar style and
170−-- the configuration parameters e.g. using the maxBuffer/maxThreads
171−-- combinators.
172−--
173−{-# INLINE fromSVar #-}
174−fromSVar :: (MonadAsync m, K.IsStream t) => SVar K.Stream m a -> t m a
175−fromSVar sv =
176− K.mkStream $ \st yld sng stp -> do
177− ref <- liftIO $ newIORef ()
178− _ <- liftIO $ mkWeakIORef ref hook
179− -- We pass a copy of sv to fromStreamVar, so that we know that it has
180− -- no other references, when that copy gets garbage collected "ref"
181− -- will get garbage collected and our hook will be called.
182− K.foldStreamShared st yld sng stp $
183− K.fromStream $ fromStreamVar sv{svarRef = Just ref}
184− where
185−
186− hook = do
187− when (svarInspectMode sv) $ do
188− r <- liftIO $ readIORef (svarStopTime (svarStats sv))
189− when (isNothing r) $
190− printSVar sv "SVar Garbage Collected"
191− cleanupSVar sv
192− -- If there are any SVars referenced by this SVar a GC will prompt
193− -- them to be cleaned up quickly.
194− when (svarInspectMode sv) performMajorGC
195−
196−data FromSVarState t m a =
197− FromSVarInit
198− | FromSVarRead (SVar t m a)
199− | FromSVarLoop (SVar t m a) [ChildEvent a]
200− | FromSVarDone (SVar t m a)
201−
202−-- | Like 'fromSVar' but generates a StreamD style stream instead of CPS.
203−--
204−{-# INLINE_NORMAL fromSVarD #-}
205−fromSVarD :: (MonadAsync m) => SVar t m a -> D.Stream m a
206−fromSVarD svar = D.Stream step FromSVarInit
207− where
208−
209− {-# INLINE_LATE step #-}
210− step _ FromSVarInit = do
211− ref <- liftIO $ newIORef ()
212− _ <- liftIO $ mkWeakIORef ref hook
213− -- when this copy of svar gets garbage collected "ref" will get
214− -- garbage collected and our GC hook will be called.
215− let sv = svar{svarRef = Just ref}
216− return $ D.Skip (FromSVarRead sv)
217−
218− where
219−
220− {-# NOINLINE hook #-}
221− hook = do
222− when (svarInspectMode svar) $ do
223− r <- liftIO $ readIORef (svarStopTime (svarStats svar))
224− when (isNothing r) $
225− printSVar svar "SVar Garbage Collected"
226− cleanupSVar svar
227− -- If there are any SVars referenced by this SVar a GC will prompt
228− -- them to be cleaned up quickly.
229− when (svarInspectMode svar) performMajorGC
230−
231− step _ (FromSVarRead sv) = do
232− list <- readOutputQ sv
233− -- Reversing the output is important to guarantee that we process the
234− -- outputs in the same order as they were generated by the constituent
235− -- streams.
236− return $ D.Skip $ FromSVarLoop sv (Prelude.reverse list)
237−
238− step _ (FromSVarLoop sv []) = do
239− done <- postProcess sv
240− return $ D.Skip $ if done
241− then FromSVarDone sv
242− else FromSVarRead sv
243−
244− step _ (FromSVarLoop sv (ev : es)) = do
245− case ev of
246− ChildYield a -> return $ D.Yield a (FromSVarLoop sv es)
247− ChildStop tid e -> do
248− accountThread sv tid
249− case e of
250− Nothing -> do
251− stop <- shouldStop tid
252− if stop
253− then do
254− liftIO (cleanupSVar sv)
255− return $ D.Skip (FromSVarDone sv)
256− else return $ D.Skip (FromSVarLoop sv es)
257− Just ex ->
258− case fromException ex of
259− Just ThreadAbort ->
260− return $ D.Skip (FromSVarLoop sv es)
261− Nothing -> liftIO (cleanupSVar sv) >> throwM ex
262− where
263−
264− shouldStop tid =
265− case svarStopStyle sv of
266− StopNone -> return False
267− StopAny -> return True
268− StopBy -> do
269− sid <- liftIO $ readIORef (svarStopBy sv)
270− return $ tid == sid
271−
272− step _ (FromSVarDone sv) = do
273− when (svarInspectMode sv) $ do
274− t <- liftIO $ getTime Monotonic
275− liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)
276− liftIO $ printSVar sv "SVar Done"
277− return D.Stop
278−
279−------------------------------------------------------------------------------
280−-- Writing streams to SVar
281−------------------------------------------------------------------------------
282−
283−-- | Write a stream to an 'SVar' in a non-blocking manner. The stream is
284−-- evaluated concurrently as it is read back from the SVar using 'fromSVar'.
285−--
286−toSVar :: (K.IsStream t, MonadAsync m) => SVar K.Stream m a -> t m a -> m ()
287−toSVar sv m = toStreamVar sv $ K.toStream m
288−
289-------------------------------------------------------------------------------
45-------------------------------------------------------------------------------
290−-- Concurrent application
46+-- Concurrent function application
291-------------------------------------------------------------------------------
47-------------------------------------------------------------------------------
292
48
293−-- | A fold to write a stream to an SVar. Unlike 'toSVar' this does not allow
294−-- for concurrent evaluation of the stream, as the fold receives the input one
295−-- element at a time, it just forwards the elements to the SVar. However, we
296−-- can safely execute the fold in an independent thread, the SVar can act as a
297−-- buffer decoupling the sender from the receiver. Also, we can have multiple
298−-- folds running concurrently pusing the streams to the SVar.
299−--
300−{-# INLINE write #-}
301−write :: MonadIO m => SVar t m a -> Maybe WorkerInfo -> Fold m a ()
302−write svar winfo = Fold step initial extract
303−
304− where
305−
306− initial = return $ FL.Partial ()
307−
308− -- XXX we can have a separate fold for unlimited buffer case to avoid a
309− -- branch in the step here.
310− step () x =
311− liftIO $ do
312− decrementBufferLimit svar
313− void $ send svar (ChildYield x)
314− return $ FL.Partial ()
315−
316− extract () = liftIO $ sendStop svar winfo
317−
318−-- | Like write, but applies a yield limit.
319−--
320−{-# INLINE writeLimited #-}
321−writeLimited :: MonadIO m
322− => SVar t m a -> Maybe WorkerInfo -> Fold m a ()
323−writeLimited svar winfo = Fold step initial extract
324−
325− where
326−
327− initial = return $ FL.Partial True
328−
329− step True x =
330− liftIO $ do
331− yieldLimitOk <- decrementYieldLimit svar
332− if yieldLimitOk
333− then do
334− decrementBufferLimit svar
335− void $ send svar (ChildYield x)
336− return $ FL.Partial True
337− else do
338− cleanupSVarFromWorker svar
339− sendStop svar winfo
340− return $ FL.Done ()
341− step False _ = return $ FL.Done ()
342−
343− extract True = liftIO $ sendStop svar winfo
344− extract False = return ()
345−
346-- Using StreamD the worker stream producing code can fuse with the code to
49-- Using StreamD the worker stream producing code can fuse with the code to
347-- queue output to the SVar giving some perf boost.
50-- queue output to the SVar giving some perf boost.
348--
51--
@@ -514,6 +217,12 @@ newFoldSVar stt f = do
514 (handleFoldException sv)
217 (handleFoldException sv)
515 return sv
218 return sv
516
219
220+data FromSVarState t m a =
221+ FromSVarInit
222+ | FromSVarRead (SVar t m a)
223+ | FromSVarLoop (SVar t m a) [ChildEvent a]
224+ | FromSVarDone (SVar t m a)
225+
517-- | Like 'fromProducer' but generates a StreamD style stream instead of
226-- | Like 'fromProducer' but generates a StreamD style stream instead of
518-- StreamK.
227-- StreamK.
519--
228--
@@ -622,7 +331,7 @@ pushToFold sv a = do
622 let qref = outputQueueFromConsumer sv
331 let qref = outputQueueFromConsumer sv
623 done <- do
332 done <- do
624 (_, n) <- liftIO $ readIORef qref
333 (_, n) <- liftIO $ readIORef qref
625− if (n > 0)
334+ if n > 0
626 then fromConsumer sv
335 then fromConsumer sv
627 else return False
336 else return False
628 if done
337 if done
src/Streamly/Internal/Data/Stream/SVar/Generate.hsnew file+297
@@ -0,0 +1,297 @@
1+#ifdef __HADDOCK_VERSION__
2+#undef INSPECTION
3+#endif
4+
5+#ifdef INSPECTION
6+{-# LANGUAGE TemplateHaskell #-}
7+{-# OPTIONS_GHC -fplugin Test.Inspection.Plugin #-}
8+#endif
9+
10+-- |
11+-- Module : Streamly.Internal.Data.Stream.SVar.Generate
12+-- Copyright : (c) 2017 Composewell Technologies
13+-- License : BSD-3-Clause
14+-- Maintainer : streamly@composewell.com
15+-- Stability : experimental
16+-- Portability : GHC
17+--
18+--
19+module Streamly.Internal.Data.Stream.SVar.Generate
20+ (
21+ -- * Write to SVar
22+ toStreamVar
23+ , toSVar
24+
25+ -- * Read from SVar
26+ -- $concurrentEval
27+ , fromSVar
28+ , fromSVarD
29+ -- , fromStreamVar
30+ )
31+where
32+
33+#include "inline.hs"
34+
35+import Control.Exception (fromException)
36+import Control.Monad (when)
37+import Control.Monad.Catch (throwM)
38+import Control.Monad.IO.Class (MonadIO(liftIO))
39+import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef)
40+import Data.Maybe (isNothing)
41+import Streamly.Internal.Control.Concurrent (MonadAsync, captureMonadState)
42+import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
43+import System.Mem (performMajorGC)
44+
45+import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
46+import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
47+
48+import Streamly.Internal.Data.SVar
49+
50+#if __GLASGOW_HASKELL__ < 810
51+#ifdef INSPECTION
52+import Control.Exception (Exception)
53+import Control.Monad.Catch (MonadThrow)
54+import Control.Monad.Trans.Control (MonadBaseControl)
55+import Data.Typeable (Typeable)
56+import Test.Inspection (inspect, hasNoTypeClassesExcept)
57+#endif
58+#endif
59+
60+------------------------------------------------------------------------------
61+-- Generating streams from SVar
62+------------------------------------------------------------------------------
63+
64+-- $concurrentEval
65+--
66+-- Usually the SVar is used to concurrently evaluate multiple actions in a
67+-- stream using many worker threads that push the results to the SVar and a
68+-- single puller that pulls them from SVar generating the evaluated stream.
69+--
70+-- @
71+-- input stream
72+-- |
73+-- <-----------------|<--------worker
74+-- | exceptions |
75+-- output stream <------SVar<------worker
76+-- |
77+-- |<--------worker
78+--
79+-- @
80+--
81+-- The puller itself schedules the worker threads based on demand.
82+-- Exceptions are propagated from the worker threads to the puller.
83+
84+-------------------------------------------------------------------------------
85+-- Write a stream to an SVar
86+-------------------------------------------------------------------------------
87+
88+-- XXX this errors out for Parallel/Ahead SVars
89+-- | Write a stream to an 'SVar' in a non-blocking manner. The stream can then
90+-- be read back from the SVar using 'fromSVar'.
91+toStreamVar :: MonadAsync m => SVar t m a -> t m a -> m ()
92+toStreamVar sv m = do
93+ runIn <- captureMonadState
94+ liftIO $ enqueue sv (runIn, m)
95+ done <- allThreadsDone sv
96+ -- XXX This is safe only when called from the consumer thread or when no
97+ -- consumer is present. There may be a race if we are not running in the
98+ -- consumer thread.
99+ -- XXX do this only if the work queue is not empty. The work may have been
100+ -- carried out by existing workers.
101+ when done $
102+ case yieldRateInfo sv of
103+ Nothing -> pushWorker 0 sv
104+ Just _ -> pushWorker 1 sv
105+
106+-- | Write a stream to an 'SVar' in a non-blocking manner. The stream is
107+-- evaluated concurrently as it is read back from the SVar using 'fromSVar'.
108+--
109+toSVar :: (K.IsStream t, MonadAsync m) => SVar K.Stream m a -> t m a -> m ()
110+toSVar sv m = toStreamVar sv $ K.toStream m
111+
112+-------------------------------------------------------------------------------
113+-- Read a stream from an SVar
114+-------------------------------------------------------------------------------
115+
116+-- | Pull a stream from an SVar.
117+{-# NOINLINE fromStreamVar #-}
118+fromStreamVar :: MonadAsync m => SVar K.Stream m a -> K.Stream m a
119+fromStreamVar sv = K.MkStream $ \st yld sng stp -> do
120+ list <- readOutputQ sv
121+ -- Reversing the output is important to guarantee that we process the
122+ -- outputs in the same order as they were generated by the constituent
123+ -- streams.
124+ K.foldStream st yld sng stp $ processEvents $ reverse list
125+
126+ where
127+
128+ allDone stp = do
129+ when (svarInspectMode sv) $ do
130+ t <- liftIO $ getTime Monotonic
131+ liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)
132+ liftIO $ printSVar sv "SVar Done"
133+ stp
134+
135+ {-# INLINE processEvents #-}
136+ processEvents [] = K.MkStream $ \st yld sng stp -> do
137+ done <- postProcess sv
138+ if done
139+ then allDone stp
140+ else K.foldStream st yld sng stp $ fromStreamVar sv
141+
142+ processEvents (ev : es) = K.MkStream $ \st yld sng stp -> do
143+ let rest = processEvents es
144+ case ev of
145+ ChildYield a -> yld a rest
146+ ChildStop tid e -> do
147+ accountThread sv tid
148+ case e of
149+ Nothing -> do
150+ stop <- shouldStop tid
151+ if stop
152+ then liftIO (cleanupSVar sv) >> allDone stp
153+ else K.foldStream st yld sng stp rest
154+ Just ex ->
155+ case fromException ex of
156+ Just ThreadAbort ->
157+ K.foldStream st yld sng stp rest
158+ Nothing -> liftIO (cleanupSVar sv) >> throwM ex
159+ shouldStop tid =
160+ case svarStopStyle sv of
161+ StopNone -> return False
162+ StopAny -> return True
163+ StopBy -> do
164+ sid <- liftIO $ readIORef (svarStopBy sv)
165+ return $ tid == sid
166+
167+#if __GLASGOW_HASKELL__ < 810
168+#ifdef INSPECTION
169+-- Use of GHC constraint tuple (GHC.Classes.(%,,%)) in fromStreamVar leads to
170+-- space leak because the tuple gets allocated in every recursive call and each
171+-- allocation holds on to the previous allocation. This test is to make sure
172+-- that we do not use the constraint tuple type class.
173+--
174+inspect $ hasNoTypeClassesExcept 'fromStreamVar
175+ [ ''Monad
176+ , ''Applicative
177+ , ''MonadThrow
178+ , ''Exception
179+ , ''MonadIO
180+ , ''MonadBaseControl
181+ , ''Typeable
182+ , ''Functor
183+ ]
184+#endif
185+#endif
186+
187+-- | Generate a stream from an SVar. An unevaluated stream can be pushed to an
188+-- SVar using 'toSVar'. As we pull a stream from the SVar the input stream
189+-- gets evaluated concurrently. The evaluation depends on the SVar style and
190+-- the configuration parameters e.g. using the maxBuffer/maxThreads
191+-- combinators.
192+--
193+{-# INLINE fromSVar #-}
194+fromSVar :: (MonadAsync m, K.IsStream t) => SVar K.Stream m a -> t m a
195+fromSVar sv =
196+ K.mkStream $ \st yld sng stp -> do
197+ ref <- liftIO $ newIORef ()
198+ _ <- liftIO $ mkWeakIORef ref hook
199+ -- We pass a copy of sv to fromStreamVar, so that we know that it has
200+ -- no other references, when that copy gets garbage collected "ref"
201+ -- will get garbage collected and our hook will be called.
202+ K.foldStreamShared st yld sng stp $
203+ K.fromStream $ fromStreamVar sv{svarRef = Just ref}
204+ where
205+
206+ hook = do
207+ when (svarInspectMode sv) $ do
208+ r <- liftIO $ readIORef (svarStopTime (svarStats sv))
209+ when (isNothing r) $
210+ printSVar sv "SVar Garbage Collected"
211+ cleanupSVar sv
212+ -- If there are any SVars referenced by this SVar a GC will prompt
213+ -- them to be cleaned up quickly.
214+ when (svarInspectMode sv) performMajorGC
215+
216+data FromSVarState t m a =
217+ FromSVarInit
218+ | FromSVarRead (SVar t m a)
219+ | FromSVarLoop (SVar t m a) [ChildEvent a]
220+ | FromSVarDone (SVar t m a)
221+
222+-- | Like 'fromSVar' but generates a StreamD style stream instead of CPS.
223+--
224+{-# INLINE_NORMAL fromSVarD #-}
225+fromSVarD :: (MonadAsync m) => SVar t m a -> D.Stream m a
226+fromSVarD svar = D.Stream step FromSVarInit
227+ where
228+
229+ {-# INLINE_LATE step #-}
230+ step _ FromSVarInit = do
231+ ref <- liftIO $ newIORef ()
232+ _ <- liftIO $ mkWeakIORef ref hook
233+ -- when this copy of svar gets garbage collected "ref" will get
234+ -- garbage collected and our GC hook will be called.
235+ let sv = svar{svarRef = Just ref}
236+ return $ D.Skip (FromSVarRead sv)
237+
238+ where
239+
240+ {-# NOINLINE hook #-}
241+ hook = do
242+ when (svarInspectMode svar) $ do
243+ r <- liftIO $ readIORef (svarStopTime (svarStats svar))
244+ when (isNothing r) $
245+ printSVar svar "SVar Garbage Collected"
246+ cleanupSVar svar
247+ -- If there are any SVars referenced by this SVar a GC will prompt
248+ -- them to be cleaned up quickly.
249+ when (svarInspectMode svar) performMajorGC
250+
251+ step _ (FromSVarRead sv) = do
252+ list <- readOutputQ sv
253+ -- Reversing the output is important to guarantee that we process the
254+ -- outputs in the same order as they were generated by the constituent
255+ -- streams.
256+ return $ D.Skip $ FromSVarLoop sv (Prelude.reverse list)
257+
258+ step _ (FromSVarLoop sv []) = do
259+ done <- postProcess sv
260+ return $ D.Skip $ if done
261+ then FromSVarDone sv
262+ else FromSVarRead sv
263+
264+ step _ (FromSVarLoop sv (ev : es)) = do
265+ case ev of
266+ ChildYield a -> return $ D.Yield a (FromSVarLoop sv es)
267+ ChildStop tid e -> do
268+ accountThread sv tid
269+ case e of
270+ Nothing -> do
271+ stop <- shouldStop tid
272+ if stop
273+ then do
274+ liftIO (cleanupSVar sv)
275+ return $ D.Skip (FromSVarDone sv)
276+ else return $ D.Skip (FromSVarLoop sv es)
277+ Just ex ->
278+ case fromException ex of
279+ Just ThreadAbort ->
280+ return $ D.Skip (FromSVarLoop sv es)
281+ Nothing -> liftIO (cleanupSVar sv) >> throwM ex
282+ where
283+
284+ shouldStop tid =
285+ case svarStopStyle sv of
286+ StopNone -> return False
287+ StopAny -> return True
288+ StopBy -> do
289+ sid <- liftIO $ readIORef (svarStopBy sv)
290+ return $ tid == sid
291+
292+ step _ (FromSVarDone sv) = do
293+ when (svarInspectMode sv) $ do
294+ t <- liftIO $ getTime Monotonic
295+ liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)
296+ liftIO $ printSVar sv "SVar Done"
297+ return D.Stop
src/Streamly/Internal/Data/Stream/StreamD/Eliminate.hs+1−1
@@ -81,6 +81,7 @@ import Control.Monad.Catch (MonadThrow, throwM)
81import GHC.Exts (SpecConstrAnnotation(..))
81import GHC.Exts (SpecConstrAnnotation(..))
82import GHC.Types (SPEC(..))
82import GHC.Types (SPEC(..))
83import Streamly.Internal.Data.Parser (ParseError(..))
83import Streamly.Internal.Data.Parser (ParseError(..))
84+import Streamly.Internal.Data.SVar.Type (defState)
84
85
85import qualified Streamly.Internal.Data.Parser as PR
86import qualified Streamly.Internal.Data.Parser as PR
86import qualified Streamly.Internal.Data.Parser.ParserD as PRD
87import qualified Streamly.Internal.Data.Parser.ParserD as PRD
@@ -90,7 +91,6 @@ import Prelude hiding
90 ( all, any, elem, foldr, foldr1, head, last, lookup, mapM, mapM_
91 ( all, any, elem, foldr, foldr1, head, last, lookup, mapM, mapM_
91 , maximum, minimum, notElem, null, splitAt, tail, (!!))
92 , maximum, minimum, notElem, null, splitAt, tail, (!!))
92import Streamly.Internal.Data.Stream.StreamD.Type
93import Streamly.Internal.Data.Stream.StreamD.Type
93−import Streamly.Internal.Data.SVar
94
94
95------------------------------------------------------------------------------
95------------------------------------------------------------------------------
96-- Elimination by Folds
96-- Elimination by Folds
src/Streamly/Internal/Data/Stream/StreamD/Exception.hs+1−1
@@ -32,6 +32,7 @@ import Control.Monad.IO.Class (MonadIO(..))
32import Control.Monad.Trans.Control (MonadBaseControl, liftBaseOp_)
32import Control.Monad.Trans.Control (MonadBaseControl, liftBaseOp_)
33import Data.Map.Strict (Map)
33import Data.Map.Strict (Map)
34import GHC.Exts (inline)
34import GHC.Exts (inline)
35+import Streamly.Internal.Control.Concurrent (MonadAsync)
35import Streamly.Internal.Data.IOFinalizer
36import Streamly.Internal.Data.IOFinalizer
36 (newIOFinalizer, runIOFinalizer, clearingIOFinalizer)
37 (newIOFinalizer, runIOFinalizer, clearingIOFinalizer)
37
38
@@ -39,7 +40,6 @@ import qualified Control.Monad.Catch as MC
39import qualified Data.Map.Strict as Map
40import qualified Data.Map.Strict as Map
40
41
41import Streamly.Internal.Data.Stream.StreamD.Type
42import Streamly.Internal.Data.Stream.StreamD.Type
42−import Streamly.Internal.Data.SVar
43
43
44data GbracketState s1 s2 v
44data GbracketState s1 s2 v
45 = GBracketInit
45 = GBracketInit
src/Streamly/Internal/Data/Stream/StreamD/Generate.hs+1−1
@@ -90,6 +90,7 @@ where
90#include "inline.hs"
90#include "inline.hs"
91
91
92import Control.Monad.IO.Class (MonadIO(..))
92import Control.Monad.IO.Class (MonadIO(..))
93+import Streamly.Internal.Control.Concurrent (MonadAsync)
93import Streamly.Internal.Data.Time.Clock
94import Streamly.Internal.Data.Time.Clock
94 (Clock(Monotonic), asyncClock, readClock)
95 (Clock(Monotonic), asyncClock, readClock)
95import Streamly.Internal.Data.Time.Units
96import Streamly.Internal.Data.Time.Units
@@ -97,7 +98,6 @@ import Streamly.Internal.Data.Time.Units
97
98
98import Prelude hiding (iterate, repeat, replicate, takeWhile)
99import Prelude hiding (iterate, repeat, replicate, takeWhile)
99import Streamly.Internal.Data.Stream.StreamD.Type
100import Streamly.Internal.Data.Stream.StreamD.Type
100−import Streamly.Internal.Data.SVar
101
101
102------------------------------------------------------------------------------
102------------------------------------------------------------------------------
103-- Primitives
103-- Primitives
src/Streamly/Internal/Data/Stream/StreamD/Lift.hs+1−1
@@ -28,7 +28,7 @@ import Control.Monad.Trans.Class (MonadTrans(lift))
28import Control.Monad.Trans.Reader (ReaderT)
28import Control.Monad.Trans.Reader (ReaderT)
29import Control.Monad.Trans.State.Strict (StateT)
29import Control.Monad.Trans.State.Strict (StateT)
30import Data.Functor.Identity (Identity(..))
30import Data.Functor.Identity (Identity(..))
31−import Streamly.Internal.Data.SVar (adaptState)
31+import Streamly.Internal.Data.SVar.Type (adaptState)
32
32
33import qualified Control.Monad.Trans.Reader as Reader
33import qualified Control.Monad.Trans.Reader as Reader
34import qualified Control.Monad.Trans.State.Strict as State
34import qualified Control.Monad.Trans.State.Strict as State
src/Streamly/Internal/Data/Stream/StreamD/Nesting.hs+1−1
@@ -160,6 +160,7 @@ import GHC.Types (SPEC(..))
160import Streamly.Internal.Data.Array.Foreign.Type (Array(..))
160import Streamly.Internal.Data.Array.Foreign.Type (Array(..))
161import Streamly.Internal.Data.Fold.Type (Fold(..))
161import Streamly.Internal.Data.Fold.Type (Fold(..))
162import Streamly.Internal.Data.Parser (ParseError(..))
162import Streamly.Internal.Data.Parser (ParseError(..))
163+import Streamly.Internal.Data.SVar.Type (adaptState)
163import Streamly.Internal.Data.Unfold.Type (Unfold(..))
164import Streamly.Internal.Data.Unfold.Type (Unfold(..))
164
165
165import qualified Streamly.Internal.Data.Array.Foreign.Type as A
166import qualified Streamly.Internal.Data.Array.Foreign.Type as A
@@ -169,7 +170,6 @@ import qualified Streamly.Internal.Data.Parser.ParserD as PRD
169import qualified Streamly.Internal.Ring.Foreign as RB
170import qualified Streamly.Internal.Ring.Foreign as RB
170
171
171import Streamly.Internal.Data.Stream.StreamD.Type
172import Streamly.Internal.Data.Stream.StreamD.Type
172−import Streamly.Internal.Data.SVar
173
173
174import Prelude hiding (concatMap, mapM, zipWith)
174import Prelude hiding (concatMap, mapM, zipWith)
175
175
src/Streamly/Internal/Data/Stream/StreamD/Transform.hs+2−1
@@ -133,8 +133,10 @@ import Foreign.Storable (Storable(..))
133import GHC.Types (SPEC(..))
133import GHC.Types (SPEC(..))
134import qualified Control.Monad.Catch as MC
134import qualified Control.Monad.Catch as MC
135
135
136+import Streamly.Internal.Control.Concurrent (MonadAsync, fork, forkManaged)
136import Streamly.Internal.Data.Fold.Type (Fold(..))
137import Streamly.Internal.Data.Fold.Type (Fold(..))
137import Streamly.Internal.Data.Pipe.Type (Pipe(..), PipeState(..))
138import Streamly.Internal.Data.Pipe.Type (Pipe(..), PipeState(..))
139+import Streamly.Internal.Data.SVar.Type (defState, adaptState)
138import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
140import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
139import Streamly.Internal.Data.Time.Units
141import Streamly.Internal.Data.Time.Units
140 (TimeUnit64, toRelTime64, diffAbsTime64)
142 (TimeUnit64, toRelTime64, diffAbsTime64)
@@ -151,7 +153,6 @@ import Prelude hiding
151 , scanl, scanl1, sequence, take, takeWhile)
153 , scanl, scanl1, sequence, take, takeWhile)
152
154
153import Streamly.Internal.Data.Stream.StreamD.Type
155import Streamly.Internal.Data.Stream.StreamD.Type
154−import Streamly.Internal.Data.SVar
155
156
156------------------------------------------------------------------------------
157------------------------------------------------------------------------------
157-- Piping
158-- Piping
src/Streamly/Internal/Data/Stream/StreamD/Type.hs+1−1
@@ -99,7 +99,7 @@ import Prelude hiding (map, mapM, foldr, take, concatMap, takeWhile)
99
99
100import Streamly.Internal.Data.Fold.Type (Fold(..), Fold2(..))
100import Streamly.Internal.Data.Fold.Type (Fold(..), Fold2(..))
101import Streamly.Internal.Data.Stream.StreamD.Step (Step (..))
101import Streamly.Internal.Data.Stream.StreamD.Step (Step (..))
102−import Streamly.Internal.Data.SVar (State, adaptState, defState)
102+import Streamly.Internal.Data.SVar.Type (State, adaptState, defState)
103import Streamly.Internal.Data.Unfold.Type (Unfold(..))
103import Streamly.Internal.Data.Unfold.Type (Unfold(..))
104
104
105import qualified Streamly.Internal.Data.Fold.Type as FL
105import qualified Streamly.Internal.Data.Fold.Type as FL
src/Streamly/Internal/Data/Stream/StreamK.hs+6−4
@@ -190,18 +190,20 @@ import Control.Monad.Trans.Class (MonadTrans(lift))
190import Control.Monad (void, join)
190import Control.Monad (void, join)
191import Control.Monad.Reader.Class (MonadReader(..))
191import Control.Monad.Reader.Class (MonadReader(..))
192import Data.Function (fix)
192import Data.Function (fix)
193+import Streamly.Internal.Control.Concurrent (MonadAsync)
194+import Streamly.Internal.Data.SVar.Type (adaptState, defState)
195+
196+import qualified Prelude
197+import qualified Streamly.Internal.Data.Fold.Type as FL
198+
193import Prelude
199import Prelude
194 hiding (foldl, foldr, last, map, mapM, mapM_, repeat, sequence,
200 hiding (foldl, foldr, last, map, mapM, mapM_, repeat, sequence,
195 take, filter, all, any, takeWhile, drop, dropWhile, minimum,
201 take, filter, all, any, takeWhile, drop, dropWhile, minimum,
196 maximum, elem, notElem, null, head, tail, init, zipWith, lookup,
202 maximum, elem, notElem, null, head, tail, init, zipWith, lookup,
197 foldr1, (!!), replicate, reverse, concatMap, iterate)
203 foldr1, (!!), replicate, reverse, concatMap, iterate)
198−import qualified Prelude
199
204
200−import Streamly.Internal.Data.SVar
201import Streamly.Internal.Data.Stream.StreamK.Type
205import Streamly.Internal.Data.Stream.StreamK.Type
202
206
203−import qualified Streamly.Internal.Data.Fold.Type as FL
204−
205-- $setup
207-- $setup
206-- >>> :m
208-- >>> :m
207
209
src/Streamly/Internal/Data/Stream/StreamK/Type.hs+2−3
@@ -81,14 +81,13 @@ where
81import Control.Monad (ap, (>=>))
81import Control.Monad (ap, (>=>))
82import Control.Monad.Trans.Class (MonadTrans(lift))
82import Control.Monad.Trans.Class (MonadTrans(lift))
83import Data.Kind (Type)
83import Data.Kind (Type)
84−
85#if __GLASGOW_HASKELL__ < 808
84#if __GLASGOW_HASKELL__ < 808
86import Data.Semigroup (Semigroup(..))
85import Data.Semigroup (Semigroup(..))
87#endif
86#endif
87+import Streamly.Internal.Control.Concurrent (MonadAsync)
88+import Streamly.Internal.Data.SVar.Type (State, adaptState, defState)
88import Prelude hiding (map, mapM, concatMap, foldr)
89import Prelude hiding (map, mapM, concatMap, foldr)
89
90
90−import Streamly.Internal.Data.SVar
91−
92-- $setup
91-- $setup
93-- >>> import Streamly.Prelude as Stream
92-- >>> import Streamly.Prelude as Stream
94
93
src/Streamly/Internal/Data/Stream/Zip.hs+1−1
@@ -51,10 +51,10 @@ import GHC.Exts (IsList(..), IsString(..))
51import Text.Read
51import Text.Read
52 ( Lexeme(Ident), lexP, parens, prec, readPrec, readListPrec
52 ( Lexeme(Ident), lexP, parens, prec, readPrec, readListPrec
53 , readListPrecDefault)
53 , readListPrecDefault)
54+import Streamly.Internal.Control.Concurrent (MonadAsync)
54import Streamly.Internal.BaseCompat ((#.), errorWithoutStackTrace, oneShot)
55import Streamly.Internal.BaseCompat ((#.), errorWithoutStackTrace, oneShot)
55import Streamly.Internal.Data.Stream.StreamK (IsStream(..), Stream)
56import Streamly.Internal.Data.Stream.StreamK (IsStream(..), Stream)
56import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)
57import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)
57−import Streamly.Internal.Data.SVar (MonadAsync)
58
58
59import qualified Streamly.Internal.Data.Stream.Parallel as Par
59import qualified Streamly.Internal.Data.Stream.Parallel as Par
60import qualified Streamly.Internal.Data.Stream.Prelude as P
60import qualified Streamly.Internal.Data.Stream.Prelude as P
src/Streamly/Internal/Data/Unfold.hs+4−139
@@ -163,9 +163,6 @@ module Streamly.Internal.Data.Unfold
163 , fromStreamK
163 , fromStreamK
164 , fromStreamD
164 , fromStreamD
165
165
166− , fromSVar
167− , fromProducer
168−
169 -- * Combinators
166 -- * Combinators
170 -- ** Mapping on Input
167 -- ** Mapping on Input
171 , lmap
168 , lmap
@@ -232,21 +229,19 @@ module Streamly.Internal.Data.Unfold
232 )
229 )
233where
230where
234
231
235−import Control.Exception (Exception, fromException, mask_)
232+import Control.Exception (Exception, mask_)
236−import Control.Monad ((>=>), when)
233+import Control.Monad ((>=>))
237import Control.Monad.Catch (MonadCatch)
234import Control.Monad.Catch (MonadCatch)
238import Control.Monad.IO.Class (MonadIO(..))
235import Control.Monad.IO.Class (MonadIO(..))
239import Control.Monad.Trans.Control (MonadBaseControl, liftBaseOp_)
236import Control.Monad.Trans.Control (MonadBaseControl, liftBaseOp_)
240−import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef)
241−import Data.Maybe (isNothing)
242import Data.Void (Void)
237import Data.Void (Void)
243import GHC.Types (SPEC(..))
238import GHC.Types (SPEC(..))
239+import Streamly.Internal.Control.Concurrent (MonadAsync)
244import Streamly.Internal.Data.Fold.Type (Fold(..))
240import Streamly.Internal.Data.Fold.Type (Fold(..))
245import Streamly.Internal.Data.IOFinalizer
241import Streamly.Internal.Data.IOFinalizer
246 (newIOFinalizer, runIOFinalizer, clearingIOFinalizer)
242 (newIOFinalizer, runIOFinalizer, clearingIOFinalizer)
247import Streamly.Internal.Data.Stream.StreamD.Type (Stream(..), Step(..))
243import Streamly.Internal.Data.Stream.StreamD.Type (Stream(..), Step(..))
248−import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
244+import Streamly.Internal.Data.SVar.Type (defState)
249−import System.Mem (performMajorGC)
250
245
251import qualified Prelude
246import qualified Prelude
252import qualified Control.Monad.Catch as MC
247import qualified Control.Monad.Catch as MC
@@ -256,7 +251,6 @@ import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
256import qualified Streamly.Internal.Data.Stream.StreamK as K (uncons)
251import qualified Streamly.Internal.Data.Stream.StreamK as K (uncons)
257import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
252import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
258
253
259−import Streamly.Internal.Data.SVar
260import Streamly.Internal.Data.Unfold.Type
254import Streamly.Internal.Data.Unfold.Type
261import Prelude
255import Prelude
262 hiding (map, mapM, takeWhile, take, filter, const, zipWith
256 hiding (map, mapM, takeWhile, take, filter, const, zipWith
@@ -885,135 +879,6 @@ enumerateFromToFractional :: (Monad m, Fractional a, Ord a) => a -> Unfold m a a
885enumerateFromToFractional to =
879enumerateFromToFractional to =
886 takeWhile (<= to + 1 / 2) $ enumerateFromStepNum 1
880 takeWhile (<= to + 1 / 2) $ enumerateFromStepNum 1
887
881
888−-------------------------------------------------------------------------------
889−-- Generation from SVar
890−-------------------------------------------------------------------------------
891−
892−data FromSVarState t m a =
893− FromSVarInit (SVar t m a)
894− | FromSVarRead (SVar t m a)
895− | FromSVarLoop (SVar t m a) [ChildEvent a]
896− | FromSVarDone (SVar t m a)
897−
898−-- | /Internal/
899−--
900−{-# INLINE_NORMAL fromSVar #-}
901−fromSVar :: MonadAsync m => Unfold m (SVar t m a) a
902−fromSVar = Unfold step (return . FromSVarInit)
903− where
904−
905− {-# INLINE_LATE step #-}
906− step (FromSVarInit svar) = do
907− ref <- liftIO $ newIORef ()
908− _ <- liftIO $ mkWeakIORef ref hook
909− -- when this copy of svar gets garbage collected "ref" will get
910− -- garbage collected and our GC hook will be called.
911− let sv = svar{svarRef = Just ref}
912− return $ Skip (FromSVarRead sv)
913−
914− where
915−
916− {-# NOINLINE hook #-}
917− hook = do
918− when (svarInspectMode svar) $ do
919− r <- liftIO $ readIORef (svarStopTime (svarStats svar))
920− when (isNothing r) $
921− printSVar svar "SVar Garbage Collected"
922− cleanupSVar svar
923− -- If there are any SVars referenced by this SVar a GC will prompt
924− -- them to be cleaned up quickly.
925− when (svarInspectMode svar) performMajorGC
926−
927− step (FromSVarRead sv) = do
928− list <- readOutputQ sv
929− -- Reversing the output is important to guarantee that we process the
930− -- outputs in the same order as they were generated by the constituent
931− -- streams.
932− return $ Skip $ FromSVarLoop sv (Prelude.reverse list)
933−
934− step (FromSVarLoop sv []) = do
935− done <- postProcess sv
936− return $ Skip $ if done
937− then FromSVarDone sv
938− else FromSVarRead sv
939−
940− step (FromSVarLoop sv (ev : es)) = do
941− case ev of
942− ChildYield a -> return $ Yield a (FromSVarLoop sv es)
943− ChildStop tid e -> do
944− accountThread sv tid
945− case e of
946− Nothing -> do
947− stop <- shouldStop tid
948− if stop
949− then do
950− liftIO (cleanupSVar sv)
951− return $ Skip (FromSVarDone sv)
952− else return $ Skip (FromSVarLoop sv es)
953− Just ex ->
954− case fromException ex of
955− Just ThreadAbort ->
956− return $ Skip (FromSVarLoop sv es)
957− Nothing -> liftIO (cleanupSVar sv) >> MC.throwM ex
958− where
959−
960− shouldStop tid =
961− case svarStopStyle sv of
962− StopNone -> return False
963− StopAny -> return True
964− StopBy -> do
965− sid <- liftIO $ readIORef (svarStopBy sv)
966− return $ tid == sid
967−
968− step (FromSVarDone sv) = do
969− when (svarInspectMode sv) $ do
970− t <- liftIO $ getTime Monotonic
971− liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)
972− liftIO $ printSVar sv "SVar Done"
973− return Stop
974−
975−-------------------------------------------------------------------------------
976−-- Process events received by a fold consumer from a stream producer
977−-------------------------------------------------------------------------------
978−
979−-- XXX Have an error for "FromSVarInit" instead of undefined. The error can
980−-- redirect the user to report the failure to the developers.
981−-- | /Internal/
982−--
983−{-# INLINE_NORMAL fromProducer #-}
984−fromProducer :: MonadAsync m => Unfold m (SVar t m a) a
985−fromProducer = Unfold step (return . FromSVarRead)
986− where
987−
988− {-# INLINE_LATE step #-}
989− step (FromSVarRead sv) = do
990− list <- readOutputQ sv
991− -- Reversing the output is important to guarantee that we process the
992− -- outputs in the same order as they were generated by the constituent
993− -- streams.
994− return $ Skip $ FromSVarLoop sv (Prelude.reverse list)
995−
996− step (FromSVarLoop sv []) = return $ Skip $ FromSVarRead sv
997− step (FromSVarLoop sv (ev : es)) = do
998− case ev of
999− ChildYield a -> return $ Yield a (FromSVarLoop sv es)
1000− ChildStop tid e -> do
1001− accountThread sv tid
1002− case e of
1003− Nothing -> do
1004− sendStopToProducer sv
1005− return $ Skip (FromSVarDone sv)
1006− Just _ -> error "Bug: fromProducer: received exception"
1007−
1008− step (FromSVarDone sv) = do
1009− when (svarInspectMode sv) $ do
1010− t <- liftIO $ getTime Monotonic
1011− liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)
1012− liftIO $ printSVar sv "SVar Done"
1013− return Stop
1014−
1015− step (FromSVarInit _) = undefined
1016−
1017------------------------------------------------------------------------------
882------------------------------------------------------------------------------
1018-- Exceptions
883-- Exceptions
1019------------------------------------------------------------------------------
884------------------------------------------------------------------------------
src/Streamly/Internal/Data/Unfold/SVar.hsnew file+164
@@ -0,0 +1,164 @@
1+-- |
2+-- Module : Streamly.Internal.Data.Unfold.SVar
3+-- Copyright : (c) 2019 Composewell Technologies
4+-- License : BSD3
5+-- Maintainer : streamly@composewell.com
6+-- Stability : experimental
7+-- Portability : GHC
8+--
9+module Streamly.Internal.Data.Unfold.SVar
10+ (
11+ fromSVar
12+ , fromProducer
13+ )
14+where
15+
16+#include "inline.hs"
17+
18+import Control.Exception (fromException)
19+import Control.Monad (when)
20+import Control.Monad.IO.Class (MonadIO(..))
21+import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef)
22+import Data.Maybe (isNothing)
23+import Streamly.Internal.Control.Concurrent (MonadAsync)
24+import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))
25+import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
26+import Streamly.Internal.Data.SVar (printSVar, cleanupSVar, sendStopToProducer)
27+import System.Mem (performMajorGC)
28+
29+import qualified Control.Monad.Catch as MC
30+
31+import Streamly.Internal.Data.SVar.Type
32+import Streamly.Internal.Data.Unfold.Type
33+import Prelude
34+ hiding (map, mapM, takeWhile, take, filter, const, zipWith
35+ , drop, dropWhile, either)
36+
37+-------------------------------------------------------------------------------
38+-- Generation from SVar
39+-------------------------------------------------------------------------------
40+
41+data FromSVarState t m a =
42+ FromSVarInit (SVar t m a)
43+ | FromSVarRead (SVar t m a)
44+ | FromSVarLoop (SVar t m a) [ChildEvent a]
45+ | FromSVarDone (SVar t m a)
46+
47+-- | /Internal/
48+--
49+{-# INLINE_NORMAL fromSVar #-}
50+fromSVar :: MonadAsync m => Unfold m (SVar t m a) a
51+fromSVar = Unfold step (return . FromSVarInit)
52+ where
53+
54+ {-# INLINE_LATE step #-}
55+ step (FromSVarInit svar) = do
56+ ref <- liftIO $ newIORef ()
57+ _ <- liftIO $ mkWeakIORef ref hook
58+ -- when this copy of svar gets garbage collected "ref" will get
59+ -- garbage collected and our GC hook will be called.
60+ let sv = svar{svarRef = Just ref}
61+ return $ Skip (FromSVarRead sv)
62+
63+ where
64+
65+ {-# NOINLINE hook #-}
66+ hook = do
67+ when (svarInspectMode svar) $ do
68+ r <- liftIO $ readIORef (svarStopTime (svarStats svar))
69+ when (isNothing r) $
70+ printSVar svar "SVar Garbage Collected"
71+ cleanupSVar svar
72+ -- If there are any SVars referenced by this SVar a GC will prompt
73+ -- them to be cleaned up quickly.
74+ when (svarInspectMode svar) performMajorGC
75+
76+ step (FromSVarRead sv) = do
77+ list <- readOutputQ sv
78+ -- Reversing the output is important to guarantee that we process the
79+ -- outputs in the same order as they were generated by the constituent
80+ -- streams.
81+ return $ Skip $ FromSVarLoop sv (Prelude.reverse list)
82+
83+ step (FromSVarLoop sv []) = do
84+ done <- postProcess sv
85+ return $ Skip $ if done
86+ then FromSVarDone sv
87+ else FromSVarRead sv
88+
89+ step (FromSVarLoop sv (ev : es)) = do
90+ case ev of
91+ ChildYield a -> return $ Yield a (FromSVarLoop sv es)
92+ ChildStop tid e -> do
93+ accountThread sv tid
94+ case e of
95+ Nothing -> do
96+ stop <- shouldStop tid
97+ if stop
98+ then do
99+ liftIO (cleanupSVar sv)
100+ return $ Skip (FromSVarDone sv)
101+ else return $ Skip (FromSVarLoop sv es)
102+ Just ex ->
103+ case fromException ex of
104+ Just ThreadAbort ->
105+ return $ Skip (FromSVarLoop sv es)
106+ Nothing -> liftIO (cleanupSVar sv) >> MC.throwM ex
107+ where
108+
109+ shouldStop tid =
110+ case svarStopStyle sv of
111+ StopNone -> return False
112+ StopAny -> return True
113+ StopBy -> do
114+ sid <- liftIO $ readIORef (svarStopBy sv)
115+ return $ tid == sid
116+
117+ step (FromSVarDone sv) = do
118+ when (svarInspectMode sv) $ do
119+ t <- liftIO $ getTime Monotonic
120+ liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)
121+ liftIO $ printSVar sv "SVar Done"
122+ return Stop
123+
124+-------------------------------------------------------------------------------
125+-- Process events received by a fold consumer from a stream producer
126+-------------------------------------------------------------------------------
127+
128+-- XXX Have an error for "FromSVarInit" instead of undefined. The error can
129+-- redirect the user to report the failure to the developers.
130+-- | /Internal/
131+--
132+{-# INLINE_NORMAL fromProducer #-}
133+fromProducer :: MonadAsync m => Unfold m (SVar t m a) a
134+fromProducer = Unfold step (return . FromSVarRead)
135+ where
136+
137+ {-# INLINE_LATE step #-}
138+ step (FromSVarRead sv) = do
139+ list <- readOutputQ sv
140+ -- Reversing the output is important to guarantee that we process the
141+ -- outputs in the same order as they were generated by the constituent
142+ -- streams.
143+ return $ Skip $ FromSVarLoop sv (Prelude.reverse list)
144+
145+ step (FromSVarLoop sv []) = return $ Skip $ FromSVarRead sv
146+ step (FromSVarLoop sv (ev : es)) = do
147+ case ev of
148+ ChildYield a -> return $ Yield a (FromSVarLoop sv es)
149+ ChildStop tid e -> do
150+ accountThread sv tid
151+ case e of
152+ Nothing -> do
153+ sendStopToProducer sv
154+ return $ Skip (FromSVarDone sv)
155+ Just _ -> error "Bug: fromProducer: received exception"
156+
157+ step (FromSVarDone sv) = do
158+ when (svarInspectMode sv) $ do
159+ t <- liftIO $ getTime Monotonic
160+ liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)
161+ liftIO $ printSVar sv "SVar Done"
162+ return Stop
163+
164+ step (FromSVarInit _) = undefined
src/Streamly/Internal/FileSystem/File.hs+1−1
@@ -94,12 +94,12 @@ import Prelude hiding (read)
94import qualified Control.Monad.Catch as MC
94import qualified Control.Monad.Catch as MC
95import qualified System.IO as SIO
95import qualified System.IO as SIO
96
96
97+import Streamly.Internal.Control.Concurrent (MonadAsync)
97import Streamly.Internal.Data.Fold.Type (Fold(..))
98import Streamly.Internal.Data.Fold.Type (Fold(..))
98import Streamly.Internal.Data.Unfold.Type (Unfold(..))
99import Streamly.Internal.Data.Unfold.Type (Unfold(..))
99import Streamly.Internal.Data.Array.Foreign.Type (Array(..), writeNUnsafe)
100import Streamly.Internal.Data.Array.Foreign.Type (Array(..), writeNUnsafe)
100import Streamly.Internal.Data.Stream.Serial (SerialT)
101import Streamly.Internal.Data.Stream.Serial (SerialT)
101import Streamly.Internal.Data.Stream.StreamK.Type (IsStream)
102import Streamly.Internal.Data.Stream.StreamK.Type (IsStream)
102−import Streamly.Internal.Data.SVar (MonadAsync)
103-- import Streamly.Data.Fold (Fold)
103-- import Streamly.Data.Fold (Fold)
104-- import Streamly.String (encodeUtf8, decodeUtf8, foldLines)
104-- import Streamly.String (encodeUtf8, decodeUtf8, foldLines)
105import Streamly.Internal.System.IO (defaultChunkSize)
105import Streamly.Internal.System.IO (defaultChunkSize)
src/Streamly/Internal/Network/Inet/TCP.hs+1−2
@@ -103,10 +103,9 @@ import Network.Socket
103 socket)
103 socket)
104import Prelude hiding (read)
104import Prelude hiding (read)
105
105
106+import Streamly.Internal.Control.Concurrent (MonadAsync, fork)
106import Streamly.Internal.Data.Array.Foreign.Type (Array(..), writeNUnsafe)
107import Streamly.Internal.Data.Array.Foreign.Type (Array(..), writeNUnsafe)
107import Streamly.Internal.Data.Fold.Type (Fold(..))
108import Streamly.Internal.Data.Fold.Type (Fold(..))
108−import Streamly.Internal.Data.Stream.IsStream (MonadAsync)
109−import Streamly.Internal.Data.SVar (fork)
110import Streamly.Internal.Data.Stream.Serial (SerialT)
109import Streamly.Internal.Data.Stream.Serial (SerialT)
111import Streamly.Internal.Data.Stream.StreamK.Type (IsStream)
110import Streamly.Internal.Data.Stream.StreamK.Type (IsStream)
112import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
111import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
streamly.cabal+8−1
@@ -370,6 +370,10 @@ library
370 , Streamly.Internal.System.IOVec.Type
370 , Streamly.Internal.System.IOVec.Type
371
371
372 -- streamly-core-stream
372 -- streamly-core-stream
373+ , Streamly.Internal.Data.SVar.Type
374+ , Streamly.Internal.Data.SVar.Worker
375+ , Streamly.Internal.Data.SVar.Dispatch
376+ , Streamly.Internal.Data.SVar.Pull
373 , Streamly.Internal.Data.SVar
377 , Streamly.Internal.Data.SVar
374 , Streamly.Internal.Data.Stream.StreamK.Type
378 , Streamly.Internal.Data.Stream.StreamK.Type
375 , Streamly.Internal.Data.Fold.Type
379 , Streamly.Internal.Data.Fold.Type
@@ -417,13 +421,16 @@ library
417
421
418 -- streamly-core
422 -- streamly-core
419 , Streamly.Internal.Data.Unfold
423 , Streamly.Internal.Data.Unfold
424+ , Streamly.Internal.Data.Unfold.SVar
420 , Streamly.Internal.Data.Fold.Tee
425 , Streamly.Internal.Data.Fold.Tee
421 , Streamly.Internal.Data.Fold
426 , Streamly.Internal.Data.Fold
427+ , Streamly.Internal.Data.Fold.SVar
422 , Streamly.Internal.Data.Sink
428 , Streamly.Internal.Data.Sink
423 , Streamly.Internal.Data.Parser
429 , Streamly.Internal.Data.Parser
424 , Streamly.Internal.Data.Pipe
430 , Streamly.Internal.Data.Pipe
425
431
426− , Streamly.Internal.Data.Stream.SVar
432+ , Streamly.Internal.Data.Stream.SVar.Generate
433+ , Streamly.Internal.Data.Stream.SVar.Eliminate
427 , Streamly.Internal.Data.Stream.Serial
434 , Streamly.Internal.Data.Stream.Serial
428 , Streamly.Internal.Data.Stream.Async
435 , Streamly.Internal.Data.Stream.Async
429 , Streamly.Internal.Data.Stream.Parallel
436 , Streamly.Internal.Data.Stream.Parallel