packages feed

creatur-2.0.10: src/ALife/Creatur/Daemon.hs

------------------------------------------------------------------------
-- |
-- 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 ()