Skip to content

Commit 6f2b941

Browse files
committed
Change the edge-case behaviour of parDistributeScan
- Add a new type of concurrent fold output indicating EOF - Add a reference in the channel: closedForInput - Check on closedForInput in sendToWorker_
1 parent a3b8f55 commit 6f2b941

3 files changed

Lines changed: 71 additions & 35 deletions

File tree

src/Streamly/Internal/Data/Fold/Channel/Type.hs

Lines changed: 40 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ import Data.List (intersperse)
4545
import Streamly.Internal.Control.Concurrent
4646
(MonadAsync, MonadRunInIO, askRunInIO)
4747
import Streamly.Internal.Control.ForkLifted (doForkWith)
48+
import Streamly.Internal.Data.Atomics (writeBarrier)
4849
import Streamly.Internal.Data.Fold (Fold(..))
4950
import Streamly.Internal.Data.Scanl (Scanl(..))
5051
import Streamly.Internal.Data.Channel.Dispatcher (dumpSVarStats)
@@ -66,6 +67,7 @@ data OutEvent b =
6667
FoldException ThreadId SomeException
6768
| FoldPartial b
6869
| FoldDone ThreadId b
70+
| FoldEOF ThreadId
6971

7072
-- | The fold driver thread queues the input of the fold in the 'inputQueue'
7173
-- The driver rings the doorbell when the queue transitions from empty to
@@ -107,6 +109,7 @@ data Channel m a b = Channel
107109
--
108110
-- [LOCKING] Infrequent, MVar.
109111
, inputItemDoorBell :: MVar ()
112+
, closedForInput :: IORef Bool
110113

111114
-- | Doorbell to tell the driver that there is now space available in the
112115
-- 'inputQueue' and more items can be queued.
@@ -212,6 +215,11 @@ sendPartialToDriver :: MonadIO m => Channel m a b -> b -> m ()
212215
sendPartialToDriver sv res = liftIO $ do
213216
void $ sendToDriver sv (FoldPartial res)
214217

