on master help sign in
Pass results from children to parent
commit9779a0b082d6d08992e3f6bdc6d58e3bd6540591 authorHarendra Kumar <harendra.kumar@gmail.com> dateThu 22 Jun 2017, 13:11 (9 years ago) parentc49bcc7
4 files changed+98−78
src/Duct/AsyncT.hs+22−18 src/Duct/Context.hs+34−19 src/Duct/Threads.hs+27−30 test/3-async.hs+15−11
src/Duct/AsyncT.hs+22−18
@@ -41,7 +41,7 @@ import Data.Maybe (isJust, isNothing)
4141
4242import Duct.Context
4343
44−-- import Debug.Trace
44+-- import Debug.Trace (traceM)
4545
4646newtype AsyncT m a = AsyncT { runAsyncT :: StateT Context m (Maybe a) }
4747
@@ -49,7 +49,9 @@ newtype AsyncT m a = AsyncT { runAsyncT :: StateT Context m (Maybe a) }
4949-- Utilities
5050------------------------------------------------------------------------------
5151
52−-- dbg x = trace x (return ())
52+-- Toggle the dbg definition for debug traces
53+--dbg :: Monad m => String -> m ()
54+--dbg = traceM
5355dbg :: Monad m => a -> m ()
5456dbg _ = return ()
5557
@@ -92,6 +94,7 @@ instance Monad m => Alternative (AsyncT m) where
9294 RemoteNode -> return Nothing
9395 _ -> maybe (runAsyncT y) (return . Just) mx
9496
97+-- XXX remove this, use mzero instead
9598-- | A synonym of 'empty' that can be used in a monadic expression. It stops
9699-- the computation, which allows the next computation in an 'Alternative'
97100-- ('<|>') composition to run.
@@ -166,24 +169,22 @@ drainChildren chan pending res =
166169 ev <- liftIO $ atomically $ readTChan chan
167170 case ev of
168171 ChildDone tid er -> do
169− dbg $ "Reaping child: " ++ show tid
172+ dbg $ "drainChildrenTop ChildDone, tid: " ++ show tid
170173 case er of
171174 Left e -> throwM e
172− Right r -> do
173− let newres = case r of
174− Nothing -> res
175− Just x -> x:res
176− drainChildren chan (delete tid pending) newres
177− PassOnResult er ->
175+ Right r ->
176+ drainChildren chan (delete tid pending) (r ++ res)
177+ PassOnResult er -> do
178+ dbg $ "drainChildrenTop PassOnResult"
178179 case er of
179180 Left e -> throwM e
180− Right x -> drainChildren chan pending (x:res)
181+ Right r -> drainChildren chan pending (r ++ res)
181182
182183waitForChildren :: (MonadIO m, MonadThrow m)
183− => TChan (ChildEvent a) -> IORef [ThreadId] -> m [a]
184−waitForChildren chan pendingRef = do
184+ => TChan (ChildEvent a) -> IORef [ThreadId] -> [a] -> m [a]
185+waitForChildren chan pendingRef results = do
185186 pending <- liftIO $ readIORef pendingRef
186− r <- drainChildren chan pending []
187+ r <- drainChildren chan pending results
187188 liftIO $ writeIORef pendingRef []
188189 return r
189190
@@ -195,24 +196,27 @@ waitForChildren chan pendingRef = do
195196-- XXX Ideally it should be a non-empty list instead.
196197-- | Run an 'AsyncT m' computation. Returns a list of results of the
197198-- computation or may throw an exception.
198−waitAsync :: forall m a. (MonadIO m, MonadThrow m, MonadCatch m)
199+waitAsync :: forall m a. (MonadIO m, MonadCatch m)
199200 => AsyncT m a -> m [a]
200201waitAsync m = do
201202 childChan <- liftIO $ atomically newTChan
202203 pendingRef <- liftIO $ newIORef []
203204 credit <- liftIO $ newIORef maxBound
205+ resultsRef <- liftIO $ newIORef []
204206
205207 -- XXX this should be moved to Context.hs and then we can make m
206208 -- existential and remove the unsafeCoerces
207209 r <- try $ runStateT (runAsyncT m) $ initContext
208− (empty :: AsyncT m a) childChan pendingRef credit
210+ (empty :: AsyncT m a) childChan pendingRef credit resultsRef
209211
210− case r of
212+ results <- liftIO $ readIORef resultsRef
213+ xs <- case r of
211214 Left (exc :: SomeException) -> do
212215 liftIO $ readIORef pendingRef >>= mapM_ killThread
213216 throwM exc
214− Right (Nothing, _) -> waitForChildren childChan pendingRef
215− Right ((Just x), _) -> return [x]
217+ Right (Nothing, _) -> return results
218+ Right ((Just x), _) -> return $ x:results
219+ waitForChildren childChan pendingRef xs
216220
217221------------------------------------------------------------------------------
218222-- * Extensible State: Session Data Management
src/Duct/Context.hs+34−19
@@ -23,21 +23,22 @@ import Control.Applicative (Alternative(..))
2323import Control.Concurrent (ThreadId)
2424import Control.Concurrent.STM (TChan)
2525import Control.Exception (SomeException)
26−import Control.Monad.State (MonadState, gets, modify, StateT, MonadPlus)
26+import Control.Monad.State (MonadState, gets, modify, StateT,
27+ MonadPlus(..), MonadIO(..))
2728import qualified Control.Monad.Trans.State.Lazy as Lazy (get, gets, modify, put)
2829import Data.Dynamic (TypeRep, Typeable, typeOf)
29−import Data.IORef (IORef)
30+import Data.IORef (IORef, modifyIORef)
3031import qualified Data.Map as M
3132import Unsafe.Coerce (unsafeCoerce)
32−import GHC.Prim (Any)
33+import GHC.Prim (Any)
3334
3435------------------------------------------------------------------------------
3536-- State of a continuation
3637------------------------------------------------------------------------------
3738
3839data ChildEvent a =
39− ChildDone ThreadId (Either SomeException (Maybe a)) -- A child is finished
40− | PassOnResult (Either SomeException a) -- Pass on the result of a child
40+ ChildDone ThreadId (Either SomeException [a]) -- A child is finished
41+ | PassOnResult (Either SomeException [a]) -- Pass on the result of a child
4142
4243-- | Describes the context of a computation.
4344data Context = Context
@@ -100,6 +101,8 @@ data Context = Context
100101 --
101102 , threadCredit :: IORef Int
102103 -- ^ How many more threads are allowed to be created?
104+ , accumResults :: IORef [Any]
105+ -- ^ Accumulated results when running synchronously
103106 } deriving Typeable
104107
105108initContext
@@ -107,8 +110,9 @@ initContext
107110 -> TChan (ChildEvent a)
108111 -> IORef [ThreadId]
109112 -> IORef Int
113+ -> IORef [a]
110114 -> Context
111−initContext x childChan pending credit =
115+initContext x childChan pending credit results =
112116 Context { event = mempty
113117 , currentm = unsafeCoerce x
114118 , fstack = []
@@ -117,7 +121,8 @@ initContext x childChan pending credit =
117121 , parentChannel = Nothing
118122 , childChannel = unsafeCoerce childChan
119123 , pendingThreads = pending
120− , threadCredit = credit }
124+ , threadCredit = credit
125+ , accumResults = unsafeCoerce results }
121126
122127------------------------------------------------------------------------------
123128-- Where is the computation running?
@@ -168,22 +173,32 @@ restoreContext x = do
168173 return mres
169174
170175-- | Resume execution of the computation from a given context. We can retrieve
171−-- a context at any point and resume that context at some later point, which
172−-- means start executing again from the same point where the context was
173−-- retrieved.
176+-- a context at any point and resume that context at some later point, upon
177+-- resumption we start executing again from the same point where the context
178+-- was retrieved.
174179--
175−-- Run the stack of pending functions in fstack. The types don't match. We just
176−-- coerce the types here, we know that they actually match.
177−
178−resumeContext :: MonadPlus m => Context -> m a
179−resumeContext Context { currentm = m, fstack = fs } =
180− unsafeCoerce m >>= runfStack (unsafeCoerce fs)
180+resumeContext :: (MonadIO m, MonadPlus m) => Context -> m a
181+resumeContext Context { currentm = m
182+ , fstack = fs
183+ , accumResults = resultsRef
184+ } =
185+ unsafeCoerce m >>= composefStack (unsafeCoerce fs)
181186
182187 where
183188
184− -- runfStack :: Monad m => [a -> AsyncT m a] -> a -> AsyncT m b
185− runfStack [] _ = empty
186− runfStack (f:ff) x = f x >>= runfStack ff
189+ -- Compose the stack of pending functions in fstack using the bind
190+ -- operation of the monad. The types don't match. We just coerce the types
191+ -- here, we know that they actually match.
192+
193+ -- The last computation is always 'mzero' to stop the whole computation.
194+ -- Before returning we save the result of the computation in the context.
195+
196+ -- composefStack :: Monad m => [a -> AsyncT m a] -> a -> AsyncT m b
197+ composefStack [] x = do
198+ -- remove this, return use the modified context
199+ liftIO $ modifyIORef resultsRef $ \r -> x : r
200+ mzero
201+ composefStack (f:ff) x = f x >>= composefStack ff
187202
188203------------------------------------------------------------------------------
189204-- * Extensible State: Session Data Management
src/Duct/Threads.hs+27−30
@@ -69,11 +69,12 @@ import Duct.Context
6969-- | Continue execution of the closure that we were executing when we migrated
7070-- to a new thread.
7171
72−resume :: Monad m => Context -> m (Maybe a)
72+resume :: MonadIO m => Context -> m Context
7373resume ctx = do
7474 let s = runAsyncT (resumeContext ctx)
75− (a, _) <- runStateT s ctx
76− return a
75+ -- The returned value is always 'Nothing', we just discard it
76+ (_, c) <- runStateT s ctx
77+ return c
7778
7879updateContextEvent :: Context -> a -> Context
7980updateContextEvent ctx evdata = ctx { event = Just $ unsafeCoerce evdata }
@@ -96,9 +97,11 @@ processOneEvent ev pchan pending exc = do
9697 Nothing ->
9798 case ev of
9899 ChildDone tid res -> do
99− dbg $ "Reaping child: " ++ show tid
100− handleDone res
101− PassOnResult res -> handlePass res
100+ dbg $ "processOneEvent ChildDone: " ++ show tid
101+ handlePass res
102+ PassOnResult res -> do
103+ dbg $ "processOneEvent PassOnResult"
104+ handlePass res
102105 Just _ -> return exc
103106 let p = case ev of
104107 ChildDone tid _ -> delete tid pending
@@ -107,29 +110,16 @@ processOneEvent ev pchan pending exc = do
107110
108111 where
109112
110− handleDone :: MonadIO m
111− => Either SomeException (Maybe a) -> m (Maybe SomeException)
112− handleDone res =
113− case res of
114− Left e -> do
115− liftIO $ mapM_ killThread pending
116− return (Just e)
117− Right r -> do
118− case r of
119− Nothing -> return ()
120− Just x -> liftIO $ atomically
121− $ writeTChan pchan
122− (PassOnResult (Right (unsafeCoerce x)))
123− return Nothing
124−
125113 handlePass :: MonadIO m
126− => Either SomeException a -> m (Maybe SomeException)
114+ => Either SomeException [a] -> m (Maybe SomeException)
127115 handlePass res =
128116 case res of
129117 Left e -> do
118+ dbg $ "handlePass: caught exception"
130119 liftIO $ mapM_ killThread pending
131120 return (Just e)
132− Right _ -> do
121+ Right [] -> return Nothing
122+ Right _ -> do
133123 liftIO $ atomically $ writeTChan pchan
134124 (PassOnResult (unsafeCoerce res))
135125 return Nothing
@@ -230,10 +220,10 @@ instance Read SomeException where
230220
231221forkFinally1 :: (MonadIO m, MonadBaseControl IO m) =>
232222 m a -> (Either SomeException a -> IO ()) -> m ThreadId
233−forkFinally1 action and_then =
223+forkFinally1 action preExit =
234224 EL.mask $ \restore ->
235225 liftBaseWith $ \runInIO -> forkIO $ do
236− _ <- runInIO $ EL.try (restore action) >>= liftIO . and_then
226+ _ <- runInIO $ EL.try (restore action) >>= liftIO . preExit
237227 return ()
238228
239229-- | Run a given context in a new thread.
@@ -258,33 +248,38 @@ forkContextWith runCtx context = do
258248 childContext ctx = do
259249 pendingRef <- liftIO $ newIORef []
260250 chan <- liftIO $ atomically newTChan
251+ resultsRef <- liftIO $ newIORef []
261252 -- shares the threadCredit of the parent by default
262253 return $ ctx
263254 { parentChannel = Just (childChannel ctx)
264255 , pendingThreads = pendingRef
265256 , childChannel = chan
257+ , accumResults = resultsRef
266258 }
267259
268260 beforeExit ctx res = do
261+ tid <- myThreadId
269262 exc <- case res of
270263 Left e -> do
264+ dbg $ "beforeExit: " ++ show tid ++ " caught exception"
271265 liftIO $ killChildren ctx
272266 return (Just e)
273267 Right _ -> return Nothing
274268
275269 e <- waitForChildren ctx exc
276270
277− tid <- myThreadId
278271 -- We are guaranteed to have a parent because we have been explicitly
279272 -- forked by some parent.
280273 let p = fromJust (parentChannel ctx)
274+ results <- liftIO $ readIORef (accumResults ctx)
281275 signalQSemB (threadCredit ctx)
282276 liftIO $ atomically $ writeTChan p
283− (ChildDone tid (maybe (unsafeCoerce res) Left e))
277+ (ChildDone tid (maybe (unsafeCoerce (Right results)) Left e))
284278
285279-- | A wrapper to first decide whether to run the context in the same thread or
286280-- a new thread and then run the context in that thread.
287281--
282+-- XXX create a canFork function
288283resumeContextWith :: (MonadBaseControl IO m, MonadIO m, MonadThrow m)
289284 => (Context -> m (Maybe a)) -> Context -> m ()
290285resumeContextWith runCtx context = do
@@ -339,9 +334,11 @@ loopContextWith ioaction ctx = do
339334 let ctx' = updateContextEvent ctx streamData
340335 case streamData of
341336 SMore _ -> do
342− resumeContextWith resume ctx'
337+ resumeContextWith (\x -> resume x >> return Nothing) ctx'
343338 loopContextWith ioaction ctx
344− _ -> resume ctx' -- run synchronously
339+ _ -> do
340+ _ <- resume ctx' -- run synchronously
341+ return Nothing -- we are done with the loop
345342
346343-- | Run an IO action one or more times to generate a stream of tasks. The IO
347344-- action returns a 'StreamData'. When it returns an 'SMore' or 'SLast' a new
@@ -360,7 +357,7 @@ parallel ioaction = AsyncT $ do
360357 -- generating the event data from the ioaction.
361358 context <- get
362359 case event context of
363− -- We have already executed the ioaction in the parent thread and put the
360+ -- We have already executed the ioaction in the parent thread and put its
364361 -- result in the context and now we are continuing the context in a new
365362 -- thread. Just return the result of the ioaction stored in the 'event'
366363 -- field.
test/3-async.hs+15−11
@@ -5,16 +5,20 @@ import System.IO
55
66import Duct
77
8−main = waitAsync $ do
9− liftIO $ hSetBuffering stdout LineBuffering
10− mainThread <- liftIO myThreadId
11− liftIO $ putStrLn $ "Main thread: " ++ show mainThread
12− x <- async (randomIO :: IO Int)
13− liftIO $ putStrLn $ show x
8+main = do
9+ hSetBuffering stdout LineBuffering
10+ xs <- waitAsync $ threads 100 $ do
11+ liftIO $ hSetBuffering stdout LineBuffering
12+ mainThread <- liftIO myThreadId
13+ liftIO $ putStrLn $ "Main thread: " ++ show mainThread
14+ x <- async (randomIO :: IO Int)
15+ liftIO $ putStrLn $ show x
1416
15− y <- async (randomIO :: IO Int)
17+ y <- async (randomIO :: IO Int)
1618
17− liftIO $ threadDelay 1000000
18− evThread <- liftIO myThreadId
19− liftIO $ putStrLn $ "Event thread: " ++ show evThread
20− liftIO $ putStrLn $ show x
19+ liftIO $ threadDelay 1000000
20+ evThread <- liftIO myThreadId
21+ liftIO $ putStrLn $ "Event thread: " ++ show evThread
22+ liftIO $ putStrLn $ show y
23+ return (x, y)
24+ putStrLn $ show xs