on master help sign in
implement StreamD style tapAsync for fusion
commitd229a4b21518d335b43a05f3b27fc661c9f3ffe9 authorHarendra Kumar <harendra.kumar@gmail.com> dateSun 8 Dec 2019, 15:39 (7 years ago) parent1baab0c
5 files changed+191−42
src/Streamly/Internal/Data/SVar.hs+14 src/Streamly/Internal/Data/Stream/Parallel.hs+5−40 src/Streamly/Internal/Data/Stream/SVar.hs+26−1 src/Streamly/Internal/Data/Stream/StreamD.hs+112 src/Streamly/Internal/Prelude.hs+34−1
src/Streamly/Internal/Data/SVar.hs+14
@@ -65,12 +65,14 @@ module Streamly.Internal.Data.SVar
6565 , sendToProducer
6666 , sendYield
6767 , sendStop
68+ , sendStopToProducer
6869 , enqueueLIFO
6970 , enqueueFIFO
7071 , enqueueAhead
7172 , reEnqueueAhead
7273 , pushWorkerPar
7374 , handleChildException
75+ , handleFoldException
7476
7577 , queueEmptyAhead
7678 , dequeueAhead
@@ -1101,6 +1103,12 @@ sendToProducer sv msg = do
11011103 sendWithDoorBell (outputQueueFromConsumer sv)
11021104 (outputDoorBellFromConsumer sv) msg
11031105
1106+-- {-# NOINLINE sendStopToProducer #-}
1107+sendStopToProducer :: MonadIO m => SVar t m a -> m ()
1108+sendStopToProducer sv = liftIO $ do
1109+ tid <- myThreadId
1110+ void $ sendToProducer sv (ChildStop tid Nothing)
1111+
11041112workerCollectLatency :: WorkerInfo -> IO (Maybe (Count, NanoSecond64))
11051113workerCollectLatency winfo = do
11061114 (cnt0, t0) <- readIORef (workerLatencyStart winfo)
@@ -1528,6 +1536,12 @@ handleChildException sv e = do
15281536 tid <- myThreadId
15291537 void $ send sv (ChildStop tid (Just e))
15301538
1539+{-# NOINLINE handleFoldException #-}
1540+handleFoldException :: SVar t m a -> SomeException -> IO ()
1541+handleFoldException sv e = do
1542+ tid <- myThreadId
1543+ void $ sendToProducer sv (ChildStop tid (Just e))
1544+
15311545{-# NOINLINE recordMaxWorkers #-}
15321546recordMaxWorkers :: MonadIO m => SVar t m a -> m ()
15331547recordMaxWorkers sv = liftIO $ do
src/Streamly/Internal/Data/Stream/Parallel.hs+5−40
@@ -35,7 +35,6 @@ module Streamly.Internal.Data.Stream.Parallel
3535where
3636
3737import Control.Concurrent (myThreadId, takeMVar)
38−import Control.Exception (SomeException(..))
3938import Control.Monad (when)
4039import Control.Monad.Base (MonadBase(..), liftBaseDefault)
4140import Control.Monad.Catch (MonadThrow, throwM)
@@ -55,7 +54,7 @@ import Prelude hiding (map)
5554import qualified Data.Set as Set
5655
5756import Streamly.Internal.Data.Stream.SVar
58− (fromSVar, fromProducer, fromConsumer)
57+ (fromSVar, fromProducer, fromConsumer, pushToFold)
5958import Streamly.Internal.Data.Stream.StreamK
6059 (IsStream(..), Stream, mkStream, foldStream, foldStreamShared, adapt)
6160
@@ -277,30 +276,6 @@ foldParallel f m = do
277276-- Clone and distribute a stream in parallel
278277------------------------------------------------------------------------------
279278
280−-- push values to a fold worker via an SVar. Returns whether the fold is done.
281−{-# INLINE pushToFold #-}
282−pushToFold :: MonadAsync m => SVar Stream m a -> a -> m Bool
283−pushToFold sv a = do
284− -- Check for exceptions before decrement so that we do not
285− -- block forever if the child already exited with an exception.
286− --
287− -- We avoid a race between the consumer fold sending an event and we
288− -- blocking on decrementBufferLimit by waking up the producer thread in
289− -- sendToProducer before any event is sent by the fold to the producer
290− -- stream.
291− let qref = outputQueueFromConsumer sv
292− done <- do
293− (_, n) <- liftIO $ readIORef qref
294− if (n > 0)
295− then fromConsumer sv
296− else return False
297− if done
298− then return True
299− else liftIO $ do
300− decrementBufferLimit sv
301− void $ send sv (ChildYield a)
302− return False
303−
304279-- Tap a stream and send the elements to the specified SVar in addition to
305280-- yielding them again.
306281--
@@ -347,18 +322,6 @@ teeToSVar svr m = mkStream $ \st yld sng stp -> do
347322-- Note: If we terminate due to an exception, we do not actively terminate the
348323-- fold. It gets cleaned up by the GC.
349324
350−{-# NOINLINE handleFoldException #-}
351−handleFoldException :: SVar t m a -> SomeException -> IO ()
352−handleFoldException sv e = do
353− tid <- myThreadId
354− void $ sendToProducer sv (ChildStop tid (Just e))
355−
356−{-# NOINLINE sendStopToConsumer #-}
357−sendStopToConsumer :: MonadIO m => SVar t m a -> m ()
358−sendStopToConsumer sv = liftIO $ do
359− tid <- myThreadId
360− void $ sendToProducer sv (ChildStop tid Nothing)
361−
362325-- | Create an SVar with a fold consumer that will fold any elements sent to it
363326-- using the supplied fold function.
364327{-# INLINE newFoldSVar #-}
@@ -371,11 +334,13 @@ newFoldSVar stt f = do
371334 -- Add the producer thread-id to the SVar.
372335 liftIO myThreadId >>= modifyThread sv
373336
337+ -- XXX move this in fromProducer itself.
338+ --
374339 -- A wrapper to send a Stop event back to the producer when the fold
375340 -- receives a stop from the producer.
376341 let pull m = mkStream $ \st yld _ stp -> do
377− let stop = sendStopToConsumer sv >> stp
378− single a = yld a (K.nilM (sendStopToConsumer sv))
342+ let stop = sendStopToProducer sv >> stp
343+ single a = yld a (K.nilM (sendStopToProducer sv))
379344 yieldk a r = yld a (pull r)
380345 in foldStreamShared st yieldk single stop m
381346
src/Streamly/Internal/Data/Stream/SVar.hs+26−1
@@ -17,11 +17,12 @@ module Streamly.Internal.Data.Stream.SVar
1717 , fromProducer
1818 , fromConsumer
1919 , toSVar
20+ , pushToFold
2021 )
2122where
2223
2324import Control.Exception (fromException)
24−import Control.Monad (when)
25+import Control.Monad (when, void)
2526import Control.Monad.Catch (throwM)
2627import Control.Monad.IO.Class (MonadIO(liftIO))
2728import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef)
@@ -177,3 +178,27 @@ fromConsumer sv = do
177178 Nothing -> return True
178179 Just ex -> throwM ex
179180 ChildYield _ -> error "Bug: fromConsumer: invalid ChildYield event"
181+
182+-- push values to a fold worker via an SVar. Returns whether the fold is done.
183+{-# INLINE pushToFold #-}
184+pushToFold :: MonadAsync m => SVar Stream m a -> a -> m Bool
185+pushToFold sv a = do
186+ -- Check for exceptions before decrement so that we do not
187+ -- block forever if the child already exited with an exception.
188+ --
189+ -- We avoid a race between the consumer fold sending an event and we
190+ -- blocking on decrementBufferLimit by waking up the producer thread in
191+ -- sendToProducer before any event is sent by the fold to the producer
192+ -- stream.
193+ let qref = outputQueueFromConsumer sv
194+ done <- do
195+ (_, n) <- liftIO $ readIORef qref
196+ if (n > 0)
197+ then fromConsumer sv
198+ else return False
199+ if done
200+ then return True
201+ else liftIO $ do
202+ decrementBufferLimit sv
203+ void $ send sv (ChildYield a)
204+ return False
src/Streamly/Internal/Data/Stream/StreamD.hs+112
@@ -125,6 +125,7 @@ module Streamly.Internal.Data.Stream.StreamD
125125
126126 -- ** Specialized Folds
127127 , tap
128+ , tapAsync
128129 , drain
129130 , null
130131 , head
@@ -299,6 +300,7 @@ module Streamly.Internal.Data.Stream.StreamD
299300 )
300301where
301302
303+import Control.Concurrent (myThreadId, takeMVar)
302304import Control.Exception (Exception, SomeException, fromException)
303305import Control.Monad (void, when)
304306import Control.Monad.Catch (MonadCatch, throwM)
@@ -336,6 +338,7 @@ import Streamly.Internal.Data.Unfold.Types (Unfold(..))
336338
337339import Streamly.Internal.Data.Stream.StreamD.Type
338340import Streamly.Internal.Data.SVar
341+import Streamly.Internal.Data.Stream.SVar (fromConsumer, pushToFold)
339342
340343import qualified Streamly.Internal.Data.Pipe.Types as Pipe
341344import qualified Streamly.Internal.Memory.Array.Types as A
@@ -732,6 +735,44 @@ fromSVar svar = Stream step FromSVarInit
732735 liftIO $ printSVar sv "SVar Done"
733736 return Stop
734737
738+-------------------------------------------------------------------------------
739+-- Process events received by a fold consumer from a stream producer
740+-------------------------------------------------------------------------------
741+
742+{-# INLINE_NORMAL fromProducer #-}
743+fromProducer :: (MonadAsync m) => SVar t m a -> Stream m a
744+fromProducer svar = Stream step (FromSVarRead svar)
745+ where
746+
747+ {-# INLINE_LATE step #-}
748+ step _ (FromSVarRead sv) = do
749+ list <- readOutputQ sv
750+ -- Reversing the output is important to guarantee that we process the
751+ -- outputs in the same order as they were generated by the constituent
752+ -- streams.
753+ return $ Skip $ FromSVarLoop sv (Prelude.reverse list)
754+
755+ step _ (FromSVarLoop sv []) = return $ Skip $ FromSVarRead sv
756+ step _ (FromSVarLoop sv (ev : es)) = do
757+ case ev of
758+ ChildYield a -> return $ Yield a (FromSVarLoop sv es)
759+ ChildStop tid e -> do
760+ accountThread sv tid
761+ case e of
762+ Nothing -> do
763+ sendStopToProducer sv
764+ return $ Skip (FromSVarDone sv)
765+ Just _ -> error "Bug: fromProducer: received exception"
766+
767+ step _ (FromSVarDone sv) = do
768+ when (svarInspectMode sv) $ do
769+ t <- liftIO $ getTime Monotonic
770+ liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)
771+ liftIO $ printSVar sv "SVar Done"
772+ return Stop
773+
774+ step _ FromSVarInit = undefined
775+
735776-------------------------------------------------------------------------------
736777-- Hoisting the inner monad
737778-------------------------------------------------------------------------------
@@ -3732,3 +3773,74 @@ foldParallel f m = do
37323773 sv <- newParallelVar StopNone defState
37333774 toSVarParallel defState sv (toStreamD m)
37343775 f $ fromStreamD $ fromSVar sv
3776+
3777+-------------------------------------------------------------------------------
3778+-- Concurrent tap
3779+-------------------------------------------------------------------------------
3780+
3781+-- | Create an SVar with a fold consumer that will fold any elements sent to it
3782+-- using the supplied fold function.
3783+{-# INLINE newFoldSVar #-}
3784+newFoldSVar :: MonadAsync m => State t m a -> Fold m a b -> m (SVar t m a)
3785+newFoldSVar stt f = do
3786+ -- Buffer size for the SVar is derived from the current state
3787+ sv <- newParallelVar StopAny (adaptState stt)
3788+ -- Add the producer thread-id to the SVar.
3789+ liftIO myThreadId >>= modifyThread sv
3790+ void $ doFork (work sv) (svarMrun sv) (handleFoldException sv)
3791+ return sv
3792+
3793+ where
3794+
3795+ {-# NOINLINE work #-}
3796+ work sv = void $ runFold f $ fromProducer sv
3797+
3798+data TapState sv st = TapInit | Tapping sv st | TapDone st
3799+
3800+{-# INLINE_NORMAL tapAsync #-}
3801+tapAsync :: MonadAsync m => Fold m a b -> Stream m a -> Stream m a
3802+tapAsync f (Stream step1 state1) = Stream step TapInit
3803+ where
3804+
3805+ drainFold svr = do
3806+ -- In general, a Stop event would come equipped with the result
3807+ -- of the fold. It is not used here but it would be useful in
3808+ -- applicative and distribute.
3809+ done <- fromConsumer svr
3810+ when (not done) $ do
3811+ liftIO $ withDiagMVar svr "teeToSVar: waiting to drain"
3812+ $ takeMVar (outputDoorBellFromConsumer svr)
3813+ drainFold svr
3814+
3815+ stopFold svr = do
3816+ liftIO $ sendStop svr Nothing
3817+ -- drain/wait until a stop event arrives from the fold.
3818+ drainFold svr
3819+
3820+ {-# INLINE_LATE step #-}
3821+ step gst TapInit = do
3822+ sv <- newFoldSVar gst f
3823+ return $ Skip (Tapping sv state1)
3824+
3825+ step gst (Tapping sv st) = do
3826+ r <- step1 gst st
3827+ case r of
3828+ Yield a s -> do
3829+ done <- pushToFold sv a
3830+ if done
3831+ then do
3832+ -- XXX we do not need to wait synchronously here
3833+ stopFold sv
3834+ return $ Yield a (TapDone s)
3835+ else return $ Yield a (Tapping sv s)
3836+ Skip s -> return $ Skip (Tapping sv s)
3837+ Stop -> do
3838+ stopFold sv
3839+ return $ Stop
3840+
3841+ step gst (TapDone st) = do
3842+ r <- step1 gst st
3843+ return $ case r of
3844+ Yield a s -> Yield a (TapDone s)
3845+ Skip s -> Skip (TapDone s)
3846+ Stop -> Stop
src/Streamly/Internal/Prelude.hs+34−1
@@ -341,7 +341,7 @@ module Streamly.Internal.Prelude
341341 -- ** Distributing
342342 , trace
343343 , tap
344− , Par.tapAsync
344+ , tapAsync
345345
346346 -- * Windowed Classification
347347
@@ -3567,9 +3567,41 @@ reassembleBy = undefined
35673567-- Compare with 'trace'.
35683568--
35693569-- @since 0.7.0
3570+{-# INLINE tap #-}
35703571tap :: (IsStream t, Monad m) => FL.Fold m a b -> t m a -> t m a
35713572tap f xs = D.fromStreamD $ D.tap f (D.toStreamD xs)
35723573
3574+-- | Redirect a copy of the stream to a supplied fold and run it concurrently
3575+-- in an independent thread. The fold may buffer some elements. The buffer size
3576+-- is determined by the prevailing 'maxBuffer' setting.
3577+--
3578+-- @
3579+-- Stream m a -> m b
3580+-- |
3581+-- -----stream m a ---------------stream m a-----
3582+--
3583+-- @
3584+--
3585+-- @
3586+-- > S.drain $ S.tapAsync (S.mapM_ print) (S.enumerateFromTo 1 2)
3587+-- 1
3588+-- 2
3589+-- @
3590+--
3591+-- Exceptions from the concurrently running fold are propagated to the current
3592+-- computation. Note that, because of buffering in the fold, exceptions may be
3593+-- delayed and may not correspond to the current element being processed in the
3594+-- parent stream, but we guarantee that before the parent stream stops the tap
3595+-- finishes and all exceptions from it are drained.
3596+--
3597+--
3598+-- Compare with 'tap'.
3599+--
3600+-- /Internal/
3601+{-# INLINE tapAsync #-}
3602+tapAsync :: (IsStream t, MonadAsync m) => FL.Fold m a b -> t m a -> t m a
3603+tapAsync f xs = D.fromStreamD $ D.tapAsync f (D.toStreamD xs)
3604+
35733605-- | Apply a monadic function to each element flowing through the stream and
35743606-- discard the results.
35753607--
@@ -3582,6 +3614,7 @@ tap f xs = D.fromStreamD $ D.tap f (D.toStreamD xs)
35823614-- Compare with 'tap'.
35833615--
35843616-- @since 0.7.0
3617+{-# INLINE trace #-}
35853618trace :: (IsStream t, MonadAsync m) => (a -> m b) -> t m a -> t m a
35863619trace f = mapM (\x -> void (f x) >> return x)
35873620