@@ -9,10 +9,11 @@ module Strands.Threads
15−import Control.Applicative ((<|>))
16+import Control.Applicative ((<|>), empty)
16import Control.Concurrent (ThreadId, forkIO, killThread,
17import Control.Concurrent (ThreadId, forkIO, killThread,
17 myThreadId, threadDelay)
18 myThreadId, threadDelay)
18import Control.Concurrent.STM (TChan, atomically, newTChan,
19import Control.Concurrent.STM (TChan, atomically, newTChan,
@@ -21,7 +22,8 @@ import Control.Concurrent.STM (TChan, atomically, newTChan,
21import Control.Exception (ErrorCall (..),
22import Control.Exception (ErrorCall (..),
22 SomeException (..), catch)
23 SomeException (..), catch)
23import qualified Control.Exception.Lifted as EL
24import qualified Control.Exception.Lifted as EL
24−import Control.Monad.Catch (MonadThrow, throwM)
25+import Control.Monad.Catch (MonadCatch, MonadThrow, throwM,
25import Control.Monad.IO.Class (MonadIO (..))
27import Control.Monad.IO.Class (MonadIO (..))
26import Control.Monad.State (get, gets, modify,
28import Control.Monad.State (get, gets, modify,
27 runStateT, when, StateT)
29 runStateT, when, StateT)
@@ -80,6 +82,53 @@ runContext ctx = do
80-- Thread Management (creation, reaping and killing)
82-- Thread Management (creation, reaping and killing)
81------------------------------------------------------------------------------
83------------------------------------------------------------------------------
85+-- XXX We are using unbounded channels so this will not block on writing to
86+-- pchan. We can use bounded channels to throttle the creation of threads based
87+-- on consumption rate.
88+processOneEvent :: MonadIO m
90+ -> Either (TChan (ChildEvent a)) (IORef [a])
92+ -> m ([ThreadId], Maybe SomeException)
93+processOneEvent ev dest pending = do
94+ -- Collect results unless we have already encountered an exception.
96+ ChildDone _ (Just e) -> handleException e
97+ ChildResult (Left e) -> handleException e
98+ ChildDone tid Nothing -> return (delete tid pending, Nothing)
99+ ChildResult (Right r) -> do
101+ Left chan -> liftIO $ atomically $ writeTChan chan ev
103+ liftIO $ modifyIORef ref $ \rs -> unsafeCoerce r : rs
104+ return (pending, Nothing)
107+ handleException e = do
108+ liftIO $ mapM_ killThread pending
109+ return (pending, Just e)
111+drainChildren :: MonadIO m
112+ => Either (TChan (ChildEvent a)) (IORef [a])
113+ -> TChan (ChildEvent a)
115+ -> m ([ThreadId], Maybe SomeException)
116+drainChildren dest cchan pending =
118+ [] -> return (pending, Nothing)
120+ ev <- liftIO $ atomically $ readTChan cchan
121+ (p, e) <- processOneEvent ev dest pending
122+ maybe (drainChildren dest cchan p) (const $ return (p, e)) e
124+waitForChildren :: MonadIO m => Context -> m (Maybe SomeException)
125+waitForChildren ctx = do
126+ let pendingRef = pendingThreads ctx
127+ pending <- liftIO $ readIORef pendingRef
128+ (p, e) <- drainChildren (getCtxResultDest ctx) (childChannel ctx) pending
129+ liftIO $ writeIORef pendingRef p
83tryReclaimZombies :: (MonadIO m, MonadThrow m) => Context -> m ()
132tryReclaimZombies :: (MonadIO m, MonadThrow m) => Context -> m ()
84tryReclaimZombies ctx = do
133tryReclaimZombies ctx = do
85 let dest = getCtxResultDest ctx
134 let dest = getCtxResultDest ctx
@@ -466,3 +515,56 @@ noTrans x = AsyncT $ x >>= return . Just
466-- modifyThreads :: MonadIO m => (Int -> Int) -> AsyncT m ()
515-- modifyThreads :: MonadIO m => (Int -> Int) -> AsyncT m ()
467-- modifyThreads f =
516-- modifyThreads f =
519+------------------------------------------------------------------------------
521+------------------------------------------------------------------------------
523+collectResult :: MonadIO m => a -> StateT Context m ()
526+ case parentChannel ctx of
528+ let ref = fromJust $ accumResults ctx
529+ liftIO $ modifyIORef ref $ \rs -> unsafeCoerce r : rs
531+ -- XXX can we pass the result directly to the root thread
532+ -- instead of passing through all the parents? We can let the
533+ -- parent go away and handle the ChildDone events as well in
535+ liftIO $ atomically $ writeTChan chan
536+ (ChildResult (Right (unsafeCoerce r)))
538+-- | Invoked to store the result of the computation in the context and finish
539+-- the computation when the computation is done
540+finishComputation :: MonadIO m => a -> AsyncT m b
541+finishComputation x = AsyncT $ do
545+-- XXX pass a collector function and return a Traversable.
546+-- XXX Ideally it should be a non-empty list instead.
547+-- | Run an 'AsyncT m' computation. Returns a list of results of the
548+-- computation or may throw an exception.
549+waitAsync :: forall m a. (MonadIO m, MonadCatch m)
550+ => AsyncT m a -> m [a]
552+ childChan <- liftIO $ atomically newTChan
553+ pendingRef <- liftIO $ newIORef []
554+ resultsRef <- liftIO $ newIORef []
555+ credit <- liftIO $ newIORef maxBound
557+ let ctx = initContext (empty :: AsyncT m a) childChan pendingRef credit
558+ finishComputation resultsRef
560+ r <- try $ runStateT (runAsyncT $ m >>= finishComputation) ctx
563+ Left (exc :: SomeException) -> do
564+ liftIO $ readIORef pendingRef >>= mapM_ killThread
567+ e <- waitForChildren ctx
569+ Just (exc :: SomeException) -> throwM exc
570+ Nothing -> liftIO $ readIORef resultsRef