on master help sign in
implement log replay
a9d6c29Harendra Kumar
src/Strands.hs+4
@@ -14,11 +14,15 @@ module Strands
14 , gather
14 , gather
15 , gatherLogged
15 , gatherLogged
16 , async
16 , async
17+ , each
17 , sample
18 , sample
18 , threads
19 , threads
19 , logged
20 , logged
20 , suspend
21 , suspend
22+ , withLog
23+ , eachWithLog
21 , Log
24 , Log
25+ , Loggable
22 )
26 )
23where
27where
24
28
src/Strands/Context.hs+1
@@ -47,6 +47,7 @@ import GHC.Prim (Any)
47-- | Constraint type synonym for a value that can be logged.
47-- | Constraint type synonym for a value that can be logged.
48type Loggable a = (Show a, Read a)
48type Loggable a = (Show a, Read a)
49
49
50+-- XXX remove the Maybe as we never log Nothing
50data LogEntry =
51data LogEntry =
51 Executing -- we are inside this computation, not yet done
52 Executing -- we are inside this computation, not yet done
52 | Result (Maybe String) -- computation done, we have the result to replay
53 | Result (Maybe String) -- computation done, we have the result to replay
src/Strands/Threads.hs+57−35
@@ -5,6 +5,7 @@ module Strands.Threads
5 ( parallel
5 ( parallel
6 , waitEvents
6 , waitEvents
7 , async
7 , async
8+ , each
8 , sample
9 , sample
9 , sync
10 , sync
10 --, react
11 --, react
@@ -17,6 +18,8 @@ module Strands.Threads
17 , suspend
18 , suspend
18 , waitLogged
19 , waitLogged
19 , gatherLogged
20 , gatherLogged
21+ , withLog
22+ , eachWithLog
20 )
23 )
21where
24where
22
25
@@ -391,6 +394,10 @@ async io = do
391 SLast x -> return x
394 SLast x -> return x
392 -- SError e -> back e
395 -- SError e -> back e
393
396
397+each :: (MonadIO m, MonadBaseControl IO m, MonadThrow m)
398+ => [a] -> AsyncT m a
399+each xs = foldl (<|>) empty $ map (async . return) xs
400+
394-- | Force an async computation to run synchronously. It can be useful in an
401-- | Force an async computation to run synchronously. It can be useful in an
395-- 'Alternative' composition to run the alternative only after finishing a
402-- 'Alternative' composition to run the alternative only after finishing a
396-- computation. Note that in Applicatives it might result in an undesired
403-- computation. Note that in Applicatives it might result in an undesired
@@ -551,39 +558,43 @@ wait m = waitAsync (const mzero) Nothing m
551
558
552logged :: (Loggable a, MonadIO m) => AsyncT m a -> AsyncT m a
559logged :: (Loggable a, MonadIO m) => AsyncT m a -> AsyncT m a
553logged m = AsyncT $ do
560logged m = AsyncT $ do
554− ctx <- get
561+ ctx <- get
555− case logsRef ctx of
562+ case logsRef ctx of
556− Nothing -> runAsyncT m
563+ Nothing -> runAsyncT m
557− Just _ -> withLog ctx m
564+ Just _ -> runLogged ctx (runAndLogResult m)
565+
558 where
566 where
559
567
560− withLog ctx action =
568+ runLogged ctx action =
561− case journal ctx of
569+ case journal ctx of
562− -- no replay
570+ -- no replay
563− j@(CtxLog _ []) -> do
571+ CtxLog ls [] -> do
564− preLog ctx j
572+ put $ ctx {journal = CtxLog (Executing : ls) []}
565− x <- runAsyncT action
573+ action
566− postLog ctx j x
574+
567− return x
568−
569− -- replaying the log
575+ -- replaying the log
570− CtxLog ls (r:rs) -> do
576+ CtxLog ls (r:rs) -> do
571− let j = CtxLog ls rs
577+ -- dbg $ "Replay: j: " ++ show j
572− preLog ctx j
573− case r of
578+ case r of
574− Executing -> do
579+ Executing -> do
575− x <- runAsyncT action
580+ put $ ctx {journal = CtxLog (r : ls) rs}
576− postLog ctx (CtxLog ls []) x
581+ action
577− return x
578− Result val -> do
582+ Result val -> do
579− let x = fmap read val
583+ let x = fmap read val
580− postLog ctx j x
584+ put $ ctx {journal = CtxLog (r : ls) rs}
581− return x
585+ return x
582−
586+
583− -- add an Executing entry at the head of the log
587+ runAndLogResult action = runAsyncT $ do
584− preLog = appendLog Executing
588+ x <- action
585− postLog ctx j x = appendLog (Result (fmap show x)) ctx j
589+ logResult x
590+ return x
591+
592+ -- replaces the head of the log with the supplied result
593+ logResult x = AsyncT $ do
594+ ctx@Context{journal = CtxLog (_ : ls) _} <- get
595+ let entry = Result (fmap show (Just x))
586− appendLog l ctx (CtxLog ls rs) = put $ ctx {journal = CtxLog (l : ls) rs}
596+ put $ ctx {journal = CtxLog (entry : ls) []}
597+ return $ Just ()
587
598
588-- XXX enable or disable the suspension points by sending an exception to the
599-- XXX enable or disable the suspension points by sending an exception to the
589-- computation.
600-- computation.
@@ -612,10 +623,22 @@ suspend = logged $ AsyncT $ do
612 _ -> error "Bug: replay inside suspend"
623 _ -> error "Bug: replay inside suspend"
613 where logResult x = Result (fmap show x)
624 where logResult x = Result (fmap show x)
614
625
626+-- | Compose a computation using previously captured logs
627+withLog :: Monad m => AsyncT m a -> Log -> AsyncT m a
628+withLog m (Log entries) = AsyncT $ do
629+ ctx <- get
630+ put $ ctx {journal = CtxLog [] (reverse entries)}
631+ runAsyncT m
632+
633+-- | Compose a computation using previously captured list of logs. Composes a
634+-- group of computations, one for each log.
635+eachWithLog :: (MonadIO m, MonadBaseControl IO m, MonadThrow m)
636+ => AsyncT m a -> [Log] -> AsyncT m a
637+eachWithLog m logs = each logs >>= withLog m
638+
615-- | Run an 'AsyncT m' computation with logging enabled and collect the results
639-- | Run an 'AsyncT m' computation with logging enabled and collect the results
616-- or logs generated by each thread of the computation.
640-- or logs generated by each thread of the computation.
617−gatherLogged :: forall m a. (MonadIO m, MonadCatch m)
641+gatherLogged :: (MonadIO m, MonadCatch m) => AsyncT m a -> m ([a], [Log])
618− => AsyncT m a -> m ([a], [Log])
619gatherLogged m = do
642gatherLogged m = do
620 resultsRef <- liftIO $ newIORef []
643 resultsRef <- liftIO $ newIORef []
621 lref <- liftIO $ newIORef []
644 lref <- liftIO $ newIORef []
@@ -627,8 +650,7 @@ gatherLogged m = do
627-- | Run an 'AsyncT m' computation with logging enabled, wait for it to finish
650-- | Run an 'AsyncT m' computation with logging enabled, wait for it to finish
628-- and discard the results. If the computation suspends collect the logs to
651-- and discard the results. If the computation suspends collect the logs to
629-- replay later.
652-- replay later.
630−waitLogged :: forall m a. (MonadIO m, MonadCatch m)
653+waitLogged :: (MonadIO m, MonadCatch m) => AsyncT m a -> m [Log]
631− => AsyncT m a -> m [Log]
632waitLogged m = do
654waitLogged m = do
633 lref <- liftIO $ newIORef []
655 lref <- liftIO $ newIORef []
634 waitAsync (const mzero) (Just lref) m
656 waitAsync (const mzero) (Just lref) m
test/logged.hs+10−3
@@ -2,11 +2,18 @@ import Strands
2import Control.Monad.IO.Class (liftIO)
2import Control.Monad.IO.Class (liftIO)
3
3
4main = do
4main = do
5+ logs <- waitLogged comp
6+ -- XXX even if we use wait replay should still work, though new logs will
7+ -- not be generated.
8+ logs1 <- waitLogged $ eachWithLog comp logs
9+ logs2 <- waitLogged $ eachWithLog comp logs1
10+ putStrLn $ show logs2
11+
12+ where
5− xs <- waitLogged $ logged $ threads 0 $ do
13+ comp = logged $ threads 5 $ do
6− r <- logged $ return (5::Int)
14+ r <- logged $ each [1..3]
7 logged $ liftIO $ print ("A",r)
15 logged $ liftIO $ print ("A",r)
8 suspend
16 suspend
9 logged $ liftIO $ print ("B",r)
17 logged $ liftIO $ print ("B",r)
10 suspend
18 suspend
11 liftIO $ print ("C",r)
19 liftIO $ print ("C",r)
12− putStrLn $ show xs