------------------------------------------------------------------------ -- | -- Module : ALife.Creatur.Daemon -- Copyright : (c) Amy de Buitléir 2012-2013 -- License : BSD-style -- Maintainer : amy@nualeargais.ie -- Stability : experimental -- Portability : portable -- -- Provides a UNIX daemon to run an experiment using the Créatúr -- framework. -- ------------------------------------------------------------------------ {-# LANGUAGE UnicodeSyntax, TypeFamilies, FlexibleContexts #-} module ALife.Creatur.Daemon ( Daemon(..), launch ) where import Control.Concurrent (MVar, newMVar, readMVar, swapMVar, threadDelay) import Control.Exception (SomeException, handle) import Control.Monad.State (StateT, execStateT) import Data.Eq.Unicode ((≠)) import System.IO.Unsafe (unsafePerformIO) import System.Posix.Daemonize (CreateDaemon(..), serviced, simpleDaemon) import System.Posix.Signals (Handler(Catch), fullSignalSet, installHandler, sigTERM) import System.Posix.User (getLoginName, getRealUserID) termReceived ∷ MVar Bool termReceived = unsafePerformIO (newMVar False) -- | Daemon configuration. -- If @username@ ≡ "", the daemon will run under the login name. data Daemon s = Daemon { onStartup ∷ s → IO s, onShutdown ∷ s → IO (), onException ∷ s → SomeException → IO s, task ∷ StateT s IO (), username ∷ String, sleepTime ∷ Int } -- | @'launch' username sleepTime state task@ creates a daemon -- running as @username@, which invokes @task@ repeatedly, sleeping -- for @sleepTime@ microseconds between invocations of @task@. launch ∷ Daemon s → s → IO () launch d s = do uid ← getRealUserID if uid ≠ 0 then putStrLn "Must run as root" else do u ← daemonUsername d serviced $ simpleDaemon { program = daemonMain d s, user = Just u } daemonUsername ∷ Daemon s → IO String daemonUsername d = if (null . username) d then getLoginName else (return . username) d daemonMain ∷ Daemon s → s → () → IO () daemonMain d s _ = do s' ← onStartup d s _ ← installHandler sigTERM (Catch handleTERM) (Just fullSignalSet) _ ← daemonMainLoop d s' return () daemonMainLoop ∷ Daemon s → s → IO () daemonMainLoop d s = do threadDelay . sleepTime $ d timeToStop ← readMVar termReceived if timeToStop then onShutdown d s else do s' ← handle ((onException d) s) $ execStateT (task d) s daemonMainLoop d s' handleTERM ∷ IO () handleTERM = do _ ← swapMVar termReceived True return ()