vigilance-0.1.0.0: src/Utils/Vigilance/Main.hs
{-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
module Main (main) where
import ClassyPrelude hiding ( FilePath
, getArgs
, fromList )
import Control.Concurrent (forkIO)
import Control.Concurrent.Async ( waitAnyCatchCancel
, cancel
, Async
, async )
import Control.Concurrent.STM ( atomically
, newBroadcastTChan
, TChan
, writeTChan
, dupTChan )
import Control.Lens
import Control.Monad.Reader ( ask
, asks )
import Data.Acid ( AcidState
, openLocalStateFrom
, createCheckpoint
, closeAcidState )
import qualified Data.Configurator as C
import qualified Data.Configurator.Types as CT
import Prelude (FilePath)
import System.Environment (getArgs)
import System.Exit ( ExitCode(..)
, exitWith )
import System.Posix.Signals ( installHandler
, sigHUP
, sigINT
, sigTERM
, Handler(Catch) )
import Text.InterpolatedString.Perl6 (qc)
import Utils.Vigilance.Config ( loadRawConfig
, convertConfig
, configNotifiers )
import Utils.Vigilance.Logger ( createLogChan
, runInLogCtx
, renameLogCtx
, vLog
, pushLogs
, pushLog )
import Utils.Vigilance.TableOps (fromList)
import Utils.Vigilance.Types
import Utils.Vigilance.Utils ( bindM2
, newWakeSig
, waitForWake
, expandHome
, wakeUp )
import Utils.Vigilance.Worker ( workForeverWithDelayed
, workForeverWith )
import Utils.Vigilance.Web.Yesod (runServer, WebApp(..))
import qualified Utils.Vigilance.Workers.LoggerWorker as LW
import qualified Utils.Vigilance.Workers.NotificationWorker as NW
import qualified Utils.Vigilance.Workers.NotificationRetryWorker as RW
import qualified Utils.Vigilance.Workers.StaticWatchWorker as WW
import qualified Utils.Vigilance.Workers.SweeperWorker as SW
main :: IO ()
main = runWithConfigPath =<< getConfigPath
getConfigPath :: IO FilePath
getConfigPath = fromMaybe defaultPath . listToMaybe <$> getArgs
where defaultPath = vigilanceDir <> "/server.conf"
runWithConfigPath :: FilePath -> IO ()
runWithConfigPath path = bindM2 runInMainLogCtx (loadRawConfig path) createLogChan
runInMainLogCtx :: CT.Config -> TChan [LogMessage] -> IO ()
runInMainLogCtx rCfg logChan = do let ctx = LogCtx "Main" logChan
runInLogCtx ctx $ runWithConfig rCfg
runWithConfig :: CT.Config -> LogCtxT IO ()
runWithConfig rCfg = do cfg <- lift $ convertConfig rCfg
lCtx <- ask
logChan <- asks (view ctxChan)
acidPath <- lift $ expandHome $ cfg ^. configAcidPath
(configChanW, configChanR, configChanR') <- lift $ atomically $ do w <- newBroadcastTChan
r <- dupTChan w
r' <- dupTChan w
return (w, r, r')
let notifiers = configNotifiers cfg
acid <- lift $ openLocalStateFrom acidPath (AppState (initialState cfg) mempty)
quitSig <- lift newWakeSig
let sweeperH = errorLogger "Sweeper" lCtx
let notifierH = errorLogger "Notifier" lCtx
let loggerH = errorLogger "Logger" lCtx
let staticH = errorLogger "Config Reload" lCtx
let retryH = errorLogger "Retry" lCtx
let sweeperWorker = runInLogCtx lCtx $ SW.runWorker acid
let notifierWorker = runInLogCtx lCtx $ NW.runWorker acid notifiers
let retryWorker = runInLogCtx lCtx $ RW.runWorker acid (cfg ^. configMaxRetries) notifiers
let loggerWorker = LW.runWorker logChan cfg configChanR
let watchWorker = runInLogCtx lCtx $ WW.runWorker acid configChanR'
let webApp = WebApp acid cfg logChan
vLog "Starting logger" -- TIME PARADOX
logger <- lift $ async $ workForeverWith loggerH loggerWorker
vLog "Starting sweeper"
sweeper <- lift $ async $ workForeverWithDelayed sweeperDelay sweeperH sweeperWorker
vLog "Sweeper started"
vLog "Starting notifier"
nworker <- lift $ async $ workForeverWithDelayed notifierDelay notifierH notifierWorker
vLog "Notifier started"
vLog "Starting retry worker"
rworker <- lift $ async $ workForeverWithDelayed retryDelay retryH retryWorker
vLog "Retry worker started"
vLog "Starting web server"
server <- lift $ async $ runServer webApp
static <- lift $ async $ workForeverWith staticH watchWorker
let workers = [ server
, sweeper
, nworker
, rworker
, static ]
vLog "configuring signal handlers"
lift $ do
void $ installHandler sigHUP (Catch $ broadcastCfgReload rCfg configChanW) Nothing
void $ installHandler sigINT (Catch $ wakeUp quitSig ExitSuccess) Nothing
void $ installHandler sigTERM (Catch $ wakeUp quitSig ExitSuccess) Nothing
vLog "waiting for any process to fail"
void . lift . forkIO $ do
void $ waitAnyCatchCancel (logger:workers)
wakeUp quitSig (ExitFailure 1)
vLog "waiting for quit signal"
code <- lift $ waitForWake quitSig
cleanUp acid workers code
where initialState :: Config -> WatchTable
initialState cfg = fromList $ cfg ^. configWatches
broadcastCfgReload :: CT.Config -> TChan Config -> IO ()
broadcastCfgReload rCfg chan = C.reload rCfg >> broadcast
where broadcast = atomically . writeTChan chan =<< convertConfig rCfg
errorLogger :: Text -> LogCtx -> SomeException -> IO ()
errorLogger name ctx e = runInLogCtx ctx $ renameLogCtx name $ pushLog errMsg
where errMsg = [qc|Error: {e}|] :: Text
sweeperDelay :: Int
sweeperDelay = 5 -- arbitrary
notifierDelay :: Int
notifierDelay = 5 -- arbitrary
retryDelay :: Int
--retryDelay = 30
retryDelay = 10
cleanUp :: AcidState AppState -> [Async ()] -> ExitCode -> LogCtxT IO ()
cleanUp acid workers code = do pushLogs ["cleaning up", "killing workers"]
lift $ mapM_ cancel workers
pushLog "creating checkpoint"
lift $ createCheckpoint acid
pushLog "closing acid"
lift $ closeAcidState acid >> exitWith code