on master help sign in
Import from as low level modules as possible
commitb10f17c4f8fdede0e4d33cb9037773eb43b1509e authorHarendra Kumar <harendra@composewell.com> dateThu 21 Jan 2021, 03:40 (6 years ago) parent515e76e
20 files changed+711−576
src/Streamly/Internal/Data/Fold.hs+2−56 src/Streamly/Internal/Data/IORef/Prim.hs+28−2 src/Streamly/Internal/Data/Stream/Ahead.hs+5−3 src/Streamly/Internal/Data/Stream/Async.hs+7−5 src/Streamly/Internal/Data/Stream/IsStream/Combinators.hs+1−1 src/Streamly/Internal/Data/Stream/IsStream/Common.hs+4−3 src/Streamly/Internal/Data/Stream/IsStream/Eliminate.hs+2−1 src/Streamly/Internal/Data/Stream/IsStream/Generate.hs+3−10 src/Streamly/Internal/Data/Stream/IsStream/Nesting.hs+4−2 src/Streamly/Internal/Data/Stream/IsStream/Transform.hs+4−4 src/Streamly/Internal/Data/Stream/Parallel.hs+141−74 src/Streamly/Internal/Data/Stream/SVar.hs+479−33 src/Streamly/Internal/Data/Stream/Serial.hs+6−3 src/Streamly/Internal/Data/Stream/StreamD/Eliminate.hs−1 src/Streamly/Internal/Data/Stream/StreamD/Generate.hs+2−37 src/Streamly/Internal/Data/Stream/StreamD/Transform.hs+2−117 src/Streamly/Internal/Data/Stream/StreamD/Type.hs+3−214 src/Streamly/Internal/Data/Stream/Zip.hs+10−6 src/Streamly/Internal/Data/Unfold.hs+3−1 streamly.cabal+5−3
src/Streamly/Internal/Data/Fold.hs+2−56
@@ -67,7 +67,6 @@ module Streamly.Internal.Data.Fold
6767 , toList
6868 , toListRevF -- experimental
6969 -- $toListRevF
70− , toParallelSVar
7170
7271 -- * Terminating Folds
7372 , drainN
@@ -89,7 +88,6 @@ module Streamly.Internal.Data.Fold
8988 , and
9089 , or
9190 -- , the
92− , toParallelSVarLimited
9391
9492 -- * Adapting
9593 , hoist
@@ -226,7 +224,6 @@ module Streamly.Internal.Data.Fold
226224where
227225
228226import Control.Monad (void, join)
229−import Control.Monad.IO.Class (MonadIO(..))
230227import Data.Bifunctor (first)
231228import Data.Functor.Identity (Identity(..))
232229import Data.Int (Int64)
@@ -235,15 +232,13 @@ import Data.Maybe (isJust, fromJust)
235232#if __GLASGOW_HASKELL__ < 804
236233import Data.Semigroup (Semigroup((<>)))
237234#endif
238−import Streamly.Internal.Data.Either.Strict (Either'(..))
235+import Streamly.Internal.Data.Either.Strict
236+ (Either'(..), fromLeft', fromRight', isLeft', isRight')
239237import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)
240238import Streamly.Internal.Data.Pipe.Types (Pipe (..), PipeState(..))
241239import Streamly.Internal.Data.Tuple.Strict (Tuple'(..), Tuple3'(..))
242−import Streamly.Internal.Data.Either.Strict
243− (fromLeft', fromRight', isLeft', isRight')
244240
245241import qualified Data.Map.Strict as Map
246−import qualified Streamly.Internal.Data.Fold.Types as FL
247242import qualified Streamly.Internal.Data.Pipe.Types as Pipe
248243import qualified Prelude
249244
@@ -254,7 +249,6 @@ import Prelude hiding
254249 , reverse, iterate, init, and, or, lookup, (!!)
255250 , scanl, scanl1, replicate, concatMap, mconcat, foldMap, unzip
256251 , span, splitAt, break, mapM)
257−import Streamly.Internal.Data.SVar
258252import Streamly.Internal.Data.Fold.Types
259253
260254------------------------------------------------------------------------------
@@ -2163,51 +2157,3 @@ concatSequence _f _p = undefined
21632157chunksBetween :: -- Monad m =>
21642158 Int -> Int -> Fold m a b -> Fold m b c -> Fold m a c
21652159chunksBetween _low _high _f1 _f2 = undefined
2166−
2167−------------------------------------------------------------------------------
2168−-- Fold to a Parallel SVar
2169−------------------------------------------------------------------------------
2170−
2171−{-# INLINE toParallelSVar #-}
2172−toParallelSVar :: MonadIO m => SVar t m a -> Maybe WorkerInfo -> Fold m a ()
2173−toParallelSVar svar winfo = Fold step initial extract
2174−
2175− where
2176−
2177− initial = return $ Partial ()
2178−
2179− -- XXX we can have a separate fold for unlimited buffer case to avoid a
2180− -- branch in the step here.
2181− step () x =
2182− liftIO $ do
2183− decrementBufferLimit svar
2184− void $ send svar (ChildYield x)
2185− return $ FL.Partial ()
2186−
2187− extract () = liftIO $ sendStop svar winfo
2188−
2189−{-# INLINE toParallelSVarLimited #-}
2190−toParallelSVarLimited :: MonadIO m
2191− => SVar t m a -> Maybe WorkerInfo -> Fold m a ()
2192−toParallelSVarLimited svar winfo = Fold step initial extract
2193−
2194− where
2195−
2196− initial = return $ Partial True
2197−
2198− step True x =
2199− liftIO $ do
2200− yieldLimitOk <- decrementYieldLimit svar
2201− if yieldLimitOk
2202− then do
2203− decrementBufferLimit svar
2204− void $ send svar (ChildYield x)
2205− return $ FL.Partial True
2206− else do
2207− cleanupSVarFromWorker svar
2208− sendStop svar winfo
2209− return $ FL.Done ()
2210− step False _ = return $ FL.Done ()
2211−
2212− extract True = liftIO $ sendStop svar winfo
2213− extract False = return ()
src/Streamly/Internal/Data/IORef/Prim.hs+28−2
@@ -1,7 +1,5 @@
11{-# LANGUAGE UnboxedTuples #-}
22
3−#include "inline.hs"
4−
53-- |
64-- Module : Streamly.Internal.Data.IORef.Prim
75-- Copyright : (c) 2019 Composewell Technologies
@@ -37,14 +35,22 @@ module Streamly.Internal.Data.IORef.Prim
3735
3836 -- * Read
3937 , readIORef
38+ , toStream
39+ , toStreamD
4040 )
4141where
4242
43+#include "inline.hs"
44+
45+import Control.Monad.IO.Class (MonadIO(..))
4346import Control.Monad.Primitive (primitive_)
4447import Data.Primitive.Types (Prim, sizeOf#, readByteArray#, writeByteArray#)
4548import GHC.Exts (MutableByteArray#, newByteArray#, RealWorld)
4649import GHC.IO (IO(..))
4750
51+import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
52+import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
53+
4854-- | An 'IORef' holds a single 'Prim' value.
4955data IORef a = IORef (MutableByteArray# RealWorld)
5056
@@ -82,3 +88,23 @@ modifyIORef' :: Prim a => IORef a -> (a -> a) -> IO ()
8288modifyIORef' (IORef arr#) g = primitive_ $ \s# ->
8389 case readByteArray# arr# 0# s# of
8490 (# s'#, a #) -> let a' = g a in a' `seq` writeByteArray# arr# 0# a' s'#
91+
92+-- | Generate a stream by continuously reading the IORef.
93+--
94+-- /Internal/
95+{-# INLINE_NORMAL toStreamD #-}
96+toStreamD :: (MonadIO m, Prim a) => IORef a -> D.Stream m a
97+toStreamD var = D.Stream step ()
98+
99+ where
100+
101+ {-# INLINE_LATE step #-}
102+ step _ () = liftIO (readIORef var) >>= \x -> return $ D.Yield x ()
103+
104+-- | Construct a stream by reading a 'Prim' 'IORef' repeatedly.
105+--
106+-- /Internal/
107+--
108+{-# INLINE toStream #-}
109+toStream :: (K.IsStream t, MonadIO m, Prim a) => IORef a -> t m a
110+toStream = D.fromStreamD . toStreamD
src/Streamly/Internal/Data/Stream/Ahead.hs+5−3
@@ -42,10 +42,12 @@ import qualified Data.Heap as H
4242
4343import Streamly.Internal.Data.Stream.SVar (fromSVar)
4444import Streamly.Internal.Data.SVar
45−import Streamly.Internal.Data.Stream.StreamK
45+import Streamly.Internal.Data.Stream.StreamK.Type
4646 (IsStream(..), Stream, mkStream, foldStream, foldStreamShared)
47−import qualified Streamly.Internal.Data.Stream.StreamK as K
48−import qualified Streamly.Internal.Data.Stream.StreamD as D
47+
48+import qualified Streamly.Internal.Data.Stream.StreamK as K (withLocal)
49+import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
50+import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
4951
5052import Prelude hiding (map)
5153
src/Streamly/Internal/Data/Stream/Async.hs+7−5
@@ -50,12 +50,14 @@ import Prelude hiding (map)
5050import qualified Data.Set as S
5151
5252import Streamly.Internal.Data.Atomics (atomicModifyIORefCAS)
53−import Streamly.Internal.Data.Stream.SVar (fromSVar)
53+import Streamly.Internal.Data.Stream.SVar (fromSVar, fromSVarD)
5454import Streamly.Internal.Data.SVar
55−import Streamly.Internal.Data.Stream.StreamK
55+import Streamly.Internal.Data.Stream.StreamK.Type
5656 (IsStream(..), Stream, mkStream, foldStream, adapt, foldStreamShared)
57−import qualified Streamly.Internal.Data.Stream.StreamK as K
58−import qualified Streamly.Internal.Data.Stream.StreamD as D
57+
58+import qualified Streamly.Internal.Data.Stream.StreamK as K (withLocal)
59+import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
60+import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
5961
6062#include "Instances.hs"
6163
@@ -504,7 +506,7 @@ mkAsyncD m = D.Stream step Nothing
504506
505507 step gst Nothing = do
506508 sv <- newAsyncVar gst (D.fromStreamD m)
507− return $ D.Skip $ Just $ D.fromSVar sv
509+ return $ D.Skip $ Just $ fromSVarD sv
508510
509511 step gst (Just (D.UnStream step1 st)) = do
510512 r <- step1 gst st
src/Streamly/Internal/Data/Stream/IsStream/Combinators.hs+1−1
@@ -27,7 +27,7 @@ import Control.Monad.IO.Class (MonadIO(liftIO))
2727import Data.Int (Int64)
2828
2929import Streamly.Internal.Data.SVar
30−import Streamly.Internal.Data.Stream.StreamK
30+import Streamly.Internal.Data.Stream.StreamK.Type
3131import Streamly.Internal.Data.Stream.Serial (SerialT)
3232
3333-------------------------------------------------------------------------------
src/Streamly/Internal/Data/Stream/IsStream/Common.hs+4−3
@@ -53,15 +53,16 @@ import Streamly.Internal.Data.Fold.Types (Fold (..))
5353import Streamly.Internal.Data.Stream.IsStream.Combinators (maxYields)
5454import Streamly.Internal.Data.Stream.Prelude (fromStreamS, toStreamS)
5555import Streamly.Internal.Data.Stream.Serial (SerialT)
56−import Streamly.Internal.Data.Stream.StreamD (fromStreamD, toStreamD)
57−import Streamly.Internal.Data.Stream.StreamK (IsStream())
56+import Streamly.Internal.Data.Stream.StreamD.Type (fromStreamD, toStreamD)
57+import Streamly.Internal.Data.Stream.StreamK.Type (IsStream())
5858import Streamly.Internal.Data.SVar (MonadAsync)
5959import Streamly.Internal.Data.Time.Units (AbsTime, RelTime64, addToAbsTime64)
6060
6161import qualified Streamly.Internal.Data.Stream.Parallel as Par
6262import qualified Streamly.Internal.Data.Stream.Prelude as P
6363import qualified Streamly.Internal.Data.Stream.Serial as Serial
64−import qualified Streamly.Internal.Data.Stream.StreamK as K
64+import qualified Streamly.Internal.Data.Stream.StreamK as K (repeatM)
65+import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
6566import qualified Streamly.Internal.Data.Stream.StreamD as D
6667#ifdef USE_STREAMK_ONLY
6768import qualified Streamly.Internal.Data.Stream.StreamK as S
src/Streamly/Internal/Data/Stream/IsStream/Eliminate.hs+2−1
@@ -164,6 +164,7 @@ import Streamly.Internal.Data.Stream.Serial (SerialT)
164164
165165import qualified Streamly.Internal.Data.Array.Storable.Foreign.Types as A
166166import qualified Streamly.Internal.Data.Fold as FL
167+import qualified Streamly.Internal.Data.Stream.Parallel as Par
167168import qualified Streamly.Internal.Data.Stream.Prelude as P
168169import qualified Streamly.Internal.Data.Stream.StreamD as D
169170import qualified Streamly.Internal.Data.Stream.StreamK as K
@@ -831,7 +832,7 @@ toPureRev = foldl' (flip K.cons) K.nil
831832{-# INLINE (|$.) #-}
832833(|$.) :: (IsStream t, MonadAsync m) => (t m a -> m b) -> (t m a -> m b)
833834-- (|$.) f = f . Async.mkAsync
834−(|$.) f = f . D.mkParallel
835+(|$.) f = f . Par.mkParallel
835836
836837infixr 0 |$.
837838
src/Streamly/Internal/Data/Stream/IsStream/Generate.hs+3−10
@@ -75,7 +75,6 @@ module Streamly.Internal.Data.Stream.IsStream.Generate
7575 , fromListM
7676 , K.fromFoldable
7777 , fromFoldableM
78− , fromPrimIORef
7978 , fromCallback
8079
8180 -- * Deprecated
@@ -102,8 +101,8 @@ import Streamly.Internal.Data.Stream.StreamK (IsStream((|:), consM))
102101import Streamly.Internal.Data.Stream.Serial (SerialT, WSerialT)
103102import Streamly.Internal.Data.Stream.Zip (ZipSerialM)
104103import Streamly.Internal.Data.Time.Units (AbsTime , RelTime64, addToAbsTime64)
105−import Streamly.Internal.Data.IORef.Prim (Prim, IORef)
106104
105+import qualified Streamly.Internal.Data.Stream.Parallel as Par
107106import qualified Streamly.Internal.Data.Stream.Prelude as P
108107import qualified Streamly.Internal.Data.Stream.Serial as Serial
109108import qualified Streamly.Internal.Data.Stream.StreamD.Generate as D
@@ -540,14 +539,8 @@ fromHandle h = go
540539 str <- liftIO $ IO.hGetLine h
541540 yld str go
542541
543−-- | Construct a stream by reading a 'Prim' 'IORef' repeatedly.
542+-- XXX This should perhaps be moved to Parallel
544543--
545−-- /Internal/
546−--
547−{-# INLINE fromPrimIORef #-}
548−fromPrimIORef :: (IsStream t, MonadIO m, Prim a) => IORef a -> t m a
549−fromPrimIORef = fromStreamD . D.fromPrimIORef
550−
551544-- | Takes a callback setter function and provides it with a callback. The
552545-- callback when invoked adds a value at the tail of the stream. Returns a
553546-- stream of values generated by the callback.
@@ -557,6 +550,6 @@ fromPrimIORef = fromStreamD . D.fromPrimIORef
557550{-# INLINE fromCallback #-}
558551fromCallback :: MonadAsync m => ((a -> m ()) -> m ()) -> SerialT m a
559552fromCallback setCallback = concatM $ do
560− (callback, stream) <- D.newCallbackStream
553+ (callback, stream) <- Par.newCallbackStream
561554 setCallback callback
562555 return stream
src/Streamly/Internal/Data/Stream/IsStream/Nesting.hs+4−2
@@ -546,8 +546,10 @@ mergeAsyncBy f = mergeAsyncByM (\a b -> return $ f a b)
546546{-# INLINE mergeAsyncByM #-}
547547mergeAsyncByM :: (IsStream t, MonadAsync m)
548548 => (a -> a -> m Ordering) -> t m a -> t m a -> t m a
549−mergeAsyncByM f m1 m2 = fromStreamD $
550− D.mergeByM f (D.mkParallelD $ toStreamD m1) (D.mkParallelD $ toStreamD m2)
549+mergeAsyncByM f m1 m2 =
550+ fromStreamD $
551+ let par = Par.mkParallelD . toStreamD
552+ in D.mergeByM f (par m1) (par m2)
551553
552554------------------------------------------------------------------------------
553555-- Combine N Streams - concatMap
src/Streamly/Internal/Data/Stream/IsStream/Transform.hs+4−4
@@ -155,8 +155,7 @@ module Streamly.Internal.Data.Stream.IsStream.Transform
155155 -- ** Concurrent Pipelines
156156 -- | Run streaming stages concurrently.
157157
158− , D.mkParallel
159− -- Par.mkParallel
158+ , Par.mkParallel
160159 , applyAsync
161160 , (|$)
162161 , (|&)
@@ -219,6 +218,7 @@ import Streamly.Internal.Data.SVar (MonadAsync, Rate(..))
219218import Streamly.Internal.Data.Time.Units ( TimeUnit64, AbsTime, RelTime64)
220219
221220import qualified Streamly.Internal.Data.Fold as FL
221+import qualified Streamly.Internal.Data.Stream.Parallel as Par
222222import qualified Streamly.Internal.Data.Stream.Prelude as P
223223import qualified Streamly.Internal.Data.Stream.Serial as Serial
224224import qualified Streamly.Internal.Data.Stream.StreamD as D
@@ -434,7 +434,7 @@ tapOffsetEvery offset n f xs =
434434-- /Internal/
435435{-# INLINE tapAsync #-}
436436tapAsync :: (IsStream t, MonadAsync m) => FL.Fold m a b -> t m a -> t m a
437−tapAsync f xs = D.fromStreamD $ D.tapAsync f (D.toStreamD xs)
437+tapAsync f xs = D.fromStreamD $ Par.tapAsyncF f (D.toStreamD xs)
438438
439439-- | @pollCounts predicate transform fold stream@ counts those elements in the
440440-- stream that pass the @predicate@. The resulting count stream is sent to
@@ -1233,7 +1233,7 @@ rights = fmap (fromRight undefined) . filter isRight
12331233{-# INLINE (|$) #-}
12341234(|$) :: (IsStream t, MonadAsync m) => (t m a -> t m b) -> (t m a -> t m b)
12351235-- (|$) f = f . Async.mkAsync
1236−(|$) f = f . D.mkParallel
1236+(|$) f = f . Par.mkParallel
12371237
12381238infixr 0 |$
12391239
src/Streamly/Internal/Data/Stream/Parallel.hs+141−74
@@ -26,10 +26,16 @@ module Streamly.Internal.Data.Stream.Parallel
2626
2727 -- * Evaluate Concurrently
2828 , mkParallel
29+ , mkParallelD
30+ , mkParallelK
2931
3032 -- * Tap Concurrently
3133 , tapAsync
34+ , tapAsyncF
3235 , distributeAsync_
36+
37+ -- * Callbacks
38+ , newCallbackStream
3339 )
3440where
3541
@@ -52,15 +58,17 @@ import Prelude hiding (map)
5258
5359import qualified Data.Set as Set
5460
55−import Streamly.Internal.Data.Stream.SVar
56− (fromSVar, fromProducer, fromConsumer, pushToFold)
61+import Streamly.Internal.Data.Fold.Types (Fold)
62+import Streamly.Internal.Data.Stream.StreamD.Type (Step(..))
5763import Streamly.Internal.Data.Stream.StreamK
5864 (IsStream(..), Stream, mkStream, foldStream, foldStreamShared, adapt)
5965
6066import Streamly.Internal.Data.SVar
6167
62−import qualified Streamly.Internal.Data.Stream.StreamK as K
63−import qualified Streamly.Internal.Data.Stream.StreamD as D
68+import qualified Streamly.Internal.Data.Stream.StreamK as K (withLocal)
69+import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
70+import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
71+import qualified Streamly.Internal.Data.Stream.SVar as SVar
6472
6573#include "Instances.hs"
6674
@@ -145,7 +153,7 @@ forkSVarPar ss m r = mkStream $ \st yld sng stp -> do
145153 writeIORef (svarStopBy sv) $ Set.elemAt 0 set
146154 _ -> return ()
147155 pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream r)
148− foldStream st yld sng stp (fromSVar sv)
156+ foldStream st yld sng stp (SVar.fromSVar sv)
149157
150158{-# INLINE joinStreamVarPar #-}
151159joinStreamVarPar :: (IsStream t, MonadAsync m)
@@ -281,84 +289,63 @@ parallelMin = joinStreamVarPar ParallelVar StopAny
281289-- Convert a stream to parallel
282290------------------------------------------------------------------------------
283291
284−-- | Generate a stream asynchronously to keep it buffered, lazily consume
285−-- from the buffer.
292+-- | Like 'mkParallel' but uses StreamK internally.
286293--
287294-- /Internal/
288295--
289−mkParallel :: (IsStream t, MonadAsync m) => t m a -> t m a
290−mkParallel m = mkStream $ \st yld sng stp -> do
296+mkParallelK :: (IsStream t, MonadAsync m) => t m a -> t m a
297+mkParallelK m = mkStream $ \st yld sng stp -> do
291298 sv <- newParallelVar StopNone (adaptState st)
292299 -- pushWorkerPar sv (runOne st{streamVar = Just sv} $ toStream m)
293− D.toSVarParallel st sv $ D.toStreamD m
294− foldStream st yld sng stp $ fromSVar sv
300+ SVar.toSVarParallel st sv $ D.toStreamD m
301+ foldStream st yld sng stp $ SVar.fromSVar sv
295302
296−------------------------------------------------------------------------------
297−-- Clone and distribute a stream in parallel
298−------------------------------------------------------------------------------
303+-------------------------------------------------------------------------------
304+-- Concurrent application and fold
305+-------------------------------------------------------------------------------
299306
300−-- Tap a stream and send the elements to the specified SVar in addition to
301−-- yielding them again.
307+-- | Same as 'mkParallel' but for StreamD stream.
302308--
303−-- XXX this could be written in StreamD style for better efficiency with fusion.
304−--
305−{-# INLINE teeToSVar #-}
306−teeToSVar :: (IsStream t, MonadAsync m) => SVar Stream m a -> t m a -> t m a
307−teeToSVar svr m = mkStream $ \st yld sng stp -> do
308− foldStreamShared st yld sng stp (go False m)
309−
309+{-# INLINE_NORMAL mkParallelD #-}
310+mkParallelD :: MonadAsync m => D.Stream m a -> D.Stream m a
311+mkParallelD m = D.Stream step Nothing
310312 where
311313
312− go False m0 = mkStream $ \st yld _ stp -> do
313− let drain = do
314− -- In general, a Stop event would come equipped with the result
315− -- of the fold. It is not used here but it would be useful in
316− -- applicative and distribute.
317− done <- fromConsumer svr
318− when (not done) $ do
319− liftIO $ withDiagMVar svr "teeToSVar: waiting to drain"
320− $ takeMVar (outputDoorBellFromConsumer svr)
321− drain
322−
323− stopFold = do
324− liftIO $ sendStop svr Nothing
325− -- drain/wait until a stop event arrives from the fold.
326− drain
327−
328− stop = stopFold >> stp
329− single a = do
330− done <- pushToFold svr a
331− yld a (go done (K.nilM stopFold))
332− yieldk a r = pushToFold svr a >>= \done -> yld a (go done r)
333− in foldStreamShared st yieldk single stop m0
334−
335− go True m0 = m0
336−
337−-- In case of folds the roles of worker and parent on an SVar are reversed. The
338−-- parent stream pushes values to an SVar instead of pulling from it and a
339−-- worker thread running the fold pulls from the SVar and folds the stream. We
340−-- keep a separate channel for pushing exceptions in the reverse direction i.e.
341−-- from the fold to the parent stream.
342−--
343−-- Note: If we terminate due to an exception, we do not actively terminate the
344−-- fold. It gets cleaned up by the GC.
345−
346−-- | Create an SVar with a fold consumer that will fold any elements sent to it
347−-- using the supplied fold function.
348−{-# INLINE newFoldSVar #-}
349−newFoldSVar :: (IsStream t, MonadAsync m)
350− => State Stream m a -> (t m a -> m b) -> m (SVar Stream m a)
351−newFoldSVar stt f = do
352− -- Buffer size for the SVar is derived from the current state
353− sv <- newParallelVar StopAny (adaptState stt)
354−
355− -- Add the producer thread-id to the SVar.
356− liftIO myThreadId >>= modifyThread sv
314+ step gst Nothing = do
315+ sv <- newParallelVar StopNone gst
316+ SVar.toSVarParallel gst sv m
317+ -- XXX use unfold instead?
318+ return $ Skip $ Just $ SVar.fromSVarD sv
319+
320+ step gst (Just (D.UnStream step1 st)) = do
321+ r <- step1 gst st
322+ return $ case r of
323+ Yield a s -> Yield a (Just $ D.Stream step1 s)
324+ Skip s -> Skip (Just $ D.Stream step1 s)
325+ Stop -> Stop
357326
358− void $ doFork (void $ f $ fromStream $ fromProducer sv)
359− (svarMrun sv)
360− (handleFoldException sv)
361− return sv
327+-- Compare with mkAsync. mkAsync uses an Async style SVar whereas this uses a
328+-- parallel style SVar for evaluation. Currently, parallel style cannot use
329+-- rate control whereas Async style can use rate control. In async style SVar
330+-- the worker thread terminates when the buffer is full whereas in Parallel
331+-- style it blocks.
332+--
333+-- | Make the stream producer and consumer run concurrently by introducing a
334+-- buffer between them. The producer thread evaluates the input stream until
335+-- the buffer fills, it blocks if the buffer is full until there is space in
336+-- the buffer. The consumer consumes the stream lazily from the buffer.
337+--
338+-- @mkParallel = D.fromStreamD . mkParallelD . D.toStreamD@
339+--
340+-- /Internal/
341+--
342+{-# INLINE_NORMAL mkParallel #-}
343+mkParallel :: (K.IsStream t, MonadAsync m) => t m a -> t m a
344+mkParallel = D.fromStreamD . mkParallelD . D.toStreamD
345+
346+-------------------------------------------------------------------------------
347+-- Concurrent tap
348+-------------------------------------------------------------------------------
362349
363350-- NOTE: In regular pull style streams, the consumer stream is pulling elements
364351-- from the SVar and we have several workers producing elements and pushing to
@@ -404,8 +391,60 @@ newFoldSVar stt f = do
404391{-# INLINE tapAsync #-}
405392tapAsync :: (IsStream t, MonadAsync m) => (t m a -> m b) -> t m a -> t m a
406393tapAsync f m = mkStream $ \st yld sng stp -> do
407− sv <- newFoldSVar st f
408− foldStreamShared st yld sng stp (teeToSVar sv m)
394+ sv <- SVar.newFoldSVar st f
395+ foldStreamShared st yld sng stp (SVar.teeToSVar sv m)
396+
397+data TapState fs st a = TapInit | Tapping !fs st | TapDone st
398+
399+-- | Like 'tapAsync' but uses a 'Fold' instead of a fold function.
400+--
401+{-# INLINE_NORMAL tapAsyncF #-}
402+tapAsyncF :: MonadAsync m => Fold m a b -> D.Stream m a -> D.Stream m a
403+tapAsyncF f (D.Stream step1 state1) = D.Stream step TapInit
404+ where
405+
406+ drainFold svr = do
407+ -- In general, a Stop event would come equipped with the result
408+ -- of the fold. It is not used here but it would be useful in
409+ -- applicative and distribute.
410+ done <- SVar.fromConsumer svr
411+ when (not done) $ do
412+ liftIO $ withDiagMVar svr "teeToSVar: waiting to drain"
413+ $ takeMVar (outputDoorBellFromConsumer svr)
414+ drainFold svr
415+
416+ stopFold svr = do
417+ liftIO $ sendStop svr Nothing
418+ -- drain/wait until a stop event arrives from the fold.
419+ drainFold svr
420+
421+ {-# INLINE_LATE step #-}
422+ step gst TapInit = do
423+ sv <- SVar.newFoldSVarF gst f
424+ return $ Skip (Tapping sv state1)
425+
426+ step gst (Tapping sv st) = do
427+ r <- step1 gst st
428+ case r of
429+ Yield a s -> do
430+ done <- SVar.pushToFold sv a
431+ if done
432+ then do
433+ -- XXX we do not need to wait synchronously here
434+ stopFold sv
435+ return $ Yield a (TapDone s)
436+ else return $ Yield a (Tapping sv s)
437+ Skip s -> return $ Skip (Tapping sv s)
438+ Stop -> do
439+ stopFold sv
440+ return Stop
441+
442+ step gst (TapDone st) = do
443+ r <- step1 gst st
444+ return $ case r of
445+ Yield a s -> Yield a (TapDone s)
446+ Skip s -> Skip (TapDone s)
447+ Stop -> Stop
409448
410449-- | Concurrently distribute a stream to a collection of fold functions,
411450-- discarding the outputs of the folds.
@@ -582,3 +621,31 @@ instance MonadAsync m => Monad (ParallelT m) where
582621------------------------------------------------------------------------------
583622
584623MONAD_COMMON_INSTANCES(ParallelT, MONADPARALLEL)
624+
625+-------------------------------------------------------------------------------
626+-- From callback
627+-------------------------------------------------------------------------------
628+
629+-- Note: we can use another API with two callbacks stop and yield if we want
630+-- the callback to be able to indicate end of stream.
631+--
632+-- | Generates a callback and a stream pair. The callback returned is used to
633+-- queue values to the stream. The stream is infinite, there is no way for the
634+-- callback to indicate that it is done now.
635+--
636+-- /Internal/
637+--
638+{-# INLINE_NORMAL newCallbackStream #-}
639+newCallbackStream :: (K.IsStream t, MonadAsync m) => m (a -> m (), t m a)
640+newCallbackStream = do
641+ sv <- newParallelVar StopNone defState
642+
643+ -- XXX Add our own thread-id to the SVar as we can not know the callback's
644+ -- thread-id and the callback is not run in a managed worker. We need to
645+ -- handle this better.
646+ liftIO myThreadId >>= modifyThread sv
647+
648+ let callback a = liftIO $ void $ send sv (ChildYield a)
649+ -- XXX we can return an SVar and then the consumer can unfold from the
650+ -- SVar?
651+ return (callback, D.fromStreamD (SVar.fromSVarD sv))
src/Streamly/Internal/Data/Stream/SVar.hs+479−33
@@ -18,26 +18,46 @@
1818--
1919--
2020module Streamly.Internal.Data.Stream.SVar
21− ( fromSVar
22− , fromStreamVar
23− , fromProducer
24− , fromConsumer
21+ (
22+ -- * Unfold streams from SVar
23+ -- $concurrentEval
24+ fromSVar
25+ , fromSVarD
26+ -- , fromStreamVar
27+
28+ -- * Fold streams to SVar
2529 , toSVar
30+ , toSVarParallel
31+
32+ -- * Concurrent folds
33+ -- $concurrentFolds
34+ , fromConsumer
2635 , pushToFold
36+ , teeToSVar
37+ , newFoldSVar
38+ , newFoldSVarF
2739 )
2840where
2941
42+#include "inline.hs"
43+
44+import Control.Concurrent (myThreadId, takeMVar)
3045import Control.Exception (fromException)
3146import Control.Monad (when, void)
3247import Control.Monad.Catch (throwM)
3348import Control.Monad.IO.Class (MonadIO(liftIO))
3449import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef)
3550import Data.Maybe (isNothing)
51+import Streamly.Internal.Data.Atomics (atomicModifyIORefCAS_)
52+import Streamly.Internal.Data.Fold.Types (Fold(..))
3653import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
3754import System.Mem (performMajorGC)
3855
56+import qualified Streamly.Internal.Data.Fold.Types as FL
57+import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
58+import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
59+
3960import Streamly.Internal.Data.SVar
40−import Streamly.Internal.Data.Stream.StreamK hiding (reverse)
4161
4262#if __GLASGOW_HASKELL__ < 810
4363#ifdef INSPECTION
@@ -49,15 +69,39 @@ import Test.Inspection (inspect, hasNoTypeClassesExcept)
4969#endif
5070#endif
5171
72+------------------------------------------------------------------------------
73+-- Generating streams from SVar
74+------------------------------------------------------------------------------
75+
76+-- $concurrentEval
77+--
78+-- Usually the SVar is used to concurrently evaluate multiple actions in a
79+-- stream using many worker threads that push the results to the SVar and a
80+-- single puller that pulls them from SVar generating the evaluated stream.
81+--
82+-- @
83+-- input stream
84+-- |
85+-- <-----------------|<--------worker
86+-- | exceptions |
87+-- output stream <------SVar<------worker
88+-- |
89+-- |<--------worker
90+--
91+-- @
92+--
93+-- The puller itself schedules the worker threads based on demand.
94+-- Exceptions are propagated from the worker threads to the puller.
95+
5296-- | Pull a stream from an SVar.
5397{-# NOINLINE fromStreamVar #-}
54−fromStreamVar :: MonadAsync m => SVar Stream m a -> Stream m a
55−fromStreamVar sv = MkStream $ \st yld sng stp -> do
98+fromStreamVar :: MonadAsync m => SVar K.Stream m a -> K.Stream m a
99+fromStreamVar sv = K.MkStream $ \st yld sng stp -> do
56100 list <- readOutputQ sv
57101 -- Reversing the output is important to guarantee that we process the
58102 -- outputs in the same order as they were generated by the constituent
59103 -- streams.
60− foldStream st yld sng stp $ processEvents $ reverse list
104+ K.foldStream st yld sng stp $ processEvents $ reverse list
61105
62106 where
63107
@@ -69,13 +113,13 @@ fromStreamVar sv = MkStream $ \st yld sng stp -> do
69113 stp
70114
71115 {-# INLINE processEvents #-}
72− processEvents [] = MkStream $ \st yld sng stp -> do
116+ processEvents [] = K.MkStream $ \st yld sng stp -> do
73117 done <- postProcess sv
74118 if done
75119 then allDone stp
76− else foldStream st yld sng stp $ fromStreamVar sv
120+ else K.foldStream st yld sng stp $ fromStreamVar sv
77121
78− processEvents (ev : es) = MkStream $ \st yld sng stp -> do
122+ processEvents (ev : es) = K.MkStream $ \st yld sng stp -> do
79123 let rest = processEvents es
80124 case ev of
81125 ChildYield a -> yld a rest
@@ -86,11 +130,11 @@ fromStreamVar sv = MkStream $ \st yld sng stp -> do
86130 stop <- shouldStop tid
87131 if stop
88132 then liftIO (cleanupSVar sv) >> allDone stp
89− else foldStream st yld sng stp rest
133+ else K.foldStream st yld sng stp rest
90134 Just ex ->
91135 case fromException ex of
92136 Just ThreadAbort ->
93− foldStream st yld sng stp rest
137+ K.foldStream st yld sng stp rest
94138 Nothing -> liftIO (cleanupSVar sv) >> throwM ex
95139 shouldStop tid =
96140 case svarStopStyle sv of
@@ -98,7 +142,7 @@ fromStreamVar sv = MkStream $ \st yld sng stp -> do
98142 StopAny -> return True
99143 StopBy -> do
100144 sid <- liftIO $ readIORef (svarStopBy sv)
101− return $ if tid == sid then True else False
145+ return $ tid == sid
102146
103147#if __GLASGOW_HASKELL__ < 810
104148#ifdef INSPECTION
@@ -120,17 +164,23 @@ inspect $ hasNoTypeClassesExcept 'fromStreamVar
120164#endif
121165#endif
122166
167+-- | Generate a stream from an SVar. An unevaluated stream can be pushed to an
168+-- SVar using 'toSVar'. As we pull a stream from the SVar the input stream
169+-- gets evaluated concurrently. The evaluation depends on the SVar style and
170+-- the configuration parameters e.g. using the maxBuffer/maxThreads
171+-- combinators.
172+--
123173{-# INLINE fromSVar #-}
124−fromSVar :: (MonadAsync m, IsStream t) => SVar Stream m a -> t m a
174+fromSVar :: (MonadAsync m, K.IsStream t) => SVar K.Stream m a -> t m a
125175fromSVar sv =
126− mkStream $ \st yld sng stp -> do
176+ K.mkStream $ \st yld sng stp -> do
127177 ref <- liftIO $ newIORef ()
128178 _ <- liftIO $ mkWeakIORef ref hook
129179 -- We pass a copy of sv to fromStreamVar, so that we know that it has
130180 -- no other references, when that copy gets garbage collected "ref"
131181 -- will get garbage collected and our hook will be called.
132− foldStreamShared st yld sng stp $
133− fromStream $ fromStreamVar sv{svarRef = Just ref}
182+ K.foldStreamShared st yld sng stp $
183+ K.fromStream $ fromStreamVar sv{svarRef = Just ref}
134184 where
135185
136186 hook = do
@@ -143,24 +193,281 @@ fromSVar sv =
143193 -- them to be cleaned up quickly.
144194 when (svarInspectMode sv) performMajorGC
145195
146−-- | Write a stream to an 'SVar' in a non-blocking manner. The stream can then
147−-- be read back from the SVar using 'fromSVar'.
148−toSVar :: (IsStream t, MonadAsync m) => SVar Stream m a -> t m a -> m ()
149−toSVar sv m = toStreamVar sv $ toStream m
196+data FromSVarState t m a =
197+ FromSVarInit
198+ | FromSVarRead (SVar t m a)
199+ | FromSVarLoop (SVar t m a) [ChildEvent a]
200+ | FromSVarDone (SVar t m a)
201+
202+-- | Like 'fromSVar' but generates a StreamD style stream instead of CPS.
203+--
204+{-# INLINE_NORMAL fromSVarD #-}
205+fromSVarD :: (MonadAsync m) => SVar t m a -> D.Stream m a
206+fromSVarD svar = D.Stream step FromSVarInit
207+ where
208+
209+ {-# INLINE_LATE step #-}
210+ step _ FromSVarInit = do
211+ ref <- liftIO $ newIORef ()
212+ _ <- liftIO $ mkWeakIORef ref hook
213+ -- when this copy of svar gets garbage collected "ref" will get
214+ -- garbage collected and our GC hook will be called.
215+ let sv = svar{svarRef = Just ref}
216+ return $ D.Skip (FromSVarRead sv)
217+
218+ where
219+
220+ {-# NOINLINE hook #-}
221+ hook = do
222+ when (svarInspectMode svar) $ do
223+ r <- liftIO $ readIORef (svarStopTime (svarStats svar))
224+ when (isNothing r) $
225+ printSVar svar "SVar Garbage Collected"
226+ cleanupSVar svar
227+ -- If there are any SVars referenced by this SVar a GC will prompt
228+ -- them to be cleaned up quickly.
229+ when (svarInspectMode svar) performMajorGC
230+
231+ step _ (FromSVarRead sv) = do
232+ list <- readOutputQ sv
233+ -- Reversing the output is important to guarantee that we process the
234+ -- outputs in the same order as they were generated by the constituent
235+ -- streams.
236+ return $ D.Skip $ FromSVarLoop sv (Prelude.reverse list)
237+
238+ step _ (FromSVarLoop sv []) = do
239+ done <- postProcess sv
240+ return $ D.Skip $ if done
241+ then FromSVarDone sv
242+ else FromSVarRead sv
243+
244+ step _ (FromSVarLoop sv (ev : es)) = do
245+ case ev of
246+ ChildYield a -> return $ D.Yield a (FromSVarLoop sv es)
247+ ChildStop tid e -> do
248+ accountThread sv tid
249+ case e of
250+ Nothing -> do
251+ stop <- shouldStop tid
252+ if stop
253+ then do
254+ liftIO (cleanupSVar sv)
255+ return $ D.Skip (FromSVarDone sv)
256+ else return $ D.Skip (FromSVarLoop sv es)
257+ Just ex ->
258+ case fromException ex of
259+ Just ThreadAbort ->
260+ return $ D.Skip (FromSVarLoop sv es)
261+ Nothing -> liftIO (cleanupSVar sv) >> throwM ex
262+ where
263+
264+ shouldStop tid =
265+ case svarStopStyle sv of
266+ StopNone -> return False
267+ StopAny -> return True
268+ StopBy -> do
269+ sid <- liftIO $ readIORef (svarStopBy sv)
270+ return $ tid == sid
271+
272+ step _ (FromSVarDone sv) = do
273+ when (svarInspectMode sv) $ do
274+ t <- liftIO $ getTime Monotonic
275+ liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)
276+ liftIO $ printSVar sv "SVar Done"
277+ return D.Stop
278+
279+------------------------------------------------------------------------------
280+-- Writing streams to SVar
281+------------------------------------------------------------------------------
282+
283+-- | Write a stream to an 'SVar' in a non-blocking manner. The stream is
284+-- evaluated concurrently as it is read back from the SVar using 'fromSVar'.
285+--
286+toSVar :: (K.IsStream t, MonadAsync m) => SVar K.Stream m a -> t m a -> m ()
287+toSVar sv m = toStreamVar sv $ K.toStream m
288+
289+-------------------------------------------------------------------------------
290+-- Concurrent application
291+-------------------------------------------------------------------------------
292+
293+-- | A fold to write a stream to an SVar. Unlike 'toSVar' this does not allow
294+-- for concurrent evaluation of the stream, as the fold receives the input one
295+-- element at a time, it just forwards the elements to the SVar. However, we
296+-- can safely execute the fold in an independent thread, the SVar can act as a
297+-- buffer decoupling the sender from the receiver. Also, we can have multiple
298+-- folds running concurrently pusing the streams to the SVar.
299+--
300+{-# INLINE write #-}
301+write :: MonadIO m => SVar t m a -> Maybe WorkerInfo -> Fold m a ()
302+write svar winfo = Fold step initial extract
303+
304+ where
305+
306+ initial = return $ FL.Partial ()
307+
308+ -- XXX we can have a separate fold for unlimited buffer case to avoid a
309+ -- branch in the step here.
310+ step () x =
311+ liftIO $ do
312+ decrementBufferLimit svar
313+ void $ send svar (ChildYield x)
314+ return $ FL.Partial ()
315+
316+ extract () = liftIO $ sendStop svar winfo
317+
318+-- | Like write, but applies a yield limit.
319+--
320+{-# INLINE writeLimited #-}
321+writeLimited :: MonadIO m
322+ => SVar t m a -> Maybe WorkerInfo -> Fold m a ()
323+writeLimited svar winfo = Fold step initial extract
324+
325+ where
326+
327+ initial = return $ FL.Partial True
328+
329+ step True x =
330+ liftIO $ do
331+ yieldLimitOk <- decrementYieldLimit svar
332+ if yieldLimitOk
333+ then do
334+ decrementBufferLimit svar
335+ void $ send svar (ChildYield x)
336+ return $ FL.Partial True
337+ else do
338+ cleanupSVarFromWorker svar
339+ sendStop svar winfo
340+ return $ FL.Done ()
341+ step False _ = return $ FL.Done ()
342+
343+ extract True = liftIO $ sendStop svar winfo
344+ extract False = return ()
345+
346+-- Using StreamD the worker stream producing code can fuse with the code to
347+-- queue output to the SVar giving some perf boost.
348+--
349+-- Note that StreamD can only be used in limited situations, specifically, we
350+-- cannot implement joinStreamVarPar using this.
351+--
352+-- XXX make sure that the SVar passed is a Parallel style SVar.
353+
354+-- | Fold the supplied stream to the SVar asynchronously using Parallel
355+-- concurrency style.
356+-- {-# INLINE_NORMAL toSVarParallel #-}
357+{-# INLINE toSVarParallel #-}
358+toSVarParallel :: MonadAsync m
359+ => State t m a -> SVar t m a -> D.Stream m a -> m ()
360+toSVarParallel st sv xs =
361+ if svarInspectMode sv
362+ then forkWithDiag
363+ else do
364+ tid <-
365+ case getYieldLimit st of
366+ Nothing -> doFork (work Nothing)
367+ (svarMrun sv)
368+ (handleChildException sv)
369+ Just _ -> doFork (workLim Nothing)
370+ (svarMrun sv)
371+ (handleChildException sv)
372+ modifyThread sv tid
373+
374+ where
375+
376+ {-# NOINLINE work #-}
377+ work info = D.foldOnce (write sv info) xs
378+
379+ {-# NOINLINE workLim #-}
380+ workLim info = D.foldOnce (writeLimited sv info) xs
381+
382+ {-# NOINLINE forkWithDiag #-}
383+ forkWithDiag = do
384+ -- We do not use workerCount in case of ParallelVar but still there is
385+ -- no harm in maintaining it correctly.
386+ liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n + 1
387+ recordMaxWorkers sv
388+ -- This allocation matters when significant number of workers are being
389+ -- sent. We allocate it only when needed. The overhead increases by 4x.
390+ winfo <-
391+ case yieldRateInfo sv of
392+ Nothing -> return Nothing
393+ Just _ -> liftIO $ do
394+ cntRef <- newIORef 0
395+ t <- getTime Monotonic
396+ lat <- newIORef (0, t)
397+ return $ Just WorkerInfo
398+ { workerYieldMax = 0
399+ , workerYieldCount = cntRef
400+ , workerLatencyStart = lat
401+ }
402+ tid <-
403+ case getYieldLimit st of
404+ Nothing -> doFork (work winfo)
405+ (svarMrun sv)
406+ (handleChildException sv)
407+ Just _ -> doFork (workLim winfo)
408+ (svarMrun sv)
409+ (handleChildException sv)
410+ modifyThread sv tid
411+
412+-------------------------------------------------------------------------------
413+-- Support for running folds concurrently
414+-------------------------------------------------------------------------------
415+
416+-- $concurrentFolds
417+--
418+-- To run folds concurrently, we need to decouple the fold execution from the
419+-- stream production. We use the SVar to do that, we have a single worker
420+-- pushing the stream elements to the SVar and on the consumer side a fold
421+-- driver pulls the values and folds them.
422+--
423+-- @
424+--
425+-- Fold worker <------SVar<------input stream
426+-- | exceptions |
427+-- --------------->
428+--
429+-- @
430+--
431+-- We need a channel for pushing exceptions from the fold worker to the stream
432+-- pusher. The stream may be pushed to multiple folds at the same time. For
433+-- that we need one SVar per fold:
434+--
435+-- @
436+--
437+-- Fold worker <------SVar<---
438+-- | |
439+-- Fold worker <------SVar<------input stream
440+-- | |
441+-- Fold worker <------SVar<---
442+--
443+-- @
444+--
445+-- Unlike in case concurrent stream evaluation, the puller does not drive the
446+-- scheduling and concurrent execution of the stream. The stream is simply
447+-- pushed by the stream producer at its own rate. The fold worker just pulls it
448+-- and folds it.
449+--
450+-- Note: If the stream pusher terminates due to an exception, we do not
451+-- actively terminate the fold. It gets cleaned up by the GC.
150452
151453-------------------------------------------------------------------------------
152454-- Process events received by a fold consumer from a stream producer
153455-------------------------------------------------------------------------------
154456
155−-- | Pull a stream from an SVar.
457+-- | Pull a stream from an SVar to fold it. Like 'fromSVar' except that it does
458+-- not drive the evaluation of the stream. It just pulls whatever is available
459+-- on the SVar. Also, when the fold stops it sends a notification to the stream
460+-- pusher/producer. No exceptions are expected to be propagated from the stream
461+-- pusher to the fold puller.
462+--
156463{-# NOINLINE fromProducer #-}
157−fromProducer :: forall m a . MonadAsync m => SVar Stream m a -> Stream m a
158−fromProducer sv = mkStream $ \st yld sng stp -> do
464+fromProducer :: forall m a . MonadAsync m => SVar K.Stream m a -> K.Stream m a
465+fromProducer sv = K.mkStream $ \st yld sng stp -> do
159466 list <- readOutputQ sv
160467 -- Reversing the output is important to guarantee that we process the
161468 -- outputs in the same order as they were generated by the constituent
162469 -- streams.
163− foldStream st yld sng stp $ processEvents $ reverse list
470+ K.foldStream st yld sng stp $ processEvents $ reverse list
164471
165472 where
166473
@@ -174,11 +481,11 @@ fromProducer sv = mkStream $ \st yld sng stp -> do
174481 stp
175482
176483 {-# INLINE processEvents #-}
177− processEvents :: [ChildEvent a] -> Stream m a
178− processEvents [] = mkStream $ \st yld sng stp -> do
179− foldStream st yld sng stp $ fromProducer sv
484+ processEvents :: [ChildEvent a] -> K.Stream m a
485+ processEvents [] = K.mkStream $ \st yld sng stp -> do
486+ K.foldStream st yld sng stp $ fromProducer sv
180487
181− processEvents (ev : es) = mkStream $ \_ yld _ stp -> do
488+ processEvents (ev : es) = K.mkStream $ \_ yld _ stp -> do
182489 let rest = processEvents es
183490 case ev of
184491 ChildYield a -> yld a rest
@@ -188,6 +495,80 @@ fromProducer sv = mkStream $ \st yld sng stp -> do
188495 Nothing -> allDone stp
189496 Just _ -> error "Bug: fromProducer: received exception"
190497
498+-- | Create a Fold style SVar that runs a supplied fold function as the
499+-- consumer. Any elements sent to the SVar are consumed by the supplied fold
500+-- function.
501+--
502+{-# INLINE newFoldSVar #-}
503+newFoldSVar :: (K.IsStream t, MonadAsync m)
504+ => State K.Stream m a -> (t m a -> m b) -> m (SVar K.Stream m a)
505+newFoldSVar stt f = do
506+ -- Buffer size for the SVar is derived from the current state
507+ sv <- newParallelVar StopAny (adaptState stt)
508+
509+ -- Add the producer thread-id to the SVar.
510+ liftIO myThreadId >>= modifyThread sv
511+
512+ void $ doFork (void $ f $ K.fromStream $ fromProducer sv)
513+ (svarMrun sv)
514+ (handleFoldException sv)
515+ return sv
516+
517+-- | Like 'fromProducer' but generates a StreamD style stream instead of
518+-- StreamK.
519+--
520+{-# INLINE_NORMAL fromProducerD #-}
521+fromProducerD :: (MonadAsync m) => SVar t m a -> D.Stream m a
522+fromProducerD svar = D.Stream step (FromSVarRead svar)
523+ where
524+
525+ {-# INLINE_LATE step #-}
526+ step _ (FromSVarRead sv) = do
527+ list <- readOutputQ sv
528+ -- Reversing the output is important to guarantee that we process the
529+ -- outputs in the same order as they were generated by the constituent
530+ -- streams.
531+ return $ D.Skip $ FromSVarLoop sv (Prelude.reverse list)
532+
533+ step _ (FromSVarLoop sv []) = return $ D.Skip $ FromSVarRead sv
534+ step _ (FromSVarLoop sv (ev : es)) = do
535+ case ev of
536+ ChildYield a -> return $ D.Yield a (FromSVarLoop sv es)
537+ ChildStop tid e -> do
538+ accountThread sv tid
539+ case e of
540+ Nothing -> do
541+ sendStopToProducer sv
542+ return $ D.Skip (FromSVarDone sv)
543+ Just _ -> error "Bug: fromProducer: received exception"
544+
545+ step _ (FromSVarDone sv) = do
546+ when (svarInspectMode sv) $ do
547+ t <- liftIO $ getTime Monotonic
548+ liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)
549+ liftIO $ printSVar sv "SVar Done"
550+ return D.Stop
551+
552+ step _ FromSVarInit = undefined
553+
554+-- | Like 'newFoldSVar' except that it uses a 'Fold' instead of a fold
555+-- function.
556+--
557+{-# INLINE newFoldSVarF #-}
558+newFoldSVarF :: MonadAsync m => State t m a -> Fold m a b -> m (SVar t m a)
559+newFoldSVarF stt f = do
560+ -- Buffer size for the SVar is derived from the current state
561+ sv <- newParallelVar StopAny (adaptState stt)
562+ -- Add the producer thread-id to the SVar.
563+ liftIO myThreadId >>= modifyThread sv
564+ void $ doFork (work sv) (svarMrun sv) (handleFoldException sv)
565+ return sv
566+
567+ where
568+
569+ {-# NOINLINE work #-}
570+ work sv = void $ D.foldOnce f $ fromProducerD sv
571+
191572-------------------------------------------------------------------------------
192573-- Process events received by the producer thread from the consumer side
193574-------------------------------------------------------------------------------
@@ -197,8 +578,13 @@ fromProducer sv = mkStream $ \st yld sng stp -> do
197578-- generate exception more than once and the producer can ignore those
198579-- exceptions or handle them and still keep driving the fold.
199580--
581+-- | Poll for events sent by the fold consumer to the stream pusher. The fold
582+-- consumer can send a "Stop" event or an exception. When a "Stop" is received
583+-- this function returns 'True'. If an exception is recieved then it throws the
584+-- exception.
585+--
200586{-# NOINLINE fromConsumer #-}
201−fromConsumer :: MonadAsync m => SVar Stream m a -> m Bool
587+fromConsumer :: MonadAsync m => SVar K.Stream m a -> m Bool
202588fromConsumer sv = do
203589 (list, _) <- liftIO $ readOutputQBasic (outputQueueFromConsumer sv)
204590 -- Reversing the output is important to guarantee that we process the
@@ -218,9 +604,13 @@ fromConsumer sv = do
218604 Just ex -> throwM ex
219605 ChildYield _ -> error "Bug: fromConsumer: invalid ChildYield event"
220606
221−-- push values to a fold worker via an SVar. Returns whether the fold is done.
607+-- | Push values from a stream to a fold worker via an SVar. Before pushing a
608+-- value to the SVar it polls for events received from the fold consumer. If a
609+-- stop event is received then it returns 'True' otherwise false. Propagates
610+-- exceptions received from the fold consumer.
611+--
222612{-# INLINE pushToFold #-}
223−pushToFold :: MonadAsync m => SVar Stream m a -> a -> m Bool
613+pushToFold :: MonadAsync m => SVar K.Stream m a -> a -> m Bool
224614pushToFold sv a = do
225615 -- Check for exceptions before decrement so that we do not
226616 -- block forever if the child already exited with an exception.
@@ -241,3 +631,59 @@ pushToFold sv a = do
241631 decrementBufferLimit sv
242632 void $ send sv (ChildYield a)
243633 return False
634+
635+------------------------------------------------------------------------------
636+-- Clone and distribute a stream in parallel
637+------------------------------------------------------------------------------
638+
639+-- XXX this could be written in StreamD style for better efficiency with fusion.
640+--
641+-- | Tap a stream and send the elements to the specified SVar in addition to
642+-- yielding them again. The SVar runs a fold consumer. Elements are tapped and
643+-- sent to the SVar until the fold finishes. Any exceptions from the fold
644+-- evaluation are propagated in the current thread.
645+--
646+-- @
647+--
648+-- ------input stream---------output stream----->
649+-- /|\\ |
650+-- exceptions | | input
651+-- | \\|/
652+-- ----SVar
653+-- |
654+-- Fold
655+--
656+-- @
657+--
658+{-# INLINE teeToSVar #-}
659+teeToSVar :: (K.IsStream t, MonadAsync m) =>
660+ SVar K.Stream m a -> t m a -> t m a
661+teeToSVar svr m = K.mkStream $ \st yld sng stp -> do
662+ K.foldStreamShared st yld sng stp (go False m)
663+
664+ where
665+
666+ go False m0 = K.mkStream $ \st yld _ stp -> do
667+ let drain = do
668+ -- In general, a Stop event would come equipped with the result
669+ -- of the fold. It is not used here but it would be useful in
670+ -- applicative and distribute.
671+ done <- fromConsumer svr
672+ when (not done) $ do
673+ liftIO $ withDiagMVar svr "teeToSVar: waiting to drain"
674+ $ takeMVar (outputDoorBellFromConsumer svr)
675+ drain
676+
677+ stopFold = do
678+ liftIO $ sendStop svr Nothing
679+ -- drain/wait until a stop event arrives from the fold.
680+ drain
681+
682+ stop = stopFold >> stp
683+ single a = do
684+ done <- pushToFold svr a
685+ yld a (go done (K.nilM stopFold))
686+ yieldk a r = pushToFold svr a >>= \done -> yld a (go done r)
687+ in K.foldStreamShared st yieldk single stop m0
688+
689+ go True m0 = m0
src/Streamly/Internal/Data/Stream/Serial.hs+6−3
@@ -64,13 +64,16 @@ import Text.Read
6464 ( Lexeme(Ident), lexP, parens, prec, readPrec, readListPrec
6565 , readListPrecDefault)
6666import Streamly.Internal.BaseCompat ((#.), errorWithoutStackTrace)
67−import Streamly.Internal.Data.Stream.StreamK
67+import Streamly.Internal.Data.Stream.StreamK.Type
6868 (IsStream(..), adapt, Stream, mkStream, foldStream)
6969import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)
7070
7171import qualified Streamly.Internal.Data.Stream.Prelude as P
72−import qualified Streamly.Internal.Data.Stream.StreamK as K
73−import qualified Streamly.Internal.Data.Stream.StreamD as D
72+ (cmpBy, foldl', foldr, eqBy, fromList, toList)
73+import qualified Streamly.Internal.Data.Stream.StreamK as K (withLocal)
74+import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
75+import qualified Streamly.Internal.Data.Stream.StreamD as D (unfoldrM)
76+import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
7477
7578import Prelude hiding (map, mapM, errorWithoutStackTrace)
7679
src/Streamly/Internal/Data/Stream/StreamD/Eliminate.hs−1
@@ -59,7 +59,6 @@ module Streamly.Internal.Data.Stream.StreamD.Eliminate
5959 -- * To containers
6060 , toList
6161 , toListRev
62− , toSVarParallel
6362
6463 -- * Multi-Stream Folds
6564 -- ** Comparisons
src/Streamly/Internal/Data/Stream/StreamD/Generate.hs+2−37
@@ -78,12 +78,6 @@ module Streamly.Internal.Data.Stream.StreamD.Generate
7878 -- Note: Direct style stream does not support @fromFoldable@.
7979 , fromList
8080 , fromListM
81− , fromPrimIORef
82− , fromProducer
83− , fromSVar
84−
85− -- * Callbacks
86− , newCallbackStream
8781
8882 -- * Conversions
8983 , fromStreamK
@@ -95,8 +89,8 @@ where
9589
9690#include "inline.hs"
9791
98−import Control.Concurrent (myThreadId, threadDelay)
99−import Control.Monad (void, forever)
92+import Control.Concurrent (threadDelay)
93+import Control.Monad (forever)
10094import Control.Monad.IO.Class (MonadIO(..))
10195import Data.Int (Int64)
10296import Streamly.Internal.Data.Time.Units (toRelTime64, RelTime64)
@@ -106,7 +100,6 @@ import Streamly.Internal.Data.Time.Units
106100import Streamly.Internal.Data.Unfold.Types (Unfold(..))
107101
108102import qualified Streamly.Internal.Data.IORef.Prim as Prim
109−import qualified Streamly.Internal.Data.Stream.StreamK as K
110103
111104import Prelude hiding (iterate, repeat, replicate, takeWhile)
112105import Streamly.Internal.Data.Stream.StreamD.Type
@@ -461,31 +454,3 @@ fromListM = Stream step
461454 {-# INLINE_LATE step #-}
462455 step _ (m:ms) = m >>= \x -> return $ Yield x ms
463456 step _ [] = return Stop
464−
465−-------------------------------------------------------------------------------
466−-- From callback
467−-------------------------------------------------------------------------------
468−
469−-- Note: we can use another API with two callbacks stop and yield if we want
470−-- the callback to be able to indicate end of stream.
471−--
472−-- | Generates a callback and a stream pair. The callback returned is used to
473−-- queue values to the stream. The stream is infinite, there is no way for the
474−-- callback to indicate that it is done now.
475−--
476−-- /Internal/
477−--
478−{-# INLINE_NORMAL newCallbackStream #-}
479−newCallbackStream :: (K.IsStream t, MonadAsync m) => m ((a -> m ()), t m a)
480−newCallbackStream = do
481− sv <- newParallelVar StopNone defState
482−
483− -- XXX Add our own thread-id to the SVar as we can not know the callback's
484− -- thread-id and the callback is not run in a managed worker. We need to
485− -- handle this better.
486− liftIO myThreadId >>= modifyThread sv
487−
488− let callback a = liftIO $ void $ send sv (ChildYield a)
489− -- XXX we can return an SVar and then the consumer can unfold from the
490− -- SVar?
491− return (callback, fromStreamD (fromSVar sv))
src/Streamly/Internal/Data/Stream/StreamD/Transform.hs+2−117
@@ -27,7 +27,6 @@ module Streamly.Internal.Data.Stream.StreamD.Transform
2727 -- * Mapping Effects
2828 , tap
2929 , tapOffsetEvery
30− , tapAsync
3130 , tapRate
3231 , pollCounts
3332
@@ -117,17 +116,12 @@ module Streamly.Internal.Data.Stream.StreamD.Transform
117116 -- * Maybe Streams
118117 , mapMaybe
119118 , mapMaybeM
120−
121− -- * Threading
122− -- | Make the stream concurrent
123− , mkParallel
124− , mkParallelD
125119 )
126120where
127121
128122#include "inline.hs"
129123
130−import Control.Concurrent (killThread, myThreadId, takeMVar, threadDelay)
124+import Control.Concurrent (killThread, threadDelay)
131125import Control.Exception (AsyncException)
132126import Control.Monad (void, when)
133127import Control.Monad.Catch (MonadCatch, throwM)
@@ -141,7 +135,6 @@ import qualified Control.Monad.Catch as MC
141135
142136import Streamly.Internal.Data.Fold.Types (Fold(..))
143137import Streamly.Internal.Data.Pipe.Types (Pipe(..), PipeState(..))
144−import Streamly.Internal.Data.Stream.SVar (fromConsumer, pushToFold)
145138import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
146139import Streamly.Internal.Data.Time.Units
147140 (TimeUnit64, toRelTime64, diffAbsTime64)
@@ -354,7 +347,7 @@ pollCounts predicate transf fld (Stream step state) = Stream step' Nothing
354347 countVar <- liftIO $ Prim.newIORef (0 :: Int)
355348 tid <- forkManaged
356349 $ void $ foldOnce fld
357− $ transf $ fromPrimIORef countVar
350+ $ transf $ Prim.toStreamD countVar
358351 return $ Skip (Just (countVar, tid, state))
359352
360353 step' gst (Just (countVar, tid, st)) = do
@@ -412,75 +405,6 @@ tapRate samplingRate action (Stream step state) = Stream step' Nothing
412405 liftIO $ killThread tid
413406 return Stop
414407
415−-------------------------------------------------------------------------------
416−-- Concurrent tap
417−-------------------------------------------------------------------------------
418−
419−-- | Create an SVar with a fold consumer that will fold any elements sent to it
420−-- using the supplied fold function.
421−{-# INLINE newFoldSVar #-}
422−newFoldSVar :: MonadAsync m => State t m a -> Fold m a b -> m (SVar t m a)
423−newFoldSVar stt f = do
424− -- Buffer size for the SVar is derived from the current state
425− sv <- newParallelVar StopAny (adaptState stt)
426− -- Add the producer thread-id to the SVar.
427− liftIO myThreadId >>= modifyThread sv
428− void $ doFork (work sv) (svarMrun sv) (handleFoldException sv)
429− return sv
430−
431− where
432−
433− {-# NOINLINE work #-}
434− work sv = void $ foldOnce f $ fromProducer sv
435−
436−{-# INLINE_NORMAL tapAsync #-}
437−tapAsync :: MonadAsync m => Fold m a b -> Stream m a -> Stream m a
438−tapAsync f (Stream step1 state1) = Stream step TapInit
439− where
440−
441− drainFold svr = do
442− -- In general, a Stop event would come equipped with the result
443− -- of the fold. It is not used here but it would be useful in
444− -- applicative and distribute.
445− done <- fromConsumer svr
446− when (not done) $ do
447− liftIO $ withDiagMVar svr "teeToSVar: waiting to drain"
448− $ takeMVar (outputDoorBellFromConsumer svr)
449− drainFold svr
450−
451− stopFold svr = do
452− liftIO $ sendStop svr Nothing
453− -- drain/wait until a stop event arrives from the fold.
454− drainFold svr
455−
456− {-# INLINE_LATE step #-}
457− step gst TapInit = do
458− sv <- newFoldSVar gst f
459− return $ Skip (Tapping sv state1)
460−
461− step gst (Tapping sv st) = do
462− r <- step1 gst st
463− case r of
464− Yield a s -> do
465− done <- pushToFold sv a
466− if done
467− then do
468− -- XXX we do not need to wait synchronously here
469− stopFold sv
470− return $ Yield a (TapDone s)
471− else return $ Yield a (Tapping sv s)
472− Skip s -> return $ Skip (Tapping sv s)
473− Stop -> do
474− stopFold sv
475− return $ Stop
476−
477− step gst (TapDone st) = do
478− r <- step1 gst st
479− return $ case r of
480− Yield a s -> Yield a (TapDone s)
481− Skip s -> Skip (TapDone s)
482− Stop -> Stop
483−
484408------------------------------------------------------------------------------
485409-- Scanning with a Fold
486410------------------------------------------------------------------------------
@@ -1300,42 +1224,3 @@ mapMaybe f = fmap fromJust . filter isJust . map f
13001224{-# INLINE_NORMAL mapMaybeM #-}
13011225mapMaybeM :: Monad m => (a -> m (Maybe b)) -> Stream m a -> Stream m b
13021226mapMaybeM f = fmap fromJust . filter isJust . mapM f
1303−
1304−-------------------------------------------------------------------------------
1305−-- Concurrent application and fold
1306−-------------------------------------------------------------------------------
1307−
1308−{-# INLINE_NORMAL mkParallelD #-}
1309−mkParallelD :: MonadAsync m => Stream m a -> Stream m a
1310−mkParallelD m = Stream step Nothing
1311− where
1312−
1313− step gst Nothing = do
1314− sv <- newParallelVar StopNone gst
1315− toSVarParallel gst sv m
1316− -- XXX use unfold instead?
1317− return $ Skip $ Just $ fromSVar sv
1318−
1319− step gst (Just (UnStream step1 st)) = do
1320− r <- step1 gst st
1321− return $ case r of
1322− Yield a s -> Yield a (Just $ Stream step1 s)
1323− Skip s -> Skip (Just $ Stream step1 s)
1324− Stop -> Stop
1325−
1326−-- Compare with mkAsync. mkAsync uses an Async style SVar whereas this uses a
1327−-- parallel style SVar for evaluation. Currently, parallel style cannot use
1328−-- rate control whereas Async style can use rate control. In async style SVar
1329−-- the worker thread terminates when the buffer is full whereas in Parallel
1330−-- style it blocks.
1331−--
1332−-- | Make the stream producer and consumer run concurrently by introducing a
1333−-- buffer between them. The producer thread evaluates the input stream until
1334−-- the buffer fills, it blocks if the buffer is full until there is space in
1335−-- the buffer. The consumer consumes the stream lazily from the buffer.
1336−--
1337−-- /Internal/
1338−--
1339−{-# INLINE_NORMAL mkParallel #-}
1340−mkParallel :: (K.IsStream t, MonadAsync m) => t m a -> t m a
1341−mkParallel = fromStreamD . mkParallelD . toStreamD
src/Streamly/Internal/Data/Stream/StreamD/Type.hs+3−214
@@ -32,9 +32,6 @@ module Streamly.Internal.Data.Stream.StreamD.Type
3232
3333 -- * From Containers
3434 , fromList
35− , fromProducer
36− , fromPrimIORef
37− , fromSVar
3835
3936 -- * Conversions From/To
4037 , fromStreamK
@@ -60,7 +57,6 @@ module Streamly.Internal.Data.Stream.StreamD.Type
6057
6158 -- * To Containers
6259 , toList
63− , toSVarParallel
6460
6561 -- * Multi-stream folds
6662 , eqBy
@@ -84,29 +80,20 @@ module Streamly.Internal.Data.Stream.StreamD.Type
8480where
8581
8682import Control.Applicative (liftA2)
87−import Control.Exception (fromException)
8883import Control.Monad (when)
8984import Control.Monad.Catch (MonadThrow, throwM)
90−import Control.Monad.IO.Class (MonadIO(..))
9185import Control.Monad.Trans.Class (lift, MonadTrans)
92−import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef)
9386import Data.Functor.Identity (Identity(..))
94−import Data.Maybe (isNothing)
9587import Fusion.Plugin.Types (Fuse(..))
9688import GHC.Base (build)
9789import GHC.Types (SPEC(..))
9890import Prelude hiding (map, mapM, foldr, take, concatMap, takeWhile)
99−import System.Mem (performMajorGC)
10091
101−import Streamly.Internal.Data.Atomics (atomicModifyIORefCAS_)
102−import Streamly.Internal.Data.IORef.Prim (Prim)
103−import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
10492import Streamly.Internal.Data.Fold.Types (Fold(..), Fold2(..))
105−import Streamly.Internal.Data.SVar
93+import Streamly.Internal.Data.SVar (State, adaptState, defState)
10694
107−import qualified Streamly.Internal.Data.Fold as FL
108−import qualified Streamly.Internal.Data.Stream.StreamK as K
109−import qualified Streamly.Internal.Data.IORef.Prim as Prim
95+import qualified Streamly.Internal.Data.Fold.Types as FL
96+import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
11097
11198------------------------------------------------------------------------------
11299-- The direct style stream type
@@ -213,132 +200,6 @@ fromList = Stream step
213200 step _ (x:xs) = pure $ Yield x xs
214201 step _ [] = pure Stop
215202
216−data FromSVarState t m a =
217− FromSVarInit
218− | FromSVarRead (SVar t m a)
219− | FromSVarLoop (SVar t m a) [ChildEvent a]
220− | FromSVarDone (SVar t m a)
221−
222−{-# INLINE_NORMAL fromSVar #-}
223−fromSVar :: (MonadAsync m) => SVar t m a -> Stream m a
224−fromSVar svar = Stream step FromSVarInit
225− where
226−
227− {-# INLINE_LATE step #-}
228− step _ FromSVarInit = do
229− ref <- liftIO $ newIORef ()
230− _ <- liftIO $ mkWeakIORef ref hook
231− -- when this copy of svar gets garbage collected "ref" will get
232− -- garbage collected and our GC hook will be called.
233− let sv = svar{svarRef = Just ref}
234− return $ Skip (FromSVarRead sv)
235−
236− where
237−
238− {-# NOINLINE hook #-}
239− hook = do
240− when (svarInspectMode svar) $ do
241− r <- liftIO $ readIORef (svarStopTime (svarStats svar))
242− when (isNothing r) $
243− printSVar svar "SVar Garbage Collected"
244− cleanupSVar svar
245− -- If there are any SVars referenced by this SVar a GC will prompt
246− -- them to be cleaned up quickly.
247− when (svarInspectMode svar) performMajorGC
248−
249− step _ (FromSVarRead sv) = do
250− list <- readOutputQ sv
251− -- Reversing the output is important to guarantee that we process the
252− -- outputs in the same order as they were generated by the constituent
253− -- streams.
254− return $ Skip $ FromSVarLoop sv (Prelude.reverse list)
255−
256− step _ (FromSVarLoop sv []) = do
257− done <- postProcess sv
258− return $ Skip $ if done
259− then (FromSVarDone sv)
260− else (FromSVarRead sv)
261−
262− step _ (FromSVarLoop sv (ev : es)) = do
263− case ev of
264− ChildYield a -> return $ Yield a (FromSVarLoop sv es)
265− ChildStop tid e -> do
266− accountThread sv tid
267− case e of
268− Nothing -> do
269− stop <- shouldStop tid
270− if stop
271− then do
272− liftIO (cleanupSVar sv)
273− return $ Skip (FromSVarDone sv)
274− else return $ Skip (FromSVarLoop sv es)
275− Just ex ->
276− case fromException ex of
277− Just ThreadAbort ->
278− return $ Skip (FromSVarLoop sv es)
279− Nothing -> liftIO (cleanupSVar sv) >> throwM ex
280− where
281−
282− shouldStop tid =
283− case svarStopStyle sv of
284− StopNone -> return False
285− StopAny -> return True
286− StopBy -> do
287− sid <- liftIO $ readIORef (svarStopBy sv)
288− return $ if tid == sid then True else False
289−
290− step _ (FromSVarDone sv) = do
291− when (svarInspectMode sv) $ do
292− t <- liftIO $ getTime Monotonic
293− liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)
294− liftIO $ printSVar sv "SVar Done"
295− return Stop
296−
297−{-# INLINE_NORMAL fromPrimIORef #-}
298−fromPrimIORef :: (MonadIO m, Prim a) => Prim.IORef a -> Stream m a
299−fromPrimIORef var = Stream step ()
300− where
301− {-# INLINE_LATE step #-}
302− step _ () = liftIO (Prim.readIORef var) >>= \x -> return $ Yield x ()
303−
304−-------------------------------------------------------------------------------
305−-- Process events received by a fold consumer from a stream producer
306−-------------------------------------------------------------------------------
307−
308−{-# INLINE_NORMAL fromProducer #-}
309−fromProducer :: (MonadAsync m) => SVar t m a -> Stream m a
310−fromProducer svar = Stream step (FromSVarRead svar)
311− where
312−
313− {-# INLINE_LATE step #-}
314− step _ (FromSVarRead sv) = do
315− list <- readOutputQ sv
316− -- Reversing the output is important to guarantee that we process the
317− -- outputs in the same order as they were generated by the constituent
318− -- streams.
319− return $ Skip $ FromSVarLoop sv (Prelude.reverse list)
320−
321− step _ (FromSVarLoop sv []) = return $ Skip $ FromSVarRead sv
322− step _ (FromSVarLoop sv (ev : es)) = do
323− case ev of
324− ChildYield a -> return $ Yield a (FromSVarLoop sv es)
325− ChildStop tid e -> do
326− accountThread sv tid
327− case e of
328− Nothing -> do
329− sendStopToProducer sv
330− return $ Skip (FromSVarDone sv)
331− Just _ -> error "Bug: fromProducer: received exception"
332−
333− step _ (FromSVarDone sv) = do
334− when (svarInspectMode sv) $ do
335− t <- liftIO $ getTime Monotonic
336− liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)
337− liftIO $ printSVar sv "SVar Done"
338− return Stop
339−
340− step _ FromSVarInit = undefined
341−
342203------------------------------------------------------------------------------
343204-- Conversions From/To
344205------------------------------------------------------------------------------
@@ -576,78 +437,6 @@ toListFB c n (Stream step state) = go state
576437toListId :: Stream Identity a -> Identity [a]
577438toListId s = Identity $ build (\c n -> toListFB c n s)
578439
579−-------------------------------------------------------------------------------
580−-- Concurrent application and fold
581−-------------------------------------------------------------------------------
582−
583−-- XXX These functions should be moved to Stream/Parallel.hs
584−--
585−-- Using StreamD the worker stream producing code can fuse with the code to
586−-- queue output to the SVar giving some perf boost.
587−--
588−-- Note that StreamD can only be used in limited situations, specifically, we
589−-- cannot implement joinStreamVarPar using this.
590−--
591−-- XXX make sure that the SVar passed is a Parallel style SVar.
592−
593−-- | Fold the supplied stream to the SVar asynchronously using Parallel
594−-- concurrency style.
595−-- {-# INLINE_NORMAL toSVarParallel #-}
596−{-# INLINE toSVarParallel #-}
597−toSVarParallel :: MonadAsync m
598− => State t m a -> SVar t m a -> Stream m a -> m ()
599−toSVarParallel st sv xs =
600− if svarInspectMode sv
601− then forkWithDiag
602− else do
603− tid <-
604− case getYieldLimit st of
605− Nothing -> doFork (work Nothing)
606− (svarMrun sv)
607− (handleChildException sv)
608− Just _ -> doFork (workLim Nothing)
609− (svarMrun sv)
610− (handleChildException sv)
611− modifyThread sv tid
612−
613− where
614−
615− {-# NOINLINE work #-}
616− work info = (foldOnce (FL.toParallelSVar sv info) xs)
617−
618− {-# NOINLINE workLim #-}
619− workLim info = foldOnce (FL.toParallelSVarLimited sv info) xs
620−
621− {-# NOINLINE forkWithDiag #-}
622− forkWithDiag = do
623− -- We do not use workerCount in case of ParallelVar but still there is
624− -- no harm in maintaining it correctly.
625− liftIO $ atomicModifyIORefCAS_ (workerCount sv) $ \n -> n + 1
626− recordMaxWorkers sv
627− -- This allocation matters when significant number of workers are being
628− -- sent. We allocate it only when needed. The overhead increases by 4x.
629− winfo <-
630− case yieldRateInfo sv of
631− Nothing -> return Nothing
632− Just _ -> liftIO $ do
633− cntRef <- newIORef 0
634− t <- getTime Monotonic
635− lat <- newIORef (0, t)
636− return $ Just WorkerInfo
637− { workerYieldMax = 0
638− , workerYieldCount = cntRef
639− , workerLatencyStart = lat
640− }
641− tid <-
642− case getYieldLimit st of
643− Nothing -> doFork (work winfo)
644− (svarMrun sv)
645− (handleChildException sv)
646− Just _ -> doFork (workLim winfo)
647− (svarMrun sv)
648− (handleChildException sv)
649− modifyThread sv tid
650−
651440------------------------------------------------------------------------------
652441-- Multi-stream folds
653442------------------------------------------------------------------------------
src/Streamly/Internal/Data/Stream/Zip.hs+10−6
@@ -53,13 +53,17 @@ import Streamly.Internal.Data.Stream.StreamK (IsStream(..), Stream)
5353import Streamly.Internal.Data.Maybe.Strict (Maybe'(..), toMaybe)
5454import Streamly.Internal.Data.SVar (MonadAsync)
5555
56+import qualified Streamly.Internal.Data.Stream.Parallel as Par
5657import qualified Streamly.Internal.Data.Stream.Prelude as P
57−import qualified Streamly.Internal.Data.Stream.StreamK as K
58−import qualified Streamly.Internal.Data.Stream.StreamD as D
58+ (cmpBy, eqBy, foldl', foldr, fromList, toList, fromStreamS, toStreamS)
59+import qualified Streamly.Internal.Data.Stream.StreamK as K (repeat)
60+import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
61+import qualified Streamly.Internal.Data.Stream.StreamD as D (zipWithM)
62+import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
5963#ifdef USE_STREAMK_ONLY
60−import qualified Streamly.Internal.Data.Stream.StreamK as S
64+import qualified Streamly.Internal.Data.Stream.StreamK as S (zipWith, zipWithM)
6165#else
62−import qualified Streamly.Internal.Data.Stream.StreamD as S
66+import qualified Streamly.Internal.Data.Stream.StreamD as S (zipWith, zipWithM)
6367#endif
6468
6569import Prelude hiding (map, repeat, zipWith, errorWithoutStackTrace)
@@ -97,8 +101,8 @@ zipWith f m1 m2 = P.fromStreamS $ S.zipWith f (P.toStreamS m1) (P.toStreamS m2)
97101zipAsyncWithM :: (IsStream t, MonadAsync m)
98102 => (a -> b -> m c) -> t m a -> t m b -> t m c
99103zipAsyncWithM f m1 m2 = D.fromStreamD $
100− D.zipWithM f (D.mkParallelD $ D.toStreamD m1)
101− (D.mkParallelD $ D.toStreamD m2)
104+ D.zipWithM f (Par.mkParallelD $ D.toStreamD m1)
105+ (Par.mkParallelD $ D.toStreamD m2)
102106
103107-- | Like 'zipWith' but zips concurrently i.e. both the streams being zipped
104108-- are generated concurrently.
src/Streamly/Internal/Data/Unfold.hs+3−1
@@ -164,8 +164,10 @@ import qualified Prelude
164164import qualified Control.Monad.Catch as MC
165165import qualified Data.Tuple as Tuple
166166import qualified Streamly.Internal.Data.Fold.Types as FL
167−import qualified Streamly.Internal.Data.Stream.StreamK as K
167+import qualified Streamly.Internal.Data.Stream.StreamK as K (uncons)
168+import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
168169import qualified Streamly.Internal.Data.Stream.StreamD as D
170+ (unfold, newFinalizedIORef, runIORefFinalizer, withIORefFinalizer)
169171
170172import Streamly.Internal.Data.SVar
171173import Prelude
streamly.cabal+5−3
@@ -418,7 +418,6 @@ library
418418 , Streamly.Internal.Data.Tuple.Strict
419419 , Streamly.Internal.Data.Maybe.Strict
420420 , Streamly.Internal.Data.Either.Strict
421− , Streamly.Internal.Data.IORef.Prim
422421 , Streamly.Internal.Foreign.Malloc
423422 , Streamly.Internal.Data.Atomics
424423 , Streamly.Internal.Data.Time
@@ -429,15 +428,18 @@ library
429428 -- streamly-core-stream
430429 , Streamly.Internal.Data.SVar
431430 , Streamly.Internal.Data.Stream.StreamK.Type
431+ , Streamly.Internal.Data.Fold.Types
432432 , Streamly.Internal.Data.Stream.StreamD.Type
433433 , Streamly.Internal.Data.Stream.StreamDK.Type
434434 , Streamly.Internal.Data.Unfold.Types
435− , Streamly.Internal.Data.Fold.Types
436435 , Streamly.Internal.Data.Sink.Types
437436 , Streamly.Internal.Data.Parser.ParserK.Types
438437 , Streamly.Internal.Data.Parser.ParserD.Types
439438 , Streamly.Internal.Data.Pipe.Types
440439
440+ -- Unboxed IORef
441+ , Streamly.Internal.Data.IORef.Prim
442+
441443 -- streamly-core-array
442444 -- May depend on streamly-core-stream
443445 , Streamly.Internal.Data.Array.Storable.Foreign.Mut.Types
@@ -477,13 +479,13 @@ library
477479 , Streamly.Internal.Data.Stream.Parallel
478480 , Streamly.Internal.Data.Stream.Ahead
479481 , Streamly.Internal.Data.Stream.Zip
482+ , Streamly.Internal.Data.Stream.IsStream.Combinators
480483 , Streamly.Internal.Data.Stream.IsStream.Common
481484 , Streamly.Internal.Data.Stream.IsStream.Types
482485 , Streamly.Internal.Data.Stream.IsStream.Enumeration
483486 , Streamly.Internal.Data.Stream.IsStream.Generate
484487 , Streamly.Internal.Data.Stream.IsStream.Eliminate
485488 , Streamly.Internal.Data.Stream.IsStream.Transform
486− , Streamly.Internal.Data.Stream.IsStream.Combinators
487489 , Streamly.Internal.Data.Stream.IsStream.Nesting
488490 , Streamly.Internal.Data.Stream.IsStream.Exception
489491 , Streamly.Internal.Data.Stream.IsStream.Lift