1 | # (c) 2005 Ian Bicking and contributors; written for Paste (http://pythonpaste.org) |
---|
2 | # Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php |
---|
3 | # @@: This should be moved to paste.deploy |
---|
4 | # For discussion of daemonizing: |
---|
5 | # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/278731 |
---|
6 | # Code taken also from QP: |
---|
7 | # http://www.mems-exchange.org/software/qp/ |
---|
8 | # From lib/site.py |
---|
9 | import re |
---|
10 | import os |
---|
11 | import errno |
---|
12 | import sys |
---|
13 | import time |
---|
14 | try: |
---|
15 | import subprocess |
---|
16 | except ImportError: |
---|
17 | from paste.util import subprocess24 as subprocess |
---|
18 | from command import Command, BadCommand |
---|
19 | from paste.deploy import loadapp, loadserver |
---|
20 | import threading |
---|
21 | import atexit |
---|
22 | import logging |
---|
23 | import ConfigParser |
---|
24 | |
---|
25 | MAXFD = 1024 |
---|
26 | |
---|
27 | jython = sys.platform.startswith('java') |
---|
28 | |
---|
29 | class DaemonizeException(Exception): |
---|
30 | pass |
---|
31 | |
---|
32 | |
---|
33 | class ServeCommand(Command): |
---|
34 | |
---|
35 | min_args = 0 |
---|
36 | usage = 'CONFIG_FILE [start|stop|restart|status] [var=value]' |
---|
37 | takes_config_file = 1 |
---|
38 | summary = "Serve the described application" |
---|
39 | description = """\ |
---|
40 | This command serves a web application that uses a paste.deploy |
---|
41 | configuration file for the server and application. |
---|
42 | |
---|
43 | If start/stop/restart is given, then --daemon is implied, and it will |
---|
44 | start (normal operation), stop (--stop-daemon), or do both. |
---|
45 | |
---|
46 | You can also include variable assignments like 'http_port=8080' |
---|
47 | and then use %(http_port)s in your config files. |
---|
48 | """ |
---|
49 | |
---|
50 | # used by subclasses that configure apps and servers differently |
---|
51 | requires_config_file = True |
---|
52 | |
---|
53 | parser = Command.standard_parser(quiet=True) |
---|
54 | parser.add_option('-n', '--app-name', |
---|
55 | dest='app_name', |
---|
56 | metavar='NAME', |
---|
57 | help="Load the named application (default main)") |
---|
58 | parser.add_option('-s', '--server', |
---|
59 | dest='server', |
---|
60 | metavar='SERVER_TYPE', |
---|
61 | help="Use the named server.") |
---|
62 | parser.add_option('--server-name', |
---|
63 | dest='server_name', |
---|
64 | metavar='SECTION_NAME', |
---|
65 | help="Use the named server as defined in the configuration file (default: main)") |
---|
66 | if hasattr(os, 'fork'): |
---|
67 | parser.add_option('--daemon', |
---|
68 | dest="daemon", |
---|
69 | action="store_true", |
---|
70 | help="Run in daemon (background) mode") |
---|
71 | parser.add_option('--pid-file', |
---|
72 | dest='pid_file', |
---|
73 | metavar='FILENAME', |
---|
74 | help="Save PID to file (default to paster.pid if running in daemon mode)") |
---|
75 | parser.add_option('--log-file', |
---|
76 | dest='log_file', |
---|
77 | metavar='LOG_FILE', |
---|
78 | help="Save output to the given log file (redirects stdout)") |
---|
79 | parser.add_option('--reload', |
---|
80 | dest='reload', |
---|
81 | action='store_true', |
---|
82 | help="Use auto-restart file monitor") |
---|
83 | parser.add_option('--reload-interval', |
---|
84 | dest='reload_interval', |
---|
85 | default=1, |
---|
86 | help="Seconds between checking files (low number can cause significant CPU usage)") |
---|
87 | parser.add_option('--monitor-restart', |
---|
88 | dest='monitor_restart', |
---|
89 | action='store_true', |
---|
90 | help="Auto-restart server if it dies") |
---|
91 | parser.add_option('--status', |
---|
92 | action='store_true', |
---|
93 | dest='show_status', |
---|
94 | help="Show the status of the (presumably daemonized) server") |
---|
95 | |
---|
96 | |
---|
97 | if hasattr(os, 'setuid'): |
---|
98 | # I don't think these are available on Windows |
---|
99 | parser.add_option('--user', |
---|
100 | dest='set_user', |
---|
101 | metavar="USERNAME", |
---|
102 | help="Set the user (usually only possible when run as root)") |
---|
103 | parser.add_option('--group', |
---|
104 | dest='set_group', |
---|
105 | metavar="GROUP", |
---|
106 | help="Set the group (usually only possible when run as root)") |
---|
107 | |
---|
108 | parser.add_option('--stop-daemon', |
---|
109 | dest='stop_daemon', |
---|
110 | action='store_true', |
---|
111 | help='Stop a daemonized server (given a PID file, or default paster.pid file)') |
---|
112 | |
---|
113 | if jython: |
---|
114 | parser.add_option('--disable-jython-reloader', |
---|
115 | action='store_true', |
---|
116 | dest='disable_jython_reloader', |
---|
117 | help="Disable the Jython reloader") |
---|
118 | |
---|
119 | |
---|
120 | _scheme_re = re.compile(r'^[a-z][a-z]+:', re.I) |
---|
121 | |
---|
122 | default_verbosity = 1 |
---|
123 | |
---|
124 | _reloader_environ_key = 'PYTHON_RELOADER_SHOULD_RUN' |
---|
125 | _monitor_environ_key = 'PASTE_MONITOR_SHOULD_RUN' |
---|
126 | |
---|
127 | possible_subcommands = ('start', 'stop', 'restart', 'status') |
---|
128 | def command(self): |
---|
129 | if self.options.stop_daemon: |
---|
130 | return self.stop_daemon() |
---|
131 | |
---|
132 | if not hasattr(self.options, 'set_user'): |
---|
133 | # Windows case: |
---|
134 | self.options.set_user = self.options.set_group = None |
---|
135 | # @@: Is this the right stage to set the user at? |
---|
136 | self.change_user_group( |
---|
137 | self.options.set_user, self.options.set_group) |
---|
138 | |
---|
139 | if self.requires_config_file: |
---|
140 | if not self.args: |
---|
141 | raise BadCommand('You must give a config file') |
---|
142 | app_spec = self.args[0] |
---|
143 | if (len(self.args) > 1 |
---|
144 | and self.args[1] in self.possible_subcommands): |
---|
145 | cmd = self.args[1] |
---|
146 | restvars = self.args[2:] |
---|
147 | else: |
---|
148 | cmd = None |
---|
149 | restvars = self.args[1:] |
---|
150 | else: |
---|
151 | app_spec = "" |
---|
152 | if (self.args |
---|
153 | and self.args[0] in self.possible_subcommands): |
---|
154 | cmd = self.args[0] |
---|
155 | restvars = self.args[1:] |
---|
156 | else: |
---|
157 | cmd = None |
---|
158 | restvars = self.args[:] |
---|
159 | |
---|
160 | jython_monitor = False |
---|
161 | if self.options.reload: |
---|
162 | if jython and not self.options.disable_jython_reloader: |
---|
163 | # JythonMonitor raises the special SystemRestart |
---|
164 | # exception that'll cause the Jython interpreter to |
---|
165 | # reload in the existing Java process (avoiding |
---|
166 | # subprocess startup time) |
---|
167 | try: |
---|
168 | from paste.reloader import JythonMonitor |
---|
169 | except ImportError: |
---|
170 | pass |
---|
171 | else: |
---|
172 | jython_monitor = JythonMonitor(poll_interval=int( |
---|
173 | self.options.reload_interval)) |
---|
174 | if self.requires_config_file: |
---|
175 | jython_monitor.watch_file(self.args[0]) |
---|
176 | |
---|
177 | if not jython_monitor: |
---|
178 | if os.environ.get(self._reloader_environ_key): |
---|
179 | from paste import reloader |
---|
180 | if self.verbose > 1: |
---|
181 | print 'Running reloading file monitor' |
---|
182 | reloader.install(int(self.options.reload_interval)) |
---|
183 | if self.requires_config_file: |
---|
184 | reloader.watch_file(self.args[0]) |
---|
185 | else: |
---|
186 | return self.restart_with_reloader() |
---|
187 | |
---|
188 | if cmd not in (None, 'start', 'stop', 'restart', 'status'): |
---|
189 | raise BadCommand( |
---|
190 | 'Error: must give start|stop|restart (not %s)' % cmd) |
---|
191 | |
---|
192 | if cmd == 'status' or self.options.show_status: |
---|
193 | return self.show_status() |
---|
194 | |
---|
195 | if cmd == 'restart' or cmd == 'stop': |
---|
196 | result = self.stop_daemon() |
---|
197 | if result: |
---|
198 | if cmd == 'restart': |
---|
199 | print "Could not stop daemon; aborting" |
---|
200 | else: |
---|
201 | print "Could not stop daemon" |
---|
202 | return result |
---|
203 | if cmd == 'stop': |
---|
204 | return result |
---|
205 | |
---|
206 | app_name = self.options.app_name |
---|
207 | vars = self.parse_vars(restvars) |
---|
208 | if not self._scheme_re.search(app_spec): |
---|
209 | app_spec = 'config:' + app_spec |
---|
210 | server_name = self.options.server_name |
---|
211 | if self.options.server: |
---|
212 | server_spec = 'egg:PasteScript' |
---|
213 | assert server_name is None |
---|
214 | server_name = self.options.server |
---|
215 | else: |
---|
216 | server_spec = app_spec |
---|
217 | base = os.getcwd() |
---|
218 | |
---|
219 | if getattr(self.options, 'daemon', False): |
---|
220 | if not self.options.pid_file: |
---|
221 | self.options.pid_file = 'paster.pid' |
---|
222 | if not self.options.log_file: |
---|
223 | self.options.log_file = 'paster.log' |
---|
224 | |
---|
225 | # Ensure the log file is writeable |
---|
226 | if self.options.log_file: |
---|
227 | try: |
---|
228 | writeable_log_file = open(self.options.log_file, 'a') |
---|
229 | except IOError, ioe: |
---|
230 | msg = 'Error: Unable to write to log file: %s' % ioe |
---|
231 | raise BadCommand(msg) |
---|
232 | writeable_log_file.close() |
---|
233 | |
---|
234 | # Ensure the pid file is writeable |
---|
235 | if self.options.pid_file: |
---|
236 | try: |
---|
237 | writeable_pid_file = open(self.options.pid_file, 'a') |
---|
238 | except IOError, ioe: |
---|
239 | msg = 'Error: Unable to write to pid file: %s' % ioe |
---|
240 | raise BadCommand(msg) |
---|
241 | writeable_pid_file.close() |
---|
242 | |
---|
243 | if getattr(self.options, 'daemon', False): |
---|
244 | try: |
---|
245 | self.daemonize() |
---|
246 | except DaemonizeException, ex: |
---|
247 | if self.verbose > 0: |
---|
248 | print str(ex) |
---|
249 | return |
---|
250 | |
---|
251 | if (self.options.monitor_restart |
---|
252 | and not os.environ.get(self._monitor_environ_key)): |
---|
253 | return self.restart_with_monitor() |
---|
254 | |
---|
255 | if self.options.pid_file: |
---|
256 | self.record_pid(self.options.pid_file) |
---|
257 | |
---|
258 | if self.options.log_file: |
---|
259 | stdout_log = LazyWriter(self.options.log_file, 'a') |
---|
260 | sys.stdout = stdout_log |
---|
261 | sys.stderr = stdout_log |
---|
262 | logging.basicConfig(stream=stdout_log) |
---|
263 | |
---|
264 | log_fn = app_spec |
---|
265 | if log_fn.startswith('config:'): |
---|
266 | log_fn = app_spec[len('config:'):] |
---|
267 | elif log_fn.startswith('egg:'): |
---|
268 | log_fn = None |
---|
269 | if log_fn: |
---|
270 | log_fn = os.path.join(base, log_fn) |
---|
271 | self.logging_file_config(log_fn) |
---|
272 | |
---|
273 | server = self.loadserver(server_spec, name=server_name, |
---|
274 | relative_to=base, global_conf=vars) |
---|
275 | app = self.loadapp(app_spec, name=app_name, |
---|
276 | relative_to=base, global_conf=vars) |
---|
277 | |
---|
278 | if self.verbose > 0: |
---|
279 | if hasattr(os, 'getpid'): |
---|
280 | msg = 'Starting server in PID %i.' % os.getpid() |
---|
281 | else: |
---|
282 | msg = 'Starting server.' |
---|
283 | print msg |
---|
284 | |
---|
285 | def serve(): |
---|
286 | try: |
---|
287 | server(app) |
---|
288 | except (SystemExit, KeyboardInterrupt), e: |
---|
289 | if self.verbose > 1: |
---|
290 | raise |
---|
291 | if str(e): |
---|
292 | msg = ' '+str(e) |
---|
293 | else: |
---|
294 | msg = '' |
---|
295 | print 'Exiting%s (-v to see traceback)' % msg |
---|
296 | |
---|
297 | if jython_monitor: |
---|
298 | # JythonMonitor has to be ran from the main thread |
---|
299 | threading.Thread(target=serve).start() |
---|
300 | print 'Starting Jython file monitor' |
---|
301 | jython_monitor.periodic_reload() |
---|
302 | else: |
---|
303 | serve() |
---|
304 | |
---|
305 | def loadserver(self, server_spec, name, relative_to, **kw): |
---|
306 | return loadserver( |
---|
307 | server_spec, name=name, |
---|
308 | relative_to=relative_to, **kw) |
---|
309 | |
---|
310 | def loadapp(self, app_spec, name, relative_to, **kw): |
---|
311 | return loadapp( |
---|
312 | app_spec, name=name, relative_to=relative_to, |
---|
313 | **kw) |
---|
314 | |
---|
315 | def daemonize(self): |
---|
316 | pid = live_pidfile(self.options.pid_file) |
---|
317 | if pid: |
---|
318 | raise DaemonizeException( |
---|
319 | "Daemon is already running (PID: %s from PID file %s)" |
---|
320 | % (pid, self.options.pid_file)) |
---|
321 | |
---|
322 | if self.verbose > 0: |
---|
323 | print 'Entering daemon mode' |
---|
324 | pid = os.fork() |
---|
325 | if pid: |
---|
326 | # The forked process also has a handle on resources, so we |
---|
327 | # *don't* want proper termination of the process, we just |
---|
328 | # want to exit quick (which os._exit() does) |
---|
329 | os._exit(0) |
---|
330 | # Make this the session leader |
---|
331 | os.setsid() |
---|
332 | # Fork again for good measure! |
---|
333 | pid = os.fork() |
---|
334 | if pid: |
---|
335 | os._exit(0) |
---|
336 | |
---|
337 | # @@: Should we set the umask and cwd now? |
---|
338 | |
---|
339 | import resource # Resource usage information. |
---|
340 | maxfd = resource.getrlimit(resource.RLIMIT_NOFILE)[1] |
---|
341 | if (maxfd == resource.RLIM_INFINITY): |
---|
342 | maxfd = MAXFD |
---|
343 | # Iterate through and close all file descriptors. |
---|
344 | for fd in range(0, maxfd): |
---|
345 | try: |
---|
346 | os.close(fd) |
---|
347 | except OSError: # ERROR, fd wasn't open to begin with (ignored) |
---|
348 | pass |
---|
349 | |
---|
350 | if (hasattr(os, "devnull")): |
---|
351 | REDIRECT_TO = os.devnull |
---|
352 | else: |
---|
353 | REDIRECT_TO = "/dev/null" |
---|
354 | os.open(REDIRECT_TO, os.O_RDWR) # standard input (0) |
---|
355 | # Duplicate standard input to standard output and standard error. |
---|
356 | os.dup2(0, 1) # standard output (1) |
---|
357 | os.dup2(0, 2) # standard error (2) |
---|
358 | |
---|
359 | def record_pid(self, pid_file): |
---|
360 | pid = os.getpid() |
---|
361 | if self.verbose > 1: |
---|
362 | print 'Writing PID %s to %s' % (pid, pid_file) |
---|
363 | f = open(pid_file, 'w') |
---|
364 | f.write(str(pid)) |
---|
365 | f.close() |
---|
366 | atexit.register(_remove_pid_file, pid, pid_file, self.verbose) |
---|
367 | |
---|
368 | def stop_daemon(self): |
---|
369 | pid_file = self.options.pid_file or 'paster.pid' |
---|
370 | if not os.path.exists(pid_file): |
---|
371 | print 'No PID file exists in %s' % pid_file |
---|
372 | return 1 |
---|
373 | pid = read_pidfile(pid_file) |
---|
374 | if not pid: |
---|
375 | print "Not a valid PID file in %s" % pid_file |
---|
376 | return 1 |
---|
377 | pid = live_pidfile(pid_file) |
---|
378 | if not pid: |
---|
379 | print "PID in %s is not valid (deleting)" % pid_file |
---|
380 | try: |
---|
381 | os.unlink(pid_file) |
---|
382 | except (OSError, IOError), e: |
---|
383 | print "Could not delete: %s" % e |
---|
384 | return 2 |
---|
385 | return 1 |
---|
386 | for j in range(10): |
---|
387 | if not live_pidfile(pid_file): |
---|
388 | break |
---|
389 | import signal |
---|
390 | os.kill(pid, signal.SIGTERM) |
---|
391 | time.sleep(1) |
---|
392 | else: |
---|
393 | print "failed to kill web process %s" % pid |
---|
394 | return 3 |
---|
395 | if os.path.exists(pid_file): |
---|
396 | os.unlink(pid_file) |
---|
397 | return 0 |
---|
398 | |
---|
399 | def show_status(self): |
---|
400 | pid_file = self.options.pid_file or 'paster.pid' |
---|
401 | if not os.path.exists(pid_file): |
---|
402 | print 'No PID file %s' % pid_file |
---|
403 | return 1 |
---|
404 | pid = read_pidfile(pid_file) |
---|
405 | if not pid: |
---|
406 | print 'No PID in file %s' % pid_file |
---|
407 | return 1 |
---|
408 | pid = live_pidfile(pid_file) |
---|
409 | if not pid: |
---|
410 | print 'PID %s in %s is not running' % (pid, pid_file) |
---|
411 | return 1 |
---|
412 | print 'Server running in PID %s' % pid |
---|
413 | return 0 |
---|
414 | |
---|
415 | def restart_with_reloader(self): |
---|
416 | self.restart_with_monitor(reloader=True) |
---|
417 | |
---|
418 | def restart_with_monitor(self, reloader=False): |
---|
419 | if self.verbose > 0: |
---|
420 | if reloader: |
---|
421 | print 'Starting subprocess with file monitor' |
---|
422 | else: |
---|
423 | print 'Starting subprocess with monitor parent' |
---|
424 | while 1: |
---|
425 | args = [self.quote_first_command_arg(sys.executable)] + sys.argv |
---|
426 | new_environ = os.environ.copy() |
---|
427 | if reloader: |
---|
428 | new_environ[self._reloader_environ_key] = 'true' |
---|
429 | else: |
---|
430 | new_environ[self._monitor_environ_key] = 'true' |
---|
431 | proc = None |
---|
432 | try: |
---|
433 | try: |
---|
434 | _turn_sigterm_into_systemexit() |
---|
435 | proc = subprocess.Popen(args, env=new_environ) |
---|
436 | exit_code = proc.wait() |
---|
437 | proc = None |
---|
438 | except KeyboardInterrupt: |
---|
439 | print '^C caught in monitor process' |
---|
440 | if self.verbose > 1: |
---|
441 | raise |
---|
442 | return 1 |
---|
443 | finally: |
---|
444 | if (proc is not None |
---|
445 | and hasattr(os, 'kill')): |
---|
446 | import signal |
---|
447 | try: |
---|
448 | os.kill(proc.pid, signal.SIGTERM) |
---|
449 | except (OSError, IOError): |
---|
450 | pass |
---|
451 | |
---|
452 | if reloader: |
---|
453 | # Reloader always exits with code 3; but if we are |
---|
454 | # a monitor, any exit code will restart |
---|
455 | if exit_code != 3: |
---|
456 | return exit_code |
---|
457 | if self.verbose > 0: |
---|
458 | print '-'*20, 'Restarting', '-'*20 |
---|
459 | |
---|
460 | def change_user_group(self, user, group): |
---|
461 | if not user and not group: |
---|
462 | return |
---|
463 | import pwd, grp |
---|
464 | uid = gid = None |
---|
465 | if group: |
---|
466 | try: |
---|
467 | gid = int(group) |
---|
468 | group = grp.getgrgid(gid).gr_name |
---|
469 | except ValueError: |
---|
470 | import grp |
---|
471 | try: |
---|
472 | entry = grp.getgrnam(group) |
---|
473 | except KeyError: |
---|
474 | raise BadCommand( |
---|
475 | "Bad group: %r; no such group exists" % group) |
---|
476 | gid = entry.gr_gid |
---|
477 | try: |
---|
478 | uid = int(user) |
---|
479 | user = pwd.getpwuid(uid).pw_name |
---|
480 | except ValueError: |
---|
481 | try: |
---|
482 | entry = pwd.getpwnam(user) |
---|
483 | except KeyError: |
---|
484 | raise BadCommand( |
---|
485 | "Bad username: %r; no such user exists" % user) |
---|
486 | if not gid: |
---|
487 | gid = entry.pw_gid |
---|
488 | uid = entry.pw_uid |
---|
489 | if self.verbose > 0: |
---|
490 | print 'Changing user to %s:%s (%s:%s)' % ( |
---|
491 | user, group or '(unknown)', uid, gid) |
---|
492 | if gid: |
---|
493 | os.setgid(gid) |
---|
494 | if uid: |
---|
495 | os.setuid(uid) |
---|
496 | |
---|
497 | class LazyWriter(object): |
---|
498 | |
---|
499 | """ |
---|
500 | File-like object that opens a file lazily when it is first written |
---|
501 | to. |
---|
502 | """ |
---|
503 | |
---|
504 | def __init__(self, filename, mode='w'): |
---|
505 | self.filename = filename |
---|
506 | self.fileobj = None |
---|
507 | self.lock = threading.Lock() |
---|
508 | self.mode = mode |
---|
509 | |
---|
510 | def open(self): |
---|
511 | if self.fileobj is None: |
---|
512 | self.lock.acquire() |
---|
513 | try: |
---|
514 | if self.fileobj is None: |
---|
515 | self.fileobj = open(self.filename, self.mode) |
---|
516 | finally: |
---|
517 | self.lock.release() |
---|
518 | return self.fileobj |
---|
519 | |
---|
520 | def write(self, text): |
---|
521 | fileobj = self.open() |
---|
522 | fileobj.write(text) |
---|
523 | fileobj.flush() |
---|
524 | |
---|
525 | def writelines(self, text): |
---|
526 | fileobj = self.open() |
---|
527 | fileobj.writelines(text) |
---|
528 | fileobj.flush() |
---|
529 | |
---|
530 | def flush(self): |
---|
531 | self.open().flush() |
---|
532 | |
---|
533 | def live_pidfile(pidfile): |
---|
534 | """(pidfile:str) -> int | None |
---|
535 | Returns an int found in the named file, if there is one, |
---|
536 | and if there is a running process with that process id. |
---|
537 | Return None if no such process exists. |
---|
538 | """ |
---|
539 | pid = read_pidfile(pidfile) |
---|
540 | if pid: |
---|
541 | try: |
---|
542 | os.kill(int(pid), 0) |
---|
543 | return pid |
---|
544 | except OSError, e: |
---|
545 | if e.errno == errno.EPERM: |
---|
546 | return pid |
---|
547 | return None |
---|
548 | |
---|
549 | def read_pidfile(filename): |
---|
550 | if os.path.exists(filename): |
---|
551 | try: |
---|
552 | f = open(filename) |
---|
553 | content = f.read() |
---|
554 | f.close() |
---|
555 | return int(content.strip()) |
---|
556 | except (ValueError, IOError): |
---|
557 | return None |
---|
558 | else: |
---|
559 | return None |
---|
560 | |
---|
561 | def _remove_pid_file(written_pid, filename, verbosity): |
---|
562 | current_pid = os.getpid() |
---|
563 | if written_pid != current_pid: |
---|
564 | # A forked process must be exiting, not the process that |
---|
565 | # wrote the PID file |
---|
566 | return |
---|
567 | if not os.path.exists(filename): |
---|
568 | return |
---|
569 | f = open(filename) |
---|
570 | content = f.read().strip() |
---|
571 | f.close() |
---|
572 | try: |
---|
573 | pid_in_file = int(content) |
---|
574 | except ValueError: |
---|
575 | pass |
---|
576 | else: |
---|
577 | if pid_in_file != current_pid: |
---|
578 | print "PID file %s contains %s, not expected PID %s" % ( |
---|
579 | filename, pid_in_file, current_pid) |
---|
580 | return |
---|
581 | if verbosity > 0: |
---|
582 | print "Removing PID file %s" % filename |
---|
583 | try: |
---|
584 | os.unlink(filename) |
---|
585 | return |
---|
586 | except OSError, e: |
---|
587 | # Record, but don't give traceback |
---|
588 | print "Cannot remove PID file: %s" % e |
---|
589 | # well, at least lets not leave the invalid PID around... |
---|
590 | try: |
---|
591 | f = open(filename, 'w') |
---|
592 | f.write('') |
---|
593 | f.close() |
---|
594 | except OSError, e: |
---|
595 | print 'Stale PID left in file: %s (%e)' % (filename, e) |
---|
596 | else: |
---|
597 | print 'Stale PID removed' |
---|
598 | |
---|
599 | |
---|
600 | def ensure_port_cleanup(bound_addresses, maxtries=30, sleeptime=2): |
---|
601 | """ |
---|
602 | This makes sure any open ports are closed. |
---|
603 | |
---|
604 | Does this by connecting to them until they give connection |
---|
605 | refused. Servers should call like:: |
---|
606 | |
---|
607 | import paste.script |
---|
608 | ensure_port_cleanup([80, 443]) |
---|
609 | """ |
---|
610 | atexit.register(_cleanup_ports, bound_addresses, maxtries=maxtries, |
---|
611 | sleeptime=sleeptime) |
---|
612 | |
---|
613 | def _cleanup_ports(bound_addresses, maxtries=30, sleeptime=2): |
---|
614 | # Wait for the server to bind to the port. |
---|
615 | import socket |
---|
616 | import errno |
---|
617 | for bound_address in bound_addresses: |
---|
618 | for attempt in range(maxtries): |
---|
619 | sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
---|
620 | try: |
---|
621 | sock.connect(bound_address) |
---|
622 | except socket.error, e: |
---|
623 | if e.args[0] != errno.ECONNREFUSED: |
---|
624 | raise |
---|
625 | break |
---|
626 | else: |
---|
627 | time.sleep(sleeptime) |
---|
628 | else: |
---|
629 | raise SystemExit('Timeout waiting for port.') |
---|
630 | sock.close() |
---|
631 | |
---|
632 | def _turn_sigterm_into_systemexit(): |
---|
633 | """ |
---|
634 | Attempts to turn a SIGTERM exception into a SystemExit exception. |
---|
635 | """ |
---|
636 | try: |
---|
637 | import signal |
---|
638 | except ImportError: |
---|
639 | return |
---|
640 | def handle_term(signo, frame): |
---|
641 | raise SystemExit |
---|
642 | signal.signal(signal.SIGTERM, handle_term) |
---|
643 | |
---|