libraft 0.3.0.0 → 0.4.0.0
raw patch · 10 files changed
+69/−46 lines, 10 files
Files
- README.md +27/−19
- app/Main.hs +1/−1
- libraft.cabal +2/−2
- src/Examples/Raft/FileStore/Log.hs +2/−2
- src/Examples/Raft/FileStore/Persistent.hs +2/−2
- src/Examples/Raft/Socket/Node.hs +2/−2
- src/Raft.hs +15/−7
- src/Raft/Log/PostgreSQL.hs +2/−2
- src/Raft/Monad.hs +14/−7
- test/TestDejaFu.hs +2/−2
README.md view
@@ -208,7 +208,7 @@ `ConcIO` monad, which has implementations of concurrency primitives that act deterministically. This allows us to test that the raft nodes run correctly in a wide space of thread interleavings giving us more confidence that our code is-correct, assuming "correct" implementations of the `MonadRaftAsync` and+correct, assuming "correct" implementations of the `MonadRaftFork` and `MonadRaftChan` typeclasses. #### Persistent State@@ -357,20 +357,22 @@ ### Concurrency The last of the type class instances the programmer must provide for the monad-they are running the raft node in is a `MonadRaftAsync`, which provides the main-raft loop with a small set of concurrency primitives; The raft node needs to-know how to fork actions in the monad, and how to run a list of actions-concurrently, waiting for all to finish. The former is necessary for the raft-node to be able to fork its event producers, and the latter is required in order-to respond to clients and other raft nodes in a timely manner. The typeclass is-defined as follows:+they are running the raft node in is a `MonadRaftFork`, which provides the main+raft loop with the ability to fork a concurrent action; The raft node needs to+know how to fork actions in the monad. This is necessary for the raft node to +be able to fork its event producers, and run other actions concurrently; e.g.+a leader responding to all followers at the same time during a heartbeat RPC+broadcast. The typeclass is defined as follows: ```haskell-class Monad m => MonadRaftAsync m where- type RaftAsync m :: * -> *- raftAsync :: m a -> m (RaftAsync m a)- raftMapConcurrently :: Traversable t => (a -> m b) -> t a -> m (t b)- raftMapConcurrently_ :: Foldable t => (a -> m b) -> t a -> m ()+-- | The typeclass encapsulating the concurrency operations necessary for the+-- implementation of the main event handling loop.+class Monad m => MonadRaftFork m where+ type RaftThreadId m+ raftFork+ :: RaftThreadRole -- ^ The role of the current thread being forked+ -> m () -- ^ The action to fork+ -> m (RaftThreadId m) ``` The implementation of this typeclass is a bit subtle, and it is advised that@@ -381,13 +383,19 @@ transformer stack with `IO` the bottom: ```haskell-instance MonadRaftAsync MyMonad where- type RaftAsync MyMonad = RaftAsync IO- raftAsync myMonad = lift $ raftAsync (runMyMonad myMonad) - raftMapConcurrently f as = lift (raftMapConcurrently (runMyMonad . f) as)- raftMapConcurrently_ f as = lift (raftMapConcurrently_ (runMyMonad . f) as)+instance MonadRaftFork MyMonad where+ type RaftThreadId MyMonad = RaftThreadId IO+ raftFork threadRole myMonad = + lift $ raftFork threadRole (runMyMonad myMonad) ``` +The last thing to mention about this typeclass is the `RaftThreadRole` value+that must be passed to invocations of the `raftFork` function. In some+applications (and, noteably our concurrency testing suite) thread names can be+used for debugging and even message passing purposes. For instance of+`MonadRaftFork` that do not need to distinguish threads by name, simply ignore+the argument. + # The Raft Example (`raft-example`) In this library we provide a full fledged, non-production ready, example@@ -396,7 +404,7 @@ `src/Examples/Raft/...` or in `app/Main.hs`: `RaftExampleT` (found in `app/Main.hs`):-- `MonadRaftAsync`+- `MonadRaftFork` - `MonadRaftChan` - `RaftStateMachine`
app/Main.hs view
@@ -98,7 +98,7 @@ instance (MonadIO m, MonadRaftFork m) => MonadRaftFork (RaftExampleT m) where type RaftThreadId (RaftExampleT m) = RaftThreadId m- raftFork m = lift $ raftFork (runRaftExampleT m)+ raftFork r m = lift $ raftFork r (runRaftExampleT m) runRaftExampleT :: RaftExampleT m a -> m a runRaftExampleT = unRaftExampleT
libraft.cabal view
@@ -2,10 +2,10 @@ -- -- see: https://github.com/sol/hpack ----- hash: 2eb0f5597b5304ede9da9c1a97cc9bc16e5df0eb384c9f1ce56e8b1dc122f5b9+-- hash: ff992f3d3136b159258f789e3155e8842a492ac71a9863ae998fa57526e5b9ec name: libraft-version: 0.3.0.0+version: 0.4.0.0 synopsis: Raft consensus algorithm description: Please see the README on GitHub at <https://github.com/adjoint-io/raft#readme> category: Distributed Systems
src/Examples/Raft/FileStore/Log.hs view
@@ -138,6 +138,6 @@ instance (MonadIO m, MonadRaftFork m) => MonadRaftFork (RaftLogFileStoreT m) where type RaftThreadId (RaftLogFileStoreT m) = RaftThreadId m- raftFork m = do+ raftFork r m = do raftLogFile <- ask- lift $ raftFork (runRaftLogFileStoreT raftLogFile m)+ lift $ raftFork r (runRaftLogFileStoreT raftLogFile m)
src/Examples/Raft/FileStore/Persistent.hs view
@@ -133,6 +133,6 @@ instance (MonadIO m, MonadRaftFork m) => MonadRaftFork (RaftPersistFileStoreT m) where type RaftThreadId (RaftPersistFileStoreT m) = RaftThreadId m- raftFork m = do+ raftFork r m = do persistFile <- ask- lift $ raftFork (runRaftPersistFileStoreT persistFile m)+ lift $ raftFork r (runRaftPersistFileStoreT persistFile m)
src/Examples/Raft/Socket/Node.hs view
@@ -151,6 +151,6 @@ instance (MonadIO m, MonadRaftFork m) => MonadRaftFork (RaftSocketT v m) where type RaftThreadId (RaftSocketT v m) = RaftThreadId m- raftFork m = do+ raftFork r m = do persistFile <- ask- lift $ raftFork (runRaftSocketT persistFile m)+ lift $ raftFork r (runRaftSocketT persistFile m)
src/Raft.hs view
@@ -168,10 +168,18 @@ runRaftT initRaftNodeState raftEnv $ do -- These event producers need access to logging, thus they live in RaftT- raftFork . lift $ electionTimeoutTimer @m @v eventChan electionTimer- raftFork . lift $ heartbeatTimeoutTimer @m @v eventChan heartbeatTimer- raftFork (rpcHandler @m @v eventChan)- raftFork (clientReqHandler @m @v eventChan)+ --+ -- Note: Changing the roles of these event producers (the strings passed as+ -- arguments to 'raftFork' should incur a minor (or major?) version bump,+ -- because some implementations of 'MonadRaftFork' rely on these strings to+ -- reliably send messages to the event producers (e.g. cloud-haskell+ -- processes).+ raftFork (CustomThreadRole "Election Timeout Timer") . lift $+ electionTimeoutTimer @m @v eventChan electionTimer+ raftFork (CustomThreadRole "Heartbeat Timeout Timer") . lift $+ heartbeatTimeoutTimer @m @v eventChan heartbeatTimer+ raftFork RPCHandler (rpcHandler @m @v eventChan)+ raftFork ClientRequestHandler (clientReqHandler @m @v eventChan) -- Start the main event handling loop handleEventLoop initRaftStateMachine@@ -311,16 +319,16 @@ lift (sendRPC nid rpcMsg) SendRPCs rpcMap -> flip mapM_ (Map.toList rpcMap) $ \(nid, sendRpcAction) ->- raftFork $ do+ raftFork (CustomThreadRole "Send RPC") $ do rpcMsg <- mkRPCfromSendRPCAction sendRpcAction lift (sendRPC nid rpcMsg) BroadcastRPC nids sendRpcAction -> do rpcMsg <- mkRPCfromSendRPCAction sendRpcAction- mapM_ (raftFork . lift . flip sendRPC rpcMsg) nids+ mapM_ (raftFork (CustomThreadRole "RPC Broadcast Thread") . lift . flip sendRPC rpcMsg) nids RespondToClient cid cr -> do clientResp <- mkClientResp cr -- TODO log failure if sendClient fails- void $ raftFork $ lift $ sendClient cid clientResp+ void $ raftFork (CustomThreadRole "Respond to Client") $ lift $ sendClient cid clientResp ResetTimeoutTimer tout -> do case tout of ElectionTimeout -> lift . resetElectionTimer =<< ask
src/Raft/Log/PostgreSQL.hs view
@@ -203,9 +203,9 @@ instance (MonadIO m, MonadRaftFork m) => MonadRaftFork (RaftPostgresT m) where type RaftThreadId (RaftPostgresT m) = RaftThreadId m- raftFork m = do+ raftFork r m = do raftPostgresEnv <- ask- lift $ raftFork (runRaftPostgresT' raftPostgresEnv m)+ lift $ raftFork r (runRaftPostgresT' raftPostgresEnv m) --------------------------------------------------------------------------------
src/Raft/Monad.hs view
@@ -69,21 +69,28 @@ writeRaftChan chan = Conc.atomically . writeTChan chan newRaftChan = Conc.atomically newTChan +data RaftThreadRole+ = RPCHandler+ | ClientRequestHandler+ | CustomThreadRole Text+ deriving Show+ -- | The typeclass encapsulating the concurrency operations necessary for the -- implementation of the main event handling loop.------ This typeclass should not be manually defined. class Monad m => MonadRaftFork m where type RaftThreadId m- raftFork :: m () -> m (RaftThreadId m)+ raftFork+ :: RaftThreadRole -- ^ The role of the current thread being forked+ -> m () -- ^ The action to fork+ -> m (RaftThreadId m) instance MonadRaftFork IO where type RaftThreadId IO = Protolude.ThreadId- raftFork = forkIO+ raftFork _ = forkIO instance MonadRaftFork ConcIO where type RaftThreadId ConcIO = TDT.ThreadId- raftFork = Conc.fork+ raftFork r = Conc.forkN (show r) -------------------------------------------------------------------------------- -- Raft Monad@@ -112,10 +119,10 @@ instance MonadRaftFork m => MonadRaftFork (RaftT v m) where type RaftThreadId (RaftT v m) = RaftThreadId m- raftFork m = do+ raftFork s m = do raftEnv <- ask raftState <- get- lift $ raftFork (runRaftT raftState raftEnv m)+ lift $ raftFork s (runRaftT raftState raftEnv m) instance Monad m => RaftLogger v (RaftT v m) where loggerCtx = (,) <$> asks (configNodeId . raftNodeConfig) <*> get
test/TestDejaFu.hs view
@@ -216,10 +216,10 @@ instance MonadRaftFork RaftTestM where type RaftThreadId RaftTestM = RaftThreadId ConcIO- raftFork m = do+ raftFork r m = do testNodeEnv <- ask testNodeStates <- get- RaftTestM . lift . lift $ raftFork (runRaftTestM testNodeEnv testNodeStates m)+ RaftTestM . lift . lift $ raftFork r (runRaftTestM testNodeEnv testNodeStates m) --------------------------------------------------------------------------------