1
# -*- coding: utf-8 -*-
4
# Part of python-daemon, an implementation of PEP 3143.
6
# Copyright © 2009–2010 Ben Finney <ben+python@benfinney.id.au>
7
# Copyright © 2007–2008 Robert Niederreiter, Jens Klein
8
# Copyright © 2003 Clark Evans
9
# Copyright © 2002 Noah Spurrier
10
# Copyright © 2001 Jürgen Hermann
12
# This is free software: you may copy, modify, and/or distribute this work
13
# under the terms of the Python Software Foundation License, version 2 or
14
# later as published by the Python Software Foundation.
15
# No warranty expressed or implied. See the file LICENSE.PSF-2 for details.
17
""" Daemon runner library.
27
from daemon import DaemonContext
30
class DaemonRunnerError(Exception):
31
""" Abstract base class for errors from DaemonRunner. """
33
class DaemonRunnerInvalidActionError(ValueError, DaemonRunnerError):
34
""" Raised when specified action for DaemonRunner is invalid. """
36
class DaemonRunnerStartFailureError(RuntimeError, DaemonRunnerError):
37
""" Raised when failure starting DaemonRunner. """
39
class DaemonRunnerStopFailureError(RuntimeError, DaemonRunnerError):
40
""" Raised when failure stopping DaemonRunner. """
43
class DaemonRunner(object):
44
""" Controller for a callable running in a separate background process.
46
The first command-line argument is the action to take:
48
* 'start': Become a daemon and call `app.run()`.
49
* 'stop': Exit the daemon process specified in the PID file.
50
* 'restart': Stop, then start.
54
start_message = "started with pid %(pid)d"
56
def __init__(self, app):
57
""" Set up the parameters of a new runner.
59
The `app` argument must have the following attributes:
61
* `stdin_path`, `stdout_path`, `stderr_path`: Filesystem
62
paths to open and replace the existing `sys.stdin`,
63
`sys.stdout`, `sys.stderr`.
65
* `pidfile_path`: Absolute filesystem path to a file that
66
will be used as the PID file for the daemon. If
67
``None``, no PID file will be used.
69
* `pidfile_timeout`: Used as the default acquisition
70
timeout value supplied to the runner's PID lock file.
72
* `run`: Callable that will be invoked when the daemon is
78
self.daemon_context = DaemonContext()
79
self.daemon_context.stdin = open(app.stdin_path, 'r')
80
self.daemon_context.stdout = open(app.stdout_path, 'w+')
81
self.daemon_context.stderr = open(
82
app.stderr_path, 'w+', buffering=0)
85
if app.pidfile_path is not None:
86
self.pidfile = make_pidlockfile(
87
app.pidfile_path, app.pidfile_timeout)
88
self.daemon_context.pidfile = self.pidfile
90
def _usage_exit(self, argv):
91
""" Emit a usage message, then exit.
93
progname = os.path.basename(argv[0])
95
action_usage = "|".join(self.action_funcs.keys())
96
message = "usage: %(progname)s %(action_usage)s" % vars()
98
sys.exit(usage_exit_code)
100
def parse_args(self, argv=None):
101
""" Parse command-line arguments.
107
if len(argv) < min_args:
108
self._usage_exit(argv)
110
self.action = argv[1]
111
if self.action not in self.action_funcs:
112
self._usage_exit(argv)
115
""" Open the daemon context and run the application.
117
if is_pidfile_stale(self.pidfile):
118
self.pidfile.break_lock()
121
self.daemon_context.open()
122
except pidlockfile.AlreadyLocked:
123
pidfile_path = self.pidfile.path
124
raise DaemonRunnerStartFailureError(
125
"PID file %(pidfile_path)r already locked" % vars())
128
message = self.start_message % vars()
129
emit_message(message)
133
def _terminate_daemon_process(self):
134
""" Terminate the daemon process specified in the current PID file.
136
pid = self.pidfile.read_pid()
138
os.kill(pid, signal.SIGTERM)
140
raise DaemonRunnerStopFailureError(
141
"Failed to terminate %(pid)d: %(exc)s" % vars())
144
""" Exit the daemon process specified in the current PID file.
146
if not self.pidfile.is_locked():
147
pidfile_path = self.pidfile.path
148
raise DaemonRunnerStopFailureError(
149
"PID file %(pidfile_path)r not locked" % vars())
151
if is_pidfile_stale(self.pidfile):
152
self.pidfile.break_lock()
154
self._terminate_daemon_process()
157
""" Stop, then start.
168
def _get_action_func(self):
169
""" Return the function for the specified action.
171
Raises ``DaemonRunnerInvalidActionError`` if the action is
176
func = self.action_funcs[self.action]
178
raise DaemonRunnerInvalidActionError(
179
"Unknown action: %(action)r" % vars(self))
183
""" Perform the requested action.
185
func = self._get_action_func()
189
def emit_message(message, stream=None):
190
""" Emit a message to the specified stream (default `sys.stderr`). """
193
stream.write("%(message)s\n" % vars())
197
def make_pidlockfile(path, acquire_timeout):
198
""" Make a PIDLockFile instance with the given filesystem path. """
199
if not isinstance(path, basestring):
200
error = ValueError("Not a filesystem path: %(path)r" % vars())
202
if not os.path.isabs(path):
203
error = ValueError("Not an absolute path: %(path)r" % vars())
205
lockfile = pidlockfile.TimeoutPIDLockFile(path, acquire_timeout)
210
def is_pidfile_stale(pidfile):
211
""" Determine whether a PID file is stale.
213
Return ``True`` (“stale”) if the contents of the PID file are
214
valid but do not match the PID of a currently-running process;
215
otherwise return ``False``.
220
pidfile_pid = pidfile.read_pid()
221
if pidfile_pid is not None:
223
os.kill(pidfile_pid, signal.SIG_DFL)
225
if exc.errno == errno.ESRCH:
226
# The specified PID does not exist