cachix-1.6.1: src/Cachix/Client/Daemon.hs
{-# LANGUAGE QuasiQuotes #-}
module Cachix.Client.Daemon
( run,
runWithSocket,
push,
stop,
)
where
import Cachix.Client.CNix (filterInvalidStorePath)
import qualified Cachix.Client.Commands as Commands
import Cachix.Client.Config.Orphans ()
import Cachix.Client.Daemon.Client (push, stop)
import Cachix.Client.Daemon.Listen as Daemon
import Cachix.Client.Daemon.Types
import Cachix.Client.Env as Env
import Cachix.Client.OptionsParser as Options
import Cachix.Client.Push
import Control.Concurrent.Extra (once)
import Control.Concurrent.STM.TBMQueue
import qualified Control.Immortal as Immortal
import Data.String.Here (i)
import qualified Data.Text as T
import qualified Hercules.CNix.Store as Store
import qualified Network.Socket as Socket
import Protolude
import System.Posix.Process (getProcessID)
run :: Env -> DaemonOptions -> PushOptions -> IO ()
run env daemonOptions pushOptions = do
socketPath <- maybe getSocketPath pure (daemonSocketPath daemonOptions)
runWithSocket env pushOptions socketPath
`finally` putErrText "Daemon shut down."
runWithSocket :: Env -> PushOptions -> FilePath -> IO ()
runWithSocket env pushOptions socketPath = do
-- Create a queue of push requests for the workers to process
queue <- newTBMQueueIO 1000
bracketOnError (startWorker queue) identity $ \shutdownWorker -> do
-- TODO: retry the connection on socket errors
bracketOnError (Daemon.openSocket socketPath) Socket.close $ \sock -> do
Socket.listen sock Socket.maxListenQueue
putText =<< readyMessage socketPath
clientStopConn <- Daemon.listen queue sock
-- Gracefully shutdown the worker before closing the socket
-- TODO: consider shutdown from Network.Socket
shutdownWorker
Socket.gracefulClose clientStopConn 5000
where
startWorker queue = do
worker <- Immortal.create $ \thread ->
Immortal.onUnexpectedFinish thread logWorkerException $
runWorker env pushOptions queue
once $ do
putErrText "Shutting down daemon..."
atomically $ closeTBMQueue queue
Immortal.mortalize worker
putErrText "Waiting for worker to finish..."
Immortal.wait worker
logWorkerException :: Either SomeException () -> IO ()
logWorkerException (Left err) =
putErrText $ "Exception in daemon worker thread: " <> show err
logWorkerException _ = return ()
runWorker :: Env -> PushOptions -> TBMQueue QueuedPushRequest -> IO ()
runWorker env pushOptions queue = loop
where
loop =
atomically (readTBMQueue queue) >>= \case
Nothing -> return ()
Just msg -> do
handleRequest env pushOptions msg
loop
handleRequest :: Env -> PushOptions -> QueuedPushRequest -> IO ()
handleRequest env pushOptions (QueuedPushRequest {..}) = do
Commands.withPushParams env pushOptions (binaryCacheName pushRequest) $ \pushParams -> do
normalized <-
for (storePaths pushRequest) $ \fp -> do
storePath <- Store.followLinksToStorePath (pushParamsStore pushParams) (encodeUtf8 $ T.pack fp)
filterInvalidStorePath (pushParamsStore pushParams) storePath
void $
pushClosure
(mapConcurrentlyBounded (numJobs pushOptions))
pushParams
(catMaybes normalized)
readyMessage :: FilePath -> IO Text
readyMessage socketPath = do
-- Get the PID of the process
pid <- getProcessID
return
[i|
Cachix Daemon is ready to push store paths.
PID: ${show pid :: Text}
Listening on socket: ${socketPath}
|]