@@ -37,7 +37,7 @@ module Streamly.SVar
3737 , atomicModifyIORefCAS
3838 , ChildEvent (..)
3939 , AheadHeapEntry (..)
40− , send
40+ , sendYield
4141 , sendStop
4242 , enqueueLIFO
4343 , workLoopLIFO
@@ -46,7 +46,6 @@ module Streamly.SVar
4646 , enqueueAhead
4747 , pushWorkerPar
4848
49− , maxHeap
5049 , queueEmptyAhead
5150 , dequeueAhead
5251 , dequeueFromHeap
@@ -158,6 +157,7 @@ data SVar t m a =
158157
159158 -- Shared output queue (events, length)
160159 , outputQueue :: IORef ([ChildEvent a], Int)
160+ , maxYieldLimit :: Maybe (IORef Int)
161161 , outputDoorBell :: MVar () -- signal the consumer about output
162162 , readOutputQ :: m [ChildEvent a]
163163 , postProcess :: m Bool
@@ -187,9 +187,10 @@ data SVar t m a =
187187 }
188188
189189data State t m a = State
190− { streamVar :: Maybe (SVar t m a)
190+ { streamVar :: Maybe (SVar t m a)
191+ , yieldLimit :: Maybe Int
191192 , threadsHigh :: Int
192− , bufferHigh :: Int
193+ , bufferHigh :: Int
193194 }
194195
195196defaultMaxThreads, defaultMaxBuffer :: Int
@@ -199,15 +200,23 @@ defaultMaxBuffer = 1500
199200defState :: State t m a
200201defState = State
201202 { streamVar = Nothing
203+ , yieldLimit = Nothing
202204 , threadsHigh = defaultMaxThreads
203205 , bufferHigh = defaultMaxBuffer
204206 }
205207
208+-- XXX if perf gets affected we can have all the Nothing params in a single
209+-- structure so that we reset is fast. We can also use rewrite rules such that
210+-- reset occurs only in concurrent streams to reduce the impact on serial
211+-- streams.
206212-- We can optimize this so that we clear it only if it is a Just value, it
207213-- results in slightly better perf for zip/zipM but the performance of scan
208214-- worsens a lot, it does not fuse.
209215rstState :: State t m a -> State t m b
210−rstState st = st {streamVar = Nothing}
216+rstState st = st
217+ { streamVar = Nothing
218+ , yieldLimit = Nothing
219+ }
211220
212221#ifdef DIAGNOSTICS
213222{-# NOINLINE dumpSVar #-}
@@ -337,7 +346,6 @@ doFork action exHandler =
337346
338347-- | This function is used by the producer threads to queue output for the
339348-- consumer thread to consume. Returns whether the queue has more space.
340−{-# NOINLINE send #-}
341349send :: Int -> SVar t m a -> ChildEvent a -> IO Bool
342350send maxOutputQLen sv msg = do
343351 len <- atomicModifyIORefCAS (outputQueue sv) $ \(es, n) ->
@@ -355,6 +363,15 @@ send maxOutputQLen sv msg = do
355363 void $ tryPutMVar (outputDoorBell sv) ()
356364 return (len < maxOutputQLen || maxOutputQLen < 0)
357365
366+{-# NOINLINE sendYield #-}
367+sendYield :: Int -> SVar t m a -> ChildEvent a -> IO Bool
368+sendYield maxOutputQLen sv msg = do
369+ ylimit <- case maxYieldLimit sv of
370+ Nothing -> return True
371+ Just ref -> atomicModifyIORefCAS ref $ \x -> (x - 1, x > 1)
372+ r <- send maxOutputQLen sv msg
373+ return $ r && ylimit
374+
358375{-# NOINLINE sendStop #-}
359376sendStop :: SVar t m a -> IO ()
360377sendStop sv = do
@@ -534,9 +551,6 @@ enqueueAhead sv q m = do
534551--
535552-- XXX review for livelock
536553--
537−maxHeap :: Int
538−maxHeap = 1500
539−
540554{-# INLINE queueEmptyAhead #-}
541555queueEmptyAhead :: MonadIO m => IORef ([t m a], Int) -> m Bool
542556queueEmptyAhead q = liftIO $ do
@@ -675,7 +689,15 @@ dispatchWorker maxWorkerLimit sv = do
675689 -- executing. In that case we should either configure the maxWorker
676690 -- count to higher or use parallel style instead of ahead or async
677691 -- style.
678− when (cnt < maxWorkerLimit || maxWorkerLimit < 0) $ pushWorker sv
692+ limit <- case maxYieldLimit sv of
693+ Nothing -> return maxWorkerLimit
694+ Just x -> do
695+ lim <- liftIO $ readIORef x
696+ return $
697+ if maxWorkerLimit > 0
698+ then min maxWorkerLimit lim
699+ else lim
700+ when (cnt < limit || limit < 0) $ pushWorker sv
679701
680702{-# NOINLINE sendWorkerWait #-}
681703sendWorkerWait :: MonadAsync m => Int -> SVar t m a -> m ()
@@ -816,6 +838,9 @@ getAheadSVar st f = do
816838 wfw <- newIORef False
817839 running <- newIORef S.empty
818840 q <- newIORef ([], -1)
841+ yl <- case yieldLimit st of
842+ Nothing -> return Nothing
843+ Just x -> Just <$> newIORef x
819844
820845#ifdef DIAGNOSTICS
821846 disp <- newIORef 0
@@ -826,6 +851,7 @@ getAheadSVar st f = do
826851#endif
827852 let sv =
828853 SVar { outputQueue = outQ
854+ , maxYieldLimit = yl
829855 , outputDoorBell = outQMv
830856 , readOutputQ = readOutputQBounded (threadsHigh st) sv
831857 , postProcess = postProcessBounded sv
@@ -879,6 +905,7 @@ getParallelSVar = do
879905#endif
880906 let sv =
881907 SVar { outputQueue = outQ
908+ , maxYieldLimit = Nothing
882909 , outputDoorBell = outQMv
883910 , readOutputQ = readOutputQPar sv
884911 , postProcess = allThreadsDone sv