{-|
Run the core Raft consensus algorithm for the indicated server.  This function
takes care of coordinating the transitions among followers, candidates, and leaders as necessary.
-}
withConsensus :: (RaftLog l e v) => Endpoint -> Name -> l -> RaftState v -> (Raft l e v -> IO ()) -> IO ()
withConsensus endpoint name initialLog initialState fn = do
    vRaft <- atomically $ mkRaft endpoint initialLog initialState
    withAsync (run vRaft)
        (\_ -> fn vRaft)
    where
        run vRaft = do
            infoM _log $ printf "Starting server %v" name
            finally (catch (participate vRaft)
                        (\e -> case e of
                                ThreadKilled -> return ()
                                _ -> debugM _log $ printf "%v encountered error: %v" name (show (e :: AsyncException))))
                (do
                    infoM _log $ printf "Stopped server %v" name)
        participate vRaft = do
            follow vRaft
            won <- volunteer vRaft
            if won
                then do
                    lead vRaft
                else return ()
            participate vRaft
withConsensus :: (RaftLog l e v) => Endpoint -> Name -> l -> RaftState v -> (Raft l e v -> IO ()) -> IO ()
withConsensus endpoint name initialLog initialState fn = do
    vRaft <- atomically $ mkRaft endpoint initialLog initialState
    withAsync (run vRaft)
        (\_ -> fn vRaft)
    where
        run vRaft = do
            infoM _log $ printf "Starting server %v" name
            finally (catch (participate vRaft)
                        (\e -> case e of
                                ThreadKilled -> return ()
                                _ -> debugM _log $ printf "%v encountered error: %v" name (show (e :: AsyncException))))
                (do
                    infoM _log $ printf "Stopped server %v" name)
        participate vRaft = do
            follow vRaft
            won <- volunteer vRaft
            if won
                then do
                    lead vRaft
                else return ()
            participate vRaft
withConsensus endpoint name initialLog initialState fn = do
    vRaft <- atomically $ mkRaft endpoint initialLog initialState
    withAsync (run vRaft)
        (\_ -> fn vRaft)
    where
        run vRaft = do
            infoM _log $ printf "Starting server %v" name
            finally (catch (participate vRaft)
                        (\e -> case e of
                                ThreadKilled -> return ()
                                _ -> debugM _log $ printf "%v encountered error: %v" name (show (e :: AsyncException))))
                (do
                    infoM _log $ printf "Stopped server %v" name)
        participate vRaft = do
            follow vRaft
            won <- volunteer vRaft
            if won
                then do
                    lead vRaft
                else return ()
            participate vRaft