[3] | 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 | |
---|