@@ -95,6 +95,7 @@ module Streamly.Streams.StreamD
9595 , fromListM
9696 , fromStreamK
9797 , fromStreamD
98+ , fromSVar
9899
99100 -- * Elimination
100101 -- ** General Folds
@@ -136,6 +137,7 @@ module Streamly.Streams.StreamD
136137 , findM
137138 , find
138139 , (!!)
140+ , toSVarParallel
139141
140142 -- ** Flattening nested streams
141143 , concatMapM
@@ -260,6 +262,8 @@ module Streamly.Streams.StreamD
260262 , indexedR
261263 , zipWith
262264 , zipWithM
265+ , zipAsyncWith
266+ , zipAsyncWithM
263267
264268 -- * Comparisons
265269 , eqBy
@@ -292,19 +296,25 @@ module Streamly.Streams.StreamD
292296 , resumeDecodeUtf8Either
293297 , decodeUtf8Arrays
294298 , decodeUtf8ArraysLenient
299+
300+ -- * Concurrent Application
301+ , mkParallel
302+ , applyParallel
303+ , foldParallel
295304 )
296305where
297306
298−import Control.Exception (Exception, SomeException)
299−import Control.Monad (void)
300−import Control.Monad.Catch (MonadCatch)
307+import Control.Exception (Exception, SomeException, fromException)
308+import Control.Monad (void, when)
309+import Control.Monad.Catch (MonadCatch, throwM)
301310import Control.Monad.IO.Class (MonadIO(..))
302311import Control.Monad.Reader (ReaderT)
303312import Control.Monad.State.Strict (StateT)
304313import Control.Monad.Trans (MonadTrans(lift))
305314import Data.Bits (shiftR, shiftL, (.|.), (.&.))
306315import Data.Functor.Identity (Identity(..))
307−import Data.Maybe (fromJust, isJust)
316+import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef)
317+import Data.Maybe (fromJust, isJust, isNothing)
308318import Data.Word (Word32)
309319import Foreign.Ptr (Ptr)
310320import Foreign.Storable (Storable(..))
@@ -315,6 +325,7 @@ import GHC.Ptr (Ptr (..))
315325import GHC.Types (SPEC(..))
316326import GHC.Word (Word8(..))
317327import System.IO.Unsafe (unsafePerformIO)
328+import System.Mem (performMajorGC)
318329import Prelude
319330 hiding (map, mapM, mapM_, repeat, foldr, last, take, filter,
320331 takeWhile, drop, dropWhile, all, any, maximum, minimum, elem,
@@ -325,18 +336,22 @@ import Prelude
325336import qualified Control.Monad.Catch as MC
326337import qualified Control.Monad.Reader as Reader
327338import qualified Control.Monad.State.Strict as State
339+import qualified Prelude
328340
341+import Streamly.Internal.Data.Atomics (atomicModifyIORefCAS_)
329342import Streamly.Internal.Memory.Array.Types (Array(..))
330343import Streamly.Internal.Data.Fold.Types (Fold(..))
331344import Streamly.Internal.Data.Pipe.Types (Pipe(..), PipeState(..))
332−import Streamly.Internal.Data.SVar (MonadAsync, defState, adaptState)
345+import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
333346import Streamly.Internal.Data.Unfold.Types (Unfold(..))
334347import Streamly.Internal.Data.Strict (Tuple'(..))
335348
336349import Streamly.Internal.Data.Stream.StreamD.Type
350+import Streamly.Internal.Data.SVar
337351
338352import qualified Streamly.Internal.Data.Pipe.Types as Pipe
339353import qualified Streamly.Internal.Memory.Array.Types as A
354+import qualified Streamly.Internal.Data.Fold as FL
340355import qualified Streamly.Memory.Ring as RB
341356import qualified Streamly.Streams.StreamK as K
342357
@@ -648,10 +663,100 @@ fromListM = Stream step
648663toStreamD :: (K.IsStream t, Monad m) => t m a -> Stream m a
649664toStreamD = fromStreamK . K.toStream
650665
666+-------------------------------------------------------------------------------
667+-- Generation from SVar
668+-------------------------------------------------------------------------------
669+
670+data FromSVarState t m a =
671+ FromSVarInit
672+ | FromSVarRead (SVar t m a)
673+ | FromSVarLoop (SVar t m a) [ChildEvent a]
674+ | FromSVarDone (SVar t m a)
675+
676+{-# INLINE_NORMAL fromSVar #-}
677+fromSVar :: (MonadAsync m) => SVar t m a -> Stream m a
678+fromSVar svar = Stream step FromSVarInit
679+ where
680+
681+ {-# INLINE_LATE step #-}
682+ step _ FromSVarInit = do
683+ ref <- liftIO $ newIORef ()
684+ _ <- liftIO $ mkWeakIORef ref hook
685+ -- when this copy of svar gets garbage collected "ref" will get
686+ -- garbage collected and our GC hook will be called.
687+ let sv = svar{svarRef = Just ref}
688+ return $ Skip (FromSVarRead sv)
689+
690+ where
691+
692+ {-# NOINLINE hook #-}
693+ hook = do
694+ when (svarInspectMode svar) $ do
695+ r <- liftIO $ readIORef (svarStopTime (svarStats svar))
696+ when (isNothing r) $
697+ printSVar svar "SVar Garbage Collected"
698+ cleanupSVar svar
699+ -- If there are any SVars referenced by this SVar a GC will prompt
700+ -- them to be cleaned up quickly.
701+ when (svarInspectMode svar) performMajorGC
702+
703+ step _ (FromSVarRead sv) = do
704+ list <- readOutputQ sv
705+ -- Reversing the output is important to guarantee that we process the
706+ -- outputs in the same order as they were generated by the constituent
707+ -- streams.
708+ return $ Skip $ FromSVarLoop sv (Prelude.reverse list)
709+
710+ step _ (FromSVarLoop sv []) = do
711+ done <- postProcess sv
712+ return $ Skip $ if done
713+ then (FromSVarDone sv)
714+ else (FromSVarRead sv)
715+
716+ step _ (FromSVarLoop sv (ev : es)) = do
717+ case ev of
718+ ChildYield a -> return $ Yield a (FromSVarLoop sv es)
719+ ChildStop tid e -> do
720+ accountThread sv tid
721+ case e of
722+ Nothing -> do
723+ stop <- shouldStop tid
724+ if stop
725+ then do
726+ liftIO (cleanupSVar sv)
727+ return $ Skip (FromSVarDone sv)
728+ else return $ Skip (FromSVarLoop sv es)
729+ Just ex ->
730+ case fromException ex of
731+ Just ThreadAbort ->
732+ return $ Skip (FromSVarLoop sv es)
733+ Nothing -> liftIO (cleanupSVar sv) >> throwM ex
734+ where
735+
736+ shouldStop tid =
737+ case svarStopStyle sv of
738+ StopNone -> return False
739+ StopAny -> return True
740+ StopBy -> do
741+ sid <- liftIO $ readIORef (svarStopBy sv)
742+ return $ if tid == sid then True else False
743+
744+ step _ (FromSVarDone sv) = do
745+ when (svarInspectMode sv) $ do
746+ t <- liftIO $ getTime Monotonic
747+ liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)
748+ liftIO $ printSVar sv "SVar Done"
749+ return Stop
750+
751+-------------------------------------------------------------------------------
752+-- Hoisting the inner monad
753+-------------------------------------------------------------------------------
754+
651755{-# INLINE_NORMAL hoist #-}
652756hoist :: Monad n => (forall x. m x -> n x) -> Stream m a -> Stream n a
653757hoist f (Stream step state) = (Stream step' state)
654758 where
759+ {-# INLINE_LATE step' #-}
655760 step' gst st = do
656761 r <- f $ step (adaptState gst) st
657762 return $ case r of
@@ -659,7 +764,7 @@ hoist f (Stream step state) = (Stream step' state)
659764 Skip s -> Skip s
660765 Stop -> Stop
661766
662−{-# INLINE_NORMAL generally #-}
767+{-# INLINE generally #-}
663768generally :: Monad m => Stream Identity a -> Stream m a
664769generally = hoist (return . runIdentity)
665770
@@ -668,6 +773,7 @@ liftInner :: (Monad m, MonadTrans t, Monad (t m))
668773 => Stream m a -> Stream (t m) a
669774liftInner (Stream step state) = Stream step' state
670775 where
776+ {-# INLINE_LATE step' #-}
671777 step' gst st = do
672778 r <- lift $ step (adaptState gst) st
673779 return $ case r of
@@ -679,6 +785,7 @@ liftInner (Stream step state) = Stream step' state
679785runReaderT :: Monad m => s -> Stream (ReaderT s m) a -> Stream m a
680786runReaderT sval (Stream step state) = Stream step' state
681787 where
788+ {-# INLINE_LATE step' #-}
682789 step' gst st = do
683790 r <- Reader.runReaderT (step (adaptState gst) st) sval
684791 return $ case r of
@@ -690,6 +797,7 @@ runReaderT sval (Stream step state) = Stream step' state
690797evalStateT :: Monad m => s -> Stream (StateT s m) a -> Stream m a
691798evalStateT sval (Stream step state) = Stream step' (state, sval)
692799 where
800+ {-# INLINE_LATE step' #-}
693801 step' gst (st, sv) = do
694802 (r, sv') <- State.runStateT (step (adaptState gst) st) sv
695803 return $ case r of
@@ -701,6 +809,7 @@ evalStateT sval (Stream step state) = Stream step' (state, sval)
701809runStateT :: Monad m => s -> Stream (StateT s m) a -> Stream m (s, a)
702810runStateT sval (Stream step state) = Stream step' (state, sval)
703811 where
812+ {-# INLINE_LATE step' #-}
704813 step' gst (st, sv) = do
705814 (r, sv') <- State.runStateT (step (adaptState gst) st) sv
706815 return $ case r of
@@ -3426,6 +3535,37 @@ zipWithM f (Stream stepa ta) (Stream stepb tb) = Stream step (ta, tb, Nothing)
34263535zipWith :: Monad m => (a -> b -> c) -> Stream m a -> Stream m b -> Stream m c
34273536zipWith f = zipWithM (\a b -> return (f a b))
34283537
3538+-- | Like 'zipWithM' but zips concurrently i.e. both the streams being zipped
3539+-- are generated concurrently.
3540+--
3541+{-# INLINE_NORMAL zipAsyncWithM #-}
3542+zipAsyncWithM :: MonadAsync m
3543+ => (a -> b -> m c) -> Stream m a -> Stream m b -> Stream m c
3544+zipAsyncWithM f m1 m2 = Stream step Nothing
3545+
3546+ where
3547+
3548+ {-# INLINE_LATE step #-}
3549+ step gst Nothing = do
3550+ ma <- mkParallel (adaptState gst) m1
3551+ mb <- mkParallel (adaptState gst) m2
3552+ return $ Skip (Just $ zipWithM f ma mb)
3553+
3554+ step gst (Just (UnStream step1 st)) = do
3555+ r <- step1 gst st
3556+ return $ case r of
3557+ Yield a s -> Yield a (Just $ Stream step1 s)
3558+ Skip s -> Skip (Just $ Stream step1 s)
3559+ Stop -> Stop
3560+
3561+-- | Like 'zipWith' but zips concurrently i.e. both the streams being zipped
3562+-- are generated concurrently.
3563+--
3564+{-# INLINE zipAsyncWith #-}
3565+zipAsyncWith :: MonadAsync m
3566+ => (a -> b -> c) -> Stream m a -> Stream m b -> Stream m c
3567+zipAsyncWith f = zipAsyncWithM (\a b -> return (f a b))
3568+
34293569------------------------------------------------------------------------------
34303570-- Merging
34313571------------------------------------------------------------------------------
@@ -3983,3 +4123,106 @@ encodeUtf8 (Stream step state) = Stream step' (EncodeState state WNil)
39834123 Skip s -> Skip (EncodeState s WNil)
39844124 Stop -> Stop
39854125 step' _ (EncodeState s (WCons x xs)) = return $ Yield x (EncodeState s xs)
4126+
4127+-------------------------------------------------------------------------------
4128+-- Concurrent application and fold
4129+-------------------------------------------------------------------------------
4130+
4131+-- Using StreamD the worker stream producing code can fuse with the code to
4132+-- queue output to the SVar giving some perf boost.
4133+--
4134+-- Note that StreamD can only be used in limited situations, specifically, we
4135+-- cannot implement joinStreamVarPar using this.
4136+--
4137+-- XXX make sure that the SVar passed is a Parallel style SVar.
4138+
4139+-- | Fold the supplied stream to the SVar asynchronously using Parallel
4140+-- concurrency style.
4141+-- {-# INLINE_NORMAL toSVarParallel #-}
4142+{-# INLINE toSVarParallel #-}
4143+toSVarParallel :: MonadAsync m
4144+ => State t m a -> SVar t m a -> Stream m a -> m ()
4145+toSVarParallel st sv xs =
4146+ if svarInspectMode sv
4147+ then forkWithDiag
4148+ else do
4149+ tid <-
4150+ case getYieldLimit st of
4151+ Nothing -> doFork (work Nothing)
4152+ (svarMrun sv)
4153+ (handleChildException sv)
4154+ Just _ -> doFork (workLim Nothing)
4155+ (svarMrun sv)
4156+ (handleChildException sv)
4157+ modifyThread sv tid
4158+
4159+ where
4160+
4161+ {-# NOINLINE work #-}
4162+ work info = (runFold (FL.toParallelSVar sv info) xs)
4163+
4164+ {-# NOINLINE workLim #-}
4165+ workLim info = runFold (FL.toParallelSVarLimited sv info) xs
4166+
4167+ {-# NOINLINE forkWithDiag #-}
4168+ forkWithDiag = do
4169+ -- We do not use workerCount in case of ParallelVar but still there is
4170+ -- no harm in maintaining it correctly.
4171+ liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n + 1
4172+ recordMaxWorkers sv
4173+ -- This allocation matters when significant number of workers are being
4174+ -- sent. We allocate it only when needed. The overhead increases by 4x.
4175+ winfo <-
4176+ case yieldRateInfo sv of
4177+ Nothing -> return Nothing
4178+ Just _ -> liftIO $ do
4179+ cntRef <- newIORef 0
4180+ t <- getTime Monotonic
4181+ lat <- newIORef (0, t)
4182+ return $ Just WorkerInfo
4183+ { workerYieldMax = 0
4184+ , workerYieldCount = cntRef
4185+ , workerLatencyStart = lat
4186+ }
4187+ tid <-
4188+ case getYieldLimit st of
4189+ Nothing -> doFork (work winfo)
4190+ (svarMrun sv)
4191+ (handleChildException sv)
4192+ Just _ -> doFork (workLim winfo)
4193+ (svarMrun sv)
4194+ (handleChildException sv)
4195+ modifyThread sv tid
4196+
4197+{-# INLINE_NORMAL mkParallel #-}
4198+mkParallel :: MonadAsync m => State t m a -> Stream m a -> m (Stream m a)
4199+mkParallel st m = do
4200+ sv <- newParallelVar StopNone defState
4201+ toSVarParallel st sv m
4202+ return $ fromSVar sv
4203+
4204+{-# INLINE_NORMAL applyParallel #-}
4205+applyParallel :: MonadAsync m
4206+ => (Stream m a -> Stream m b) -> Stream m a -> Stream m b
4207+applyParallel f m = Stream step Nothing
4208+ where
4209+
4210+ {-# INLINE_LATE step #-}
4211+ step gst Nothing = do
4212+ sv <- newParallelVar StopNone (adaptState gst)
4213+ toSVarParallel (adaptState gst) sv m
4214+ return $ Skip $ Just $ f (fromSVar sv)
4215+
4216+ step gst (Just (UnStream step1 st)) = do
4217+ r <- step1 gst st
4218+ return $ case r of
4219+ Yield a s -> Yield a (Just $ Stream step1 s)
4220+ Skip s -> Skip (Just $ Stream step1 s)
4221+ Stop -> Stop
4222+
4223+{-# INLINE foldParallel #-}
4224+foldParallel :: (K.IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> m b
4225+foldParallel f m = do
4226+ sv <- newParallelVar StopNone defState
4227+ toSVarParallel defState sv (toStreamD m)
4228+ f $ fromStreamD $ fromSVar sv