shrub/pkg/hs/urbit-king/lib/Urbit/Vere/Serf.hs

164 lines
4.8 KiB
Haskell
Raw Normal View History

{-# OPTIONS_GHC -Wwarn #-}
2020-01-23 07:16:09 +03:00
{-|
Serf Interface
TODO: `recvLen` is not big-endian safe.
2019-07-17 02:14:46 +03:00
-}
module Urbit.Vere.Serf
( module Urbit.Vere.Serf.IPC
, withSerf
, execReplay
, execSnapshot
, execShutdown
)
where
import Urbit.Prelude
import Data.Conduit
import System.Process
import System.ProgressBar
import Urbit.Arvo
import Urbit.Vere.Pier.Types
import Urbit.Vere.Serf.IPC
import System.Posix.Signals
2019-07-22 00:24:07 +03:00
import Data.Bits (setBit)
2019-07-12 04:16:40 +03:00
import Data.ByteString (hGet)
import Data.ByteString.Unsafe (unsafeUseAsCString)
2019-07-12 04:16:40 +03:00
import Foreign.Marshal.Alloc (alloca)
import Foreign.Ptr (castPtr)
2019-07-12 22:24:44 +03:00
import Foreign.Storable (peek, poke)
2019-07-12 04:16:40 +03:00
import System.Exit (ExitCode)
2020-02-04 04:27:16 +03:00
import Urbit.King.App (HasStderrLogFunc(..))
import qualified Data.ByteString.Unsafe as BS
import qualified Data.Conduit.Combinators as CC
import qualified Data.Text as T
import qualified System.IO as IO
import qualified System.IO.Error as IO
import qualified Urbit.Ob as Ob
import qualified Urbit.Time as Time
import qualified Urbit.Vere.Log as Log
--------------------------------------------------------------------------------
-- TODO XXX HACK FIXME
data MissingBootEventsInEventLog = MissingBootEventsInEventLog Word Word
deriving (Show, Exception)
--------------------------------------------------------------------------------
bytesNouns :: MonadIO m => ConduitT ByteString Noun m ()
bytesNouns = await >>= \case
Nothing -> pure ()
Just bs -> do
noun <- cueBSExn bs
(mug :: Noun, bod) <- fromNounExn noun
yield bod
bytesNouns
withSerf :: HasLogFunc e => Config -> RAcquire e (Serf, SerfInfo)
withSerf config = mkRAcquire (io $ start config) kill
where
kill (serf, _) = do
void $ rio $ execShutdown serf
{-
TODO This needs to be thought through carfully once the callsites
have stabilized.
2019-07-17 02:14:46 +03:00
-}
execShutdown :: HasLogFunc e => Serf -> RIO e ()
execShutdown serf = do
race_ (wait2sec >> forceKill) $ do
logTrace "Getting current serf state (taking lock, might block if in use)."
finalState <- takeMVar (serfLock serf)
logTrace "Got serf state (and took lock). Requesting shutdown."
io (shutdown serf 0)
logTrace "Sent shutdown request. Waiting for process to die."
io $ waitForProcess (serfProc serf)
logTrace "RIP Serf process."
where
wait2sec = threadDelay 5_000_000
forceKill = do
logTrace "Serf taking too long to go down, kill with fire (SIGTERM)."
io (getPid $ serfProc serf) >>= \case
Nothing -> do
logTrace "Serf process already dead."
Just pid -> do
io $ signalProcess sigKILL pid
io $ waitForProcess (serfProc serf)
logTrace "Finished killing serf process with fire."
execSnapshot :: forall e . HasLogFunc e => Serf -> RIO e ()
execSnapshot serf = do
logTrace "execSnapshot: taking lock"
serfState <- takeMVar (serfLock serf)
io (sendSnapshotRequest serf (ssLast serfState))
logTrace "execSnapshot: releasing lock"
putMVar (serfLock serf) serfState
execReplay
:: forall e
. HasLogFunc e
=> Serf
-> Log.EventLog
-> Maybe Word64
-> RIO e (Maybe PlayBail)
execReplay serf log last = do
lastEventInSnap <- io (ssLast <$> serfCurrentStateBlocking serf)
if lastEventInSnap == 0 then doBoot else doReplay
where
doBoot :: RIO e (Maybe PlayBail)
doBoot = do
let bootSeqLen = lifecycleLen (Log.identity log)
evs <- runConduit $ Log.streamEvents log 1
.| CC.take (fromIntegral bootSeqLen)
.| bytesNouns
.| CC.sinkList
let numEvs = fromIntegral (length evs)
let bootLn = bootSeqLen
when (numEvs /= bootLn) $ do
throwIO (MissingBootEventsInEventLog numEvs bootLn)
io (bootSeq serf evs) >>= \case
Just err -> pure (Just err)
Nothing -> doReplay
doReplay :: RIO e (Maybe PlayBail)
doReplay = do
2020-02-06 02:20:32 +03:00
logTrace "Beginning event log replay"
lastEventInSnap <- io (ssLast <$> serfCurrentStateBlocking serf)
2020-02-06 02:20:32 +03:00
last & \case
Nothing -> pure ()
Just lt -> logTrace $ display $
"User requested to replay up to event #" <> tshow lt
logLastEv :: Word64 <- fromIntegral <$> Log.lastEv log
logTrace $ display $ "Last event in event log is #" <> tshow logLastEv
let replayUpTo = min (fromMaybe logLastEv last) logLastEv
2020-02-06 02:20:32 +03:00
let numEvs :: Int = fromIntegral replayUpTo - fromIntegral lastEventInSnap
when (numEvs < 0) $ do
error "impossible"
2020-02-06 02:20:32 +03:00
logTrace $ display $ "Replaying up to event #" <> tshow replayUpTo
logTrace $ display $ "Will replay " <> tshow numEvs <> " in total."
runConduit $ Log.streamEvents log (lastEventInSnap + 1)
2020-02-06 02:20:32 +03:00
.| CC.take (fromIntegral numEvs)
.| bytesNouns
.| replay serf