Commit 489c9037 authored by Klaus Aehlig's avatar Klaus Aehlig

Merge branch 'stable-2.8' into stable-2.9

* stable-2.8
  Rename queryd to luxid
  Document the rapi client not to have a QueryNetworks method
  Enable unit tests again
  Document ganeti-queryd
  Add ganeti-queryd to QA env test
  Add queryd daemon (split from confd)
  Extract ConfigReader from Confd/Server.hs
  Add timestamps to haskell network query fields

Conflicts:
	src/Ganeti/Confd/Server.hs (trivial)
Signed-off-by: default avatarKlaus Aehlig <aehlig@google.com>
Reviewed-by: default avatarMichele Tartara <mtartara@google.com>
parents 2650ea6b 3695a4e0
......@@ -126,7 +126,9 @@
/src/mon-collector
/src/htools
/src/hconfd
/src/hluxid
/src/ganeti-confd
/src/ganeti-luxid
/src/ganeti-mond
/src/rpc-test
......
......@@ -177,8 +177,8 @@ Haskell optional features
~~~~~~~~~~~~~~~~~~~~~~~~~
Optionally, more functionality can be enabled if your build machine has
a few more Haskell libraries enabled: the ``ganeti-confd`` daemon
(``--enable-confd``) and the monitoring daemon
a few more Haskell libraries enabled: the ``ganeti-confd`` and
``ganeti-luxid`` daemon (``--enable-confd``) and the monitoring daemon
(``--enable-mond``). The list of extra dependencies for these is:
- `hslogger <http://software.complete.org/hslogger>`_, version 1.1 and
......
......@@ -212,6 +212,7 @@ CLEANFILES = \
$(HS_ALL_PROGS) $(HS_BUILT_SRCS) \
$(HS_BUILT_TEST_HELPERS) \
src/ganeti-confd \
src/ganeti-luxid \
src/ganeti-mond \
.hpc/*.mix src/*.tix test/hs/*.tix \
doc/hs-lint.html
......@@ -225,7 +226,7 @@ HS_GENERATED_FILES =
if WANT_HTOOLS
HS_GENERATED_FILES += $(HS_PROGS)
if ENABLE_CONFD
HS_GENERATED_FILES += src/hconfd src/ganeti-confd
HS_GENERATED_FILES += src/hconfd src/ganeti-confd src/hluxid src/ganeti-luxid
endif
if ENABLE_MOND
......@@ -499,6 +500,7 @@ endif
HS_COMPILE_PROGS= \
src/ganeti-mond \
src/hconfd \
src/hluxid \
src/rpc-test
# All Haskell non-test programs to be compiled but not automatically installed
......@@ -550,6 +552,7 @@ HS_LIB_SRCS = \
src/Ganeti/Confd/Types.hs \
src/Ganeti/Confd/Utils.hs \
src/Ganeti/Config.hs \
src/Ganeti/ConfigReader.hs \
src/Ganeti/Curl/Multi.hs \
src/Ganeti/Daemon.hs \
src/Ganeti/DataCollectors/CLI.hs \
......@@ -904,7 +907,11 @@ if ENABLE_CONFD
src/ganeti-confd: src/hconfd
cp -f $< $@
src/ganeti-luxid: src/hluxid
cp -f $< $@
nodist_sbin_SCRIPTS += src/ganeti-confd
nodist_sbin_SCRIPTS += src/ganeti-luxid
endif
if ENABLE_MOND
......@@ -1020,6 +1027,7 @@ EXTRA_DIST = \
man_MANS = \
man/ganeti-cleaner.8 \
man/ganeti-confd.8 \
man/ganeti-luxid.8 \
man/ganeti-listrunner.8 \
man/ganeti-masterd.8 \
man/ganeti-mond.8 \
......@@ -1302,8 +1310,8 @@ dist_TESTS = \
test/py/cli-test.bash \
test/py/bash_completion.bash
if !PY_NODEV
dist_TESTS += $(python_tests)
if PY_UNIT
dist_TESTS += $(python_tests)
endif
nodist_TESTS =
......@@ -1354,8 +1362,8 @@ all_python_code = \
$(noinst_PYTHON) \
$(qa_scripts)
if !PY_NODEV
all_python_code += $(python_tests)
if PY_UNIT
all_python_code += $(python_tests)
endif
srclink_files = \
......@@ -1601,6 +1609,8 @@ lib/_autoconf.py: Makefile | stamp-directories
echo "RAPI_GROUP = '$(RAPI_GROUP)'"; \
echo "CONFD_USER = '$(CONFD_USER)'"; \
echo "CONFD_GROUP = '$(CONFD_GROUP)'"; \
echo "LUXID_USER = '$(LUXID_USER)'"; \
echo "LUXID_GROUP = '$(LUXID_GROUP)'"; \
echo "NODED_USER = '$(NODED_USER)'"; \
echo "NODED_GROUP = '$(NODED_GROUP)'"; \
echo "MOND_USER = '$(MOND_USER)'"; \
......@@ -1681,11 +1691,13 @@ $(REPLACE_VARS_SED): $(SHELL_ENV_INIT) Makefile stamp-directories
echo 's#@''GNTMASTERUSER@#$(MASTERD_USER)#g'; \
echo 's#@''GNTRAPIUSER@#$(RAPI_USER)#g'; \
echo 's#@''GNTCONFDUSER@#$(CONFD_USER)#g'; \
echo 's#@''GNTLUXIDUSER@#$(LUXID_USER)#g'; \
echo 's#@''GNTNODEDUSER@#$(NODED_USER)#g'; \
echo 's#@''GNTMONDUSER@#$(MOND_USER)#g'; \
echo 's#@''GNTRAPIGROUP@#$(RAPI_GROUP)#g'; \
echo 's#@''GNTADMINGROUP@#$(ADMIN_GROUP)#g'; \
echo 's#@''GNTCONFDGROUP@#$(CONFD_GROUP)#g'; \
echo 's#@''GNTLUXIDGROUP@#$(LUXID_GROUP)#g'; \
echo 's#@''GNTMASTERDGROUP@#$(MASTERD_GROUP)#g'; \
echo 's#@''GNTMONDGROUP@#$(MOND_GROUP)#g'; \
echo 's#@''GNTDAEMONSGROUP@#$(DAEMONS_GROUP)#g'; \
......@@ -2053,7 +2065,7 @@ COVERAGE_TESTS=
if WANT_HTOOLS
COVERAGE_TESTS += hs-coverage
endif
if !PY_NODEV
if PY_UNIT
COVERAGE_TESTS += py-coverage
endif
......
......@@ -212,16 +212,19 @@ AC_ARG_WITH([user-prefix],
[user_masterd="${withval}masterd";
user_rapi="${withval}rapi";
user_confd="${withval}confd";
user_luxid="${withval}luxid";
user_noded="$user_default";
user_mond="$user_default"],
[user_masterd="$user_default";
user_rapi="$user_default";
user_confd="$user_default";
user_luxid="$user_default";
user_noded="$user_default";
user_mond="$user_default"])
AC_SUBST(MASTERD_USER, $user_masterd)
AC_SUBST(RAPI_USER, $user_rapi)
AC_SUBST(CONFD_USER, $user_confd)
AC_SUBST(LUXID_USER, $user_luxid)
AC_SUBST(NODED_USER, $user_noded)
AC_SUBST(MOND_USER, $user_mond)
......@@ -235,6 +238,7 @@ AC_ARG_WITH([group-prefix],
[group_rapi="${withval}rapi";
group_admin="${withval}admin";
group_confd="${withval}confd";
group_luxid="${withval}luxid";
group_masterd="${withval}masterd";
group_noded="$group_default";
group_daemons="${withval}daemons";
......@@ -242,6 +246,7 @@ AC_ARG_WITH([group-prefix],
[group_rapi="$group_default";
group_admin="$group_default";
group_confd="$group_default";
group_luxid="$group_default";
group_masterd="$group_default";
group_noded="$group_default";
group_daemons="$group_default";
......@@ -249,6 +254,7 @@ AC_ARG_WITH([group-prefix],
AC_SUBST(RAPI_GROUP, $group_rapi)
AC_SUBST(ADMIN_GROUP, $group_admin)
AC_SUBST(CONFD_GROUP, $group_confd)
AC_SUBST(LUXID_GROUP, $group_luxid)
AC_SUBST(MASTERD_GROUP, $group_masterd)
AC_SUBST(NODED_GROUP, $group_noded)
AC_SUBST(DAEMONS_GROUP, $group_daemons)
......@@ -258,6 +264,7 @@ AC_SUBST(MOND_GROUP, $group_mond)
AC_MSG_NOTICE([Running ganeti-masterd as $group_masterd:$group_masterd])
AC_MSG_NOTICE([Running ganeti-rapi as $user_rapi:$group_rapi])
AC_MSG_NOTICE([Running ganeti-confd as $user_confd:$group_confd])
AC_MSG_NOTICE([Running ganeti-luxid as $user_luxid:$group_luxid])
AC_MSG_NOTICE([Group for daemons is $group_daemons])
AC_MSG_NOTICE([Group for clients is $group_admin])
......@@ -764,10 +771,10 @@ if test -n "$PY_NODEV"; then
AC_MSG_WARN(m4_normalize([Required development modules ($PY_NODEV) were not
found, you won't be able to run Python unittests]))
else
AC_MSG_NOTICE([Python development modules found, unittests enabled])
AC_MSG_NOTICE([Python development modules found, unittests enabled])
fi
AC_SUBST(PY_NODEV)
AM_CONDITIONAL([PY_NODEV], [test -n $PY_NODEV])
AM_CONDITIONAL([PY_UNIT], [test -n $PY_NODEV])
AC_CONFIG_FILES([ Makefile ])
......
......@@ -39,6 +39,7 @@ _confd_enabled() {
if _confd_enabled; then
DAEMONS+=( ganeti-confd )
DAEMONS+=( ganeti-luxid )
fi
_mond_enabled() {
......@@ -52,6 +53,7 @@ fi
NODED_ARGS=
MASTERD_ARGS=
CONFD_ARGS=
LUXID_ARGS=
RAPI_ARGS=
MOND_ARGS=
......@@ -82,6 +84,9 @@ _daemon_usergroup() {
confd)
echo "@GNTCONFDUSER@:@GNTDAEMONSGROUP@"
;;
luxid)
echo "@GNTLUXIDUSER@:@GNTLUXIDGROUP@"
;;
rapi)
echo "@GNTRAPIUSER@:@GNTRAPIGROUP@"
;;
......@@ -228,7 +233,8 @@ start() {
local usergroup=$(_daemon_usergroup $plain_name)
local daemonexec=$(_daemon_executable $name)
if [[ "$name" == ganeti-confd ]] && ! _confd_enabled; then
if ( [[ "$name" == ganeti-confd ]] || [[ "$name" == ganeti-luxid ]] ) \
&& ! _confd_enabled; then
echo 'ganeti-confd disabled at build time' >&2
return 1
fi
......
......@@ -77,7 +77,7 @@ the instance console), to a split model:
- if just masterd is stopped, then other cluster functionality remains
available: listing instances, connecting to the console of an
instance, etc.
- if just "queryd" is stopped, masterd can still process jobs, and one
- if just "luxid" is stopped, masterd can still process jobs, and one
can furthermore run queries from other nodes (MCs)
- only if both are stopped, we end up with the previous state
......@@ -141,12 +141,12 @@ configuration queries.
The redirection of Luxi requests can be easily done based on the
request type, if we have both sockets open, or if we open on demand.
We don't want the masterd to talk to the queryd itself (hidden
We don't want the masterd to talk to the luxid itself (hidden
redirection), since we want to be able to run queries while masterd is
down.
During the 2.7 release cycle, we can test all queries against both
masterd and queryd in QA, so we know we have exactly the same
masterd and luxid in QA, so we know we have exactly the same
interface and it is consistent.
.. vim: set textwidth=72 :
......
......@@ -104,6 +104,8 @@ RAPI_USER = _autoconf.RAPI_USER
RAPI_GROUP = _autoconf.RAPI_GROUP
CONFD_USER = _autoconf.CONFD_USER
CONFD_GROUP = _autoconf.CONFD_GROUP
LUXID_USER = _autoconf.LUXID_USER
LUXID_GROUP = _autoconf.LUXID_GROUP
NODED_USER = _autoconf.NODED_USER
NODED_GROUP = _autoconf.NODED_GROUP
MOND_USER = _autoconf.MOND_USER
......@@ -155,6 +157,7 @@ SCP = "scp"
NODED = "ganeti-noded"
CONFD = "ganeti-confd"
LUXID = "ganeti-luxid"
RAPI = "ganeti-rapi"
MASTERD = "ganeti-masterd"
MOND = "ganeti-mond"
......@@ -162,6 +165,7 @@ MOND = "ganeti-mond"
DAEMONS = compat.UniqueFrozenset([
NODED,
CONFD,
LUXID,
RAPI,
MASTERD,
MOND,
......@@ -187,6 +191,7 @@ LAST_DRBD_PORT = 14999
DAEMONS_LOGBASE = {
NODED: "node-daemon",
CONFD: "conf-daemon",
LUXID: "query-daemon",
RAPI: "rapi-daemon",
MASTERD: "master-daemon",
MOND: "monitoring-daemon",
......
......@@ -75,6 +75,8 @@ class GetentResolver:
@ivar masterd_gid: The resolved gid of the masterd group
@ivar confd_uid: The resolved uid of the confd user
@ivar confd_gid: The resolved gid of the confd group
@ivar luxid_uid: The resolved uid of the luxid user
@ivar luxid_gid: The resolved gid of the luxid group
@ivar rapi_uid: The resolved uid of the rapi user
@ivar rapi_gid: The resolved gid of the rapi group
@ivar noded_uid: The resolved uid of the noded user
......@@ -93,6 +95,9 @@ class GetentResolver:
self.confd_uid = GetUid(constants.CONFD_USER, _getpwnam)
self.confd_gid = GetGid(constants.CONFD_GROUP, _getgrnam)
self.luxid_uid = GetUid(constants.LUXID_USER, _getpwnam)
self.luxid_gid = GetGid(constants.LUXID_GROUP, _getgrnam)
self.rapi_uid = GetUid(constants.RAPI_USER, _getpwnam)
self.rapi_gid = GetGid(constants.RAPI_GROUP, _getgrnam)
......@@ -106,6 +111,7 @@ class GetentResolver:
self._uid2user = {
self.masterd_uid: constants.MASTERD_USER,
self.confd_uid: constants.CONFD_USER,
self.luxid_uid: constants.LUXID_USER,
self.rapi_uid: constants.RAPI_USER,
self.noded_uid: constants.NODED_USER,
}
......@@ -113,6 +119,7 @@ class GetentResolver:
self._gid2group = {
self.masterd_gid: constants.MASTERD_GROUP,
self.confd_gid: constants.CONFD_GROUP,
self.luxid_gid: constants.LUXID_GROUP,
self.rapi_gid: constants.RAPI_GROUP,
self.noded_gid: constants.NODED_GROUP,
self.daemons_gid: constants.DAEMONS_GROUP,
......
......@@ -180,7 +180,7 @@ def GetPaths():
(pathutils.MASTER_SOCKET, FILE, 0660,
getent.masterd_uid, getent.daemons_gid, False),
(pathutils.QUERY_SOCKET, FILE, 0660,
getent.confd_uid, getent.daemons_gid, False),
getent.luxid_uid, getent.daemons_gid, False),
(pathutils.BDEV_CACHE_DIR, DIR, 0755,
getent.noded_uid, getent.masterd_gid),
(pathutils.UIDPOOL_LOCKDIR, DIR, 0750,
......
......@@ -29,11 +29,7 @@ The **ganeti-confd** daemon listens to port 1814 UDP, on all interfaces,
by default. The port can be overridden by an entry the services database
(usually ``/etc/services``) or by passing the ``-p`` option. The ``-b``
option can be used to specify the address to bind to (defaults to
``0.0.0.0``). The daemon also listens on a Unix socket
(``@LOCALSTATEDIR@/run/ganeti/socket/ganeti-query``) on which it exports
a ``Luxi`` endpoint, serving query operations only. Commands and tools
use this socket if the build-time option for split queries has been
enabled.
``0.0.0.0``).
The daemon will refuse to start if the user and group do not match the
one defined at build time; this behaviour can be overridden by the
......
ganeti-luxid(8) Ganeti | Version @GANETI_VERSION@
=================================================
Name
----
ganeti-luxid - Ganeti query daemon
Synopsis
--------
**ganeti-luxid** [-f] [-d]
DESCRIPTION
-----------
**ganeti-luxid** is a daemon used to answer queries related to the
configuration and the current live state of a Ganeti cluster.
For testing purposes, you can give the ``-f`` option and the
program won't detach from the running terminal.
Debug-level message can be activated by giving the ``-d`` option.
Logging to syslog, rather than its own log file, can be enabled by
passing in the ``--syslog`` option.
The **ganeti-luxid** daemon listens on a Unix socket
(``@LOCALSTATEDIR@/run/ganeti/socket/ganeti-query``) on which it exports
a ``Luxi`` endpoint, serving query operations only. Commands and tools
use this socket if the build-time option for split queries has been
enabled.
The daemon will refuse to start if the user and group do not match the
one defined at build time; this behaviour can be overridden by the
``--no-user-checks`` option.
ROLE
~~~~
The role of the query daemon is to answer queries about the (live)
cluster state without going through the master daemon. Only queries
which don't require locks can be handles by the query daemon, which
might lead to slightly outdated results in some cases.
The config is reloaded from disk automatically when it changes, with a
rate limit of once per second.
COMMUNICATION PROTOCOL
~~~~~~~~~~~~~~~~~~~~~~
See **gnt-master**\(8).
.. vim: set textwidth=72 :
.. Local Variables:
.. mode: rst
.. fill-column: 72
.. End:
......@@ -54,6 +54,7 @@ def TestGanetiCommands():
["ganeti-rapi", "--version"],
["ganeti-watcher", "--version"],
["ganeti-confd", "--version"],
["ganeti-luxid", "--version"],
)
cmd = " && ".join([utils.ShellQuoteArgs(i) for i in cmds])
......
{-# LANGUAGE BangPatterns #-}
{-| Implementation of the Ganeti confd server functionality.
-}
......@@ -32,8 +30,7 @@ module Ganeti.Confd.Server
) where
import Control.Concurrent
import Control.Exception
import Control.Monad (forever, liftM, unless)
import Control.Monad (forever, liftM)
import Data.IORef
import Data.List
import qualified Data.Map as M
......@@ -41,10 +38,7 @@ import Data.Maybe (fromMaybe)
import qualified Network.Socket as S
import System.Exit
import System.IO
import System.Posix.Files
import System.Posix.Types
import qualified Text.JSON as J
import System.INotify
import Ganeti.BasicTypes
import Ganeti.Errors
......@@ -54,11 +48,10 @@ import Ganeti.Objects
import Ganeti.Confd.Types
import Ganeti.Confd.Utils
import Ganeti.Config
import Ganeti.ConfigReader
import Ganeti.Hash
import Ganeti.Logging
import qualified Ganeti.Constants as C
import qualified Ganeti.Path as Path
import Ganeti.Query.Server (prepQueryD, runQueryD)
import qualified Ganeti.Query.Cluster as QCluster
import Ganeti.Utils
......@@ -67,54 +60,9 @@ import Ganeti.Utils
-- | What we store as configuration.
type CRef = IORef (Result (ConfigData, LinkIpMap))
-- | File stat identifier.
type FStat = (EpochTime, FileID, FileOffset)
-- | Null 'FStat' value.
nullFStat :: FStat
nullFStat = (-1, -1, -1)
-- | A small type alias for readability.
type StatusAnswer = (ConfdReplyStatus, J.JSValue)
-- | Reload model data type.
data ReloadModel = ReloadNotify -- ^ We are using notifications
| ReloadPoll Int -- ^ We are using polling
deriving (Eq, Show)
-- | Server state data type.
data ServerState = ServerState
{ reloadModel :: ReloadModel
, reloadTime :: Integer -- ^ Reload time (epoch) in microseconds
, reloadFStat :: FStat
}
-- | Maximum no-reload poll rounds before reverting to inotify.
maxIdlePollRounds :: Int
maxIdlePollRounds = 3
-- | Reload timeout in microseconds.
watchInterval :: Int
watchInterval = C.confdConfigReloadTimeout * 1000000
-- | Ratelimit timeout in microseconds.
pollInterval :: Int
pollInterval = C.confdConfigReloadRatelimit
-- | Ratelimit timeout in microseconds, as an 'Integer'.
reloadRatelimit :: Integer
reloadRatelimit = fromIntegral C.confdConfigReloadRatelimit
-- | Initial poll round.
initialPoll :: ReloadModel
initialPoll = ReloadPoll 0
-- | Reload status data type.
data ConfigReload = ConfigToDate -- ^ No need to reload
| ConfigReloaded -- ^ Configuration reloaded
| ConfigIOError -- ^ Error during configuration reload
deriving (Eq)
-- | Unknown entry standard response.
queryUnknownEntry :: StatusAnswer
queryUnknownEntry = (ReplyStatusError, J.showJSON ConfdErrorUnknownEntry)
......@@ -264,205 +212,6 @@ serializeResponse r =
, confdReplyAnswer = result
, confdReplySerial = 0 }
-- * Configuration handling
-- ** Helper functions
-- | Helper function for logging transition into polling mode.
moveToPolling :: String -> INotify -> FilePath -> CRef -> MVar ServerState
-> IO ReloadModel
moveToPolling msg inotify path cref mstate = do
logInfo $ "Moving to polling mode: " ++ msg
let inotiaction = addNotifier inotify path cref mstate
_ <- forkIO $ onPollTimer inotiaction path cref mstate
return initialPoll
-- | Helper function for logging transition into inotify mode.
moveToNotify :: IO ReloadModel
moveToNotify = do
logInfo "Moving to inotify mode"
return ReloadNotify
-- ** Configuration loading
-- | (Re)loads the configuration.
updateConfig :: FilePath -> CRef -> IO ()
updateConfig path r = do
newcfg <- loadConfig path
let !newdata = case newcfg of
Ok !cfg -> Ok (cfg, buildLinkIpInstnameMap cfg)
Bad _ -> Bad "Cannot load configuration"
writeIORef r newdata
case newcfg of
Ok cfg -> logInfo ("Loaded new config, serial " ++
show (configSerial cfg))
Bad msg -> logError $ "Failed to load config: " ++ msg
return ()
-- | Wrapper over 'updateConfig' that handles IO errors.
safeUpdateConfig :: FilePath -> FStat -> CRef -> IO (FStat, ConfigReload)
safeUpdateConfig path oldfstat cref =
Control.Exception.catch
(do
nt <- needsReload oldfstat path
case nt of
Nothing -> return (oldfstat, ConfigToDate)
Just nt' -> do
updateConfig path cref
return (nt', ConfigReloaded)
) (\e -> do
let msg = "Failure during configuration update: " ++
show (e::IOError)
writeIORef cref (Bad msg)
return (nullFStat, ConfigIOError)
)
-- | Computes the file cache data from a FileStatus structure.
buildFileStatus :: FileStatus -> FStat
buildFileStatus ofs =
let modt = modificationTime ofs
inum = fileID ofs
fsize = fileSize ofs
in (modt, inum, fsize)
-- | Wrapper over 'buildFileStatus'. This reads the data from the
-- filesystem and then builds our cache structure.
getFStat :: FilePath -> IO FStat
getFStat p = liftM buildFileStatus (getFileStatus p)
-- | Check if the file needs reloading
needsReload :: FStat -> FilePath -> IO (Maybe FStat)
needsReload oldstat path = do
newstat <- getFStat path
return $ if newstat /= oldstat
then Just newstat
else Nothing
-- ** Watcher threads
-- $watcher
-- We have three threads/functions that can mutate the server state:
--
-- 1. the long-interval watcher ('onWatcherTimer')
--
-- 2. the polling watcher ('onPollTimer')
--
-- 3. the inotify event handler ('onInotify')
--
-- All of these will mutate the server state under 'modifyMVar' or
-- 'modifyMVar_', so that server transitions are more or less
-- atomic. The inotify handler remains active during polling mode, but
-- checks for polling mode and doesn't do anything in this case (this
-- check is needed even if we would unregister the event handler due
-- to how events are serialised).
-- | Long-interval reload watcher.
--
-- This is on top of the inotify-based triggered reload.
onWatcherTimer :: IO Bool -> FilePath -> CRef -> MVar ServerState -> IO ()
onWatcherTimer inotiaction path cref state = do
threadDelay watchInterval
logDebug "Watcher timer fired"
modifyMVar_ state (onWatcherInner path cref)
_ <- inotiaction
onWatcherTimer inotiaction path cref state
-- | Inner onWatcher handler.
--
-- This mutates the server state under a modifyMVar_ call. It never
-- changes the reload model, just does a safety reload and tried to
-- re-establish the inotify watcher.
onWatcherInner :: FilePath -> CRef -> ServerState -> IO ServerState
onWatcherInner path cref state = do
(newfstat, _) <- safeUpdateConfig path (reloadFStat state) cref
return state { reloadFStat = newfstat }
-- | Short-interval (polling) reload watcher.
--
-- This is only active when we're in polling mode; it will
-- automatically exit when it detects that the state has changed to
-- notification.
onPollTimer :: IO Bool -> FilePath -> CRef -> MVar ServerState -> IO ()
onPollTimer inotiaction path cref state = do
threadDelay pollInterval
logDebug "Poll timer fired"
continue <- modifyMVar state (onPollInner inotiaction path cref)
if continue
then onPollTimer inotiaction path cref state
else logDebug "Inotify watch active, polling thread exiting"
-- | Inner onPoll handler.
--
-- This again mutates the state under a modifyMVar call, and also
-- returns whether the thread should continue or not.
onPollInner :: IO Bool -> FilePath -> CRef -> ServerState
-> IO (ServerState, Bool)
onPollInner _ _ _ state@(ServerState { reloadModel = ReloadNotify } ) =
return (state, False)