comparison python-daemon-2.0.5/daemon/runner.py @ 33:7ceb967147c3

start xena with no gui add library files
author jingchunzhu <jingchunzhu@gmail.com>
date Wed, 22 Jul 2015 13:24:44 -0700
parents
children
comparison
equal deleted inserted replaced
32:63b1ba1e3424 33:7ceb967147c3
1 # -*- coding: utf-8 -*-
2
3 # daemon/runner.py
4 # Part of ‘python-daemon’, an implementation of PEP 3143.
5 #
6 # Copyright © 2009–2015 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
11 #
12 # This is free software: you may copy, modify, and/or distribute this work
13 # under the terms of the Apache License, version 2.0 as published by the
14 # Apache Software Foundation.
15 # No warranty expressed or implied. See the file ‘LICENSE.ASF-2’ for details.
16
17 """ Daemon runner library.
18 """
19
20 from __future__ import (absolute_import, unicode_literals)
21
22 import sys
23 import os
24 import signal
25 import errno
26 try:
27 # Python 3 standard library.
28 ProcessLookupError
29 except NameError:
30 # No such class in Python 2.
31 ProcessLookupError = NotImplemented
32
33 import lockfile
34
35 from . import pidfile
36 from .daemon import (basestring, unicode)
37 from .daemon import DaemonContext
38 from .daemon import _chain_exception_from_existing_exception_context
39
40
41 class DaemonRunnerError(Exception):
42 """ Abstract base class for errors from DaemonRunner. """
43
44 def __init__(self, *args, **kwargs):
45 self._chain_from_context()
46
47 super(DaemonRunnerError, self).__init__(*args, **kwargs)
48
49 def _chain_from_context(self):
50 _chain_exception_from_existing_exception_context(self, as_cause=True)
51
52
53 class DaemonRunnerInvalidActionError(DaemonRunnerError, ValueError):
54 """ Raised when specified action for DaemonRunner is invalid. """
55
56 def _chain_from_context(self):
57 # This exception is normally not caused by another.
58 _chain_exception_from_existing_exception_context(self, as_cause=False)
59
60
61 class DaemonRunnerStartFailureError(DaemonRunnerError, RuntimeError):
62 """ Raised when failure starting DaemonRunner. """
63
64
65 class DaemonRunnerStopFailureError(DaemonRunnerError, RuntimeError):
66 """ Raised when failure stopping DaemonRunner. """
67
68
69 class DaemonRunner:
70 """ Controller for a callable running in a separate background process.
71
72 The first command-line argument is the action to take:
73
74 * 'start': Become a daemon and call `app.run()`.
75 * 'stop': Exit the daemon process specified in the PID file.
76 * 'restart': Stop, then start.
77
78 """
79
80 __metaclass__ = type
81
82 start_message = "started with pid {pid:d}"
83
84 def __init__(self, app):
85 """ Set up the parameters of a new runner.
86
87 :param app: The application instance; see below.
88 :return: ``None``.
89
90 The `app` argument must have the following attributes:
91
92 * `stdin_path`, `stdout_path`, `stderr_path`: Filesystem paths
93 to open and replace the existing `sys.stdin`, `sys.stdout`,
94 `sys.stderr`.
95
96 * `pidfile_path`: Absolute filesystem path to a file that will
97 be used as the PID file for the daemon. If ``None``, no PID
98 file will be used.
99
100 * `pidfile_timeout`: Used as the default acquisition timeout
101 value supplied to the runner's PID lock file.
102
103 * `run`: Callable that will be invoked when the daemon is
104 started.
105
106 """
107 self.parse_args()
108 self.app = app
109 self.daemon_context = DaemonContext()
110 self.daemon_context.stdin = open(app.stdin_path, 'rt')
111 self.daemon_context.stdout = open(app.stdout_path, 'w+t')
112 self.daemon_context.stderr = open(
113 app.stderr_path, 'w+t', buffering=0)
114
115 self.pidfile = None
116 if app.pidfile_path is not None:
117 self.pidfile = make_pidlockfile(
118 app.pidfile_path, app.pidfile_timeout)
119 self.daemon_context.pidfile = self.pidfile
120
121 def _usage_exit(self, argv):
122 """ Emit a usage message, then exit.
123
124 :param argv: The command-line arguments used to invoke the
125 program, as a sequence of strings.
126 :return: ``None``.
127
128 """
129 progname = os.path.basename(argv[0])
130 usage_exit_code = 2
131 action_usage = "|".join(self.action_funcs.keys())
132 message = "usage: {progname} {usage}".format(
133 progname=progname, usage=action_usage)
134 emit_message(message)
135 sys.exit(usage_exit_code)
136
137 def parse_args(self, argv=None):
138 """ Parse command-line arguments.
139
140 :param argv: The command-line arguments used to invoke the
141 program, as a sequence of strings.
142
143 :return: ``None``.
144
145 The parser expects the first argument as the program name, the
146 second argument as the action to perform.
147
148 If the parser fails to parse the arguments, emit a usage
149 message and exit the program.
150
151 """
152 if argv is None:
153 argv = sys.argv
154
155 min_args = 2
156 if len(argv) < min_args:
157 self._usage_exit(argv)
158
159 self.action = unicode(argv[1])
160 if self.action not in self.action_funcs:
161 self._usage_exit(argv)
162
163 def _start(self):
164 """ Open the daemon context and run the application.
165
166 :return: ``None``.
167 :raises DaemonRunnerStartFailureError: If the PID file cannot
168 be locked by this process.
169
170 """
171 if is_pidfile_stale(self.pidfile):
172 self.pidfile.break_lock()
173
174 try:
175 self.daemon_context.open()
176 except lockfile.AlreadyLocked:
177 error = DaemonRunnerStartFailureError(
178 "PID file {pidfile.path!r} already locked".format(
179 pidfile=self.pidfile))
180 raise error
181
182 pid = os.getpid()
183 message = self.start_message.format(pid=pid)
184 emit_message(message)
185
186 self.app.run()
187
188 def _terminate_daemon_process(self):
189 """ Terminate the daemon process specified in the current PID file.
190
191 :return: ``None``.
192 :raises DaemonRunnerStopFailureError: If terminating the daemon
193 fails with an OS error.
194
195 """
196 pid = self.pidfile.read_pid()
197 try:
198 os.kill(pid, signal.SIGTERM)
199 except OSError as exc:
200 error = DaemonRunnerStopFailureError(
201 "Failed to terminate {pid:d}: {exc}".format(
202 pid=pid, exc=exc))
203 raise error
204
205 def _stop(self):
206 """ Exit the daemon process specified in the current PID file.
207
208 :return: ``None``.
209 :raises DaemonRunnerStopFailureError: If the PID file is not
210 already locked.
211
212 """
213 if not self.pidfile.is_locked():
214 error = DaemonRunnerStopFailureError(
215 "PID file {pidfile.path!r} not locked".format(
216 pidfile=self.pidfile))
217 raise error
218
219 if is_pidfile_stale(self.pidfile):
220 self.pidfile.break_lock()
221 else:
222 self._terminate_daemon_process()
223
224 def _restart(self):
225 """ Stop, then start.
226 """
227 self._stop()
228 self._start()
229
230 action_funcs = {
231 'start': _start,
232 'stop': _stop,
233 'restart': _restart,
234 }
235
236 def _get_action_func(self):
237 """ Get the function for the specified action.
238
239 :return: The function object corresponding to the specified
240 action.
241 :raises DaemonRunnerInvalidActionError: if the action is
242 unknown.
243
244 The action is specified by the `action` attribute, which is set
245 during `parse_args`.
246
247 """
248 try:
249 func = self.action_funcs[self.action]
250 except KeyError:
251 error = DaemonRunnerInvalidActionError(
252 "Unknown action: {action!r}".format(
253 action=self.action))
254 raise error
255 return func
256
257 def do_action(self):
258 """ Perform the requested action.
259
260 :return: ``None``.
261
262 The action is specified by the `action` attribute, which is set
263 during `parse_args`.
264
265 """
266 func = self._get_action_func()
267 func(self)
268
269
270 def emit_message(message, stream=None):
271 """ Emit a message to the specified stream (default `sys.stderr`). """
272 if stream is None:
273 stream = sys.stderr
274 stream.write("{message}\n".format(message=message))
275 stream.flush()
276
277
278 def make_pidlockfile(path, acquire_timeout):
279 """ Make a PIDLockFile instance with the given filesystem path. """
280 if not isinstance(path, basestring):
281 error = ValueError("Not a filesystem path: {path!r}".format(
282 path=path))
283 raise error
284 if not os.path.isabs(path):
285 error = ValueError("Not an absolute path: {path!r}".format(
286 path=path))
287 raise error
288 lockfile = pidfile.TimeoutPIDLockFile(path, acquire_timeout)
289
290 return lockfile
291
292
293 def is_pidfile_stale(pidfile):
294 """ Determine whether a PID file is stale.
295
296 :return: ``True`` iff the PID file is stale; otherwise ``False``.
297
298 The PID file is “stale” if its contents are valid but do not
299 match the PID of a currently-running process.
300
301 """
302 result = False
303
304 pidfile_pid = pidfile.read_pid()
305 if pidfile_pid is not None:
306 try:
307 os.kill(pidfile_pid, signal.SIG_DFL)
308 except ProcessLookupError:
309 # The specified PID does not exist.
310 result = True
311 except OSError as exc:
312 if exc.errno == errno.ESRCH:
313 # Under Python 2, process lookup error is an OSError.
314 # The specified PID does not exist.
315 result = True
316
317 return result
318
319
320 # Local variables:
321 # coding: utf-8
322 # mode: python
323 # End:
324 # vim: fileencoding=utf-8 filetype=python :