diff --git a/patroni/daemon.py b/patroni/daemon.py index 6bac504b..404e4061 100644 --- a/patroni/daemon.py +++ b/patroni/daemon.py @@ -1,3 +1,8 @@ +"""Daemon processes abstraction module. + +This module implements abstraction classes and functions for creating and managing daemon processes in Patroni. +Currently it is only used for the main "Thread" of ``patroni`` and ``patroni_raft_controller`` commands. +""" from __future__ import print_function import abc @@ -6,11 +11,30 @@ import signal import sys from threading import Lock +from typing import Any, Optional, Type + +from .config import Config +from .validator import Schema class AbstractPatroniDaemon(abc.ABC): + """A Patroni daemon process. - def __init__(self, config): + .. note:: + + When inheriting from :class:`AbstractPatroniDaemon` you are expected to define the methods :func:`_run_cycle` + to determine what it should do in each execution cycle, and :func:`_shutdown` to determine what it should do + when shutting down. + + :ivar logger: log handler used by this daemon. + :ivar config: configuration options for this daemon. + """ + + def __init__(self, config: Config) -> None: + """Set up signal handlers, logging handler and configuration. + + :param config: configuration options for this daemon. + """ from patroni.log import PatroniLogger self.setup_signal_handlers() @@ -19,20 +43,44 @@ class AbstractPatroniDaemon(abc.ABC): self.config = config AbstractPatroniDaemon.reload_config(self, local=True) - def sighup_handler(self, *args): + def sighup_handler(self, *_: Any) -> None: + """Handle SIGHUP signals. + + Flag the daemon as "SIGHUP received". + """ self._received_sighup = True - def api_sigterm(self): + def api_sigterm(self) -> bool: + """Guarantee only a single SIGTERM is being processed. + + Flag the daemon as "SIGTERM received" with a lock-based approach. + + :returns: ``True`` if the daemon was flagged as "SIGTERM received". + """ + ret = False with self._sigterm_lock: if not self._received_sigterm: self._received_sigterm = True - return True + ret = True + return ret - def sigterm_handler(self, *args): + def sigterm_handler(self, *_: Any) -> None: + """Handle SIGTERM signals. + + Terminate the daemon process through :func:`api_sigterm`. + """ if self.api_sigterm(): sys.exit() - def setup_signal_handlers(self): + def setup_signal_handlers(self) -> None: + """Set up daemon signal handlers. + + Set up SIGHUP and SIGTERM signal handlers. + + .. note:: + + SIGHUP is only handled in non-Windows environments. + """ self._received_sighup = False self._sigterm_lock = Lock() self._received_sigterm = False @@ -41,19 +89,34 @@ class AbstractPatroniDaemon(abc.ABC): signal.signal(signal.SIGTERM, self.sigterm_handler) @property - def received_sigterm(self): + def received_sigterm(self) -> bool: + """If daemon was signaled with SIGTERM.""" with self._sigterm_lock: return self._received_sigterm - def reload_config(self, sighup=False, local=False): + def reload_config(self, sighup: Optional[bool] = False, local: Optional[bool] = False) -> None: + """Reload configuration. + + :param sighup: if it is related to a SIGHUP signal. + The sighup parameter could be used in the method overridden in a child class. + :param local: will be ``True`` if there are changes in the local configuration file. + """ if local: self.logger.reload_config(self.config.get('log', {})) @abc.abstractmethod - def _run_cycle(self): - """_run_cycle""" + def _run_cycle(self) -> None: + """Define what the daemon should do in each execution cycle. - def run(self): + Keep being called in the daemon's main loop until the daemon is eventually terminated. + """ + + def run(self) -> None: + """Run the daemon process. + + Start the logger thread and keep running execution cycles until a SIGTERM is eventually received. Also reload + configuration uppon receiving SIGHUP. + """ self.logger.start() while not self.received_sigterm: if self._received_sighup: @@ -63,17 +126,29 @@ class AbstractPatroniDaemon(abc.ABC): self._run_cycle() @abc.abstractmethod - def _shutdown(self): - """_shutdown""" + def _shutdown(self) -> None: + """Define what the daemon should do when shutting down.""" - def shutdown(self): + def shutdown(self) -> None: + """Shut the daemon down when a SIGTERM is received. + + Shut down the daemon process and the logger thread. + """ with self._sigterm_lock: self._received_sigterm = True self._shutdown() self.logger.shutdown() -def abstract_main(cls, validator=None): +def abstract_main(cls: Type[AbstractPatroniDaemon], validator: Optional[Schema] = None) -> None: + """Create the main entry point of a given daemon process. + + Expose a basic argument parser, parse the command-line arguments, and run the given daemon process. + + :param cls: a class that should inherit from :class:`AbstractPatroniDaemon`. + :param validator: used to validate the daemon configuration schema, if requested by the user through + ``--validate-config`` CLI option. + """ import argparse from .config import Config, ConfigParseError