on master help sign in
Alternative now forks automatically
f5cfeadHarendra Kumar
src/Asyncly.hs+13−1
@@ -8,6 +8,18 @@
8-- Portability : GHC
8-- Portability : GHC
9--
9--
10
10
11+-- | Asyncly allows writing and composing asynchronous event driven
12+-- applications conveniently. It can be thought of as a non-deterministic
13+-- continuation monad or a combination of ContT and ListT. It allows to capture
14+-- the state of the application at any point and trigger arbitrary number of
15+-- continuations from the capture point. Non-determinism or parallel
16+-- continuations are introduced using the <|> operator. It provides a
17+-- convenient way to implement and compose state machines without using
18+-- callbacks. An event in the state machine corresponds to a continuation.
19+-- There are no cycles in the state machine as each transition in the state
20+-- machine is an independent instance of the state machine. It is an immutable
21+-- state machine!
22+
11module Asyncly
23module Asyncly
12 ( AsyncT
24 ( AsyncT
13 , MonadAsync
25 , MonadAsync
@@ -31,5 +43,5 @@ module Asyncly
31 )
43 )
32where
44where
33
45
34−import Asyncly.AsyncT (AsyncT, Log, Loggable)
46+import Asyncly.AsyncT
35import Asyncly.Threads
47import Asyncly.Threads
src/Asyncly/AsyncT.hs+332−22
@@ -12,6 +12,7 @@
12
12
13module Asyncly.AsyncT
13module Asyncly.AsyncT
14 ( AsyncT (..)
14 ( AsyncT (..)
15+ , MonadAsync
15 , ChildEvent(..)
16 , ChildEvent(..)
16 , Loggable
17 , Loggable
17 , Log (..)
18 , Log (..)
@@ -22,16 +23,29 @@ module Asyncly.AsyncT
22 , Location(..)
23 , Location(..)
23 , getLocation
24 , getLocation
24 , setLocation
25 , setLocation
26+ , threads
27+ , waitForChildren
28+
29+ , async
30+ , makeAsync
31+ , each
25 , (<**)
32 , (<**)
26 , (**>)
33 , (**>)
34+
35+ -- internal
27 , dbg
36 , dbg
28 )
37 )
29where
38where
30
39
31import Control.Applicative (Alternative (..))
40import Control.Applicative (Alternative (..))
32−import Control.Concurrent (ThreadId)
41+import Control.Concurrent (ThreadId, forkIO, killThread,
42+ myThreadId)
33−import Control.Concurrent.STM (TChan)
43+import Control.Concurrent.STM (TChan, atomically, newTChan,
44+ readTChan, tryReadTChan,
45+ writeTChan)
34−import Control.Exception (SomeException)
46+import Control.Exception (SomeException (..))
47+import qualified Control.Exception.Lifted as EL
48+import Control.Monad (ap, mzero, when)
35import Control.Monad.Base (MonadBase (..), liftBaseDefault)
49import Control.Monad.Base (MonadBase (..), liftBaseDefault)
36import Control.Monad.Catch (MonadThrow, throwM)
50import Control.Monad.Catch (MonadThrow, throwM)
37import Control.Monad.State (MonadIO (..), MonadPlus (..),
51import Control.Monad.State (MonadIO (..), MonadPlus (..),
@@ -41,8 +55,12 @@ import Control.Monad.Trans.Class (MonadTrans (lift))
41import Control.Monad.Trans.Control (ComposeSt, MonadBaseControl (..),
55import Control.Monad.Trans.Control (ComposeSt, MonadBaseControl (..),
42 MonadTransControl (..),
56 MonadTransControl (..),
43 defaultLiftBaseWith,
57 defaultLiftBaseWith,
44− defaultRestoreM)
58+ defaultRestoreM, liftBaseWith)
45−import Data.IORef (IORef)
59+import Data.IORef (IORef, atomicModifyIORef,
60+ modifyIORef, newIORef, readIORef,
61+ writeIORef)
62+import Data.List (delete)
63+import Data.Maybe (isJust)
46import GHC.Prim (Any)
64import GHC.Prim (Any)
47import Unsafe.Coerce (unsafeCoerce)
65import Unsafe.Coerce (unsafeCoerce)
48
66
@@ -189,41 +207,333 @@ setLocation loc = modify $ \Context { .. } -> Context { location = loc, .. }
189dbg :: Monad m => a -> m ()
207dbg :: Monad m => a -> m ()
190dbg _ = return ()
208dbg _ = return ()
191
209
210+type MonadAsync m = (MonadIO m, MonadBaseControl IO m, MonadThrow m)
211+
212+------------------------------------------------------------------------------
213+-- Pick up from where we left in the previous thread
214+------------------------------------------------------------------------------
215+
216+-- | Run the continuation of an action
217+runContext :: MonadAsync m => Context -> AsyncT m a -> StateT Context m ()
218+runContext ctx action = do
219+ let s = runAsyncT $ action >>= unsafeCoerce (continuation ctx)
220+ _ <- lift $ runStateT s ctx
221+ return ()
222+
223+------------------------------------------------------------------------------
224+-- Thread Management (creation, reaping and killing)
225+------------------------------------------------------------------------------
226+
227+-- XXX We are using unbounded channels so this will not block on writing to
228+-- pchan. We can use bounded channels to throttle the creation of threads based
229+-- on consumption rate.
230+processOneEvent :: MonadIO m
231+ => ChildEvent
232+ -> [ThreadId]
233+ -> m ([ThreadId], Maybe SomeException)
234+processOneEvent (ChildDone tid e) pending = do
235+ when (isJust e) $ liftIO $ mapM_ killThread pending
236+ return (delete tid pending, Nothing)
237+
238+drainChildren :: MonadIO m
239+ => TChan ChildEvent
240+ -> [ThreadId]
241+ -> m ([ThreadId], Maybe SomeException)
242+drainChildren cchan pending =
243+ case pending of
244+ [] -> return (pending, Nothing)
245+ _ -> do
246+ ev <- liftIO $ atomically $ readTChan cchan
247+ (p, e) <- processOneEvent ev pending
248+ maybe (drainChildren cchan p) (const $ return (p, e)) e
249+
250+waitForChildren :: MonadIO m => Context -> m (Maybe SomeException)
251+waitForChildren ctx = do
252+ let pendingRef = pendingThreads ctx
253+ pending <- liftIO $ readIORef pendingRef
254+ (p, e) <- drainChildren (childChannel ctx) pending
255+ liftIO $ writeIORef pendingRef p
256+ return e
257+
258+tryReclaimZombies :: (MonadIO m, MonadThrow m) => Context -> m ()
259+tryReclaimZombies ctx = do
260+ let cchan = childChannel ctx
261+ pendingRef = pendingThreads ctx
262+
263+ pending <- liftIO $ readIORef pendingRef
264+ case pending of
265+ [] -> return ()
266+ _ -> do
267+ mev <- liftIO $ atomically $ tryReadTChan cchan
268+ case mev of
269+ Nothing -> return ()
270+ Just ev -> do
271+ (p, e) <- processOneEvent ev pending
272+ liftIO $ writeIORef pendingRef p
273+ maybe (return ()) throwM e
274+ tryReclaimZombies ctx
275+
276+waitForOneEvent :: (MonadIO m, MonadThrow m) => Context -> m ()
277+waitForOneEvent ctx = do
278+ -- XXX assert pending must have at least one element
279+ -- assert that the tid is found in our list
280+ let cchan = childChannel ctx
281+ pendingRef = pendingThreads ctx
282+
283+ ev <- liftIO $ atomically $ readTChan cchan
284+ pending <- liftIO $ readIORef pendingRef
285+ (p, e) <- processOneEvent ev pending
286+ liftIO $ writeIORef pendingRef p
287+ maybe (return ()) throwM e
288+
289+-- XXX this is not a real semaphore as it does not really block on wait,
290+-- instead it returns whether the value is zero or non-zero.
291+--
292+waitQSemB :: IORef Int -> IO Bool
293+waitQSemB sem = atomicModifyIORef sem $ \n ->
294+ if n > 0
295+ then (n - 1, True)
296+ else (n, False)
297+
298+signalQSemB :: IORef Int -> IO ()
299+signalQSemB sem = atomicModifyIORef sem $ \n -> (n + 1, ())
300+
301+-- Allocation of threads
302+--
303+-- global thread limit
304+-- thread fan-out i.e. per thread children limit
305+-- min per thread allocation to avoid starvation
306+--
307+-- dynamic adjustment based on the cost, speed of consumption, cpu utilization
308+-- etc. We need to adjust the limits based on cost, throughput and latencies.
309+--
310+-- The event producer thread must put the work on a work-queue and the child
311+-- threads can pick it up from there. But if there is just one consumer then it
312+-- may not make sense to have a separate producer unless the producing cost is
313+-- high.
314+--
315+
316+forkFinally1 :: MonadAsync m
317+ => Context
318+ -> AsyncT m a
319+ -> (Either SomeException () -> IO ())
320+ -> StateT Context m ThreadId
321+forkFinally1 ctx action preExit =
322+ EL.mask $ \restore ->
323+ liftBaseWith $ \runInIO -> forkIO $ do
324+ _ <- runInIO $ EL.try (restore (runContext ctx action))
325+ >>= liftIO . preExit
326+ -- XXX restore state here
327+ return ()
328+
329+-- | Run a given context in a new thread.
330+--
331+forkContext :: (MonadBaseControl IO m, MonadIO m, MonadThrow m)
332+ => AsyncT m a -> StateT Context m ()
333+forkContext action = do
334+ parentCtx <- get
335+ childCtx <- childContext parentCtx
336+ tid <- forkFinally1 childCtx action
337+ (beforeExit childCtx (childChannel parentCtx))
338+ updatePendingThreads parentCtx tid
339+
340+ where
341+
342+ updatePendingThreads :: (MonadIO m, MonadThrow m)
343+ => Context -> ThreadId -> m ()
344+ updatePendingThreads ctx tid = do
345+ -- update the new thread before reclaiming zombies so that if it exited
346+ -- already reclaim finds it in the list and does not panic.
347+ liftIO $ modifyIORef (pendingThreads ctx) $ (\ts -> tid:ts)
348+ tryReclaimZombies ctx
349+
350+ childContext ctx = do
351+ pendingRef <- liftIO $ newIORef []
352+ chan <- liftIO $ atomically newTChan
353+ -- shares the threadCredit of the parent by default
354+ return $ ctx
355+ { pendingThreads = pendingRef
356+ , childChannel = chan
357+ }
358+
359+ beforeExit ctx pchan res = do
360+ tid <- myThreadId
361+ r <- case res of
362+ Left e -> do
363+ dbg $ "beforeExit: " ++ show tid ++ " caught exception"
364+ liftIO $ readIORef (pendingThreads ctx) >>= mapM_ killThread
365+ return (Just e)
366+ Right _ -> waitForChildren ctx
367+
368+ signalQSemB (threadCredit ctx)
369+ liftIO $ atomically $ writeTChan pchan (ChildDone tid r)
370+
371+-- | Decide whether to resume the context in the same thread or a new thread
372+--
373+canFork :: Context -> IO Bool
374+canFork context = do
375+ gotCredit <- liftIO $ waitQSemB (threadCredit context)
376+ case gotCredit of
377+ False -> do
378+ pending <- liftIO $ readIORef $ pendingThreads context
379+ case pending of
380+ [] -> return False
381+ _ -> do
382+ -- XXX If we have unreclaimable child threads e.g.
383+ -- infinite loop, this is going to deadlock us. We need
384+ -- special handling for those cases. Add those to
385+ -- unreclaimable list? And always execute them in an
386+ -- async thread, cannot use sync for those.
387+ --
388+ waitForOneEvent context
389+ canFork context
390+ True -> return True
391+
392+-- Housekeeping, invoked after spawning of all child tasks is done and the
393+-- parent task needs to terminate. Either the task is fully done or we handed
394+-- it over to another thread, in any case the current thread is done.
395+
396+spawningParentDone :: MonadIO m => StateT Context m (Maybe a)
397+spawningParentDone = do
398+ loc <- getLocation
399+ when (loc /= RemoteNode) $ setLocation WaitingParent
400+ return Nothing
401+
402+-- | The task may be run in the same thread or in a new thread depending on the
403+-- forceAsync parameter and the current thread quota.
404+--
405+runAsyncTask :: MonadAsync m
406+ => Bool
407+ -> AsyncT m a
408+ -> StateT Context m (Maybe a)
409+runAsyncTask forceAsync action = do
410+ if forceAsync
411+ then forkContext action
412+ else do
413+ context <- get
414+ can <- liftIO $ canFork context
415+ case can of
416+ False -> runContext context action
417+ True -> forkContext action
418+ spawningParentDone
419+
420+-- The current model is to start a new thread for every task. The input is
421+-- provided at the time of the creation and therefore no synchronization is
422+-- needed compared to a pool of threads contending to get the input from a
423+-- channel. However the thread creation overhead may be more than the
424+-- synchronization cost?
425+--
426+-- When the task is over the outputs need to be collected and that requires
427+-- synchronization irrespective of a thread pool model or per task new thread
428+-- model.
429+--
430+-- XXX instead of starting a new thread every time, reuse the existing child
431+-- threads and send them work via a shared channel. When there is no more work
432+-- available we need a way to close the channel and wakeup all waiters so that
433+-- they can go away rather than waiting indefinitely.
434+--
435+
436+-- Only those actions that are marked with 'async' are guaranteed to be
437+-- asynchronous. Asyncly is free to run other actions synchronously or
438+-- asynchronously and it should not matter to the semantics of the program, if
439+-- it does then use async to force.
440+--
441+-- Why not make async as default and ask the programmer to use a 'sync'
442+-- primitive to force an action to run synchronously? But then we would not
443+-- have the freedom to convert the async actions to sync dynamically. Note that
444+-- it is safe to convert a sync action to async but vice-versa is not true.
445+-- Converting an async to sync can cause change in semantics if the async
446+-- action was an infinite loop for example.
447+--
448+-- | In an 'Alternative' composition, force the action to run asynchronously.
449+-- The <|> operator implies "can be parallel", whereas async implies "must be
450+-- parallel". Note that async is not useful and should never be used outside an
451+-- 'Alternative' composition. Even in an 'Alternative' composition 'async' is
452+-- not useful in the last action.
453+async :: MonadAsync m => AsyncT m a -> AsyncT m a
454+async action = AsyncT $ runAsyncTask True action
455+
456+-- XXX After setting the callback we should wait for the callback otherwise we
457+-- will just go away. We should use the same finalization mechanism that we use
458+-- in fork.
459+--
460+-- | Makes an asyncly action from a callback setter function; can be used to
461+-- convert existing callback style code into asyncly style code. The first
462+-- parameter is a callback setter function. When we are called back,
463+-- 'makeAsync' behaves as if it was an async computation that just returned a
464+-- value of type 'a'. After the computation is done the result of the action
465+-- in second parameter is returned to the callback.
466+--
467+makeAsync :: MonadAsync m => ((a -> m ()) -> m ()) -> AsyncT m a
468+makeAsync cbsetter = AsyncT $ do
469+ -- XXX should we force fork a thread or keep running in the context of the
470+ -- callback?
471+ ctx <- get
472+ lift $ cbsetter $ \a -> do
473+ let s = runAsyncTask False (return a)
474+ _ <- runStateT s ctx
475+ return ()
476+ spawningParentDone
477+
478+-- scatter
479+each :: (MonadIO m, MonadBaseControl IO m, MonadThrow m)
480+ => [a] -> AsyncT m a
481+each xs = foldl (<|>) empty $ map return xs
482+
483+------------------------------------------------------------------------------
484+-- Controlling thread quota
485+------------------------------------------------------------------------------
486+
487+-- XXX Should n be Word32 instead?
488+-- | Runs a computation under a given thread limit. A limit of 0 means new
489+-- tasks start synchronously in the current thread. New threads are created by
490+-- 'parallel', and APIs that use parallel.
491+threads :: MonadIO m => Int -> AsyncT m a -> AsyncT m a
492+threads n process = AsyncT $ do
493+ oldCr <- gets threadCredit
494+ newCr <- liftIO $ newIORef n
495+ modify $ \s -> s { threadCredit = newCr }
496+ r <- runAsyncT $ process
497+ <** (AsyncT $ do
498+ modify $ \s -> s { threadCredit = oldCr }
499+ return (Just ())
500+ ) -- restore old credit
501+ return r
502+
192------------------------------------------------------------------------------
503------------------------------------------------------------------------------
193-- Functor
504-- Functor
194------------------------------------------------------------------------------
505------------------------------------------------------------------------------
195
506
196−instance Monad (AsyncT m) => Functor (AsyncT m) where
507+instance MonadAsync m => Functor (AsyncT m) where
197− fmap f mx = do
508+ fmap = liftM
198− x <- mx
199− return $ f x
200
509
201------------------------------------------------------------------------------
510------------------------------------------------------------------------------
202-- Applicative
511-- Applicative
203------------------------------------------------------------------------------
512------------------------------------------------------------------------------
204
513
514+
205−instance Monad m => Applicative (AsyncT m) where
515+instance MonadAsync m => Applicative (AsyncT m) where
206 pure a = AsyncT . return $ Just a
516 pure a = AsyncT . return $ Just a
207− m1 <*> m2 = do { x1 <- m1; x2 <- m2; return (x1 x2) }
517+ (<*>) = ap
208
518
209------------------------------------------------------------------------------
519------------------------------------------------------------------------------
210-- Alternative
520-- Alternative
211------------------------------------------------------------------------------
521------------------------------------------------------------------------------
212
522
213−instance Monad m => Alternative (AsyncT m) where
523+instance MonadAsync m => Alternative (AsyncT m) where
214 empty = AsyncT $ return Nothing
524 empty = AsyncT $ return Nothing
215− (<|>) x y = AsyncT $ do
525+ (<|>) m1 m2 = AsyncT $ do
216− mx <- runAsyncT x
526+ r <- runAsyncTask False m1
217 loc <- getLocation
527 loc <- getLocation
218 case loc of
528 case loc of
219 RemoteNode -> return Nothing
529 RemoteNode -> return Nothing
220− _ -> maybe (runAsyncT y) (return . Just) mx
530+ _ -> maybe (runAsyncT m2) (return . Just) r
221
531
222------------------------------------------------------------------------------
532------------------------------------------------------------------------------
223-- Monad
533-- Monad
224------------------------------------------------------------------------------
534------------------------------------------------------------------------------
225
535
226−instance Monad m => Monad (AsyncT m) where
536+instance MonadAsync m => Monad (AsyncT m) where
227 return = pure
537 return = pure
228 m >>= f = AsyncT $ do
538 m >>= f = AsyncT $ do
229 modify $ \Context { continuation = g, .. } ->
539 modify $ \Context { continuation = g, .. } ->
@@ -234,11 +544,11 @@ instance Monad m => Monad (AsyncT m) where
234 Context { continuation = g } <- get
544 Context { continuation = g } <- get
235 runAsyncT $ maybe mzero (unsafeCoerce g) x
545 runAsyncT $ maybe mzero (unsafeCoerce g) x
236
546
237−instance Monad m => MonadPlus (AsyncT m) where
547+instance MonadAsync m => MonadPlus (AsyncT m) where
238 mzero = empty
548 mzero = empty
239 mplus = (<|>)
549 mplus = (<|>)
240
550
241−instance (Monoid a, Monad m) => Monoid (AsyncT m a) where
551+instance (Monoid a, MonadAsync m) => Monoid (AsyncT m a) where
242 mappend x y = mappend <$> x <*> y
552 mappend x y = mappend <$> x <*> y
243 mempty = return mempty
553 mempty = return mempty
244
554
@@ -246,7 +556,7 @@ instance (Monoid a, Monad m) => Monoid (AsyncT m a) where
246-- MonadIO
556-- MonadIO
247------------------------------------------------------------------------------
557------------------------------------------------------------------------------
248
558
249−instance MonadIO m => MonadIO (AsyncT m) where
559+instance MonadAsync m => MonadIO (AsyncT m) where
250 liftIO mx = AsyncT $ liftIO mx >>= return . Just
560 liftIO mx = AsyncT $ liftIO mx >>= return . Just
251
561
252-------------------------------------------------------------------------------
562-------------------------------------------------------------------------------
@@ -265,17 +575,17 @@ instance MonadTransControl AsyncT where
265 {-# INLINABLE liftWith #-}
575 {-# INLINABLE liftWith #-}
266 {-# INLINABLE restoreT #-}
576 {-# INLINABLE restoreT #-}
267
577
268−instance (MonadBase b m, MonadIO m) => MonadBase b (AsyncT m) where
578+instance (MonadBase b m, MonadAsync m) => MonadBase b (AsyncT m) where
269 liftBase = liftBaseDefault
579 liftBase = liftBaseDefault
270
580
271−instance (MonadBaseControl b m, MonadIO m) => MonadBaseControl b (AsyncT m) where
581+instance (MonadBaseControl b m, MonadAsync m) => MonadBaseControl b (AsyncT m) where
272 type StM (AsyncT m) a = ComposeSt AsyncT m a
582 type StM (AsyncT m) a = ComposeSt AsyncT m a
273 liftBaseWith = defaultLiftBaseWith
583 liftBaseWith = defaultLiftBaseWith
274 restoreM = defaultRestoreM
584 restoreM = defaultRestoreM
275 {-# INLINABLE liftBaseWith #-}
585 {-# INLINABLE liftBaseWith #-}
276 {-# INLINABLE restoreM #-}
586 {-# INLINABLE restoreM #-}
277
587
278−instance MonadThrow m => MonadThrow (AsyncT m) where
588+instance MonadAsync m => MonadThrow (AsyncT m) where
279 throwM e = lift $ throwM e
589 throwM e = lift $ throwM e
280
590
281------------------------------------------------------------------------------
591------------------------------------------------------------------------------
src/Asyncly/Threads.hs+16−348
@@ -1,16 +1,9 @@
1−{-# LANGUAGE ConstraintKinds #-}
2{-# LANGUAGE FlexibleContexts #-}
1{-# LANGUAGE FlexibleContexts #-}
3{-# LANGUAGE ScopedTypeVariables #-}
2{-# LANGUAGE ScopedTypeVariables #-}
4
3
5module Asyncly.Threads
4module Asyncly.Threads
6− ( MonadAsync
7− , wait
5+ ( wait
8 , wait_
6 , wait_
9− , threads
10−
11− , async
12− , makeAsync
13− , each
14 , gather
7 , gather
15
8
16 , waitLogged
9 , waitLogged
@@ -22,349 +15,25 @@ module Asyncly.Threads
22 )
15 )
23where
16where
24
17
25−import Control.Applicative ((<|>), empty)
26−import Control.Concurrent (ThreadId, forkIO, killThread,
18+import Control.Concurrent (killThread)
27− myThreadId)
28−import Control.Concurrent.STM (TChan, atomically, newTChan,
19+import Control.Concurrent.STM (atomically, newTChan)
29− readTChan, tryReadTChan,
30− writeTChan)
31import Control.Exception (SomeException (..))
20import Control.Exception (SomeException (..))
32−import qualified Control.Exception.Lifted as EL
33−import Control.Monad.Catch (MonadCatch, MonadThrow, throwM,
21+import Control.Monad.Catch (MonadCatch, throwM, try)
34− try)
35import Control.Monad.IO.Class (MonadIO (..))
22import Control.Monad.IO.Class (MonadIO (..))
36−import Control.Monad.State (get, gets, modify, mzero, put,
23+import Control.Monad.State (get, mzero, put, runStateT)
37− runStateT, when, StateT)
38import Control.Monad.Trans.Class (MonadTrans (lift))
24import Control.Monad.Trans.Class (MonadTrans (lift))
39−import Control.Monad.Trans.Control (MonadBaseControl, liftBaseWith)
40import Data.IORef (IORef, atomicModifyIORef,
25import Data.IORef (IORef, atomicModifyIORef,
41− modifyIORef, newIORef, readIORef,
26+ newIORef, readIORef)
42− writeIORef)
43−import Data.List (delete)
44−import Data.Maybe (isJust)
45−import Unsafe.Coerce (unsafeCoerce)
46
27
47import Asyncly.AsyncT
28import Asyncly.AsyncT
48
29
49−type MonadAsync m = (MonadIO m, MonadBaseControl IO m, MonadThrow m)
50−
51−------------------------------------------------------------------------------
52−-- Model of computation
53−------------------------------------------------------------------------------
54−
55−-- A computation starts in a top level thread. If no "forking primitives" are
56−-- used then the thread finishes in a straight line flow just like the IO
57−-- monad. However, if a "forking primitive" is used it "forks" the computation
58−-- into multiple flows. The "forked" computations may run concurrently (and
59−-- therefore in parallel when possible) or serially. When multiple forking
60−-- primitives are composed it results into a tree of computations where each
61−-- branch of the tree can run concurrently.
62−--
63−-- A forking primitive may create multiple forks at that point, each fork
64−-- provides a specific input value to be used in the forked computation, this
65−-- value defines the fork.
66−--
67−-- The final result of the computation is the collection of all the values
68−-- generated by all the leaf level forks. These values are then propagated up
69−-- the tree and collected at the root of the tree.
70−--
71−-- Since AsyncT is a transformer we can use things like pipe, conduit or any
72−-- other transformer monads inside the computations to utilize single threaded
73−-- composition or data flow techniques.
74−--
75−------------------------------------------------------------------------------
76−-- Pick up from where we left in the previous thread
77−------------------------------------------------------------------------------
78−
79−-- | Continue execution of the closure that we were executing when we migrated
80−-- to a new thread.
81−
82−runContext :: Monad m => Context -> AsyncT m a -> StateT Context m ()
83−runContext ctx action = do
84− let s = runAsyncT $ action >>= unsafeCoerce (continuation ctx)
85− _ <- lift $ runStateT s ctx
86− return ()
87−
88−------------------------------------------------------------------------------
89−-- Thread Management (creation, reaping and killing)
90−------------------------------------------------------------------------------
91−
92−-- XXX We are using unbounded channels so this will not block on writing to
93−-- pchan. We can use bounded channels to throttle the creation of threads based
94−-- on consumption rate.
95−processOneEvent :: MonadIO m
96− => ChildEvent
97− -> [ThreadId]
98− -> m ([ThreadId], Maybe SomeException)
99−processOneEvent (ChildDone tid e) pending = do
100− when (isJust e) $ liftIO $ mapM_ killThread pending
101− return (delete tid pending, Nothing)
102−
103−drainChildren :: MonadIO m
104− => TChan ChildEvent
105− -> [ThreadId]
106− -> m ([ThreadId], Maybe SomeException)
107−drainChildren cchan pending =
108− case pending of
109− [] -> return (pending, Nothing)
110− _ -> do
111− ev <- liftIO $ atomically $ readTChan cchan
112− (p, e) <- processOneEvent ev pending
113− maybe (drainChildren cchan p) (const $ return (p, e)) e
114−
115−waitForChildren :: MonadIO m => Context -> m (Maybe SomeException)
116−waitForChildren ctx = do
117− let pendingRef = pendingThreads ctx
118− pending <- liftIO $ readIORef pendingRef
119− (p, e) <- drainChildren (childChannel ctx) pending
120− liftIO $ writeIORef pendingRef p
121− return e
122−
123−tryReclaimZombies :: (MonadIO m, MonadThrow m) => Context -> m ()
124−tryReclaimZombies ctx = do
125− let cchan = childChannel ctx
126− pendingRef = pendingThreads ctx
127−
128− pending <- liftIO $ readIORef pendingRef
129− case pending of
130− [] -> return ()
131− _ -> do
132− mev <- liftIO $ atomically $ tryReadTChan cchan
133− case mev of
134− Nothing -> return ()
135− Just ev -> do
136− (p, e) <- processOneEvent ev pending
137− liftIO $ writeIORef pendingRef p
138− maybe (return ()) throwM e
139− tryReclaimZombies ctx
140−
141−waitForOneEvent :: (MonadIO m, MonadThrow m) => Context -> m ()
142−waitForOneEvent ctx = do
143− -- XXX assert pending must have at least one element
144− -- assert that the tid is found in our list
145− let cchan = childChannel ctx
146− pendingRef = pendingThreads ctx
147−
148− ev <- liftIO $ atomically $ readTChan cchan
149− pending <- liftIO $ readIORef pendingRef
150− (p, e) <- processOneEvent ev pending
151− liftIO $ writeIORef pendingRef p
152− maybe (return ()) throwM e
153−
154−-- XXX this is not a real semaphore as it does not really block on wait,
155−-- instead it returns whether the value is zero or non-zero.
156−--
157−waitQSemB :: IORef Int -> IO Bool
158−waitQSemB sem = atomicModifyIORef sem $ \n ->
159− if n > 0
160− then (n - 1, True)
161− else (n, False)
162−
163−signalQSemB :: IORef Int -> IO ()
164−signalQSemB sem = atomicModifyIORef sem $ \n -> (n + 1, ())
165−
166−-- Allocation of threads
167−--
168−-- global thread limit
169−-- thread fan-out i.e. per thread children limit
170−-- min per thread allocation to avoid starvation
171−--
172−-- dynamic adjustment based on the cost, speed of consumption, cpu utilization
173−-- etc. We need to adjust the limits based on cost, throughput and latencies.
174−--
175−-- The event producer thread must put the work on a work-queue and the child
176−-- threads can pick it up from there. But if there is just one consumer then it
177−-- may not make sense to have a separate producer unless the producing cost is
178−-- high.
179−--
180−
181−forkFinally1 :: (MonadIO m, MonadBaseControl IO m)
182− => Context
183− -> AsyncT m a
184− -> (Either SomeException () -> IO ())
185− -> StateT Context m ThreadId
186−forkFinally1 ctx action preExit =
187− EL.mask $ \restore ->
188− liftBaseWith $ \runInIO -> forkIO $ do
189− _ <- runInIO $ EL.try (restore (runContext ctx action))
190− >>= liftIO . preExit
191− -- XXX restore state here
192− return ()
193−
194−-- | Run a given context in a new thread.
195−--
196−forkContext :: (MonadBaseControl IO m, MonadIO m, MonadThrow m)
197− => AsyncT m a -> StateT Context m ()
198−forkContext action = do
199− parentCtx <- get
200− childCtx <- childContext parentCtx
201− tid <- forkFinally1 childCtx action
202− (beforeExit childCtx (childChannel parentCtx))
203− updatePendingThreads parentCtx tid
204−
205− where
206−
207− updatePendingThreads :: (MonadIO m, MonadThrow m)
208− => Context -> ThreadId -> m ()
209− updatePendingThreads ctx tid = do
210− -- update the new thread before reclaiming zombies so that if it exited
211− -- already reclaim finds it in the list and does not panic.
212− liftIO $ modifyIORef (pendingThreads ctx) $ (\ts -> tid:ts)
213− tryReclaimZombies ctx
214−
215− childContext ctx = do
216− pendingRef <- liftIO $ newIORef []
217− chan <- liftIO $ atomically newTChan
218− -- shares the threadCredit of the parent by default
219− return $ ctx
220− { pendingThreads = pendingRef
221− , childChannel = chan
222− }
223−
224− beforeExit ctx pchan res = do
225− tid <- myThreadId
226− r <- case res of
227− Left e -> do
228− dbg $ "beforeExit: " ++ show tid ++ " caught exception"
229− liftIO $ readIORef (pendingThreads ctx) >>= mapM_ killThread
230− return (Just e)
231− Right _ -> waitForChildren ctx
232−
233− signalQSemB (threadCredit ctx)
234− liftIO $ atomically $ writeTChan pchan (ChildDone tid r)
235−
236−-- | Decide whether to resume the context in the same thread or a new thread
237−--
238−canFork :: Context -> IO Bool
239−canFork context = do
240− gotCredit <- liftIO $ waitQSemB (threadCredit context)
241− case gotCredit of
242− False -> do
243− pending <- liftIO $ readIORef $ pendingThreads context
244− case pending of
245− [] -> return False
246− _ -> do
247− -- XXX If we have unreclaimable child threads e.g.
248− -- infinite loop, this is going to deadlock us. We need
249− -- special handling for those cases. Add those to
250− -- unreclaimable list? And always execute them in an
251− -- async thread, cannot use sync for those.
252− --
253− waitForOneEvent context
254− canFork context
255− True -> return True
256−
257−-- | Resume a captured context with a given action. The context may be resumed
258−-- in the same thread or in a new thread depending on the synch parameter and
259−-- the current thread quota.
260−--
261−resumeContextWith :: MonadAsync m
262− => Bool -- force synchronous
263− -> AsyncT m a
264− -> StateT Context m ()
265−resumeContextWith synch action = do
266− context <- get
267− can <- liftIO $ canFork context
268− case can && (not synch) of
269− False -> runContext context action
270− True -> forkContext action
271−
272−-- The current model is to start a new thread for every task. The input is
273−-- provided at the time of the creation and therefore no synchronization is
274−-- needed compared to a pool of threads contending to get the input from a
275−-- channel. However the thread creation overhead may be more than the
276−-- synchronization cost?
277−--
278−-- When the task is over the outputs need to be collected and that requires
279−-- synchronization irrespective of a thread pool model or per task new thread
280−-- model.
281−--
282−-- XXX instead of starting a new thread every time, reuse the existing child
283−-- threads and send them work via a shared channel. When there is no more work
284−-- available we need a way to close the channel and wakeup all waiters so that
285−-- they can go away rather than waiting indefinitely.
286−--
287−
288−-- Housekeeping, invoked after spawning of all child tasks is done and the
289−-- parent task needs to terminate. Either the task is fully done or we handed
290−-- it over to another thread, in any case the current thread is done.
291−
292−spawningParentDone :: MonadIO m => StateT Context m (Maybe a)
293−spawningParentDone = do
294− loc <- getLocation
295− when (loc /= RemoteNode) $ setLocation WaitingParent
296− return Nothing
297−
298−-- If a new thread cannot be created then the computation is run in the same
299−-- thread, but the functional behavior remains the same.
300−
301−async :: MonadAsync m => AsyncT m a -> AsyncT m a
302−async action = AsyncT $ do
303− resumeContextWith False action
304− spawningParentDone
305−
306−-- | Makes an asyncly action from a callback setter function; can be used to
307−-- convert existing callback style code into asyncly style code. The first
308−-- parameter is a callback setter function. When we are called back,
309−-- 'makeAsync' behaves as if it was an async computation that just returned a
310−-- value of type 'a'. After the computation is done the result of the action
311−-- in second parameter is returned to the callback.
312−--
313−makeAsync :: MonadAsync m => ((a -> m ()) -> m ()) -> AsyncT m a
314−makeAsync cbsetter = AsyncT $ do
315− -- XXX should we force fork a thread or keep running the context of the
316− -- callback?
317− ctx <- get
318− lift $ cbsetter $ \a -> do
319− let s = resumeContextWith False (return a)
320− _ <- runStateT s ctx
321− return ()
322− spawningParentDone
323−
324−-- scatter
325−each :: (MonadIO m, MonadBaseControl IO m, MonadThrow m)
326− => [a] -> AsyncT m a
327−each xs = foldl (<|>) empty $ map (async . return) xs
328−
329−------------------------------------------------------------------------------
330−-- Controlling thread quota
331−------------------------------------------------------------------------------
332−
333−-- XXX Should n be Word32 instead?
334−-- | Runs a computation under a given thread limit. A limit of 0 means new
335−-- tasks start synchronously in the current thread. New threads are created by
336−-- 'parallel', and APIs that use parallel.
337−threads :: MonadIO m => Int -> AsyncT m a -> AsyncT m a
338−threads n process = AsyncT $ do
339− oldCr <- gets threadCredit
340− newCr <- liftIO $ newIORef n
341− modify $ \s -> s { threadCredit = newCr }
342− r <- runAsyncT $ process
343− <** (AsyncT $ do
344− modify $ \s -> s { threadCredit = oldCr }
345− return (Just ())
346− ) -- restore old credit
347− return r
348−
349−{-
350−-- This can be used to set, increase or decrease the existing limit. The limit
351−-- is shared by multiple threads and therefore needs to modified atomically.
352−-- Note that when there is no limit the limit is set to maxBound it can
353−-- overflow with an increment and get reduced instead of increasing.
354−-- XXX should we use a Maybe instead? Or use separate inc/dec/set APIs to
355−-- handle overflow properly?
356−--
357−-- modifyThreads :: MonadIO m => (Int -> Int) -> AsyncT m ()
358−-- modifyThreads f =
359−-}
360−
361------------------------------------------------------------------------------
30------------------------------------------------------------------------------
362-- Running the monad
31-- Running the monad
363------------------------------------------------------------------------------
32------------------------------------------------------------------------------
364
33
365-- | Run an 'AsyncT m' computation and collect the results generated by each
34-- | Run an 'AsyncT m' computation and collect the results generated by each
366-- thread of the computation in a list.
35-- thread of the computation in a list.
367−waitAsync :: (MonadIO m, MonadCatch m)
36+waitAsync :: (MonadAsync m, MonadCatch m)
368 => (a -> AsyncT m a) -> Maybe (IORef [Log]) -> AsyncT m a -> m ()
37 => (a -> AsyncT m a) -> Maybe (IORef [Log]) -> AsyncT m a -> m ()
369waitAsync finalizer lref m = do
38waitAsync finalizer lref m = do
370 childChan <- liftIO $ atomically newTChan
39 childChan <- liftIO $ atomically newTChan
@@ -389,12 +58,12 @@ waitAsync finalizer lref m = do
389
58
390-- | Invoked to store the result of the computation in the context and finish
59-- | Invoked to store the result of the computation in the context and finish
391-- the computation when the computation is done
60-- the computation when the computation is done
392−gatherResult :: MonadIO m => IORef [a] -> a -> AsyncT m a
61+gatherResult :: MonadAsync m => IORef [a] -> a -> AsyncT m a
393gatherResult ref r = do
62gatherResult ref r = do
394 liftIO $ atomicModifyIORef ref $ \rs -> (r : rs, ())
63 liftIO $ atomicModifyIORef ref $ \rs -> (r : rs, ())
395 mzero
64 mzero
396
65
397−gather :: (MonadIO m, MonadCatch m) => AsyncT m a -> AsyncT m [a]
66+gather :: (MonadAsync m, MonadCatch m) => AsyncT m a -> AsyncT m [a]
398gather m = AsyncT $ do
67gather m = AsyncT $ do
399 resultsRef <- liftIO $ newIORef []
68 resultsRef <- liftIO $ newIORef []
400 lift $ waitAsync (gatherResult resultsRef) Nothing m
69 lift $ waitAsync (gatherResult resultsRef) Nothing m
@@ -403,7 +72,7 @@ gather m = AsyncT $ do
403
72
404-- | Run an 'AsyncT m' computation and collect the results generated by each
73-- | Run an 'AsyncT m' computation and collect the results generated by each
405-- thread of the computation in a list.
74-- thread of the computation in a list.
406−wait :: (MonadIO m, MonadCatch m) => AsyncT m a -> m [a]
75+wait :: (MonadAsync m, MonadCatch m) => AsyncT m a -> m [a]
407wait m = do
76wait m = do
408 resultsRef <- liftIO $ newIORef []
77 resultsRef <- liftIO $ newIORef []
409 waitAsync (gatherResult resultsRef) Nothing m
78 waitAsync (gatherResult resultsRef) Nothing m
@@ -411,14 +80,14 @@ wait m = do
411
80
412-- | Run an 'AsyncT m' computation, wait for it to finish and discard the
81-- | Run an 'AsyncT m' computation, wait for it to finish and discard the
413-- results.
82-- results.
414−wait_ :: (MonadIO m, MonadCatch m) => AsyncT m a -> m ()
83+wait_ :: (MonadAsync m, MonadCatch m) => AsyncT m a -> m ()
415wait_ m = waitAsync (const mzero) Nothing m
84wait_ m = waitAsync (const mzero) Nothing m
416
85
417------------------------------------------------------------------------------
86------------------------------------------------------------------------------
418-- Logging
87-- Logging
419------------------------------------------------------------------------------
88------------------------------------------------------------------------------
420
89
421−logged :: (Loggable a, MonadIO m) => AsyncT m a -> AsyncT m a
90+logged :: (Loggable a, MonadAsync m) => AsyncT m a -> AsyncT m a
422logged m = AsyncT $ do
91logged m = AsyncT $ do
423 ctx <- get
92 ctx <- get
424 case journal ctx of
93 case journal ctx of
@@ -467,7 +136,7 @@ logged m = AsyncT $ do
467-- completed threads output is returned and for suspended threads the logs to
136-- completed threads output is returned and for suspended threads the logs to
468-- resume them are returned.
137-- resume them are returned.
469
138
470−suspend :: MonadIO m => AsyncT m ()
139+suspend :: MonadAsync m => AsyncT m ()
471suspend = logged $ AsyncT $ do
140suspend = logged $ AsyncT $ do
472 ctx <- get
141 ctx <- get
473 case logsRef ctx of
142 case logsRef ctx of
@@ -492,13 +161,12 @@ withLog m (Log entries) = AsyncT $ do
492
161
493-- | Compose a computation using previously captured list of logs. Composes a
162-- | Compose a computation using previously captured list of logs. Composes a
494-- group of computations, one for each log.
163-- group of computations, one for each log.
495−eachWithLog :: (MonadIO m, MonadBaseControl IO m, MonadThrow m)
496− => AsyncT m a -> [Log] -> AsyncT m a
164+eachWithLog :: MonadAsync m => AsyncT m a -> [Log] -> AsyncT m a
497eachWithLog m logs = each logs >>= withLog m
165eachWithLog m logs = each logs >>= withLog m
498
166
499-- | Run an 'AsyncT m' computation with logging enabled and collect the results
167-- | Run an 'AsyncT m' computation with logging enabled and collect the results
500-- or logs generated by each thread of the computation.
168-- or logs generated by each thread of the computation.
501−waitLogged :: (MonadIO m, MonadCatch m) => AsyncT m a -> m ([a], [Log])
169+waitLogged :: (MonadAsync m, MonadCatch m) => AsyncT m a -> m ([a], [Log])
502waitLogged m = do
170waitLogged m = do
503 resultsRef <- liftIO $ newIORef []
171 resultsRef <- liftIO $ newIORef []
504 lref <- liftIO $ newIORef []
172 lref <- liftIO $ newIORef []
@@ -510,7 +178,7 @@ waitLogged m = do
510-- | Run an 'AsyncT m' computation with logging enabled, wait for it to finish
178-- | Run an 'AsyncT m' computation with logging enabled, wait for it to finish
511-- and discard the results. If the computation suspends collect the logs to
179-- and discard the results. If the computation suspends collect the logs to
512-- replay later.
180-- replay later.
513−waitLogged_ :: (MonadIO m, MonadCatch m) => AsyncT m a -> m [Log]
181+waitLogged_ :: (MonadAsync m, MonadCatch m) => AsyncT m a -> m [Log]
514waitLogged_ m = do
182waitLogged_ m = do
515 lref <- liftIO $ newIORef []
183 lref <- liftIO $ newIORef []
516 waitAsync (const mzero) (Just lref) m
184 waitAsync (const mzero) (Just lref) m
test/3-async.hs+2−2
@@ -11,10 +11,10 @@ main = do
11 liftIO $ hSetBuffering stdout LineBuffering
11 liftIO $ hSetBuffering stdout LineBuffering
12 mainThread <- liftIO myThreadId
12 mainThread <- liftIO myThreadId
13 liftIO $ putStrLn $ "Main thread: " ++ show mainThread
13 liftIO $ putStrLn $ "Main thread: " ++ show mainThread
14− x <- async $ liftIO (randomIO :: IO Int)
14+ x <- liftIO (randomIO :: IO Int)
15 liftIO $ putStrLn $ show x
15 liftIO $ putStrLn $ show x
16
16
17− y <- async $ liftIO (randomIO :: IO Int)
17+ y <- liftIO (randomIO :: IO Int)
18
18
19 liftIO $ threadDelay 1000000
19 liftIO $ threadDelay 1000000
20 evThread <- liftIO myThreadId
20 evThread <- liftIO myThreadId
test/3-sample.hs+1−1
@@ -33,7 +33,7 @@ main = wait_ $ threads 0 $ do
33sample :: (Eq a, MonadIO m, MonadBaseControl IO m, MonadThrow m)
33sample :: (Eq a, MonadIO m, MonadBaseControl IO m, MonadThrow m)
34 => IO a -> Int -> AsyncT m a
34 => IO a -> Int -> AsyncT m a
35sample action interval = do
35sample action interval = do
36− v <- async $ liftIO action
36+ v <- liftIO action
37 prev <- liftIO $ newIORef v
37 prev <- liftIO $ newIORef v
38 async (return v) <|> loop action prev
38 async (return v) <|> loop action prev
39 where loop act prev = loop'
39 where loop act prev = loop'
test/Main.hs+69−26
@@ -1,11 +1,11 @@
1module Main (main) where
1module Main (main) where
2
2
3−import Test.Hspec
3+import Asyncly
4−
5import Control.Applicative ((<|>))
4import Control.Applicative ((<|>))
5+import Control.Concurrent (myThreadId, threadDelay)
6import Control.Monad.IO.Class (liftIO)
6import Control.Monad.IO.Class (liftIO)
7−import Asyncly
8−import Data.List (sort)
7+import Data.List (nub, sort)
8+import Test.Hspec
9
9
10default (Int)
10default (Int)
11
11
@@ -14,50 +14,93 @@ main = hspec $ do
14 it "Captures a return value using wait" $
14 it "Captures a return value using wait" $
15 wait (return 0) `shouldReturn` ([0] :: [Int])
15 wait (return 0) `shouldReturn` ([0] :: [Int])
16 it "Alternative composition of sync tasks" $
16 it "Alternative composition of sync tasks" $
17− wait (return 0 <|> return 1) `shouldReturn` ([0] :: [Int])
18− it "Creates a single async thread" $
19− wait (async $ return 0) `shouldReturn` ([0] :: [Int])
17+ ((wait $ threads 0 $ (return 0 <|> return 1)) >>= return . sort) `shouldReturn` ([0, 1] :: [Int])
20− it "async ignoring return value" $
21− wait (async (return 0 :: AsyncT IO Int) >> return 1)
22− `shouldReturn` ([1] :: [Int])
23 it "Alternative composition of async and sync tasks" $
18 it "Alternative composition of async and sync tasks" $
24− (wait (async (return 0) <|> return 1) >>= return . sort)
19+ ((wait (threads 0 ((async (return 0) <|> return 1)))) >>= return . sort)
25 `shouldReturn` ([0,1] :: [Int])
20 `shouldReturn` ([0,1] :: [Int])
26 it "Alternative composition of async tasks" $
21 it "Alternative composition of async tasks" $
27 (wait (async (return 0) <|> async (return 1)) >>= return . sort)
22 (wait (async (return 0) <|> async (return 1)) >>= return . sort)
28 `shouldReturn` ([0,1] :: [Int])
23 `shouldReturn` ([0,1] :: [Int])
29− it "Nested async tasks" $
30− (wait $ (async $ async $ return 0)) `shouldReturn` ([0] :: [Int])
31− it "Nested async tasks ignoring return values" $
32− (wait $ (async $ async $ return 0 :: AsyncT IO Int) >> return 1)
33− `shouldReturn` ([1] :: [Int])
34 it "Nested async tasks with Alternative" $
24 it "Nested async tasks with Alternative" $
35− (wait (async (async $ return 0) <|> return 1) >>= return . sort)
25+ (wait (async (async $ return 0 <|> return 1) <|> return 1) >>= return . sort)
36− `shouldReturn` ([0,1] :: [Int])
26+ `shouldReturn` ([0,1,1] :: [Int])
37 it "General example" $
27 it "General example" $
38 (wait generalExample >>= return . sort)
28 (wait generalExample >>= return . sort)
39− `shouldReturn` ([7,7,8,8,8,8,8,8,9,9,9,9,9,9,10,10] :: [Int])
29+ `shouldReturn` ([8,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,13] :: [Int])
40 it "General example synchronous" $
30 it "General example synchronous" $
41 (wait (threads 0 generalExample) >>= return . sort)
31 (wait (threads 0 generalExample) >>= return . sort)
42− `shouldReturn` ([7,7,8,8,8,8,8,8,9,9,9,9,9,9,10,10] :: [Int])
32+ `shouldReturn` ([8,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,13] :: [Int])
33+ it "Alternative forks threads - 1" $
34+ ((wait $ (liftIO myThreadId) <|> (liftIO myThreadId))
35+ >>= (\x -> print x >> return x)
36+ >>= return . \x -> (length x, length (nub x)))
37+ `shouldReturn` ((2, 2) :: (Int, Int))
38+ it "Alternative forks threads - 2" $
39+ ((wait $
40+ (liftIO myThreadId)
41+ <|> (liftIO myThreadId)
42+ <|> (liftIO myThreadId))
43+ >>= (\x -> print x >> return x)
44+ >>= return . \x -> (length x, length (nub x)))
45+ `shouldReturn` ((3, 3) :: (Int, Int))
46+ it "Alternative does not fork when using threads 0" $
47+ ((wait $ threads 0 $ (liftIO myThreadId) <|> (liftIO myThreadId))
48+ >>= (\x -> print x >> return x)
49+ >>= return . \x -> (length x, length (nub x)))
50+ `shouldReturn` ((2, 1) :: (Int, Int))
51+ it "async primitive forces a fork at the end of Alternative\
52+ \ (undesired but that's how the implementation behaves)" $
53+ ((wait $ threads 0 $ (liftIO myThreadId) <|> async (liftIO myThreadId))
54+ >>= (\x -> print x >> return x)
55+ >>= return . \x -> (length x, length (nub x)))
56+ `shouldReturn` ((2, 2) :: (Int, Int))
57+ it "async primitive forces a fork at the beginning" $
58+ ((wait $ threads 0 $ async (liftIO myThreadId) <|> (liftIO myThreadId))
59+ >>= (\x -> print x >> return x)
60+ >>= return . \x -> (length x, length (nub x)))
61+ `shouldReturn` ((2, 2) :: (Int, Int))
62+ it "async primitive forces a fork in the middle" $
63+ ((wait $ threads 0 $
64+ (liftIO myThreadId)
65+ <|> async (liftIO (threadDelay 1000000) >> liftIO myThreadId)
66+ <|> (liftIO myThreadId))
67+ >>= (\x -> print x >> return x)
68+ >>= return . \x -> (length x, length (nub x)))
69+ `shouldReturn` ((3, 2) :: (Int, Int))
70+ it "async primitive forces a fork - 3" $
71+ ((wait $ threads 0 $
72+ (liftIO myThreadId)
73+ <|> async (liftIO (threadDelay 1000000) >> liftIO myThreadId)
74+ <|> async (liftIO (threadDelay 1000000) >> liftIO myThreadId))
75+ >>= (\x -> print x >> return x)
76+ >>= return . \x -> (length x, length (nub x)))
77+ `shouldReturn` ((3, 3) :: (Int, Int))
78+ it "async primitive forces a fork - 4" $
79+ ((wait $ threads 0 $
80+ async (liftIO (threadDelay 1000000) >> liftIO myThreadId)
81+ <|> async (liftIO (threadDelay 1000000) >> liftIO myThreadId)
82+ <|> async (liftIO (threadDelay 1000000) >> liftIO myThreadId))
83+ >>= (\x -> print x >> return x)
84+ >>= return . \x -> (length x, length (nub x)))
85+ `shouldReturn` ((3, 3) :: (Int, Int))
43
86
44generalExample :: AsyncT IO Int
87generalExample :: AsyncT IO Int
45generalExample = do
88generalExample = do
46 liftIO $ return ()
89 liftIO $ return ()
47− liftIO $ print "hello"
90+ liftIO $ putStr ""
48− x <- async (return 1)
91+ x <- return 1
49− y <- async (return 2)
92+ y <- return 2
50 z <- do
93 z <- do
51 x1 <- async (return 1) <|> async (return 2)
94 x1 <- async (return 1) <|> async (return 2)
52 liftIO $ return ()
95 liftIO $ return ()
53− liftIO $ print "hello"
96+ liftIO $ putStr ""
54 y1 <- (return 1) <|> async (return 2)
97 y1 <- (return 1) <|> async (return 2)
55 z1 <- do
98 z1 <- do
56 x11 <- async (return 1) <|> (return 2)
99 x11 <- async (return 1) <|> (return 2)
57 y11 <- async (return 1) <|> async (return 2)
100 y11 <- async (return 1) <|> async (return 2)
58 z11 <- async (return 1) <|> (return 2)
101 z11 <- async (return 1) <|> (return 2)
59 liftIO $ return ()
102 liftIO $ return ()
60− liftIO $ print "hello"
103+ liftIO $ putStr ""
61− return (x11 + y11)
104+ return (x11 + y11 + z11)
62 return (x1 + y1 + z1)
105 return (x1 + y1 + z1)
63 return (x + y + z)
106 return (x + y + z)
test/alternative.hs+3−3
@@ -6,7 +6,7 @@ import Control.Applicative
6
6
7import Asyncly
7import Asyncly
8
8
9−main = wait_ $ threads 3 $ do
9+main = wait_ $ threads 1 $ do
10 liftIO $ hSetBuffering stdout LineBuffering
10 liftIO $ hSetBuffering stdout LineBuffering
11 mainThread <- liftIO myThreadId
11 mainThread <- liftIO myThreadId
12 liftIO $ putStrLn $ "Main thread: " ++ show mainThread
12 liftIO $ putStrLn $ "Main thread: " ++ show mainThread
@@ -14,14 +14,14 @@ main = wait_ $ threads 3 $ do
14 eventA <|> eventB
14 eventA <|> eventB
15
15
16eventA = do
16eventA = do
17− x <- async $ liftIO (randomIO :: IO Int)
17+ x <- liftIO (randomIO :: IO Int)
18 evThread <- liftIO myThreadId
18 evThread <- liftIO myThreadId
19 liftIO $ putStrLn $ "X Event thread: " ++ show evThread
19 liftIO $ putStrLn $ "X Event thread: " ++ show evThread
20 liftIO $ putStrLn $ "x = " ++ (show x)
20 liftIO $ putStrLn $ "x = " ++ (show x)
21 return x
21 return x
22
22
23eventB = do
23eventB = do
24− y <- async $ liftIO (randomIO :: IO Int)
24+ y <- liftIO (randomIO :: IO Int)
25
25
26 -- liftIO $ threadDelay 10000000
26 -- liftIO $ threadDelay 10000000
27 evThread <- liftIO myThreadId
27 evThread <- liftIO myThreadId