on master help sign in
Refactor SVar module

* Break it up into smaller modules with clearly separated functionality * Keep minimal stuff in the lower level module SVar/Type, and push the rest in a higher level module to reduce dependencies of lower level modules. This will help in separating the lower modules in streamly-core package.

commita296fac8dbc5d0c2d1fbc6e99b507a848f1d13b2 authorHarendra Kumar <harendra@composewell.com> dateThu 19 Aug 2021, 04:47 (5 years ago) parent3c14488
42 files changed+2759−2481
benchmark/Streamly/Benchmark/Data/Stream/StreamK.hs+1−1 src/Streamly.hs+2−1 src/Streamly/Internal/Control/Concurrent.hs+11 src/Streamly/Internal/Data/Array/Foreign/Mut/Type.hs+1−1 src/Streamly/Internal/Data/Fold/SVar.hs+78 src/Streamly/Internal/Data/Fold/Type.hs+1−1 src/Streamly/Internal/Data/IOFinalizer.hs+1−1 src/Streamly/Internal/Data/Producer.hs+1−1 src/Streamly/Internal/Data/SVar.hs+114−1987 src/Streamly/Internal/Data/SVar/Dispatch.hs+742 src/Streamly/Internal/Data/SVar/Pull.hs+164 src/Streamly/Internal/Data/SVar/Type.hs+531 src/Streamly/Internal/Data/SVar/Worker.hs+544 src/Streamly/Internal/Data/Stream/Ahead.hs+5−3 src/Streamly/Internal/Data/Stream/Async.hs+34−4 src/Streamly/Internal/Data/Stream/IsStream/Common.hs+1−1 src/Streamly/Internal/Data/Stream/IsStream/Eliminate.hs+2−1 src/Streamly/Internal/Data/Stream/IsStream/Exception.hs+1−1 src/Streamly/Internal/Data/Stream/IsStream/Expand.hs+1−1 src/Streamly/Internal/Data/Stream/IsStream/Generate.hs+2−1 src/Streamly/Internal/Data/Stream/IsStream/Reduce.hs+1−1 src/Streamly/Internal/Data/Stream/IsStream/Top.hs+1−1 src/Streamly/Internal/Data/Stream/IsStream/Transform.hs+2−1 src/Streamly/Internal/Data/Stream/IsStream/Types.hs+1−1 src/Streamly/Internal/Data/Stream/Parallel.hs+5−3 src/Streamly/Internal/Data/Stream/SVar.hs → src/Streamly/Internal/Data/Stream/SVar/Eliminate.hs+20−311 src/Streamly/Internal/Data/Stream/SVar/Generate.hs+297 src/Streamly/Internal/Data/Stream/StreamD/Eliminate.hs+1−1 src/Streamly/Internal/Data/Stream/StreamD/Exception.hs+1−1 src/Streamly/Internal/Data/Stream/StreamD/Generate.hs+1−1 src/Streamly/Internal/Data/Stream/StreamD/Lift.hs+1−1 src/Streamly/Internal/Data/Stream/StreamD/Nesting.hs+1−1 src/Streamly/Internal/Data/Stream/StreamD/Transform.hs+2−1 src/Streamly/Internal/Data/Stream/StreamD/Type.hs+1−1 src/Streamly/Internal/Data/Stream/StreamK.hs+6−4 src/Streamly/Internal/Data/Stream/StreamK/Type.hs+2−3 src/Streamly/Internal/Data/Stream/Zip.hs+1−1 src/Streamly/Internal/Data/Unfold.hs+4−139 src/Streamly/Internal/Data/Unfold/SVar.hs+164 src/Streamly/Internal/FileSystem/File.hs+1−1 src/Streamly/Internal/Network/Inet/TCP.hs+1−2 streamly.cabal+8−1
benchmark/Streamly/Benchmark/Data/Stream/StreamK.hs+1−1
@@ -31,8 +31,8 @@ import Prelude hiding
3131import qualified Prelude as P
3232import qualified Data.List as List
3333
34+import qualified Streamly.Internal.Control.Concurrent as S
3435import qualified Streamly.Internal.Data.Stream.StreamK as S
35−import qualified Streamly.Internal.Data.SVar as S
3636
3737import Gauge (bench, nfIO, bgroup, Benchmark, defaultMain)
3838
src/Streamly.hs+2−1
@@ -210,7 +210,8 @@ module Streamly {-# DEPRECATED "Please use \"Streamly.Prelude\" instead." #-}
210210where
211211
212212import 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(..))
214215import Streamly.Internal.Data.Stream.Ahead
215216import Streamly.Internal.Data.Stream.Async hiding (mkAsync)
216217import Streamly.Internal.Data.Stream.IsStream.Combinators
src/Streamly/Internal/Control/Concurrent.hs+11
@@ -12,6 +12,7 @@ module Streamly.Internal.Control.Concurrent
1212 (
1313 MonadAsync
1414 , RunInIO(..)
15+ , captureMonadState
1516 , doFork
1617 , fork
1718 , forkManaged
@@ -43,6 +44,16 @@ type MonadAsync m = (MonadIO m, MonadBaseControl IO m, MonadThrow m)
4344
4445newtype RunInIO m = RunInIO { runInIO :: forall b. m b -> IO (StM m b) }
4546
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+
4657-- Stolen from the async package. The perf improvement is modest, 2% on a
4758-- thread heavy benchmark (parallel composition using noop computations).
4859-- 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(..))
119119import Streamly.Internal.BaseCompat
120120import Streamly.Internal.Data.Fold.Type (Fold(..))
121121import Streamly.Internal.Data.Producer.Type (Producer (..))
122−import Streamly.Internal.Data.SVar (adaptState)
122+import Streamly.Internal.Data.SVar.Type (adaptState)
123123import Streamly.Internal.Data.Unfold.Type (Unfold(..))
124124
125125import 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)
278278import Fusion.Plugin.Types (Fuse(..))
279279import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)
280280import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3'(..))
281−import Streamly.Internal.Data.SVar (MonadAsync)
281+import Streamly.Internal.Control.Concurrent (MonadAsync)
282282
283283import Prelude hiding (concatMap, filter, foldr, map, take)
284284
src/Streamly/Internal/Data/IOFinalizer.hs+1−1
@@ -23,7 +23,7 @@ import Control.Monad (void)
2323import Control.Monad.IO.Class (MonadIO(..))
2424import Control.Monad.Trans.Control (MonadBaseControl, control)
2525import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef, IORef)
26−import Streamly.Internal.Data.SVar (captureMonadState, runInIO)
26+import Streamly.Internal.Control.Concurrent (captureMonadState, runInIO)
2727
2828-- | An 'IOFinalizer' has an associated IO action that is automatically called
2929-- 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
4646
4747import Streamly.Internal.Data.Stream.StreamD.Step (Step(..))
4848import Streamly.Internal.Data.Stream.StreamD.Type (Stream(..))
49−import Streamly.Internal.Data.SVar (defState)
49+import Streamly.Internal.Data.SVar.Type (defState)
5050import Streamly.Internal.Data.Unfold.Type (Unfold(..))
5151
5252import Streamly.Internal.Data.Producer.Type
src/Streamly/Internal/Data/SVar.hs+114−1987
@@ -1,69 +1,31 @@
1−{-# LANGUAGE UnboxedTuples #-}
2−
31-- |
42-- Module : Streamly.Internal.Data.SVar
53-- Copyright : (c) 2017 Composewell Technologies
6−--
7−-- License : BSD3
4+-- License : BSD-3-Clause
85-- Maintainer : streamly@composewell.com
96-- Stability : experimental
107-- Portability : GHC
11−
8+--
9+--
1210module Streamly.Internal.Data.SVar
1311 (
14− MonadAsync
15− , SVarStyle (..)
16− , SVarStopStyle (..)
17− , SVar (..)
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
12+ module Streamly.Internal.Data.SVar.Type
13+ , module Streamly.Internal.Data.SVar.Worker
14+ , module Streamly.Internal.Data.SVar.Dispatch
15+ , module Streamly.Internal.Data.SVar.Pull
3616
37− , cleanupSVar
38− , cleanupSVarFromWorker
17+ -- * New SVar
18+ , getYieldRateInfo
19+ , newSVarStats
3920
40− -- SVar related
41− , newAheadVar
21+ -- ** Parallel
4222 , newParallelVar
43− , captureMonadState
44− , RunInIO (..)
4523
46− , WorkerInfo (..)
47− , YieldRateInfo (..)
48− , ThreadAbort (..)
49− , ChildEvent (..)
50− , AheadHeapEntry (..)
51− , send
52− , sendToProducer
53− , sendYield
54− , sendStop
55− , sendStopToProducer
56− , enqueueLIFO
57− , enqueueFIFO
24+ -- ** Ahead
5825 , enqueueAhead
5926 , reEnqueueAhead
60− , pushWorkerPar
61− , handleChildException
62− , handleFoldException
63−
6427 , queueEmptyAhead
6528 , dequeueAhead
66−
6729 , HeapDequeueResult(..)
6830 , dequeueFromHeap
6931 , dequeueFromHeapSeq
@@ -71,1166 +33,98 @@ module Streamly.Internal.Data.SVar
7133 , updateHeapSeq
7234 , withIORef
7335 , heapIsSane
74−
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
36+ , newAheadVar
10537 )
10638where
10739
108−import Control.Concurrent (ThreadId, myThreadId, threadDelay, throwTo)
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)
40+#include "inline.hs"
13541
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)
13650import Streamly.Internal.Control.Concurrent
137− (MonadAsync, RunInIO(..), doFork, fork, forkManaged)
138−import Streamly.Internal.Data.Time.Clock.Type (Clock(..), getTime)
139−import Streamly.Internal.Data.Time.Units
140− (AbsTime, NanoSecond64(..), MicroSecond64(..), diffAbsTime64,
141− fromRelTime64, toRelTime64, showNanoSecond64, showRelTime64)
51+ (MonadAsync, captureMonadState, RunInIO(..))
52+import Streamly.Internal.Data.Atomics
53+ (atomicModifyIORefCAS, atomicModifyIORefCAS_, writeBarrier)
54+import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
55+import Streamly.Internal.Data.Time.Units (NanoSecond64(..))
14256
14357import qualified Data.Heap as H
144−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− )
58+import qualified Data.Set as S
15759
158−------------------------------------------------------------------------------
159−-- Parent child thread communication type
160−------------------------------------------------------------------------------
60+import Streamly.Internal.Data.SVar.Dispatch
61+import Streamly.Internal.Data.SVar.Pull
62+import Streamly.Internal.Data.SVar.Type
63+import Streamly.Internal.Data.SVar.Worker
16164
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
61465-------------------------------------------------------------------------------
615−
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
66+-- Creating an SVar
74967-------------------------------------------------------------------------------
75068
751−dumpSVarStats :: SVar t m a -> SVarStats -> SVarStyle -> IO String
752−dumpSVarStats sv ss style = do
753− case yieldRateInfo sv of
754− Nothing -> return ()
755− Just yinfo -> do
756− _ <- liftIO $ collectLatency sv yinfo True
757− return ()
758−
759− dispatches <- readIORef $ totalDispatches ss
760− maxWrk <- readIORef $ maxWorkers ss
761− maxOq <- readIORef $ maxOutQSize ss
762− maxHp <- readIORef $ maxHeapSize ss
763− minLat <- readIORef $ minWorkerLatency ss
764− maxLat <- readIORef $ maxWorkerLatency ss
765− (avgCnt, avgTime) <- readIORef $ avgWorkerLatency ss
766− (svarCnt, svarGainLossCnt, svarLat) <- case yieldRateInfo sv of
767− Nothing -> return (0, 0, 0)
768− Just yinfo -> do
769− (cnt, startTime) <- readIORef $ svarAllTimeLatency yinfo
770− if cnt > 0
771− then do
772− t <- readIORef (svarStopTime ss)
773− gl <- readIORef (svarGainedLostYields yinfo)
774− case t of
775− Nothing -> do
776− now <- getTime Monotonic
777− let interval = diffAbsTime64 now startTime
778− return (cnt, gl, interval `div` fromIntegral cnt)
779− Just stopTime -> do
780− let interval = diffAbsTime64 stopTime startTime
781− return (cnt, gl, interval `div` fromIntegral cnt)
782− else return (0, 0, 0)
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
69+getYieldRateInfo :: State t m a -> IO (Maybe YieldRateInfo)
70+getYieldRateInfo st = do
71+ -- convert rate in Hertz to latency in Nanoseconds
72+ let rateToLatency r = if r <= 0 then maxBound else round $ 1.0e9 / r
73+ case getStreamRate st of
74+ Just (Rate low goal high buf) ->
75+ let l = rateToLatency goal
76+ minl = rateToLatency high
77+ maxl = rateToLatency low
78+ in mkYieldRateInfo l (LatencyRange minl maxl) buf
79+ Nothing -> return Nothing
97980
98081 where
98182
982− blockAndRetry = do
983− let ref = pushBufferSpace sv
984− liftIO $ takeMVar (pushBufferMVar sv)
985− old <- atomicModifyIORefCAS ref $ \x ->
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
1066− let period = fromRelTime64 $ diffAbsTime64 t1 t0
1067− writeIORef (workerLatencyStart winfo) (cnt1, t1)
1068− return $ Just (cnt, period)
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
83+ mkYieldRateInfo latency latRange buf = do
84+ measured <- newIORef 0
85+ wcur <- newIORef (0,0,0)
86+ wcol <- newIORef (0,0,0)
87+ now <- getTime Monotonic
88+ wlong <- newIORef (0,now)
89+ period <- newIORef 1
90+ gainLoss <- newIORef (Count 0)
121691
1217−-------------------------------------------------------------------------------
1218−-- WAsync
1219−-------------------------------------------------------------------------------
92+ return $ Just YieldRateInfo
93+ { svarLatencyTarget = latency
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+ }
1220104
1221−-- XXX we can use the Ahead style sequence/heap mechanism to make the best
1222−-- effort to always try to finish the streams on the left side of an expression
1223−-- first as long as possible.
105+newSVarStats :: IO SVarStats
106+newSVarStats = do
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
1224116
1225−{-# INLINE enqueueFIFO #-}
1226−enqueueFIFO ::
1227− SVar t m a
1228− -> LinkedQueue (RunInIO m, t m a)
1229− -> (RunInIO m, t m a)
1230− -> IO ()
1231−enqueueFIFO sv q m = do
1232− pushL q m
1233− ringDoorBell sv
117+ return SVarStats
118+ { totalDispatches = disp
119+ , maxWorkers = maxWrk
120+ , maxOutQSize = maxOq
121+ , maxHeapSize = maxHs
122+ , maxWorkQSize = maxWq
123+ , avgWorkerLatency = avgLat
124+ , minWorkerLatency = minLat
125+ , maxWorkerLatency = maxLat
126+ , svarStopTime = stpTime
127+ }
1234128
1235129-------------------------------------------------------------------------------
1236130-- Ahead
@@ -1424,748 +318,6 @@ updateHeapSeq hpVar seqNo =
1424318 atomicModifyIORef_ hpVar $ \(hp, snum) ->
1425319 assert (heapIsSane snum seqNo) (hp, Just seqNo)
1426320
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−
2169321-- XXX remove polymorphism in t, inline f
2170322getAheadSVar :: MonadAsync m
2171323 => State t m a
@@ -2268,6 +420,34 @@ getAheadSVar st f mrun = do
2268420 (xs, _) <- readIORef q
2269421 return $ null xs
2270422
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+
2271451getParallelSVar :: MonadIO m
2272452 => SVarStopStyle -> State t m a -> RunInIO m -> IO (SVar t m a)
2273453getParallelSVar ss st mrun = do
@@ -2349,62 +529,9 @@ getParallelSVar ss st mrun = do
2349529 void $ tryPutMVar (pushBufferMVar sv) ()
2350530 return r
2351531
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−
2383532{-# INLINABLE newParallelVar #-}
2384533newParallelVar :: MonadAsync m
2385534 => SVarStopStyle -> State t m a -> m (SVar t m a)
2386535newParallelVar ss st = do
2387536 mrun <- captureMonadState
2388537 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)
5050
5151import qualified Data.Heap as H
5252
53−import Streamly.Internal.Data.Stream.SVar (fromSVar)
54−import Streamly.Internal.Data.SVar
53+import Streamly.Internal.Control.Concurrent
54+ (MonadAsync, RunInIO(..), captureMonadState)
5555import Streamly.Internal.Data.Stream.StreamK.Type
56− (IsStream(..), Stream, mkStream, foldStream, foldStreamShared)
56+ (IsStream(..), Stream, mkStream, foldStream, foldStreamShared)
5757
5858import qualified Streamly.Internal.Data.Stream.StreamK as K (withLocal)
5959import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
6060import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
6161
62+import Streamly.Internal.Data.Stream.SVar.Generate
63+import Streamly.Internal.Data.SVar
6264import Prelude hiding (map)
6365
6466#include "Instances.hs"
src/Streamly/Internal/Data/Stream/Async.hs+34−4
@@ -49,7 +49,7 @@ import Control.Monad.IO.Class (MonadIO(..))
4949import Control.Monad.Reader.Class (MonadReader(..))
5050import Control.Monad.State.Class (MonadState(..))
5151import 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)
5353import Data.IORef (IORef, newIORef, readIORef)
5454import Data.Maybe (fromJust)
5555#if __GLASGOW_HASKELL__ < 808
@@ -59,9 +59,11 @@ import Data.Semigroup (Semigroup(..))
5959import Prelude hiding (map)
6060import qualified Data.Set as S
6161
62−import Streamly.Internal.Data.Atomics (atomicModifyIORefCAS)
63−import Streamly.Internal.Data.Stream.SVar (fromSVar, fromSVarD)
64−import Streamly.Internal.Data.SVar
62+import Streamly.Internal.Control.Concurrent
63+ (MonadAsync, RunInIO(..), captureMonadState)
64+import Streamly.Internal.Data.Atomics
65+ (atomicModifyIORefCAS, atomicModifyIORefCAS_)
66+import Streamly.Internal.Data.Stream.SVar.Generate (fromSVar, fromSVarD)
6567import Streamly.Internal.Data.Stream.StreamK.Type
6668 (IsStream(..), Stream, mkStream, foldStream, adapt, foldStreamShared)
6769
@@ -69,6 +71,9 @@ import qualified Streamly.Internal.Data.Stream.StreamK as K (withLocal)
6971import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
7072import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
7173
74+import Streamly.Internal.Data.SVar.Type
75+import Streamly.Internal.Data.SVar
76+
7277#include "Instances.hs"
7378
7479-- $setup
@@ -86,6 +91,17 @@ import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
8691-- Async
8792-------------------------------------------------------------------------------
8893
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+
89105data WorkerStatus = Continue | Suspend
90106
91107{-# INLINE workLoopLIFO #-}
@@ -208,6 +224,20 @@ workLoopLIFOLimited q st sv winfo = run
208224-- WAsync
209225-------------------------------------------------------------------------------
210226
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+
211241-- XXX we can remove sv as it is derivable from st
212242
213243{-# INLINE workLoopFIFO #-}
src/Streamly/Internal/Data/Stream/IsStream/Common.hs+1−1
@@ -56,6 +56,7 @@ where
5656import Control.Concurrent (threadDelay)
5757import Control.Monad.IO.Class (MonadIO(..))
5858import Foreign.Storable (Storable)
59+import Streamly.Internal.Control.Concurrent (MonadAsync)
5960import Streamly.Internal.Data.Array.Foreign.Type (Array)
6061import Streamly.Internal.Data.Fold.Type (Fold (..))
6162import Streamly.Internal.Data.Stream.IsStream.Combinators (maxYields)
@@ -63,7 +64,6 @@ import Streamly.Internal.Data.Stream.Prelude (fromStreamS, toStreamS)
6364import Streamly.Internal.Data.Stream.Serial (SerialT)
6465import Streamly.Internal.Data.Stream.StreamD.Type (fromStreamD, toStreamD)
6566import Streamly.Internal.Data.Stream.StreamK.Type (IsStream())
66−import Streamly.Internal.Data.SVar (MonadAsync)
6767import Streamly.Internal.Data.Time.Units (AbsTime, RelTime64, addToAbsTime64)
6868
6969import qualified Streamly.Internal.Data.Stream.Parallel as Par
src/Streamly/Internal/Data/Stream/IsStream/Eliminate.hs+2−1
@@ -152,8 +152,9 @@ where
152152import Control.Monad.Catch (MonadThrow)
153153import Control.Monad.IO.Class (MonadIO(..))
154154import Foreign.Storable (Storable)
155+import Streamly.Internal.Control.Concurrent (MonadAsync)
155156import Streamly.Internal.Data.Parser (Parser (..))
156−import Streamly.Internal.Data.SVar (MonadAsync, defState)
157+import Streamly.Internal.Data.SVar (defState)
157158import Streamly.Internal.Data.Stream.IsStream.Common
158159 ( fold, fold_, drop, findIndices, reverse, splitOnSeq, take
159160 , takeWhile)
src/Streamly/Internal/Data/Stream/IsStream/Exception.hs+1−1
@@ -28,9 +28,9 @@ import Control.Monad.Catch (MonadCatch)
2828import Control.Monad.IO.Class (MonadIO(..))
2929import Control.Monad.Trans.Control (MonadBaseControl)
3030import Data.Map.Strict (Map)
31+import Streamly.Internal.Control.Concurrent (MonadAsync)
3132import Streamly.Internal.Data.Stream.StreamD (toStreamD)
3233import Streamly.Internal.Data.Stream.StreamK (IsStream)
33−import Streamly.Internal.Data.SVar (MonadAsync)
3434
3535import qualified Streamly.Internal.Data.Stream.StreamD as D
3636
src/Streamly/Internal/Data/Stream/IsStream/Expand.hs+1−1
@@ -158,6 +158,7 @@ where
158158
159159#include "inline.hs"
160160
161+import Streamly.Internal.Control.Concurrent (MonadAsync)
161162import Streamly.Internal.Data.Stream.Ahead (ahead)
162163import Streamly.Internal.Data.Stream.Async (async, wAsync)
163164import Streamly.Internal.Data.Stream.IsStream.Common
@@ -169,7 +170,6 @@ import Streamly.Internal.Data.Stream.Prelude
169170import Streamly.Internal.Data.Stream.Serial (serial, wSerial)
170171import Streamly.Internal.Data.Stream.StreamD (fromStreamD, toStreamD)
171172import Streamly.Internal.Data.Stream.StreamK (IsStream)
172−import Streamly.Internal.Data.SVar (MonadAsync)
173173import Streamly.Internal.Data.Unfold.Type (Unfold)
174174
175175import qualified Streamly.Internal.Data.Stream.Parallel as Par
src/Streamly/Internal/Data/Stream/IsStream/Generate.hs+2−1
@@ -91,8 +91,9 @@ where
9191
9292import Control.Monad.IO.Class (MonadIO(..))
9393import Data.Void (Void)
94+import Streamly.Internal.Control.Concurrent (MonadAsync)
9495import Streamly.Internal.Data.Unfold.Type (Unfold)
95−import Streamly.Internal.Data.SVar (MonadAsync, Rate (..))
96+import Streamly.Internal.Data.SVar (Rate (..))
9697import Streamly.Internal.Data.Stream.IsStream.Enumeration
9798 (Enumerable(..), enumerate, enumerateTo)
9899import Streamly.Internal.Data.Stream.IsStream.Common
src/Streamly/Internal/Data/Stream/IsStream/Reduce.hs+1−1
@@ -154,10 +154,10 @@ import Data.Heap (Entry(..))
154154import Data.Kind (Type)
155155import Data.Maybe (isNothing)
156156import Foreign.Storable (Storable)
157+import Streamly.Internal.Control.Concurrent (MonadAsync)
157158import Streamly.Internal.Data.Fold.Type (Fold (..), Fold2 (..))
158159import Streamly.Internal.Data.Parser (Parser (..))
159160import Streamly.Internal.Data.Array.Foreign.Type (Array)
160−import Streamly.Internal.Data.SVar (MonadAsync)
161161import Streamly.Internal.Data.Stream.IsStream.Common
162162 ( concatMap
163163 , fold
src/Streamly/Internal/Data/Stream/IsStream/Top.hs+1−1
@@ -59,7 +59,7 @@ import Data.Kind (Type)
5959#if !(MIN_VERSION_base(4,11,0))
6060import Data.Semigroup (Semigroup(..))
6161#endif
62−import Streamly.Internal.Data.SVar (MonadAsync)
62+import Streamly.Internal.Control.Concurrent (MonadAsync)
6363import Streamly.Internal.Data.Stream.IsStream.Common (concatM)
6464import Streamly.Internal.Data.Stream.Prelude (foldl', fromList)
6565import 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
237237 , interjectSuffix
238238 , intersperseM
239239 )
240+import Streamly.Internal.Control.Concurrent (MonadAsync)
240241import Streamly.Internal.Data.Stream.Prelude (fromStreamS, toStreamS)
241242import Streamly.Internal.Data.Stream.Serial (SerialT)
242243import Streamly.Internal.Data.Stream.StreamD (fromStreamD, toStreamD)
243244import Streamly.Internal.Data.Stream.StreamK (IsStream)
244−import Streamly.Internal.Data.SVar (MonadAsync, Rate(..))
245+import Streamly.Internal.Data.SVar (Rate(..))
245246import Streamly.Internal.Data.Time.Units (TimeUnit64, AbsTime, RelTime64)
246247
247248import 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
7171import Streamly.Internal.Data.Stream.StreamK (IsStream(), adapt)
7272import Streamly.Internal.Data.Stream.Zip
7373 (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)
6868
6969import qualified Data.Set as Set
7070
71+import Streamly.Internal.Control.Concurrent (MonadAsync)
7172import Streamly.Internal.Data.Fold.Type (Fold)
7273import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))
7374import Streamly.Internal.Data.Stream.StreamK
7475 (IsStream(..), Stream, mkStream, foldStream, foldStreamShared, adapt)
7576
76−import Streamly.Internal.Data.SVar
77−
7877import qualified Streamly.Internal.Data.Stream.StreamK as K (withLocal)
7978import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
8079import 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
8284
8385#include "Instances.hs"
8486
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−
101-- |
11−-- Module : Streamly.Internal.Data.Stream.SVar
2+-- Module : Streamly.Internal.Data.Stream.SVar.Eliminate
123-- Copyright : (c) 2017 Composewell Technologies
13−--
14−-- License : BSD3
4+-- License : BSD-3-Clause
155-- Maintainer : streamly@composewell.com
166-- Stability : experimental
177-- Portability : GHC
188--
9+-- Eliminate a stream by distributing it to multiple SVars concurrently.
1910--
20−module Streamly.Internal.Data.Stream.SVar
11+module Streamly.Internal.Data.Stream.SVar.Eliminate
2112 (
22− -- * Unfold streams from SVar
23− -- $concurrentEval
24− fromSVar
25− , fromSVarD
26− -- , fromStreamVar
27−
28− -- * Fold streams to SVar
29− , toSVar
30− , toSVarParallel
13+ -- * Concurrent Function Application
14+ toSVarParallel
3115
3216 -- * Concurrent folds
3317 -- $concurrentFolds
18+ , newFoldSVar
19+ , newFoldSVarF
20+
3421 , fromConsumer
3522 , pushToFold
3623 , teeToSVar
37− , newFoldSVar
38− , newFoldSVarF
3924 )
4025where
4126
4227#include "inline.hs"
4328
4429import Control.Concurrent (myThreadId, takeMVar)
45−import Control.Exception (fromException)
4630import Control.Monad (when, void)
4731import Control.Monad.Catch (throwM)
4832import Control.Monad.IO.Class (MonadIO(liftIO))
49−import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef)
50−import Data.Maybe (isNothing)
33+import Data.IORef (newIORef, readIORef, writeIORef)
34+import Streamly.Internal.Control.Concurrent (MonadAsync, doFork)
5135import Streamly.Internal.Data.Atomics (atomicModifyIORefCAS_)
36+import Streamly.Internal.Data.Fold.SVar (write, writeLimited)
5237import Streamly.Internal.Data.Fold.Type (Fold(..))
5338import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
54−import System.Mem (performMajorGC)
5539
56−import qualified Streamly.Internal.Data.Fold.Type as FL
5740import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
5841import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
5942
6043import Streamly.Internal.Data.SVar
6144
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−
28945-------------------------------------------------------------------------------
290−-- Concurrent application
46+-- Concurrent function application
29147-------------------------------------------------------------------------------
29248
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−
34649-- Using StreamD the worker stream producing code can fuse with the code to
34750-- queue output to the SVar giving some perf boost.
34851--
@@ -514,6 +217,12 @@ newFoldSVar stt f = do
514217 (handleFoldException sv)
515218 return sv
516219
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+
517226-- | Like 'fromProducer' but generates a StreamD style stream instead of
518227-- StreamK.
519228--
@@ -622,7 +331,7 @@ pushToFold sv a = do
622331 let qref = outputQueueFromConsumer sv
623332 done <- do
624333 (_, n) <- liftIO $ readIORef qref
625− if (n > 0)
334+ if n > 0
626335 then fromConsumer sv
627336 else return False
628337 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)
8181import GHC.Exts (SpecConstrAnnotation(..))
8282import GHC.Types (SPEC(..))
8383import Streamly.Internal.Data.Parser (ParseError(..))
84+import Streamly.Internal.Data.SVar.Type (defState)
8485
8586import qualified Streamly.Internal.Data.Parser as PR
8687import qualified Streamly.Internal.Data.Parser.ParserD as PRD
@@ -90,7 +91,6 @@ import Prelude hiding
9091 ( all, any, elem, foldr, foldr1, head, last, lookup, mapM, mapM_
9192 , maximum, minimum, notElem, null, splitAt, tail, (!!))
9293import Streamly.Internal.Data.Stream.StreamD.Type
93−import Streamly.Internal.Data.SVar
9494
9595------------------------------------------------------------------------------
9696-- Elimination by Folds
src/Streamly/Internal/Data/Stream/StreamD/Exception.hs+1−1
@@ -32,6 +32,7 @@ import Control.Monad.IO.Class (MonadIO(..))
3232import Control.Monad.Trans.Control (MonadBaseControl, liftBaseOp_)
3333import Data.Map.Strict (Map)
3434import GHC.Exts (inline)
35+import Streamly.Internal.Control.Concurrent (MonadAsync)
3536import Streamly.Internal.Data.IOFinalizer
3637 (newIOFinalizer, runIOFinalizer, clearingIOFinalizer)
3738
@@ -39,7 +40,6 @@ import qualified Control.Monad.Catch as MC
3940import qualified Data.Map.Strict as Map
4041
4142import Streamly.Internal.Data.Stream.StreamD.Type
42−import Streamly.Internal.Data.SVar
4343
4444data GbracketState s1 s2 v
4545 = GBracketInit
src/Streamly/Internal/Data/Stream/StreamD/Generate.hs+1−1
@@ -90,6 +90,7 @@ where
9090#include "inline.hs"
9191
9292import Control.Monad.IO.Class (MonadIO(..))
93+import Streamly.Internal.Control.Concurrent (MonadAsync)
9394import Streamly.Internal.Data.Time.Clock
9495 (Clock(Monotonic), asyncClock, readClock)
9596import Streamly.Internal.Data.Time.Units
@@ -97,7 +98,6 @@ import Streamly.Internal.Data.Time.Units
9798
9899import Prelude hiding (iterate, repeat, replicate, takeWhile)
99100import Streamly.Internal.Data.Stream.StreamD.Type
100−import Streamly.Internal.Data.SVar
101101
102102------------------------------------------------------------------------------
103103-- Primitives
src/Streamly/Internal/Data/Stream/StreamD/Lift.hs+1−1
@@ -28,7 +28,7 @@ import Control.Monad.Trans.Class (MonadTrans(lift))
2828import Control.Monad.Trans.Reader (ReaderT)
2929import Control.Monad.Trans.State.Strict (StateT)
3030import Data.Functor.Identity (Identity(..))
31−import Streamly.Internal.Data.SVar (adaptState)
31+import Streamly.Internal.Data.SVar.Type (adaptState)
3232
3333import qualified Control.Monad.Trans.Reader as Reader
3434import 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(..))
160160import Streamly.Internal.Data.Array.Foreign.Type (Array(..))
161161import Streamly.Internal.Data.Fold.Type (Fold(..))
162162import Streamly.Internal.Data.Parser (ParseError(..))
163+import Streamly.Internal.Data.SVar.Type (adaptState)
163164import Streamly.Internal.Data.Unfold.Type (Unfold(..))
164165
165166import qualified Streamly.Internal.Data.Array.Foreign.Type as A
@@ -169,7 +170,6 @@ import qualified Streamly.Internal.Data.Parser.ParserD as PRD
169170import qualified Streamly.Internal.Ring.Foreign as RB
170171
171172import Streamly.Internal.Data.Stream.StreamD.Type
172−import Streamly.Internal.Data.SVar
173173
174174import Prelude hiding (concatMap, mapM, zipWith)
175175
src/Streamly/Internal/Data/Stream/StreamD/Transform.hs+2−1
@@ -133,8 +133,10 @@ import Foreign.Storable (Storable(..))
133133import GHC.Types (SPEC(..))
134134import qualified Control.Monad.Catch as MC
135135
136+import Streamly.Internal.Control.Concurrent (MonadAsync, fork, forkManaged)
136137import Streamly.Internal.Data.Fold.Type (Fold(..))
137138import Streamly.Internal.Data.Pipe.Type (Pipe(..), PipeState(..))
139+import Streamly.Internal.Data.SVar.Type (defState, adaptState)
138140import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
139141import Streamly.Internal.Data.Time.Units
140142 (TimeUnit64, toRelTime64, diffAbsTime64)
@@ -151,7 +153,6 @@ import Prelude hiding
151153 , scanl, scanl1, sequence, take, takeWhile)
152154
153155import Streamly.Internal.Data.Stream.StreamD.Type
154−import Streamly.Internal.Data.SVar
155156
156157------------------------------------------------------------------------------
157158-- Piping
src/Streamly/Internal/Data/Stream/StreamD/Type.hs+1−1
@@ -99,7 +99,7 @@ import Prelude hiding (map, mapM, foldr, take, concatMap, takeWhile)
9999
100100import Streamly.Internal.Data.Fold.Type (Fold(..), Fold2(..))
101101import 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)
103103import Streamly.Internal.Data.Unfold.Type (Unfold(..))
104104
105105import 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))
190190import Control.Monad (void, join)
191191import Control.Monad.Reader.Class (MonadReader(..))
192192import 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+
193199import Prelude
194200 hiding (foldl, foldr, last, map, mapM, mapM_, repeat, sequence,
195201 take, filter, all, any, takeWhile, drop, dropWhile, minimum,
196202 maximum, elem, notElem, null, head, tail, init, zipWith, lookup,
197203 foldr1, (!!), replicate, reverse, concatMap, iterate)
198−import qualified Prelude
199204
200−import Streamly.Internal.Data.SVar
201205import Streamly.Internal.Data.Stream.StreamK.Type
202206
203−import qualified Streamly.Internal.Data.Fold.Type as FL
204−
205207-- $setup
206208-- >>> :m
207209
src/Streamly/Internal/Data/Stream/StreamK/Type.hs+2−3
@@ -81,14 +81,13 @@ where
8181import Control.Monad (ap, (>=>))
8282import Control.Monad.Trans.Class (MonadTrans(lift))
8383import Data.Kind (Type)
84−
8584#if __GLASGOW_HASKELL__ < 808
8685import Data.Semigroup (Semigroup(..))
8786#endif
87+import Streamly.Internal.Control.Concurrent (MonadAsync)
88+import Streamly.Internal.Data.SVar.Type (State, adaptState, defState)
8889import Prelude hiding (map, mapM, concatMap, foldr)
8990
90−import Streamly.Internal.Data.SVar
91−
9291-- $setup
9392-- >>> import Streamly.Prelude as Stream
9493
src/Streamly/Internal/Data/Stream/Zip.hs+1−1
@@ -51,10 +51,10 @@ import GHC.Exts (IsList(..), IsString(..))
5151import Text.Read
5252 ( Lexeme(Ident), lexP, parens, prec, readPrec, readListPrec
5353 , readListPrecDefault)
54+import Streamly.Internal.Control.Concurrent (MonadAsync)
5455import Streamly.Internal.BaseCompat ((#.), errorWithoutStackTrace, oneShot)
5556import Streamly.Internal.Data.Stream.StreamK (IsStream(..), Stream)
5657import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)
57−import Streamly.Internal.Data.SVar (MonadAsync)
5858
5959import qualified Streamly.Internal.Data.Stream.Parallel as Par
6060import 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
163163 , fromStreamK
164164 , fromStreamD
165165
166− , fromSVar
167− , fromProducer
168−
169166 -- * Combinators
170167 -- ** Mapping on Input
171168 , lmap
@@ -232,21 +229,19 @@ module Streamly.Internal.Data.Unfold
232229 )
233230where
234231
235−import Control.Exception (Exception, fromException, mask_)
236−import Control.Monad ((>=>), when)
232+import Control.Exception (Exception, mask_)
233+import Control.Monad ((>=>))
237234import Control.Monad.Catch (MonadCatch)
238235import Control.Monad.IO.Class (MonadIO(..))
239236import Control.Monad.Trans.Control (MonadBaseControl, liftBaseOp_)
240−import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef)
241−import Data.Maybe (isNothing)
242237import Data.Void (Void)
243238import GHC.Types (SPEC(..))
239+import Streamly.Internal.Control.Concurrent (MonadAsync)
244240import Streamly.Internal.Data.Fold.Type (Fold(..))
245241import Streamly.Internal.Data.IOFinalizer
246242 (newIOFinalizer, runIOFinalizer, clearingIOFinalizer)
247243import Streamly.Internal.Data.Stream.StreamD.Type (Stream(..), Step(..))
248−import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
249−import System.Mem (performMajorGC)
244+import Streamly.Internal.Data.SVar.Type (defState)
250245
251246import qualified Prelude
252247import qualified Control.Monad.Catch as MC
@@ -256,7 +251,6 @@ import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
256251import qualified Streamly.Internal.Data.Stream.StreamK as K (uncons)
257252import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
258253
259−import Streamly.Internal.Data.SVar
260254import Streamly.Internal.Data.Unfold.Type
261255import Prelude
262256 hiding (map, mapM, takeWhile, take, filter, const, zipWith
@@ -885,135 +879,6 @@ enumerateFromToFractional :: (Monad m, Fractional a, Ord a) => a -> Unfold m a a
885879enumerateFromToFractional to =
886880 takeWhile (<= to + 1 / 2) $ enumerateFromStepNum 1
887881
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−
1017882------------------------------------------------------------------------------
1018883-- Exceptions
1019884------------------------------------------------------------------------------
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)
9494import qualified Control.Monad.Catch as MC
9595import qualified System.IO as SIO
9696
97+import Streamly.Internal.Control.Concurrent (MonadAsync)
9798import Streamly.Internal.Data.Fold.Type (Fold(..))
9899import Streamly.Internal.Data.Unfold.Type (Unfold(..))
99100import Streamly.Internal.Data.Array.Foreign.Type (Array(..), writeNUnsafe)
100101import Streamly.Internal.Data.Stream.Serial (SerialT)
101102import Streamly.Internal.Data.Stream.StreamK.Type (IsStream)
102−import Streamly.Internal.Data.SVar (MonadAsync)
103103-- import Streamly.Data.Fold (Fold)
104104-- import Streamly.String (encodeUtf8, decodeUtf8, foldLines)
105105import Streamly.Internal.System.IO (defaultChunkSize)
src/Streamly/Internal/Network/Inet/TCP.hs+1−2
@@ -103,10 +103,9 @@ import Network.Socket
103103 socket)
104104import Prelude hiding (read)
105105
106+import Streamly.Internal.Control.Concurrent (MonadAsync, fork)
106107import Streamly.Internal.Data.Array.Foreign.Type (Array(..), writeNUnsafe)
107108import Streamly.Internal.Data.Fold.Type (Fold(..))
108−import Streamly.Internal.Data.Stream.IsStream (MonadAsync)
109−import Streamly.Internal.Data.SVar (fork)
110109import Streamly.Internal.Data.Stream.Serial (SerialT)
111110import Streamly.Internal.Data.Stream.StreamK.Type (IsStream)
112111import Streamly.Internal.Data.Tuple.Strict (Tuple'(..))
streamly.cabal+8−1
@@ -370,6 +370,10 @@ library
370370 , Streamly.Internal.System.IOVec.Type
371371
372372 -- 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
373377 , Streamly.Internal.Data.SVar
374378 , Streamly.Internal.Data.Stream.StreamK.Type
375379 , Streamly.Internal.Data.Fold.Type
@@ -417,13 +421,16 @@ library
417421
418422 -- streamly-core
419423 , Streamly.Internal.Data.Unfold
424+ , Streamly.Internal.Data.Unfold.SVar
420425 , Streamly.Internal.Data.Fold.Tee
421426 , Streamly.Internal.Data.Fold
427+ , Streamly.Internal.Data.Fold.SVar
422428 , Streamly.Internal.Data.Sink
423429 , Streamly.Internal.Data.Parser
424430 , Streamly.Internal.Data.Pipe
425431
426− , Streamly.Internal.Data.Stream.SVar
432+ , Streamly.Internal.Data.Stream.SVar.Generate
433+ , Streamly.Internal.Data.Stream.SVar.Eliminate
427434 , Streamly.Internal.Data.Stream.Serial
428435 , Streamly.Internal.Data.Stream.Async
429436 , Streamly.Internal.Data.Stream.Parallel