diff options
author | Lorry Tar Creator <lorry-tar-importer@baserock.org> | 2010-03-02 09:35:54 +0000 |
---|---|---|
committer | <> | 2014-12-08 18:37:23 +0000 |
commit | 02378192d5bb4b16498d87ace57da425166426bf (patch) | |
tree | 2e940dd7284d31c7d32808d9c6635a57547363cb /daemon/runner.py | |
download | python-daemon-02378192d5bb4b16498d87ace57da425166426bf.tar.gz |
Imported from /home/lorry/working-area/delta_python-packages_python-daemon/python-daemon-1.5.5.tar.gz.python-daemon-1.5.5
Diffstat (limited to 'daemon/runner.py')
-rw-r--r-- | daemon/runner.py | 229 |
1 files changed, 229 insertions, 0 deletions
diff --git a/daemon/runner.py b/daemon/runner.py new file mode 100644 index 0000000..0642695 --- /dev/null +++ b/daemon/runner.py @@ -0,0 +1,229 @@ +# -*- coding: utf-8 -*- + +# daemon/runner.py +# Part of python-daemon, an implementation of PEP 3143. +# +# Copyright © 2009–2010 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 |