@@ -28,7 +28,7 @@ module Streamly.Streams.Ahead
30import Control.Concurrent.MVar (putMVar, takeMVar)
30import Control.Concurrent.MVar (putMVar, takeMVar)
31−import Control.Monad (ap)
31+import Control.Monad (ap, void)
32import Control.Monad.Base (MonadBase(..), liftBaseDefault)
32import Control.Monad.Base (MonadBase(..), liftBaseDefault)
33import Control.Monad.Catch (MonadThrow, throwM)
33import Control.Monad.Catch (MonadThrow, throwM)
34-- import Control.Monad.Error.Class (MonadError(..))
34-- import Control.Monad.Error.Class (MonadError(..))
@@ -40,6 +40,7 @@ import Data.Heap (Heap, Entry(..))
40import Data.IORef (IORef, readIORef, atomicModifyIORef)
40import Data.IORef (IORef, readIORef, atomicModifyIORef)
41import Data.Maybe (fromJust)
41import Data.Maybe (fromJust)
42import Data.Semigroup (Semigroup(..))
42import Data.Semigroup (Semigroup(..))
43+import GHC.Exts (inline)
44import qualified Data.Heap as H
45import qualified Data.Heap as H
@@ -127,6 +128,65 @@ import Prelude hiding (map)
127-- updates the heap count and the other updates the yield count.
128-- updates the heap count and the other updates the yield count.
128-- we can pass that as an update function instead.
129-- we can pass that as an update function instead.
133+-- * A worker should always ensure that it pushes all the consecutive items in
134+-- the heap to the outputQueue especially the items on behalf of the workers
135+-- that have already left when we were holding the token. This avoids deadlock
136+-- conditions when the later workers completion depends on the consumption of
137+-- earlier results. For more details see comments in the consumer pull side
140+{-# INLINE underMaxHeap #-}
143+ -> Heap (Entry Int (AheadHeapEntry Stream m a))
145+underMaxHeap sv hp = do
146+ (_, len) <- readIORef (outputQueue sv)
148+ -- XXX simplify this
149+ let maxHeap = case maxBufferLimit sv of
150+ Limited lim -> Limited $
151+ if (fromIntegral lim) >= len
152+ then lim - (fromIntegral len)
154+ Unlimited -> Unlimited
158+ active <- readIORef (workerCount sv)
159+ return $ H.size hp + active <= (fromIntegral lim)
160+ Unlimited -> return True
167+ -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)) , Int)
169+preStopCheck sv heap = do
170+ -- check the stop condition under a lock before actually
171+ -- stopping so that the whole herd does not stop at once.
172+ takeMVar (workerStopMVar sv)
174+ putMVar (workerStopMVar sv) ()
177+ putMVar (workerStopMVar sv) ()
179+ (hp, _) <- readIORef heap
180+ heapOk <- underMaxHeap sv hp
183+ case yieldRateInfo sv of
186+ rateOk <- isBeyondMaxRate sv yinfo
187+ if rateOk then continue else stop
130processHeap :: MonadIO m
190processHeap :: MonadIO m
131 => IORef ([Stream m a], Int)
191 => IORef ([Stream m a], Int)
132 -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)) , Int)
192 -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)) , Int)
@@ -135,11 +195,22 @@ processHeap :: MonadIO m
135 -> WorkerInfo
195 -> WorkerInfo
136 -> AheadHeapEntry Stream m a
196 -> AheadHeapEntry Stream m a
198+ -> Bool -- we are draining the heap before we stop
139−processHeap q heap st sv winfo entry sno = loopHeap sno entry
200+processHeap q heap st sv winfo entry sno stopping = loopHeap sno entry
204+ stopIfNeeded ent seqNo r = do
205+ stopIt <- liftIO $ preStopCheck sv heap
208+ -- put the entry back in the heap and stop
209+ atomicModifyIORef heap $ \(h, _) ->
210+ ((H.insert (Entry seqNo ent) h, seqNo), ())
212+ else runStreamWithYieldLimit True seqNo r
143 loopHeap seqNo ent = do
214 loopHeap seqNo ent = do
144#ifdef DIAGNOSTICS
215#ifdef DIAGNOSTICS
145 liftIO $ do
216 liftIO $ do
@@ -151,15 +222,14 @@ processHeap q heap st sv winfo entry sno = loopHeap sno entry
151 AheadEntryPure a -> do
222 AheadEntryPure a -> do
152 -- Use 'send' directly so that we do not account this in worker
223 -- Use 'send' directly so that we do not account this in worker
153 -- latency as this will not be the real latency.
224 -- latency as this will not be the real latency.
225+ -- Don't stop the worker in this case as we are just
226+ -- transferring available results from heap to outputQueue.
154− continue <- liftIO $ send sv (ChildYield a)
227+ void $ liftIO $ send sv (ChildYield a)
155− if continue
228+ nextHeap seqNo
229+ AheadEntryStream r -> do
156− then nextHeap seqNo
231+ then stopIfNeeded ent seqNo r
158− AheadEntryStream r -> runStreamWithYieldLimit True seqNo r
232+ else runStreamWithYieldLimit True seqNo r
160− stopHeap seqNo = liftIO $ do
161− atomicModifyIORef heap $ \(h, _) -> ((h, seqNo + 1), ())
164 nextHeap prevSeqNo = do
234 nextHeap prevSeqNo = do
165 -- XXX use "dequeueIfSeqential prevSeqNo" instead of always
235 -- XXX use "dequeueIfSeqential prevSeqNo" instead of always
@@ -169,25 +239,36 @@ processHeap q heap st sv winfo entry sno = loopHeap sno entry
169 case ent of
239 case ent of
170 Just (Entry seqNo hent) -> loopHeap seqNo hent
240 Just (Entry seqNo hent) -> loopHeap seqNo hent
171 Nothing -> do
241 Nothing -> do
172− work <- dequeueAhead q
242+ if stopping
173− case work of
243+ then do
244+ r <- liftIO $ preStopCheck sv heap
174− Nothing -> liftIO $ sendStop sv winfo
246+ then liftIO $ sendStop sv winfo
175− Just (m, seqNo) -> do
247+ else processWorkQueue prevSeqNo
176− yieldLimitOk <- liftIO $ decrementYieldLimit sv
248+ else (inline processWorkQueue) prevSeqNo
179− if seqNo == prevSeqNo + 1
180− then processWithToken q heap st sv winfo m seqNo
181− else processWithoutToken q heap st sv winfo m seqNo
183− incrementYieldLimit sv
250+ processWorkQueue prevSeqNo = do
251+ work <- dequeueAhead q
253+ Nothing -> liftIO $ sendStop sv winfo
254+ Just (m, seqNo) -> do
255+ yieldLimitOk <- liftIO $ decrementYieldLimit sv
258+ if seqNo == prevSeqNo + 1
259+ then processWithToken q heap st sv winfo m seqNo
260+ else processWithoutToken q heap st sv winfo m seqNo
262+ incrementYieldLimit sv
265+ -- We do not stop the worker on buffer full here as we want to proceed to
266+ -- nextHeap anyway so that we can clear any subsequent entries. We stop
267+ -- only in yield continuation where we may have a remaining stream to be
268+ -- pushed on the heap.
186 singleStreamFromHeap seqNo a = do
269 singleStreamFromHeap seqNo a = do
187− continue <- liftIO $ sendYield sv winfo (ChildYield a)
270+ void $ liftIO $ sendYield sv winfo (ChildYield a)
189− then nextHeap seqNo
271+ nextHeap seqNo
192 runStreamWithYieldLimit continue seqNo r = do
273 runStreamWithYieldLimit continue seqNo r = do
193 yieldLimitOk <- liftIO $ decrementYieldLimit sv
274 yieldLimitOk <- liftIO $ decrementYieldLimit sv
@@ -209,6 +290,21 @@ processHeap q heap st sv winfo entry sno = loopHeap sno entry
209 continue <- liftIO $ sendYield sv winfo (ChildYield a)
290 continue <- liftIO $ sendYield sv winfo (ChildYield a)
210 runStreamWithYieldLimit continue seqNo r
291 runStreamWithYieldLimit continue seqNo r
293+{-# NOINLINE drainHeap #-}
294+drainHeap :: MonadIO m
295+ => IORef ([Stream m a], Int)
296+ -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)) , Int)
301+drainHeap q heap st sv winfo = do
302+ ent <- liftIO $ dequeueFromHeap heap
304+ Nothing -> liftIO $ sendStop sv winfo
305+ Just (Entry seqNo hent) ->
306+ processHeap q heap st sv winfo hent seqNo True
212processWithoutToken :: MonadIO m
308processWithoutToken :: MonadIO m
213 => IORef ([Stream m a], Int)
309 => IORef ([Stream m a], Int)
214 -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)) , Int)
310 -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)) , Int)
@@ -228,25 +324,6 @@ processWithoutToken q heap st sv winfo m sno = do
231− maxBuf = maxBufferLimit sv
233− underMaxHeap hp = do
234− (_, len) <- liftIO $ readIORef (outputQueue sv)
236− -- XXX simplify this
237− let maxHeap = case maxBuf of
238− Limited lim -> Limited $
239− if (fromIntegral lim) >= len
240− then lim - (fromIntegral len)
242− Unlimited -> Unlimited
246− active <- liftIO $ readIORef (workerCount sv)
247− return $ H.size hp + active <= (fromIntegral lim)
248− Unlimited -> return True
250 -- XXX to reduce contention each CPU can have its own heap
327 -- XXX to reduce contention each CPU can have its own heap
251 toHeap seqNo ent = do
328 toHeap seqNo ent = do
252 -- Heap insertion is an expensive affair so we use a non CAS based
329 -- Heap insertion is an expensive affair so we use a non CAS based
@@ -255,25 +332,18 @@ processWithoutToken q heap st sv winfo m sno = do
255 -- sequence.
332 -- sequence.
256 hp <- liftIO $ atomicModifyIORef heap $ \(h, snum) ->
333 hp <- liftIO $ atomicModifyIORef heap $ \(h, snum) ->
257 ((H.insert (Entry seqNo ent) h, snum), h)
334 ((H.insert (Entry seqNo ent) h, snum), h)
258− -- XXX heap check should also be under MVar
335+
259− heapOk <- underMaxHeap hp
336+ heapOk <- liftIO $ underMaxHeap sv hp
260 if heapOk
337 if heapOk
262 case yieldRateInfo sv of
339 case yieldRateInfo sv of
263 Nothing -> workLoopAhead q heap st sv winfo
340 Nothing -> workLoopAhead q heap st sv winfo
264 Just yinfo -> do
341 Just yinfo -> do
265− -- check the stop condition under a lock before actually
266− -- stopping so that the whole herd does not stop at once.
267− liftIO $ takeMVar (workerStopMVar yinfo)
268 rateOk <- liftIO $ workerRateControl sv yinfo winfo
342 rateOk <- liftIO $ workerRateControl sv yinfo winfo
269 if rateOk
343 if rateOk
271− liftIO $ putMVar (workerStopMVar yinfo) ()
272− workLoopAhead q heap st sv winfo
344+ then workLoopAhead q heap st sv winfo
273− else do
345+ else drainHeap q heap st sv winfo
274− liftIO $ sendStop sv winfo
275− liftIO $ putMVar (workerStopMVar yinfo) ()
276− else liftIO $ sendStop sv winfo
346+ else drainHeap q heap st sv winfo
278 singleToHeap seqNo a = toHeap seqNo (AheadEntryPure a)
348 singleToHeap seqNo a = toHeap seqNo (AheadEntryPure a)
279 yieldToHeap seqNo a r = toHeap seqNo (AheadEntryStream (a `K.cons` r))
349 yieldToHeap seqNo a r = toHeap seqNo (AheadEntryStream (a `K.cons` r))
@@ -302,9 +372,9 @@ processWithToken q heap st sv winfo action sno = do
302 continue <- liftIO $ sendYield sv winfo (ChildYield a)
372 continue <- liftIO $ sendYield sv winfo (ChildYield a)
303 if continue
373 if continue
304 then loopWithToken seqNo
374 then loopWithToken seqNo
305− else liftIO $ do
375+ else do
306− atomicModifyIORef heap $ \(h, _) -> ((h, seqNo + 1), ())
376+ liftIO $ atomicModifyIORef heap $ \(h, _) -> ((h, seqNo + 1), ())
307− sendStop sv winfo
377+ drainHeap q heap st sv winfo
309 -- XXX use a wrapper function around stop so that we never miss
379 -- XXX use a wrapper function around stop so that we never miss
310 -- incrementing the yield in a stop continuation. Essentiatlly all
380 -- incrementing the yield in a stop continuation. Essentiatlly all
@@ -320,11 +390,11 @@ processWithToken q heap st sv winfo action sno = do
320 unStream r st stop
390 unStream r st stop
321 (singleOutput seqNo)
391 (singleOutput seqNo)
322 (yieldOutput seqNo)
392 (yieldOutput seqNo)
323− else liftIO $ do
393+ else do
324− atomicModifyIORef heap $ \(h, _) ->
394+ liftIO $ atomicModifyIORef heap $ \(h, _) ->
325 ((H.insert (Entry seqNo (AheadEntryStream r)) h, seqNo), ())
395 ((H.insert (Entry seqNo (AheadEntryStream r)) h, seqNo), ())
326− incrementYieldLimit sv
396+ liftIO $ incrementYieldLimit sv
327− sendStop sv winfo
397+ drainHeap q heap st sv winfo
329 loopWithToken prevSeqNo = do
399 loopWithToken prevSeqNo = do
330 work <- dequeueAhead q
400 work <- dequeueAhead q
@@ -332,12 +402,7 @@ processWithToken q heap st sv winfo action sno = do
332 Nothing -> do
402 Nothing -> do
333 liftIO $ atomicModifyIORef heap $ \(h, _) ->
403 liftIO $ atomicModifyIORef heap $ \(h, _) ->
334 ((h, prevSeqNo + 1), ())
404 ((h, prevSeqNo + 1), ())
336− ent <- liftIO $ dequeueFromHeap heap
338− Just (Entry seqNo hent) ->
339− processHeap q heap st sv winfo hent seqNo
405+ workLoopAhead q heap st sv winfo
340− Nothing -> liftIO $ sendStop sv winfo
342 Just (m, seqNo) -> do
407 Just (m, seqNo) -> do
343 yieldLimitOk <- liftIO $ decrementYieldLimit sv
408 yieldLimitOk <- liftIO $ decrementYieldLimit sv
@@ -354,19 +419,26 @@ processWithToken q heap st sv winfo action sno = do
355 liftIO $ atomicModifyIORef heap $ \(h, _) ->
420 liftIO $ atomicModifyIORef heap $ \(h, _) ->
356 ((h, prevSeqNo + 1), ())
421 ((h, prevSeqNo + 1), ())
357− -- XXX put it back on the queue and start with the Heap?
422+ -- To avoid a race when another thread puts something
423+ -- on the heap and goes away, the consumer will not get
424+ -- a doorBell and we will not clear the heap before
425+ -- executing the next action. If the consumer depends
426+ -- on the output that is stuck in the heap then this
427+ -- will result in a deadlock. So we always clear the
428+ -- heap before executing the next action.
358− processWithoutToken q heap st sv winfo m seqNo
429+ -- processWithoutToken q heap st sv winfo m seqNo
430+ liftIO $ reEnqueueAhead sv q m
431+ workLoopAhead q heap st sv winfo
359− else liftIO $ do
432+ else do
360− atomicModifyIORef heap $ \(h, _) ->
433+ liftIO $ atomicModifyIORef heap $ \(h, _) ->
361 ((h, prevSeqNo + 1), ())
434 ((h, prevSeqNo + 1), ())
362− incrementYieldLimit sv
435+ liftIO $ incrementYieldLimit sv
363− sendStop sv winfo
436+ drainHeap q heap st sv winfo
365-- XXX the yield limit changes increased the performance overhead by 30-40%.
438-- XXX the yield limit changes increased the performance overhead by 30-40%.
366-- Just like AsyncT we can use an implementation without yeidlimit and even
439-- Just like AsyncT we can use an implementation without yeidlimit and even
367-- without pacing code to keep the performance higher in the unlimited and
440-- without pacing code to keep the performance higher in the unlimited and
368-- unpaced case.
441-- unpaced case.
369−{-# INLINE workLoopAhead #-}
370workLoopAhead :: MonadIO m
442workLoopAhead :: MonadIO m
371 => IORef ([Stream m a], Int)
443 => IORef ([Stream m a], Int)
372 -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)) , Int)
444 -> IORef (Heap (Entry Int (AheadHeapEntry Stream m a)) , Int)
@@ -413,7 +485,8 @@ workLoopAhead q heap st sv winfo = do
413 else liftIO $ do
485 else liftIO $ do
414 incrementYieldLimit sv
486 incrementYieldLimit sv
415 sendStop sv winfo
487 sendStop sv winfo
488+ Just (Entry seqNo hent) ->
416− Just (Entry seqNo hent) -> processHeap q heap st sv winfo hent seqNo
489+ processHeap q heap st sv winfo hent seqNo False
418-------------------------------------------------------------------------------
491-------------------------------------------------------------------------------