@@ -12,6 +12,11 @@ module Strands.Threads
@@ -27,7 +32,7 @@ import qualified Control.Exception.Lifted as EL
27import Control.Monad.Catch (MonadCatch, MonadThrow, throwM,
32import Control.Monad.Catch (MonadCatch, MonadThrow, throwM,
29import Control.Monad.IO.Class (MonadIO (..))
34import Control.Monad.IO.Class (MonadIO (..))
30−import Control.Monad.State (get, gets, modify, mzero,
35+import Control.Monad.State (get, gets, modify, mzero, put,
31 runStateT, when, StateT)
36 runStateT, when, StateT)
32import Control.Monad.Trans.Class (MonadTrans (lift))
37import Control.Monad.Trans.Class (MonadTrans (lift))
33import Control.Monad.Trans.Control (MonadBaseControl, liftBaseWith)
38import Control.Monad.Trans.Control (MonadBaseControl, liftBaseWith)
@@ -496,14 +501,14 @@ noTrans x = AsyncT $ x >>= return . Just
496-- | Run an 'AsyncT m' computation and collect the results generated by each
501-- | Run an 'AsyncT m' computation and collect the results generated by each
497-- thread of the computation in a list.
502-- thread of the computation in a list.
498waitAsync :: forall m a. (MonadIO m, MonadCatch m)
503waitAsync :: forall m a. (MonadIO m, MonadCatch m)
499− => (a -> AsyncT m a) -> AsyncT m a -> m ()
504+ => (a -> AsyncT m a) -> Maybe (IORef [Log]) -> AsyncT m a -> m ()
500−waitAsync finalizer m = do
505+waitAsync finalizer lref m = do
501 childChan <- liftIO $ atomically newTChan
506 childChan <- liftIO $ atomically newTChan
502 pendingRef <- liftIO $ newIORef []
507 pendingRef <- liftIO $ newIORef []
503 credit <- liftIO $ newIORef maxBound
508 credit <- liftIO $ newIORef maxBound
505 let ctx = initContext (empty :: AsyncT m a) childChan pendingRef credit
510 let ctx = initContext (empty :: AsyncT m a) childChan pendingRef credit
506− finalizer
511+ finalizer lref
508 r <- try $ runStateT (runAsyncT $ m >>= finalizer) ctx
513 r <- try $ runStateT (runAsyncT $ m >>= finalizer) ctx
@@ -532,11 +537,101 @@ gather :: forall m a. (MonadIO m, MonadCatch m)
532 => AsyncT m a -> m [a]
537 => AsyncT m a -> m [a]
533gather m = do
538gather m = do
534 resultsRef <- liftIO $ newIORef []
539 resultsRef <- liftIO $ newIORef []
535− waitAsync (gatherResult resultsRef) m
540+ waitAsync (gatherResult resultsRef) Nothing m
536 liftIO $ readIORef resultsRef
541 liftIO $ readIORef resultsRef
538-- | Run an 'AsyncT m' computation, wait for it to finish and discard the
543-- | Run an 'AsyncT m' computation, wait for it to finish and discard the
539-- results.
544-- results.
540wait :: forall m a. (MonadIO m, MonadCatch m)
545wait :: forall m a. (MonadIO m, MonadCatch m)
541 => AsyncT m a -> m ()
546 => AsyncT m a -> m ()
542−wait m = waitAsync (const mzero) m
547+wait m = waitAsync (const mzero) Nothing m
549+------------------------------------------------------------------------------
551+------------------------------------------------------------------------------
553+logged :: (Loggable a, MonadIO m) => AsyncT m a -> AsyncT m a
554+logged m = AsyncT $ do
557+ Nothing -> runAsyncT m
558+ Just _ -> withLog ctx m
561+ withLog ctx action =
564+ j@(CtxLog _ []) -> do
566+ x <- runAsyncT action
570+ -- replaying the log
571+ CtxLog ls (r:rs) -> do
572+ let j = CtxLog ls rs
576+ x <- runAsyncT action
577+ postLog ctx (CtxLog ls []) x
580+ let x = fmap read val
584+ -- add an Executing entry at the head of the log
585+ preLog = appendLog Executing
586+ postLog ctx j x = appendLog (Result (fmap show x)) ctx j
587+ appendLog l ctx (CtxLog ls rs) = put $ ctx {journal = CtxLog (l : ls) rs}
589+-- XXX enable or disable the suspension points by sending an exception to the
592+-- | Suspend works as an exit point with current thread state (partially done)
593+-- saved. The computation exits only if all the threads in the computation hit
594+-- the suspend point or exit normally. The threads which hit the suspension and
595+-- were suspended can be re-started by replaying the log. If there are threads
596+-- which did not suspend or did not exit, the computation will not exit. For
597+-- completed threads output is returned and for suspended threads the logs to
598+-- resume them are returned.
600+suspend :: MonadIO m => AsyncT m ()
601+suspend = logged $ AsyncT $ do
604+ Nothing -> return $ Just ()
608+ -- replace the "Executing" entry at the head of the log
609+ -- with a "()" so that we do not enter suspend on replay
610+ liftIO $ atomicModifyIORef ref $ \logs ->
611+ (Log (logResult (Just ()) : tail ls) : logs, ())
613+ _ -> error "Bug: replay inside suspend"
614+ where logResult x = Result (fmap show x)
616+-- | Run an 'AsyncT m' computation with logging enabled and collect the results
617+-- or logs generated by each thread of the computation.
618+gatherLogged :: forall m a. (MonadIO m, MonadCatch m)
619+ => AsyncT m a -> m ([a], [Log])
621+ resultsRef <- liftIO $ newIORef []
622+ lref <- liftIO $ newIORef []
623+ waitAsync (gatherResult resultsRef) (Just lref) m
624+ res <- liftIO $ readIORef resultsRef
625+ logs <- liftIO $ readIORef lref
628+-- | Run an 'AsyncT m' computation with logging enabled, wait for it to finish
629+-- and discard the results. If the computation suspends collect the logs to
631+waitLogged :: forall m a. (MonadIO m, MonadCatch m)
632+ => AsyncT m a -> m [Log]
634+ lref <- liftIO $ newIORef []
635+ waitAsync (const mzero) (Just lref) m
636+ logs <- liftIO $ readIORef lref