218+
sendEOFToDriver :: MonadIO m => Channel m a b -> m ()
219+
sendEOFToDriver sv = liftIO $ do
220+
tid <- myThreadId
221+
void $ sendToDriver sv (FoldEOF tid)
222+
215223
{-# NOINLINE sendExceptionToDriver #-}
216224
sendExceptionToDriver :: Channel m a b -> SomeException -> IO ()
217225
sendExceptionToDriver sv e = do
@@ -281,6 +289,7 @@ mkNewChannelWith outQRev outQMvRev cfg = do
281289
outQ <- newIORef ([], 0)
282290
outQMv <- newEmptyMVar
283291
bufferMv <- newEmptyMVar
292+
ref <- newIORef False
284293

285294
stats <- newSVarStats
286295
tid <- myThreadId
@@ -292,6 +301,7 @@ mkNewChannelWith outQRev outQMvRev cfg = do
292301
, outputQueue = outQRev
293302
, outputDoorBell = outQMvRev
294303
, inputSpaceDoorBell = bufferMv
304+
, closedForInput = ref
295305
, maxInputBuffer = getMaxBuffer cfg
296306
, readInputQ = liftIO $ fmap fst (readInputQWithDB sv)
297307
, svarRef = Nothing
@@ -330,10 +340,12 @@ newChannelWith outq outqDBell modifier f = do
330340
let f1 = Fold.rmapM (void . sendYieldToDriver chan) f
331341
in D.fold f1 $ fromInputQueue chan
332342

343+
-- | Returns True if the fold terminated due to completion and False when due
344+
-- to end-of-stream.
333345
{-# INLINE scanToChannel #-}
334-
scanToChannel :: MonadIO m => Channel m a b -> Scanl m a b -> Scanl m a ()
346+
scanToChannel :: MonadIO m => Channel m a b -> Scanl m a b -> Fold m a Bool
335347
scanToChannel chan (Scanl step initial extract final) =
336-
Scanl step1 initial1 extract1 final1
348+
Fold step1 initial1 extract1 final1
337349

338350
where
339351

@@ -344,8 +356,9 @@ scanToChannel chan (Scanl step initial extract final) =
344356
b <- extract s
345357
void $ sendPartialToDriver chan b
346358
return $ Fold.Partial s
347-
Fold.Done b ->
348-
Fold.Done <$> void (sendYieldToDriver chan b)
359+
Fold.Done b -> do
360+
sendYieldToDriver chan b
361+
return $ Fold.Done True
349362

350363
step1 st x = do
351364
r <- step st x
@@ -354,13 +367,16 @@ scanToChannel chan (Scanl step initial extract final) =
354367
b <- extract s
355368
void $ sendPartialToDriver chan b
356369
return $ Fold.Partial s
357-
Fold.Done b ->
358-
Fold.Done <$> void (sendYieldToDriver chan b)
370+
Fold.Done b -> do
371+
sendYieldToDriver chan b
372+
return $ Fold.Done True
359373

360-
extract1 _ = return ()
374+
extract1 _ = error "extract: not supported by folds"
361375

362376
-- XXX Should we not discard the result?
363-
final1 st = void (final st)
377+
final1 st = do
378+
void (final st)
379+
return False
364380

365381
{-# INLINABLE newChannelWithScan #-}
366382
{-# SPECIALIZE newChannelWithScan ::
@@ -386,7 +402,15 @@ newChannelWithScan outq outqDBell modifier f = do
386402
where
387403

388404
{-# NOINLINE work #-}
389-
work chan = D.drain $ D.scanl (scanToChannel chan f) $ fromInputQueue chan
405+
work chan = do
406+
completed <- D.fold (scanToChannel chan f) (fromInputQueue chan)
407+
-- We check for only one item in the outputqueue, for example in
408+
-- parTeeWith, multiple messages can make that complicated. Therefore,
409+
-- we first check if we already sent a FoldDone.
410+
when (not completed) $ sendEOFToDriver chan
411+
liftIO $ writeIORef (closedForInput chan) True
412+
liftIO writeBarrier
413+
void $ liftIO $ tryPutMVar (inputSpaceDoorBell chan) ()
390414

391415
{-# INLINABLE newChannel #-}
392416
{-# SPECIALIZE newChannel ::
@@ -441,7 +465,10 @@ checkFoldStatus sv = do
441465
case ev of
442466
FoldException _ e -> throwM e
443467
FoldDone _ b -> return (Just b)
444-
FoldPartial _ -> undefined
468+
FoldPartial _ ->
469+
error "checkFoldStatus: FoldPartial can occur only for scans"
470+
FoldEOF _ ->
471+
error "checkFoldStatus: FoldEOF can occur only for scans"
445472

446473
{-# INLINE isBufferAvailable #-}
447474
isBufferAvailable :: MonadIO m => Channel m a b -> m Bool
@@ -510,10 +537,10 @@ sendToWorker_ chan a = go
510537
(inputItemDoorBell chan)
511538
(ChildYield a)
512539
else do
513-
error "sendToWorker_: No space available in the buffer"
514540
-- Block for space
515-
-- () <- liftIO $ takeMVar (inputSpaceDoorBell chan)
516-
-- go
541+
() <- liftIO $ takeMVar (inputSpaceDoorBell chan)
542+
closed <- liftIO $ readIORef (closedForInput chan)
543+
when (not closed) go
517544

518545
-- XXX Cleanup the fold if the stream is interrupted. Add a GC hook.
519546

src/Streamly/Internal/Data/Fold/Concurrent.hs

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,11 @@ parDistributeScan cfg getFolds (Stream sstep state) =
340340
FoldDone tid b ->
341341
let ch = filter (\(_, t) -> t /= tid) chans
342342
in processOutputs ch xs (b:done)
343-
FoldPartial _ -> undefined
343+
FoldPartial _ ->
344+
error "parDistributeScan: cannot occur for folds"
345+
FoldEOF _ ->
346+
error
347+
"parDistributeScan: FoldEOF cannot occur for folds"
344348

345349
collectOutputs qref chans = do
346350
(_, n) <- liftIO $ readIORef qref
@@ -464,7 +468,10 @@ parDemuxScan cfg getKey getFold (Stream sstep state) =
464468
FoldDone _tid o@(k, _) ->
465469
let ch = Map.delete k keyToChan
466470
in processOutputs ch xs (o:done)
467-
FoldPartial _ -> undefined
471+
FoldPartial _ ->
472+
error "parDemuxScan: cannot occur for folds"
473+
FoldEOF _ ->
474+
error "parDemuxScan: FoldEOF cannot occur for folds"
468475

469476
collectOutputs qref keyToChan = do
470477
(_, n) <- liftIO $ readIORef qref

src/Streamly/Internal/Data/Scanl/Concurrent.hs

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ parTeeWith cfg f c1 c2 = Scanl step initial extract final
8989
liftIO $ throwM ex
9090
FoldDone _tid b -> return (Left b)
9191
FoldPartial b -> return (Right b)
92+
FoldEOF _ -> error "parTeeWith: FoldEOF cannot occur here"
9293
_ -> error "parTeeWith: not expecting more than one msg in q"
9394

9495
processResponses ch1 ch2 r1 r2 =
@@ -145,12 +146,18 @@ data ScanState s q db f =
145146
-- XXX We can use a one way mailbox type abstraction instead of using an IORef
146147
-- for adding new folds dynamically.
147148

148-
-- | Evaluate a stream and scan its outputs using zero or more dynamically
149-
-- generated parallel scans. It checks for any new folds at each input
150-
-- generation step. Any new fold is added to the list of folds which are
151-
-- currently running. If there are no folds available, the input is discarded.
152-
-- If a fold completes its output is emitted in the output of the scan. The
153-
-- outputs of the parallel scans are merged in the output stream.
149+
-- | Evaluate a stream and scan its outputs using zero or more parallel scans,
150+
-- which can be generated dynamically. It takes an action for producing new
151+
-- scans which is run before processing each input. The list of scans produced
152+
-- is added to the currently running scans. If you do not want the same scan
153+
-- added every time then the action should generate it only once (see the
154+
-- example below). If there are no scans available, the input is discarded. The
155+
-- outputs of all the scans are merged in the output stream.
156+
--
157+
-- If the input buffer (see maxBuffer) is limited then a scan may block until
158+
-- space becomes available in the input buffer. If a scan blocks then input is
159+
-- not provided to any of the scans, input is distributed to scans only when
160+
-- all scans have input buffer available.
154161
--
155162
-- >>> import Data.IORef
156163
-- >>> ref <- newIORef [Scanl.take 5 Scanl.sum, Scanl.take 5 Scanl.length :: Scanl.Scanl IO Int Int]
@@ -180,6 +187,9 @@ parDistributeScan cfg getFolds (Stream sstep state) =
180187
FoldDone tid b ->
181188
let ch = filter (\(_, t) -> t /= tid) chans
182189
in processOutputs ch xs (b:done)
190+
FoldEOF tid -> do
191+
let ch = filter (\(_, t) -> t /= tid) chans
192+
in processOutputs ch xs done
183193
FoldPartial b ->
184194
processOutputs chans xs (b:done)
185195

@@ -209,20 +219,8 @@ parDistributeScan cfg getFolds (Stream sstep state) =
209219
res <- sstep (adaptState gst) st
210220
next <- case res of
211221
Yield x s -> do
212-
-- XXX We might block forever if some folds are already
213-
-- done but we have not read the output queue yet. To
214-
-- avoid that we have to either (1) precheck if space
215-
-- is available in the input queues of all folds so
216-
-- that this does not block, or (2) we have to use a
217-
-- non-blocking read and track progress so that we can
218-
-- restart from where we left.
219-
--
220-
-- If there is no space available then we should block
221-
-- on doorbell db or inputSpaceDoorBell of the relevant
222-
-- channel. To avoid deadlock the output space can be
223-
-- kept unlimited. However, the blocking will delay the
224-
-- processing of outputs. We should yield the outputs
225-
-- before blocking.
222+
-- XXX The blocking will delay the processing of outputs.
223+
-- Should we yield the outputs before blocking?
226224
Prelude.mapM_ (`sendToWorker_` x) (fmap fst running)
227225
return $ ScanGo s q db running
228226
Skip s -> do
@@ -305,6 +303,10 @@ parDemuxScan cfg getKey getFold (Stream sstep state) =
305303
FoldDone _tid o@(k, _) ->
306304
let ch = Map.delete k keyToChan
307305
in processOutputs ch xs (o:done)
306+
FoldEOF tid ->
307+
let chans = Map.toList keyToChan
308+
ch = filter (\(_, (_, t)) -> t /= tid) chans
309+
in processOutputs (Map.fromList ch) xs done
308310
FoldPartial b ->
309311
processOutputs keyToChan xs (b:done)
310312

0 commit comments

Comments
 (0)