@@ -22,18 +22,8 @@
23module Asyncly.AsyncT
23module Asyncly.AsyncT
24 ( AsyncT (..)
24 ( AsyncT (..)
38 , makeAsync
28 , makeAsync
@@ -51,138 +41,26 @@ module Asyncly.AsyncT
53import Control.Applicative (Alternative (..))
43import Control.Applicative (Alternative (..))
54−import Control.Concurrent (ThreadId, forkIO, killThread,
56−import Control.Concurrent.STM (TChan, atomically, newTChan,
57− readTChan, tryReadTChan,
59−import Control.Exception (fromException,
61−import qualified Control.Exception.Lifted as EL
62import Control.Monad (ap, mzero, when)
44import Control.Monad (ap, mzero, when)
63import Control.Monad.Base (MonadBase (..), liftBaseDefault)
45import Control.Monad.Base (MonadBase (..), liftBaseDefault)
64import Control.Monad.Catch (MonadThrow, throwM)
46import Control.Monad.Catch (MonadThrow, throwM)
65import Control.Monad.State (MonadIO (..), MonadPlus (..),
47import Control.Monad.State (MonadIO (..), MonadPlus (..),
66 StateT (..), liftM, runStateT,
48 StateT (..), liftM, runStateT,
67− get, gets, modify)
49+ gets, modify)
68import Control.Monad.Trans.Class (MonadTrans (lift))
50import Control.Monad.Trans.Class (MonadTrans (lift))
69import Control.Monad.Trans.Control (ComposeSt, MonadBaseControl (..),
51import Control.Monad.Trans.Control (ComposeSt, MonadBaseControl (..),
70 MonadTransControl (..),
52 MonadTransControl (..),
71 defaultLiftBaseWith,
53 defaultLiftBaseWith,
72 defaultRestoreM, liftBaseWith)
54 defaultRestoreM, liftBaseWith)
73−import Data.Atomics (atomicModifyIORefCAS)
74−import Data.IORef (IORef, modifyIORef, newIORef,
75− readIORef, writeIORef)
76−import Data.List (delete)
77−import Data.Maybe (isJust)
79import Unsafe.Coerce (unsafeCoerce)
55import Unsafe.Coerce (unsafeCoerce)
81−import Control.Monad.Trans.Recorder (MonadRecorder(..), Paused(..),
57+import Control.Monad.Trans.Recorder (MonadRecorder(..))
82− Recording)
58+import Data.IORef (newIORef, readIORef, writeIORef)
83--import Debug.Trace (traceM)
59--import Debug.Trace (traceM)
85−newtype AsyncT m a = AsyncT { runAsyncT :: StateT Context m (Maybe a) }
61+import Asyncly.Threads
87−------------------------------------------------------------------------------
88−-- Parent child thread communication types
89−------------------------------------------------------------------------------
91−data ChildEvent = ChildDone ThreadId (Maybe SomeException)
93−------------------------------------------------------------------------------
94−-- State threaded around the monad
95−------------------------------------------------------------------------------
97−data Context = Context
99− ---------------------------------------------------------------------------
101− ---------------------------------------------------------------------------
104− continuations :: [Any]
106− -- When we suspend we save the logs in this IORef and exit.
107− , logsRef :: Maybe (IORef [Recording])
108− -- XXX this functionality can be in a separate layer?
109− , location :: Location
111− ---------------------------------------------------------------------------
112− -- Thread creation, communication and cleanup
113− ---------------------------------------------------------------------------
114− -- When a new thread is created the parent records it in the
115− -- 'pendingThreads' field and 'threadCredit' is decremented. When a child
116− -- thread is done it sends a done event to the parent on an unbounded
117− -- channel and goes away. Before starting a new thread the parent always
118− -- processes the unbounded channel to clear up the pending zombies. This
119− -- strategy ensures that the zombie queue will never grow more than the
120− -- number of running threads. Before exiting, the parent thread waits on
121− -- the channel until all its children are cleaned up.
122− ---------------------------------------------------------------------------
124− -- XXX setup a cleanup computation to run rather than passing all these
127− , childChannel :: TChan ChildEvent
128− -- ^ A channel for the immediate children to communicate to us when they
129− -- die. Each thread has its own dedicated channel for its children
131− -- We always track the child threads, otherwise the programmer needs to
132− -- worry about if some threads may remain hanging because they are stuck in
133− -- an infinite loop or a forever blocked IO. Also, it is not clear whether
134− -- there is a significant gain by disabling tracking. Instead of using
135− -- threadIds we can also keep a count instead, that will allow us to wait
136− -- for all children to drain but we cannot kill them.
138− -- We need these as IORefs since we need any changes to these to be
139− -- reflected back in the calling code that waits for the children.
141− , pendingThreads :: IORef [ThreadId]
142− -- ^ Active immediate child threads spawned by this thread, modified only
143− -- by this thread, therefore atomic modification is not needed.
145− -- By default this limit is shared by the current tree of threads. But at
146− -- any point the 'threads' primitive can be used to start a new limit for
147− -- the computation under that primitive.
149− -- Shared across many threads, therefore atomic access is required.
150− -- This is incremented by the child thread itelf before it exits.
152− , threadCredit :: IORef Int
153− -- ^ How many more threads are allowed to be created?
160− -> (a -> AsyncT m a)
161− -> Maybe (IORef [Recording])
163−initContext childChan pending credit finalizer lref =
164− Context { continuations = [unsafeCoerce finalizer]
167− , childChannel = childChan
168− , pendingThreads = pending
169− , threadCredit = credit }
171−------------------------------------------------------------------------------
63+newtype AsyncT m a = AsyncT { runAsyncT :: StateT Context m (Maybe a) }
172−-- Where is the computation running?
173−------------------------------------------------------------------------------
175−-- RemoteNode is used in the Alternative instance. We stop the computation when
176−-- the location is RemoteNode, we do not execute any alternatives.
178−data Location = Worker | WaitingParent | RemoteNode
181−getLocation :: Monad m => StateT Context m Location
182−getLocation = gets location
184−setLocation :: Monad m => Location -> StateT Context m ()
185−setLocation loc = modify $ \Context { .. } -> Context { location = loc, .. }
187------------------------------------------------------------------------------
65------------------------------------------------------------------------------
188-- Utilities
66-- Utilities
@@ -194,8 +72,6 @@ setLocation loc = modify $ \Context { .. } -> Context { location = loc, .. }
194dbg :: Monad m => a -> m ()
72dbg :: Monad m => a -> m ()
195dbg _ = return ()
73dbg _ = return ()
197−type MonadAsync m = (MonadIO m, MonadBaseControl IO m, MonadThrow m)
199------------------------------------------------------------------------------
75------------------------------------------------------------------------------
201------------------------------------------------------------------------------
77------------------------------------------------------------------------------
@@ -233,259 +109,6 @@ instance MonadAsync m => Applicative (AsyncT m) where
233 pure = return
109 pure = return
234 (<*>) = ap
110 (<*>) = ap
236−------------------------------------------------------------------------------
237−-- Pick up from where we left in the previous thread
238−------------------------------------------------------------------------------
240−{-# SPECIALIZE continue :: [Any] -> a -> StateT Context IO (Maybe a) #-}
241−continue :: Monad m => [Any] -> a -> StateT Context m (Maybe a)
246− modify $ \ctx -> ctx {continuations = []}
247− runAsyncT $ (unsafeCoerce f) x
249−continue (f:fs) x = do
250− modify $ \ctx -> ctx {continuations = fs}
251− y <- runAsyncT $ (unsafeCoerce f) x
254− Just z -> continue fs z
256−{-# SPECIALIZE runContext :: Context -> AsyncT IO a -> StateT Context IO () #-}
257−runContext :: MonadAsync m => Context -> AsyncT m a -> StateT Context m ()
258−runContext ctx action = do
259− _ <- lift $ runStateT m ctx
265− let conts = continuations ctx
266− x <- runAsyncT action
268− Just y -> continue conts y >> return ()
269− Nothing -> return ()
271−------------------------------------------------------------------------------
272−-- Thread Management (creation, reaping and killing)
273−------------------------------------------------------------------------------
275−-- XXX We are using unbounded channels so this will not block on writing to
276−-- pchan. We can use bounded channels to throttle the creation of threads based
277−-- on consumption rate.
278−processOneEvent :: MonadIO m
281− -> m ([ThreadId], Maybe SomeException)
282−processOneEvent (ChildDone tid e) pending = do
283− when (isJust e) $ liftIO $ mapM_ killThread pending
284− return (delete tid pending, Nothing)
286−drainChildren :: MonadIO m
289− -> m ([ThreadId], Maybe SomeException)
290−drainChildren cchan pending =
292− [] -> return (pending, Nothing)
294− ev <- liftIO $ atomically $ readTChan cchan
295− (p, e) <- processOneEvent ev pending
296− maybe (drainChildren cchan p) (const $ return (p, e)) e
298−waitForChildren :: MonadIO m => Context -> m (Maybe SomeException)
299−waitForChildren ctx = do
300− let pendingRef = pendingThreads ctx
301− pending <- liftIO $ readIORef pendingRef
302− (p, e) <- drainChildren (childChannel ctx) pending
303− liftIO $ writeIORef pendingRef p
306−tryReclaimZombies :: (MonadIO m, MonadThrow m) => Context -> m ()
307−tryReclaimZombies ctx = do
308− let cchan = childChannel ctx
309− pendingRef = pendingThreads ctx
311− pending <- liftIO $ readIORef pendingRef
315− mev <- liftIO $ atomically $ tryReadTChan cchan
317− Nothing -> return ()
319− (p, e) <- processOneEvent ev pending
320− liftIO $ writeIORef pendingRef p
321− maybe (return ()) throwM e
322− tryReclaimZombies ctx
324−{-# NOINLINE waitForOneEvent #-}
325−waitForOneEvent :: (MonadIO m, MonadThrow m) => Context -> m ()
326−waitForOneEvent ctx = do
327− -- XXX assert pending must have at least one element
328− -- assert that the tid is found in our list
329− let cchan = childChannel ctx
330− pendingRef = pendingThreads ctx
332− ev <- liftIO $ atomically $ readTChan cchan
333− pending <- liftIO $ readIORef pendingRef
334− (p, e) <- processOneEvent ev pending
335− liftIO $ writeIORef pendingRef p
336− maybe (return ()) throwM e
338−-- XXX this is not a real semaphore as it does not really block on wait,
339−-- instead it returns whether the value is zero or non-zero.
341−{-# INLINE waitQSemB #-}
342−waitQSemB :: IORef Int -> IO Bool
343−waitQSemB sem = atomicModifyIORefCAS sem $ \n ->
348−signalQSemB :: IORef Int -> IO ()
349−signalQSemB sem = atomicModifyIORefCAS sem $ \n -> (n + 1, ())
351−-- Allocation of threads
353−-- global thread limit
354−-- thread fan-out i.e. per thread children limit
355−-- min per thread allocation to avoid starvation
357−-- dynamic adjustment based on the cost, speed of consumption, cpu utilization
358−-- etc. We need to adjust the limits based on cost, throughput and latencies.
360−-- The event producer thread must put the work on a work-queue and the child
361−-- threads can pick it up from there. But if there is just one consumer then it
362−-- may not make sense to have a separate producer unless the producing cost is
366−{-# INLINE handleResult #-}
367−handleResult :: MonadIO m
368− => Context -> Either SomeException a -> m (Maybe SomeException)
369−handleResult ctx res =
372− case fromException e of
373− Just (Paused recording) ->
374− maybe (passException e)
375− (handleRecording recording) (logsRef ctx)
376− Nothing -> passException e
377− Right _ -> waitForChildren ctx
381− handleRecording recording ref = do
382− liftIO $ atomicModifyIORefCAS ref $ \logs -> (recording : logs, ())
385− passException e = do
386− liftIO $ readIORef (pendingThreads ctx) >>= mapM_ killThread
389−forkFinally1 :: MonadAsync m
392− -> (Either SomeException () -> IO ())
393− -> StateT Context m ThreadId
394−forkFinally1 ctx action preExit =
395− EL.mask $ \restore ->
396− liftBaseWith $ \runInIO -> forkIO $ do
397− _ <- runInIO $ EL.try (restore (runContext ctx action))
398− >>= liftIO . preExit
399− -- XXX restore state here
402−-- | Run a given context in a new thread.
404−forkContext :: (MonadBaseControl IO m, MonadIO m, MonadThrow m)
405− => AsyncT m a -> StateT Context m ()
406−forkContext action = do
408− childCtx <- childContext parentCtx
409− tid <- forkFinally1 childCtx action
410− (beforeExit childCtx (childChannel parentCtx))
411− updatePendingThreads parentCtx tid
415− updatePendingThreads :: (MonadIO m, MonadThrow m)
416− => Context -> ThreadId -> m ()
417− updatePendingThreads ctx tid = do
418− -- update the new thread before reclaiming zombies so that if it exited
419− -- already reclaim finds it in the list and does not panic.
420− liftIO $ modifyIORef (pendingThreads ctx) $ (\ts -> tid:ts)
421− tryReclaimZombies ctx
423− childContext ctx = do
424− pendingRef <- liftIO $ newIORef []
425− chan <- liftIO $ atomically newTChan
426− -- shares the threadCredit of the parent by default
428− { pendingThreads = pendingRef
429− , childChannel = chan
432− beforeExit ctx pchan res = do
433− r <- handleResult ctx res
434− signalQSemB (threadCredit ctx)
436− liftIO $ atomically $ writeTChan pchan (ChildDone tid r)
438−-- | Decide whether to resume the context in the same thread or a new thread
440−canFork :: Context -> IO Bool
442− gotCredit <- waitQSemB (threadCredit context)
445− pending <- readIORef $ pendingThreads context
449− -- XXX If we have unreclaimable child threads e.g.
450− -- infinite loop, this is going to deadlock us. We need
451− -- special handling for those cases. Add those to
452− -- unreclaimable list? And always execute them in an
453− -- async thread, cannot use sync for those.
455− waitForOneEvent context
459−-- Housekeeping, invoked after spawning of all child tasks is done and the
460−-- parent task needs to terminate. Either the task is fully done or we handed
461−-- it over to another thread, in any case the current thread is done.
463−spawningParentDone :: MonadIO m => StateT Context m (Maybe a)
464−spawningParentDone = do
466− when (loc /= RemoteNode) $ setLocation WaitingParent
469−-- | The task may be run in the same thread or in a new thread depending on the
470−-- forceAsync parameter and the current thread quota.
472−{-# SPECIALIZE runAsyncTask :: Bool -> AsyncT IO a -> StateT Context IO (Maybe a) #-}
473−{-# INLINE runAsyncTask #-}
474−runAsyncTask :: MonadAsync m
477− -> StateT Context m (Maybe a)
478−runAsyncTask forceAsync action = do
480− then forkContext action
483− can <- liftIO $ canFork context
485− False -> runContext context action
486− True -> forkContext action
489-- The current model is to start a new thread for every task. The input is
112-- The current model is to start a new thread for every task. The input is
490-- provided at the time of the creation and therefore no synchronization is
113-- provided at the time of the creation and therefore no synchronization is
491-- needed compared to a pool of threads contending to get the input from a
114-- needed compared to a pool of threads contending to get the input from a
@@ -521,29 +144,10 @@ runAsyncTask forceAsync action = do
521-- 'async' is not useful as the last action as the last action always runs in
144-- 'async' is not useful as the last action as the last action always runs in
522-- the current thread.
145-- the current thread.
523async :: MonadAsync m => AsyncT m a -> AsyncT m a
146async :: MonadAsync m => AsyncT m a -> AsyncT m a
524−async action = AsyncT $ runAsyncTask True action
147+async action = AsyncT $ runAsyncTask True (runAsyncT action)
526−-- XXX After setting the callback we should wait for the callback otherwise we
527−-- will just go away. We should use the same finalization mechanism that we use
530−-- | Makes an asyncly action from a callback setter function; can be used to
531−-- convert existing callback style code into asyncly style code. The first
532−-- parameter is a callback setter function. When we are called back,
533−-- 'makeAsync' behaves as if it was an async computation that just returned a
534−-- value of type 'a'. After the computation is done the result of the action
535−-- in second parameter is returned to the callback.
537makeAsync :: MonadAsync m => ((a -> m ()) -> m ()) -> AsyncT m a
149makeAsync :: MonadAsync m => ((a -> m ()) -> m ()) -> AsyncT m a
538−makeAsync cbsetter = AsyncT $ do
150+makeAsync = AsyncT . makeCont
539− -- XXX should we force fork a thread or keep running in the context of the
542− lift $ cbsetter $ \a -> do
543− let s = runAsyncTask False (return a)
544− _ <- runStateT s ctx
548------------------------------------------------------------------------------
152------------------------------------------------------------------------------
549-- Alternative
153-- Alternative
@@ -553,7 +157,7 @@ instance MonadAsync m => Alternative (AsyncT m) where
553 {-# SPECIALIZE instance Alternative (AsyncT IO) #-}
157 {-# SPECIALIZE instance Alternative (AsyncT IO) #-}
554 empty = AsyncT $ return Nothing
158 empty = AsyncT $ return Nothing
555 (<|>) m1 m2 = AsyncT $ do
159 (<|>) m1 m2 = AsyncT $ do
556− r <- runAsyncTask False m1
160+ r <- runAsyncTask False (runAsyncT m1)
557 maybe (runAsyncT m2) (return . Just) r
161 maybe (runAsyncT m2) (return . Just) r
559instance MonadAsync m => MonadPlus (AsyncT m) where
163instance MonadAsync m => MonadPlus (AsyncT m) where