| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- # -*- coding: utf-8 -*-
- # daemon/runner.py
- # Part of python-daemon, an implementation of PEP 3143.
- #
- # Copyright © 2009 Ben Finney <ben+python@benfinney.id.au>
- # Copyright © 2007–2008 Robert Niederreiter, Jens Klein
- # Copyright © 2003 Clark Evans
- # Copyright © 2002 Noah Spurrier
- # Copyright © 2001 Jürgen Hermann
- #
- # This is free software: you may copy, modify, and/or distribute this work
- # under the terms of the Python Software Foundation License, version 2 or
- # later as published by the Python Software Foundation.
- # No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
- """ Daemon runner library.
- """
- import sys
- import os
- import signal
- import errno
- import pidlockfile
- from daemon import DaemonContext
- class DaemonRunnerError(Exception):
- """ Abstract base class for errors from DaemonRunner. """
- class DaemonRunnerInvalidActionError(ValueError, DaemonRunnerError):
- """ Raised when specified action for DaemonRunner is invalid. """
- class DaemonRunnerStartFailureError(RuntimeError, DaemonRunnerError):
- """ Raised when failure starting DaemonRunner. """
- class DaemonRunnerStopFailureError(RuntimeError, DaemonRunnerError):
- """ Raised when failure stopping DaemonRunner. """
- class DaemonRunner(object):
- """ Controller for a callable running in a separate background process.
- The first command-line argument is the action to take:
- * 'start': Become a daemon and call `app.run()`.
- * 'stop': Exit the daemon process specified in the PID file.
- * 'restart': Stop, then start.
- """
- start_message = "started with pid %(pid)d"
- def __init__(self, app):
- """ Set up the parameters of a new runner.
- The `app` argument must have the following attributes:
- * `stdin_path`, `stdout_path`, `stderr_path`: Filesystem
- paths to open and replace the existing `sys.stdin`,
- `sys.stdout`, `sys.stderr`.
- * `pidfile_path`: Absolute filesystem path to a file that
- will be used as the PID file for the daemon. If
- ``None``, no PID file will be used.
- * `pidfile_timeout`: Used as the default acquisition
- timeout value supplied to the runner's PID lock file.
- * `run`: Callable that will be invoked when the daemon is
- started.
-
- """
- self.parse_args()
- self.app = app
- self.daemon_context = DaemonContext()
- self.daemon_context.stdin = open(app.stdin_path, 'r')
- self.daemon_context.stdout = open(app.stdout_path, 'w+')
- self.daemon_context.stderr = open(
- app.stderr_path, 'w+', buffering=0)
- self.pidfile = None
- if app.pidfile_path is not None:
- self.pidfile = make_pidlockfile(
- app.pidfile_path, app.pidfile_timeout)
- self.daemon_context.pidfile = self.pidfile
- def _usage_exit(self, argv):
- """ Emit a usage message, then exit.
- """
- progname = os.path.basename(argv[0])
- usage_exit_code = 2
- action_usage = "|".join(self.action_funcs.keys())
- message = "usage: %(progname)s %(action_usage)s" % vars()
- emit_message(message)
- sys.exit(usage_exit_code)
- def parse_args(self, argv=None):
- """ Parse command-line arguments.
- """
- if argv is None:
- argv = sys.argv
- min_args = 2
- if len(argv) < min_args:
- self._usage_exit(argv)
- self.action = argv[1]
- if self.action not in self.action_funcs:
- self._usage_exit(argv)
- def _start(self):
- """ Open the daemon context and run the application.
- """
- if is_pidfile_stale(self.pidfile):
- self.pidfile.break_lock()
- try:
- self.daemon_context.open()
- except pidlockfile.AlreadyLocked:
- pidfile_path = self.pidfile.path
- raise DaemonRunnerStartFailureError(
- "PID file %(pidfile_path)r already locked" % vars())
- pid = os.getpid()
- message = self.start_message % vars()
- emit_message(message)
- self.app.run()
- def _terminate_daemon_process(self):
- """ Terminate the daemon process specified in the current PID file.
- """
- pid = self.pidfile.read_pid()
- try:
- os.kill(pid, signal.SIGTERM)
- except OSError, exc:
- raise DaemonRunnerStopFailureError(
- "Failed to terminate %(pid)d: %(exc)s" % vars())
- def _stop(self):
- """ Exit the daemon process specified in the current PID file.
- """
- if not self.pidfile.is_locked():
- pidfile_path = self.pidfile.path
- raise DaemonRunnerStopFailureError(
- "PID file %(pidfile_path)r not locked" % vars())
- if is_pidfile_stale(self.pidfile):
- self.pidfile.break_lock()
- else:
- self._terminate_daemon_process()
- def _restart(self):
- """ Stop, then start.
- """
- self._stop()
- self._start()
- action_funcs = {
- 'start': _start,
- 'stop': _stop,
- 'restart': _restart,
- }
- def _get_action_func(self):
- """ Return the function for the specified action.
- Raises ``DaemonRunnerInvalidActionError`` if the action is
- unknown.
- """
- try:
- func = self.action_funcs[self.action]
- except KeyError:
- raise DaemonRunnerInvalidActionError(
- "Unknown action: %(action)r" % vars(self))
- return func
- def do_action(self):
- """ Perform the requested action.
- """
- func = self._get_action_func()
- func(self)
- def emit_message(message, stream=None):
- """ Emit a message to the specified stream (default `sys.stderr`). """
- if stream is None:
- stream = sys.stderr
- stream.write("%(message)s\n" % vars())
- stream.flush()
- def make_pidlockfile(path, acquire_timeout):
- """ Make a PIDLockFile instance with the given filesystem path. """
- if not isinstance(path, basestring):
- error = ValueError("Not a filesystem path: %(path)r" % vars())
- raise error
- if not os.path.isabs(path):
- error = ValueError("Not an absolute path: %(path)r" % vars())
- raise error
- lockfile = pidlockfile.TimeoutPIDLockFile(path, acquire_timeout)
- return lockfile
- def is_pidfile_stale(pidfile):
- """ Determine whether a PID file is stale.
- Return ``True`` (“stale”) if the contents of the PID file are
- valid but do not match the PID of a currently-running process;
- otherwise return ``False``.
- """
- result = False
- pidfile_pid = pidfile.read_pid()
- if pidfile_pid is not None:
- try:
- os.kill(pidfile_pid, signal.SIG_DFL)
- except OSError, exc:
- if exc.errno == errno.ESRCH:
- # The specified PID does not exist
- result = True
- return result
|