1{-# LANGUAGE ConstraintKinds #-}
2{-# LANGUAGE FlexibleContexts #-}
3{-# LANGUAGE FlexibleInstances #-}
4{-# LANGUAGE MultiParamTypeClasses #-}
5{-# LANGUAGE RankNTypes #-}
6{-# LANGUAGE UndecidableInstances #-} -- XXX
9-- Module : Asyncly.AsyncT
10-- Copyright : (c) 2017 Harendra Kumar
13-- Maintainer : harendra.kumar@gmail.com
14-- Stability : experimental
27import Control.Applicative (Alternative (..))
28import Control.Concurrent (ThreadId, forkIO, killThread,
30import Control.Concurrent.STM (TChan, atomically, newTChan,
31 readTChan, tryReadTChan,
33import Control.Exception (SomeException (..))
34import qualified Control.Exception.Lifted as EL
35import Control.Monad (ap, liftM, MonadPlus(..), mzero)
36--import Control.Monad.Base (MonadBase (..), liftBaseDefault)
37import Control.Monad.Catch (MonadThrow, throwM)
38import Control.Monad.IO.Class (MonadIO(..))
39import Control.Monad.Trans.Class (MonadTrans (lift))
40import Control.Monad.Trans.Control (MonadBaseControl, liftBaseWith)
42import Control.Monad.Trans.Control (ComposeSt, MonadBaseControl (..),
43 MonadTransControl (..),
45 defaultRestoreM, liftBaseWith)
47import Data.Functor (void)
48import Data.IORef (IORef, modifyIORef, newIORef,
49 writeIORef, readIORef)
50import Data.Maybe (maybe)
51import Data.Monoid ((<>))
53import qualified Data.Set as S
55import Control.Monad.Trans.Recorder (MonadRecorder(..))
58------------------------------------------------------------------------------
59-- Concurrency Semantics
60------------------------------------------------------------------------------
62-- Asyncly is essentially a concurrent list transformer. To concatenate the
63-- lists it provides three different ways of composing them. The Monoid
64-- instance concatenates lists in a non-concurrent in-order fashion. The
65-- Alternative instance concatenates list in a concurrent manner potentially
66-- running each action in parallel and therefore the order of the resulting
67-- items is not deterministic. Computations in Alternative composition may or
68-- may not run in parallel. Thirdly, the <||> operator provides a way to
69-- compose computations that are guaranteed to run in parallel. This provides a
70-- way to run infinite loops or long running computations without blocking
71-- or starving other computations.
73------------------------------------------------------------------------------
74-- Parent child thread communication types
75------------------------------------------------------------------------------
79 | ChildDone ThreadId a
80 | ChildStop ThreadId (Maybe SomeException)
81 | ChildCreate ThreadId
83------------------------------------------------------------------------------
84-- State threaded around the monad for thread management
85------------------------------------------------------------------------------
88 Context { childChannel :: TChan (ChildEvent a)
90 , runningThreads :: IORef (Set ThreadId)
91 , doneThreads :: IORef (Set ThreadId)
94-- The 'Maybe (AsyncT m a)' is redundant as we can use 'stop' value for the
95-- Nothing case, but it makes the fold using '<|>' 25% faster. Need to try
96-- again as the code has gone through many changes after this was tested.
97-- With the Maybe, 'stop' is required only to represent 'empty' in an
98-- Alternative composition.
100-- Currently the only state we need is the thread context, For generality we
101-- can parameterize the type with a state type 's'.
104 runAsyncT :: forall r.
105 Maybe (Context a) -- state
107 -> (a -> Maybe (Context a) -> Maybe (AsyncT m a) -> m r) -- yield
111type MonadAsync m = (MonadIO m, MonadBaseControl IO m, MonadThrow m)
113------------------------------------------------------------------------------
115------------------------------------------------------------------------------
117-- | Appends the results of two AsyncT computations in order.
118instance Monad m => Monoid (AsyncT m a) where
119 mempty = AsyncT $ \_ stp _ -> stp
120 mappend (AsyncT m1) m2 = AsyncT $ \ctx stp yld ->
121 m1 ctx ((runAsyncT m2) ctx stp yld) $ \a _ r ->
122 let yielder x = yld a ctx (Just x)
123 in maybe (yielder m2) (\rx -> yielder $ mappend rx m2) r
125-- We do not use bind for parallelism. That is, we do not start each iteration
126-- of the list in parallel. That will introduce too much uncontrolled
127-- parallelism. Instead we control parallelism using <|>, it is used for
128-- actions that 'may' be run in parallel. The library decides whether they will
129-- be started in parallel. This puts the parallel semantics in the hands of the
130-- user and operational semantics in the hands of the library. Ideally we would
131-- want parallelism to be completely hidden from the user, for example we can
132-- automatically decide where to start an action in parallel so that it is most
133-- efficient. We can decide whether a particular bind should fork a parallel
134-- thread or not based on the level of the bind in the tree. Maybe at some
135-- point we shall be able to do that?
137instance Monad m => Monad (AsyncT m) where
138 return a = AsyncT $ \ctx _ yld -> yld a ctx Nothing
140 -- | A thread context is valid only until the next bind. Upon a bind we
141 -- reset the context to Nothing.
142 AsyncT m >>= f = AsyncT $ \_ stp yld ->
143 m Nothing stp $ \a _ r ->
144 let run x = (runAsyncT x) Nothing stp yld
146 in maybe (run next) (\rx -> run $ next <> (rx >>= f)) r
148yield :: a -> AsyncT m a -> AsyncT m a
149yield a m = AsyncT $ \ctx _ yld -> yld a ctx (Just m)
151------------------------------------------------------------------------------
153------------------------------------------------------------------------------
155instance Monad m => Functor (AsyncT m) where
158------------------------------------------------------------------------------
160------------------------------------------------------------------------------
162instance Monad m => Applicative (AsyncT m) where
166------------------------------------------------------------------------------
168------------------------------------------------------------------------------
171doFork :: (MonadIO m, MonadBaseControl IO m)
173 -> (SomeException -> IO ())
175doFork action exHandler =
176 EL.mask $ \restore ->
177 liftBaseWith $ \runInIO -> forkIO $ do
178 -- XXX test the exception handling
179 _ <- runInIO $ EL.catch (restore action) (liftIO . exHandler)
180 -- XXX restore state here?
184push :: MonadIO m => Context a -> AsyncT m a -> m ()
185push context action = run (Just context) action
189 run ctx m = (runAsyncT m) ctx channelStop yielder
191 -- XXX make this a bounded channel so that we block if the previous value
192 -- is not consumed yet.
193 chan = childChannel context
194 channelYield a = liftIO $ atomically $ writeTChan chan (ChildYield a)
196 tid <- liftIO myThreadId
197 liftIO $ atomically $ writeTChan chan (ChildDone tid a)
199 tid <- liftIO myThreadId
200 liftIO $ atomically $ writeTChan chan (ChildStop tid Nothing)
202 done a = channelDone a
203 continue a ctx m = channelYield a >> run ctx m
204 yielder a ctx r = maybe (done a) (\rx -> continue a ctx rx) r
206-- If an exception occurs we push it to the channel so that it can handled by
207-- the parent. 'Paused' exceptions are to be collected at the top level.
208-- XXX Paused exceptions should only bubble up to the runRecorder computation
209{-# NOINLINE handleChildException #-}
210handleChildException :: TChan (ChildEvent a) -> SomeException -> IO ()
211handleChildException pchan e = do
213 atomically $ writeTChan pchan (ChildStop tid (Just e))
215{-# NOINLINE pushSideDispatch #-}
216pushSideDispatch :: MonadAsync m
217 => Context a -> AsyncT m a -> AsyncT m a -> AsyncT m a
218pushSideDispatch ctx m1 m2 = AsyncT $ \_ stp yld -> do
219 let chan = childChannel ctx
220 tid <- doFork (push ctx m1) (handleChildException chan)
221 liftIO $ atomically $ writeTChan chan (ChildCreate tid)
222 (runAsyncT m2) (Just ctx) stp yld
224-- This is a bit messy because ChildCreate and ChildDone events can arrive out
225-- of order in case of pushSideDispatch. Returns whether we are done draining
227{-# INLINE accountThread #-}
228accountThread :: MonadIO m
229 => ThreadId -> IORef (Set ThreadId) -> IORef (Set ThreadId) -> m Bool
230accountThread tid ref1 ref2 = liftIO $ do
234 if (S.member tid s1) then do
235 let r = S.delete tid s1
237 return $ S.null r && S.null s2
239 liftIO $ writeIORef ref2 (S.insert tid s2)
242{-# NOINLINE addThread #-}
243addThread :: MonadIO m => Context a -> ThreadId -> m Bool
244addThread ctx tid = accountThread tid (doneThreads ctx) (runningThreads ctx)
246{-# INLINE delThread #-}
247delThread :: MonadIO m => Context a -> ThreadId -> m Bool
248delThread ctx tid = accountThread tid (runningThreads ctx) (doneThreads ctx)
250handleException :: (MonadIO m, MonadThrow m)
251 => SomeException -> Context a -> ThreadId -> m r
252handleException e ctx tid = do
253 void (delThread ctx tid)
254 liftIO $ readIORef (runningThreads ctx) >>= mapM_ killThread
257-- XXX the TBQueue size should be proportional to the pending threads.
258-- We re-raise any exceptions received from the child threads, that way
259-- exceptions get propagated to the top level computation and can be handled
261{-# NOINLINE pullDispatch #-}
262pullDispatch :: (MonadIO m, MonadThrow m)
263 => Context a -> AsyncT m a -> Bool -> AsyncT m a
264pullDispatch ctx m dispatch = AsyncT $ \_ stp yld -> do
266 (runAsyncT m) (Just ctx) stp yld
268 res <- liftIO $ atomically $ tryReadTChan (childChannel ctx)
269 maybe (continue stp yld) (\ev -> handleEvent ev stp yld) res
273 {-# INLINE continue #-}
274 continue stp yld = (runAsyncT (pullDispatch ctx m True)) (Just ctx) stp yld
276 {-# INLINE handleEvent #-}
277 handleEvent ev stp yld =
279 ChildYield a -> yld a Nothing (Just (pullDispatch ctx m False))
280 ChildDone tid a -> do
281 void $ delThread ctx tid
282 yld a Nothing (Just (pullDispatch ctx m True))
283 ChildStop tid e -> do
285 Nothing -> void (delThread ctx tid) >> continue stp yld
286 Just x -> handleException x ctx tid
287 ChildCreate tid -> void (addThread ctx tid) >> continue stp yld
289-- | run m1 in a new thread, pushing its results to a pull channel and then run
290-- m2 in the parent thread. Any exceptions are also pushed to the channel.
291{-# INLINE pullSideDispatch #-}
292pullSideDispatch :: MonadAsync m
293 => Context a -> AsyncT m a -> AsyncT m a -> AsyncT m a
294pullSideDispatch ctx m1 m2 = AsyncT $ \_ stp yld -> do
295 let chan = childChannel ctx
296 tid <- doFork (push (ctx {pullSide = False}) m1)
297 (handleChildException chan)
298 liftIO $ modifyIORef (runningThreads ctx) $ (\s -> S.insert tid s)
299 (runAsyncT (pullDispatch ctx m2 False)) Nothing stp yld
301{-# NOINLINE pullDrain #-}
302pullDrain :: (MonadIO m, MonadThrow m) => Context a -> AsyncT m a
303pullDrain ctx = AsyncT $ \_ stp yld -> do
304 let yielder a = yld a Nothing (Just (pullDrain ctx))
305 continue = (runAsyncT (pullDrain ctx)) Nothing stp yld
307 res <- liftIO $ atomically $ readTChan (childChannel ctx)
309 ChildYield a -> yielder a
310 ChildDone tid a -> do
311 done <- delThread ctx tid
312 if done then (yld a Nothing Nothing) else (yielder a)
313 ChildStop tid e -> do
316 done <- delThread ctx tid
317 if done then stp else continue
318 Just x -> handleException x ctx tid
319 ChildCreate tid -> do
320 done <- addThread ctx tid
321 if done then stp else continue
323-- | Split the original computation in a pull-push pair. The original
324-- computation pulls from a Channel while m1 and m2 push to the channel.
325{-# NOINLINE pullFork #-}
326pullFork :: MonadAsync m => AsyncT m a -> AsyncT m a -> AsyncT m a
327pullFork m1 m2 = AsyncT $ \_ stp yld -> do
328 ctx <- liftIO $ newContext
329 let m = pullDispatch ctx ((m1 <|> m2) <> finalizer ctx) True
330 (runAsyncT m) Nothing stp yld
334 finalizer ctx = AsyncT $ \_ stp yld -> do
335 running <- liftIO $ readIORef (runningThreads ctx)
336 done <- liftIO $ readIORef (runningThreads ctx)
337 if (S.null running && S.null done) then stp
338 else (runAsyncT (pullDrain ctx)) Nothing stp yld
341 channel <- atomically newTChan
342 running <- liftIO $ newIORef S.empty
343 done <- liftIO $ newIORef S.empty
344 return $ Context { childChannel = channel
346 , runningThreads = running
350-- Concurrency rate control. Our objective is to create more threads on
351-- demand if the consumer is running faster than us. As soon as we
352-- encounter an Alternative composition we create a push pull pair of
353-- threads. We use a channel for communication between the consumer that
354-- pulls from the channel and the producer that pushes to the channel. The
355-- producer creates more threads if the channel becomes empty at times,
356-- that is the consumer is running faster. However this mechanism can be
357-- problematic if the initial production latency is high, we may end up
358-- creating too many threads. So we need some way to monitor and use the
361-- TBD For quick response we may have to increase the rate in the middle of
362-- a serially running computation. For that we can use a state flag to fork
363-- the rest of the computation at any point of time inside the Monad bind
364-- operation if the consumer is running at a faster speed.
366-- TBD the alternative composition allows us to dispatch a chunkSize of only 1.
367-- If we have to dispatch in arbitrary chunksizes we will need to compose the
368-- parallel actions using a data constructor instead so that we can divide it
369-- in chunks of arbitrary size before dispatch. When batching we can convert
370-- the structure into Alternative batches of Monoid composition. That will also
371-- allow us to dispatch more work to existing threads rather than creating new
374-- TBD for pure work (when we are not in the IO monad) we can divide it into
375-- just the number of CPUs.
377-- XXX to rate control left folded structrues we will have to return the
378-- residual work back to the dispatcher. It will also consume a lot of
379-- memory due to queueing of all the work before execution starts.
381instance MonadAsync m => Alternative (AsyncT m) where
384 -- Note: This is designed to scale for right associated compositions,
385 -- therefore always use a right fold for folding bigger structures.
387 m1 <|> m2 = AsyncT $ \ctx stp yld -> do
389 Nothing -> (runAsyncT (pullFork m1 m2)) ctx stp yld
392 (runAsyncT (pullSideDispatch c m1 m2)) ctx stp yld
394 -- for left associated compositions
395 (runAsyncT (pushSideDispatch c m1 m2)) ctx stp yld
397instance MonadAsync m => MonadPlus (AsyncT m) where
401------------------------------------------------------------------------------
403------------------------------------------------------------------------------
405instance (Num a, Monad (AsyncT m)) => Num (AsyncT m a) where
406 fromInteger = return . fromInteger
407 mf + mg = (+) <$> mf <*> mg
408 mf * mg = (*) <$> mf <*> mg
409 negate f = f >>= return . negate
410 abs f = f >>= return . abs
411 signum f = f >>= return . signum
413-------------------------------------------------------------------------------
415-------------------------------------------------------------------------------
417instance MonadTrans AsyncT where
418 lift mx = AsyncT $ \ctx _ yld -> mx >>= (\a -> (yld a ctx Nothing))
421instance (MonadBase b m, Monad m) => MonadBase b (AsyncT m) where
422 liftBase = liftBaseDefault
424-------------------------------------------------------------------------------
426-------------------------------------------------------------------------------
428instance MonadTransControl AsyncT where
429 type StT AsyncT a = (Maybe a, Context)
430 liftWith f = AsyncT $ StateT $ \s ->
431 liftM (\x -> (Just x, s))
432 (f $ \t -> runStateT (runAsyncT t) s)
433 restoreT = AsyncT . StateT . const
434 {-# INLINABLE liftWith #-}
435 {-# INLINABLE restoreT #-}
437instance (MonadBaseControl b m, Monad m) => MonadBaseControl b (AsyncT m) where
438 type StM (AsyncT m) a = ComposeSt AsyncT m a
439 liftBaseWith = defaultLiftBaseWith
440 restoreM = defaultRestoreM
441 {-# INLINABLE liftBaseWith #-}
442 {-# INLINABLE restoreM #-}
445------------------------------------------------------------------------------
446-- Standard transformer instances
447------------------------------------------------------------------------------
449instance MonadIO m => MonadIO (AsyncT m) where
450 liftIO = lift . liftIO
452instance MonadThrow m => MonadThrow (AsyncT m) where
453 throwM = lift . throwM
455------------------------------------------------------------------------------
457------------------------------------------------------------------------------
459instance MonadRecorder m => MonadRecorder (AsyncT m) where
460 getJournal = lift getJournal
461 putJournal = lift . putJournal
464------------------------------------------------------------------------------
466------------------------------------------------------------------------------
468-- Only those actions that are marked with 'async' are guaranteed to be
469-- asynchronous. Asyncly is free to run other actions synchronously or
470-- asynchronously and it should not matter to the semantics of the program, if
471-- it does then use async to force.
473-- Why not make async as default and ask the programmer to use a 'sync'
474-- primitive to force an action to run synchronously? But then we would not
475-- have the freedom to convert the async actions to sync dynamically. Note that
476-- it is safe to convert a sync action to async but vice-versa is not true.
477-- Converting an async to sync can cause change in semantics if the async
478-- action was an infinite loop for example.
480-- | In an 'Alternative' composition, force the action to run asynchronously.
481-- The @\<|\>@ operator implies "can be parallel", whereas 'async' implies
482-- "must be parallel". Note that outside an 'Alternative' composition 'async'
483-- is not useful and should not be used. Even in an 'Alternative' composition
484-- 'async' is not useful as the last action as the last action always runs in
485-- the current thread.
487async :: Monad m => AsyncT m a -> AsyncT m a
488async action = AsyncT $ runAsyncTask True (runAsyncT action)
490makeAsync :: Monad m => ((a -> m ()) -> m ()) -> AsyncT m a
491makeAsync = AsyncT . makeCont
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