1#ifdef __HADDOCK_VERSION__
6{-# LANGUAGE TemplateHaskell #-}
7{-# OPTIONS_GHC -fplugin Test.Inspection.Plugin #-}
11-- Module : Streamly.Internal.Data.Stream.Concurrent.Channel.Operations
12-- Copyright : (c) 2017 Composewell Technologies
13-- License : BSD-3-Clause
14-- Maintainer : streamly@composewell.com
15-- Stability : experimental
18module Streamly.Internal.Data.Stream.Concurrent.Channel.Operations
29import Control.Exception (fromException)
30import Control.Monad (when)
31import Control.Monad.Catch (throwM, MonadThrow)
32import Control.Monad.IO.Class (MonadIO(liftIO))
33import Data.IORef (newIORef, readIORef, mkWeakIORef, writeIORef)
34import Data.Maybe (isNothing)
35import Streamly.Internal.Control.Concurrent
36 (MonadAsync, MonadRunInIO, askRunInIO)
37import Streamly.Internal.Data.Stream.StreamD.Type (Stream)
38import Streamly.Internal.Data.Time.Clock (Clock(Monotonic), getTime)
39import System.Mem (performMajorGC)
41import qualified Streamly.Internal.Data.Stream as Stream
42import qualified Streamly.Internal.Data.Stream.StreamD.Type as D
43import qualified Streamly.Internal.Data.Stream.StreamK.Type as K
45import Streamly.Internal.Data.Stream.Concurrent.Channel.Dispatcher
46import Streamly.Internal.Data.Stream.Concurrent.Channel.Type
47import Streamly.Internal.Data.Stream.Channel.Types hiding (inspect)
49import Prelude hiding (map, concat, concatMap)
52import Control.Exception (Exception)
53import Control.Monad.Trans.Control (MonadBaseControl)
54import Data.Typeable (Typeable)
55import Test.Inspection (inspect, hasNoTypeClassesExcept)
58------------------------------------------------------------------------------
59-- Generating streams from a channel
60------------------------------------------------------------------------------
64-- Usually a channel is used to concurrently evaluate multiple actions in a
65-- stream using many worker threads that push the results to the channel and a
66-- single puller that pulls them from channel generating the evaluated stream.
71-- <-----------------|<--------worker
73-- output stream <---Channel<------worker
79-- The puller itself schedules the worker threads based on demand.
80-- Exceptions are propagated from the worker threads to the puller.
82-------------------------------------------------------------------------------
83-- Write a stream to a channel
84-------------------------------------------------------------------------------
86-- XXX Should be a Fold, singleton API could be called joinChannel, or the fold
87-- can be called joinChannel.
89-- | Write a stream to an 'SVar' in a non-blocking manner. The stream can then
90-- be read back from the SVar using 'fromSVar'.
91{-# INLINE toChannelK #-}
92toChannelK :: MonadRunInIO m => Channel m a -> K.StreamK m a -> m ()
95 liftIO $ enqueue sv False (runIn, m)
97-- INLINE for fromStreamK/toStreamK fusion
99-- | Send a stream to a given channel for concurrent evaluation.
100{-# INLINE toChannel #-}
101toChannel :: MonadRunInIO m => Channel m a -> Stream m a -> m ()
102toChannel chan = toChannelK chan . Stream.toStreamK
105-- | Send a stream of streams to a concurrent channel for evaluation.
106{-# INLINE joinChannel #-}
107joinChannel :: Channel m a -> Fold m (Stream m a) ()
108joinChannel = undefined
111-------------------------------------------------------------------------------
112-- Read a stream from a channel
113-------------------------------------------------------------------------------
115-- | Pull a stream from an SVar.
116{-# NOINLINE fromChannelRaw #-}
117fromChannelRaw :: (MonadIO m, MonadThrow m) => Channel m a -> K.StreamK m a
118fromChannelRaw sv = K.MkStream $ \st yld sng stp -> do
119 list <- readOutputQ sv
120 -- Reversing the output is important to guarantee that we process the
121 -- outputs in the same order as they were generated by the constituent
123 K.foldStream st yld sng stp $ processEvents $ reverse list
128 when (svarInspectMode sv) $ liftIO $ do
129 t <- getTime Monotonic
130 writeIORef (svarStopTime (svarStats sv)) (Just t)
131 printSVar (dumpSVar sv) "SVar Done"
133 {-# INLINE processEvents #-}
134 processEvents [] = K.MkStream $ \st yld sng stp -> do
135 done <- postProcess sv
138 else K.foldStream st yld sng stp $ fromChannelRaw sv
140 processEvents (ev : es) = K.MkStream $ \st yld sng stp -> do
141 let rest = processEvents es
143 ChildYield a -> yld a rest
144 ChildStopChannel -> do
145 liftIO (cleanupSVar (workerThreads sv))
147 ChildStop tid e -> do
150 Nothing -> K.foldStream st yld sng stp rest
152 case fromException ex of
154 -- We terminate the loop after sending
155 -- ThreadAbort to workers so we should never
156 -- get it unless it is thrown from inside a
157 -- worker thread or by someone else to our
159 error "processEvents: got ThreadAbort"
160 -- K.foldStream st yld sng stp rest
162 liftIO (cleanupSVar (workerThreads sv))
166-- Use of GHC constraint tuple (GHC.Classes.(%,,%)) in fromStreamVar leads to
167-- space leak because the tuple gets allocated in every recursive call and each
168-- allocation holds on to the previous allocation. This test is to make sure
169-- that we do not use the constraint tuple type class.
171inspect $ hasNoTypeClassesExcept 'fromChannelRaw
183-- XXX fromChannel Should not be called multiple times, we can add a
184-- safeguard for that. Or we can replicate the stream so that we can distribute
185-- it to multiple consumers. or should we use an explicit dupChannel for that?
187{-# INLINE fromChannelK #-}
188fromChannelK :: MonadAsync m => Channel m a -> K.StreamK m a
190 K.mkStream $ \st yld sng stp -> do
191 ref <- liftIO $ newIORef ()
192 _ <- liftIO $ mkWeakIORef ref hook
195 -- We pass a copy of sv to fromStreamVar, so that we know that it has
196 -- no other references, when that copy gets garbage collected "ref"
197 -- will get garbage collected and our hook will be called.
198 K.foldStreamShared st yld sng stp $
199 fromChannelRaw sv{svarRef = Just ref}
203 when (svarInspectMode sv) $ do
204 r <- liftIO $ readIORef (svarStopTime (svarStats sv))
206 printSVar (dumpSVar sv) "SVar Garbage Collected"
207 cleanupSVar (workerThreads sv)
208 -- If there are any SVars referenced by this SVar a GC will prompt
209 -- them to be cleaned up quickly.
210 when (svarInspectMode sv) performMajorGC
212-- | Generate a stream of results from concurrent evaluations from a channel.
213-- Evaluation of the channel does not start until this API is called. This API
214-- must not be called more than once on a channel. It kicks off evaluation of
215-- the channel by dispatching concurrent workers and ensures that as long there
216-- is work queued on the channel workers are dispatched proportional to the
217-- demand by the consumer.
219{-# INLINE fromChannel #-}
220fromChannel :: MonadAsync m => Channel m a -> Stream m a
221fromChannel = Stream.fromStreamK . fromChannelK
223data FromSVarState t m a =
225 | FromSVarRead (Channel m a)
226 | FromSVarLoop (Channel m a) [ChildEvent a]
227 | FromSVarDone (Channel m a)
229-- | Like 'fromSVar' but generates a StreamD style stream instead of CPS.
231{-# INLINE_NORMAL _fromChannelD #-}
232_fromChannelD :: (MonadIO m, MonadThrow m) => Channel m a -> D.Stream m a
233_fromChannelD svar = D.Stream step FromSVarInit
236 {-# INLINE_LATE step #-}
237 step _ FromSVarInit = do
238 ref <- liftIO $ newIORef ()
239 _ <- liftIO $ mkWeakIORef ref hook
240 -- when this copy of svar gets garbage collected "ref" will get
241 -- garbage collected and our GC hook will be called.
242 let sv = svar{svarRef = Just ref}
243 return $ D.Skip (FromSVarRead sv)
247 {-# NOINLINE hook #-}
249 when (svarInspectMode svar) $ do
250 r <- liftIO $ readIORef (svarStopTime (svarStats svar))
252 printSVar (dumpSVar svar) "SVar Garbage Collected"
253 cleanupSVar (workerThreads svar)
254 -- If there are any SVars referenced by this SVar a GC will prompt
255 -- them to be cleaned up quickly.
256 when (svarInspectMode svar) performMajorGC
258 step _ (FromSVarRead sv) = do
259 list <- readOutputQ sv
260 -- Reversing the output is important to guarantee that we process the
261 -- outputs in the same order as they were generated by the constituent
263 return $ D.Skip $ FromSVarLoop sv (Prelude.reverse list)
265 step _ (FromSVarLoop sv []) = do
266 done <- postProcess sv
267 return $ D.Skip $ if done
271 step _ (FromSVarLoop sv (ev : es)) = do
273 ChildYield a -> return $ D.Yield a (FromSVarLoop sv es)
274 ChildStopChannel -> do
275 liftIO (cleanupSVar (workerThreads sv))
276 return $ D.Skip (FromSVarDone sv)
277 ChildStop tid e -> do
280 Nothing -> return $ D.Skip (FromSVarLoop sv es)
282 case fromException ex of
284 return $ D.Skip (FromSVarLoop sv es)
286 liftIO (cleanupSVar (workerThreads sv))
289 step _ (FromSVarDone sv) = do
290 when (svarInspectMode sv) $ do
291 t <- liftIO $ getTime Monotonic
292 liftIO $ writeIORef (svarStopTime (svarStats sv)) (Just t)
293 liftIO $ printSVar (dumpSVar sv) "SVar Done"
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list