cachix-1.7.9: src/Cachix/Daemon/EventLoop.hs
module Cachix.Daemon.EventLoop
( new,
send,
sendIO,
run,
exitLoopWith,
exitLoopWithFailure,
EventLoop,
)
where
import Cachix.Daemon.ShutdownLatch qualified as ShutdownLatch
import Cachix.Daemon.Types.EventLoop (EventLoop (..), EventLoopError (..))
import Control.Concurrent.STM
import Control.Concurrent.STM.TBMQueue
( isFullTBMQueue,
newTBMQueueIO,
readTBMQueue,
tryWriteTBMQueue,
)
import Data.Text.Lazy.Builder (toLazyText)
import Katip qualified
import Protolude
new :: (MonadIO m) => m (EventLoop event a)
new = do
shutdownLatch <- ShutdownLatch.newShutdownLatch
queue <- liftIO $ newTBMQueueIO 100_000
return $ EventLoop {queue, shutdownLatch}
-- | Send an event to the event loop with logging.
send :: (Katip.KatipContext m) => EventLoop event a -> event -> m ()
send = send' Katip.logFM
-- | Same as 'send', but does not require a 'Katip.KatipContext'.
sendIO :: forall m event a. (MonadIO m) => EventLoop event a -> event -> m ()
sendIO = send' logger
where
logger :: Katip.Severity -> Katip.LogStr -> m ()
logger Katip.ErrorS msg = liftIO $ hPutStrLn stderr (toLazyText $ Katip.unLogStr msg)
logger _ _ = return ()
send' :: (MonadIO m) => (Katip.Severity -> Katip.LogStr -> m ()) -> EventLoop event a -> event -> m ()
send' logger eventloop@(EventLoop {queue, shutdownLatch}) event = do
-- First check if shutdown has been requested
isExiting <- ShutdownLatch.isShuttingDown shutdownLatch
if isExiting
then logger Katip.DebugS "Ignored an event because the event loop is shutting down"
else do
res <- liftIO $ atomically $ tryWriteTBMQueue queue event
case res of
-- The queue is closed.
Nothing ->
logger Katip.DebugS "Ignored an event because the event loop is closed"
-- Successfully wrote to the queue
Just True -> return ()
-- Failed to write to the queue
Just False -> do
isFull <- liftIO $ atomically $ isFullTBMQueue queue
let message =
if isFull
then "Event loop is full"
else "Unknown error"
logger Katip.ErrorS $ "Failed to write to event loop: " <> message
exitLoopWithFailure EventLoopFull eventloop
-- | Run the event loop until it exits with 'exitLoopWith'.
run :: (MonadIO m) => EventLoop event a -> (event -> m ()) -> m (Either EventLoopError a)
run (EventLoop {queue, shutdownLatch}) f =
fix $ \loop -> do
-- Wait for either a shutdown signal or a message from the queue
eitherResult <-
liftIO $
atomically $
fmap Left (ShutdownLatch.waitForShutdownSTM shutdownLatch)
`orElse`
-- Try to read from queue
( do
mevent <- readTBMQueue queue
case mevent of
-- Got an event, return it
Just event -> return $ Right event
-- Queue is closed, signal shutdown
Nothing -> do
ShutdownLatch.initiateShutdownWithResultSTM (Left EventLoopClosed) shutdownLatch
result <- ShutdownLatch.waitForShutdownSTM shutdownLatch
return $ Left result
)
-- Process the result
case eitherResult of
-- Shutdown requested, return the result
Left result -> return result
-- Got an event, process it and continue looping
Right event -> do
f event
loop
-- | Short-circuit the event loop and exit with a given return value.
exitLoopWith :: (MonadIO m) => a -> EventLoop event a -> m ()
exitLoopWith exitValue (EventLoop {shutdownLatch}) =
ShutdownLatch.initiateShutdown exitValue shutdownLatch
-- | Short-circuit the event loop in case of an internal error.
exitLoopWithFailure :: (MonadIO m) => EventLoopError -> EventLoop event a -> m ()
exitLoopWithFailure err (EventLoop {shutdownLatch}) =
ShutdownLatch.initiateShutdownWithResult (Left err) shutdownLatch