2-- Module : Streamly.Internal.Data.Fold.Concurrent.Channel
3-- Copyright : (c) 2022 Composewell Technologies
4-- License : BSD-3-Clause
5-- Maintainer : streamly@composewell.com
6-- Stability : experimental
9module Streamly.Internal.Data.Fold.Concurrent.Channel
25import Control.Concurrent (takeMVar)
26import Control.Monad (void, when)
27import Control.Monad.IO.Class (MonadIO(liftIO))
28import Data.IORef (writeIORef)
29import Streamly.Internal.Control.Concurrent (MonadAsync)
30import Streamly.Internal.Data.Fold (Fold(..), Step (..))
31import Streamly.Internal.Data.Stream.Channel.Worker (sendWithDoorBell)
32import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
34import Streamly.Internal.Data.Fold.Concurrent.Channel.Type
35import Streamly.Internal.Data.Stream.Channel.Types
37-------------------------------------------------------------------------------
39-------------------------------------------------------------------------------
41-- XXX Cleanup the fold if the stream is interrupted. Add a GC hook.
43-- | Evaluate a fold asynchronously using a concurrent channel. The driver just
44-- queues the input stream values to the fold channel buffer and returns. The
45-- fold evaluates the queued values asynchronously. On finalization, 'parEval'
46-- waits for the asynchronous fold to complete before it returns.
48{-# INLINABLE parEval #-}
49parEval :: MonadAsync m => (Config -> Config) -> Fold m a b -> Fold m a b
51 Fold step initial extract final
55 initial = Partial <$> newChannel modifier f
57 -- XXX This is not truly asynchronous. If the fold is done we only get to
58 -- know when we send the next input unless the stream ends. We could
59 -- potentially throw an async exception to the driver to inform it
60 -- asynchronously. Alternatively, the stream should not block forever, it
61 -- should keep polling the fold status. We can insert a timer tick in the
62 -- input stream to do that.
64 -- A polled stream abstraction may be useful, it would consist of normal
65 -- events and tick events, latter are guaranteed to arrive.
67 -- XXX We can use the config to indicate if the fold is a scanning type or
68 -- one-shot, or use a separate parEvalScan for scanning. For a scanning
69 -- type fold the worker would always send the intermediate values back to
70 -- the driver. An intermediate value can be returned on an input, or the
71 -- driver can poll even without input, if we have the Skip input support.
72 -- When the buffer is full we can return "Skip" and then the next step
73 -- without input can wait for an output to arrive. Similarly, when "final"
74 -- is called it can return "Skip" to continue or "Done" to indicate
77 status <- sendToWorker chan a
78 return $ case status of
79 Nothing -> Partial chan
82 -- XXX We can use a separate type for non-scanning folds that will
83 -- introduce a lot of complexity. Are there combinators that rely on the
84 -- "extract" function even in non-scanning use cases?
85 -- Instead of making such folds partial we can also make them return a
87 extract _ = error "Concurrent folds do not support scanning"
89 -- XXX depending on the use case we may want to either wait for the result
90 -- or cancel the ongoing work. We can use the config to control that?
91 -- Currently it waits for the work to complete.
98 status <- checkFoldStatus chan
103 (svarInspectMode chan)
105 "parEval: waiting to drain"
106 $ takeMVar (outputDoorBellFromConsumer chan)
107 -- XXX remove recursion
110 when (svarInspectMode chan) $ liftIO $ do
111 t <- getTime Monotonic
112 writeIORef (svarStopTime (svarStats chan)) (Just t)
113 printSVar (dumpSVar chan) "SVar Done"
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list