on master help sign in
Add context save, restore and continue functions

Modularize the context functionality

commit4adbddf74c37eb8c75df439730ff709337645bbf authorHarendra Kumar <harendra.kumar@gmail.com> dateSun 11 Jun 2017, 02:56 (9 years ago) parenta39705b
5 files changed+105−80
duct.cabal+2−1 src/Duct/AsyncT.hs+9−43 src/Duct/Event.hs → src/Duct/Context.hs+77−11 src/Duct/Threads.hs+12−19 test/1-event.hs+5−6
duct.cabal+2−1
@@ -20,7 +20,7 @@ cabal-version: >= 1.10
2020library
2121 hs-source-dirs: src
2222 exposed-modules: Duct
23− , Duct.Event
23+ , Duct.Context
2424 , Duct.Log
2525 , Duct.AsyncT
2626 , Duct.Threads
@@ -33,6 +33,7 @@ library
3333 , containers >= 0.5.6
3434 , time >= 1.5
3535 , transformers >= 0.4.2
36+ , ghc-prim
3637
3738 -- libraries not bundled with GHC
3839 , lifted-base
src/Duct/AsyncT.hs+9−43
@@ -14,7 +14,6 @@ module Duct.AsyncT
1414 , waitAsync
1515 , (<**)
1616 , onNothing
17− , RemoteStatus (..)
1817 , waitForChildren
1918 , dbg
2019 )
@@ -39,21 +38,13 @@ import Data.IORef (IORef, newIORef, readIORef,
3938 writeIORef)
4039import Data.List (delete)
4140import Data.Maybe (isJust, isNothing)
42−import Unsafe.Coerce (unsafeCoerce)
4341
44−import Duct.Event
42+import Duct.Context
4543
4644-- import Debug.Trace
4745
4846newtype AsyncT m a = AsyncT { runAsyncT :: StateT Context m (Maybe a) }
4947
50−------------------------------------------------------------------------------
51−-- Remote data types
52−------------------------------------------------------------------------------
53−
54−data RemoteStatus = WasRemote | WasParallel | NoRemote
55− deriving (Typeable, Eq, Show)
56−
5748------------------------------------------------------------------------------
5849-- Utilities
5950------------------------------------------------------------------------------
@@ -96,12 +87,10 @@ instance Monad m => Alternative (AsyncT m) where
9687 empty = AsyncT $ return Nothing
9788 (<|>) x y = AsyncT $ do
9889 mx <- runAsyncT x
99− was <- getData `onNothing` return NoRemote
100− if was == WasRemote
101− then return Nothing
102− else case mx of
103− Nothing -> runAsyncT y
104− justx -> return justx
90+ loc <- getLocation
91+ case loc of
92+ RemoteNode -> return Nothing
93+ _ -> maybe (runAsyncT y) (return . Just) mx
10594
10695-- | A synonym of 'empty' that can be used in a monadic expression. It stops
10796-- the computation, which allows the next computation in an 'Alternative'
@@ -113,38 +102,15 @@ stop = empty
113102-- Monad
114103------------------------------------------------------------------------------
115104
116−-- | Total variant of `tail` that returns an empty list when given an empty list.
117−tailsafe :: [a] -> [a]
118−tailsafe [] = []
119−tailsafe (_:xs) = xs
120−
121105instance Monad m => Monad (AsyncT m) where
122− return = pure
123−
124− m >>= f = AsyncT $ do
125− -- Save the 'm' and 'f', in case we migrate to a new thread before this
126− -- bind operation completes, we run the operation manually in the new
127− -- thread.
128− modify $ \Context { fstack = fs, .. } ->
129− Context { currentm = unsafeCoerce m
130− , fstack = unsafeCoerce f : fs
131− , .. }
106+ return = pure
132107
133108 -- Inner bind operations in 'm' add their 'f' to fstack. If we migrate to
134109 -- a new thread somewhere in the middle we unwind the fstack and run these
135110 -- functions manually after migration.
136− mres <- runAsyncT m
137−
138− let res = case mres of
139− Nothing -> empty
140− Just x -> f x
141−
142− modify $ \Context { fstack = fs, .. } ->
143− Context { currentm = unsafeCoerce res
144− , fstack = tailsafe fs
145− , .. }
146−
147− runAsyncT res
111+ m >>= f = AsyncT $ do
112+ saveContext m f
113+ runAsyncT m >>= restoreContext >>= runAsyncT
148114
149115instance Monad m => MonadPlus (AsyncT m) where
150116 mzero = empty
src/Duct/Event.hs → src/Duct/Context.hsrenamed+77−11
@@ -1,9 +1,16 @@
11{-# LANGUAGE ExistentialQuantification #-}
22{-# LANGUAGE FlexibleContexts #-}
3+{-# LANGUAGE RecordWildCards #-}
34
4−module Duct.Event
5+module Duct.Context
56 ( Context(..)
67 , initContext
8+ , saveContext
9+ , restoreContext
10+ , continueContext
11+ , Location(..)
12+ , getLocation
13+ , setLocation
714 , getData
815 , setData
916 , modifyData
@@ -11,33 +18,39 @@ module Duct.Event
1118 )
1219where
1320
21+import Control.Applicative (Alternative(..))
1422import Control.Concurrent (ThreadId)
1523import Control.Concurrent.STM (TChan)
16−import Control.Monad.State (MonadState, gets, modify)
24+import Control.Monad.State (MonadState, gets, modify, StateT, MonadPlus)
25+import qualified Control.Monad.Trans.State.Lazy as Lazy (get, gets, modify, put)
1726import Data.Dynamic (TypeRep, Typeable, typeOf)
1827import Data.IORef (IORef)
1928import qualified Data.Map as M
2029import Unsafe.Coerce (unsafeCoerce)
30+import GHC.Prim (Any)
2131
2232------------------------------------------------------------------------------
2333-- State of a continuation
2434------------------------------------------------------------------------------
2535
26−type SData = ()
27−
2836-- | Describes the context of a computation.
29−data Context = forall m a b. Context
30− { event :: Maybe SData -- untyped, XXX rename to mailbox - can we do
37+data Context = Context
38+ { event :: Maybe () -- untyped, XXX rename to mailbox - can we do
3139 -- away with this?
3240 -- ^ event data to use in a continuation in a new thread
3341
3442 -- the 'm' and 'f' in an 'm >>= f' operation of the monad
3543 -- In nested binds we store the current m only, but the whole stack of fs
36− , currentm :: m a
37− , fstack :: [a -> m b]
44+ , currentm :: Any -- untyped, the real type is: m a
45+ , fstack :: [Any] -- untyped, the real type is: [a -> m b]
3846 -- ^ List of continuations
3947
40− , mfData :: M.Map TypeRep SData
48+ -- log only when there is a restore or if we are teleporting
49+ -- We can use a HasLog constraint to statically enable/disable logging
50+ -- , journal :: Maybe Log
51+ , location :: Location
52+
53+ , mfData :: M.Map TypeRep Any -- untyped, type coerced
4154 -- ^ State data accessed with get or put operations
4255
4356 -- XXX All of the following can be removed
@@ -92,15 +105,68 @@ initContext
92105 -> IORef Int
93106 -> Context
94107initContext x childChan pending credit =
95− Context { event = mempty
96− , currentm = x
108+ Context { event = mempty
109+ , currentm = unsafeCoerce x
97110 , fstack = []
111+ , location = Local
98112 , mfData = mempty
99113 , parentChannel = Nothing
100114 , childChannel = childChan
101115 , pendingThreads = pending
102116 , threadCredit = credit }
103117
118+------------------------------------------------------------------------------
119+-- Where is the computation running?
120+------------------------------------------------------------------------------
121+
122+data Location = Local | ForkedThread | RemoteNode
123+ deriving (Typeable, Eq, Show)
124+
125+getLocation :: Monad m => StateT Context m Location
126+getLocation = Lazy.gets location
127+
128+setLocation :: Monad m => Location -> StateT Context m ()
129+setLocation loc = Lazy.modify $ \Context { .. } -> Context { location = loc, .. }
130+
131+------------------------------------------------------------------------------
132+-- Saving and restoring the context of a computation
133+------------------------------------------------------------------------------
134+
135+-- Save the 'm' and 'f', in case we migrate to a new thread before the current
136+-- bind operation completes, we run the operation manually in the new thread
137+-- using this context.
138+saveContext :: Monad m => f a -> (a -> f b) -> StateT Context m ()
139+saveContext m f =
140+ Lazy.modify $ \Context { fstack = fs, .. } ->
141+ Context { currentm = unsafeCoerce m
142+ , fstack = unsafeCoerce f : fs
143+ , .. }
144+
145+-- pop the top function from the continuation stack, create the next closure,
146+-- set it as the current closure and return it.
147+restoreContext :: (Alternative f, Monad m) => Maybe a -> StateT Context m (f b)
148+restoreContext x = do
149+ -- XXX fstack must be non-empty when this is called.
150+ ctx@Context { fstack = f:fs } <- Lazy.get
151+
152+ let mres = case x of
153+ Nothing -> empty
154+ Just y -> (unsafeCoerce f) y
155+
156+ Lazy.put ctx { currentm = unsafeCoerce mres, fstack = fs }
157+
158+ return mres
159+
160+continueContext :: MonadPlus m => Context -> m a
161+continueContext Context { currentm = m, fstack = fs } =
162+ unsafeCoerce m >>= runfStack (unsafeCoerce fs)
163+
164+ where
165+
166+ -- runfStack :: Monad m => [a -> AsyncT m a] -> a -> AsyncT m b
167+ runfStack [] _ = empty
168+ runfStack (f:ff) x = f x >>= runfStack ff
169+
104170------------------------------------------------------------------------------
105171-- * Extensible State: Session Data Management
106172------------------------------------------------------------------------------
src/Duct/Threads.hs+12−19
@@ -12,7 +12,7 @@ module Duct.Threads
1212 )
1313where
1414
15−import Control.Applicative ((<|>), empty)
15+import Control.Applicative ((<|>))
1616import Control.Concurrent (ThreadId, forkIO, killThread,
1717 myThreadId, threadDelay)
1818import Control.Concurrent.STM (TChan, atomically, newTChan,
@@ -35,7 +35,7 @@ import Data.Maybe (fromJust)
3535import Unsafe.Coerce (unsafeCoerce)
3636
3737import Duct.AsyncT
38−import Duct.Event
38+import Duct.Context
3939
4040------------------------------------------------------------------------------
4141-- Pick up from where we left in the previous thread
@@ -45,15 +45,8 @@ import Duct.Event
4545-- to a new thread. Run the stack of pending functions in fstack. The types
4646-- don't match. We just coerce the types here, we know that they actually match.
4747
48−continue :: (MonadIO m, Monad (AsyncT m)) => Context -> StateT Context m (Maybe a)
49−continue Context { currentm = x, fstack = fs } =
50− runAsyncT $ unsafeCoerce x >>= runfStack (unsafeCoerce fs)
51−
52− where
53−
54− runfStack :: Monad m => [a -> AsyncT m a] -> a -> AsyncT m b
55− runfStack [] _ = empty
56− runfStack (f:fs) x = f x >>= runfStack fs
48+continue :: Monad m => Context -> StateT Context m (Maybe a)
49+continue = runAsyncT . continueContext
5750
5851------------------------------------------------------------------------------
5952-- Thread Management (creation, reaping and killing)
@@ -244,8 +237,8 @@ parallel ioaction = AsyncT $ do
244237 -- in this thread or a new thread.
245238 Nothing -> do
246239 lift $ genAsyncEvents cont ioaction
247− was <- getData `onNothing` return NoRemote
248− when (was /= WasRemote) $ setData WasParallel
240+ loc <- getLocation
241+ when (loc /= RemoteNode) $ setLocation ForkedThread
249242 return Nothing
250243
251244-- | An task stream generator that produces an infinite stream of tasks by
@@ -273,10 +266,10 @@ async io = do
273266-- computation. Note that in Applicatives it might result in an undesired
274267-- serialization.
275268sync :: MonadIO m => AsyncT m a -> AsyncT m a
276−sync x = do
277− setData WasRemote
278− r <- x
279− delData WasRemote
269+sync x = AsyncT $ do
270+ setLocation RemoteNode
271+ r <- runAsyncT x
272+ setLocation Local
280273 return r
281274
282275-- | An task stream generator that produces an infinite stream of tasks by
@@ -320,8 +313,8 @@ react setHandler iob = AsyncT $ do
320313 lift $ setHandler $ \dat ->do
321314 runStateT (continue cont) cont{event= Just $ unsafeCoerce dat}
322315 liftIO iob
323− was <- getData `onNothing` return NoRemote
324− when (was /= WasRemote) $ setData WasParallel
316+ loc <- getLocation
317+ when (loc /= RemoteNode) $ setLocation ForkedThread
325318 return Nothing
326319
327320 j@(Just _) -> do
test/1-event.hs+5−6
@@ -6,23 +6,22 @@ import Control.Concurrent.STM
66import Control.Monad.State
77import Data.IORef
88
9−import Duct.Event
9+import Duct.Context
1010
11−f :: StateT EventF IO String
11+f :: StateT Context IO String
1212f = do
1313 setData "x"
1414 Just x <- getData
1515 return x
1616
17−runEvent :: forall m a. (Alternative m, MonadIO m) => StateT EventF m a -> m a
17+runEvent :: forall m a. (Alternative m, MonadIO m) => StateT Context m a -> m a
1818runEvent t = do
1919 zombieChan <- liftIO $ atomically newTChan
2020 pendingRef <- liftIO $ newIORef []
2121 credit <- liftIO $ newIORef maxBound
22− th <- liftIO $ myThreadId
2322
24− (r, _) <- runStateT t $ initEventF
25− (empty :: m a) th zombieChan pendingRef credit
23+ (r, _) <- runStateT t $ initContext
24+ (empty :: m a) zombieChan pendingRef credit
2625 return r
2726
2827main = do