diff options
author | Marcel Hellkamp <marc@gsites.de> | 2012-10-04 18:46:32 +0200 |
---|---|---|
committer | Marcel Hellkamp <marc@gsites.de> | 2012-10-04 18:46:32 +0200 |
commit | c38bf7544e03d779ab30013cebd90d79d155c66f (patch) | |
tree | 5f3a17eac7955769ad6bee52de1c1102d665ba42 | |
parent | d3a6e1b9758bf93b91db16d99e39edcd2ec50711 (diff) | |
download | bottle-gevently.tar.gz |
New runtime and reloading classgevently
-rw-r--r-- | bottle.py | 207 |
1 files changed, 108 insertions, 99 deletions
@@ -2348,8 +2348,9 @@ class ServerAdapter(object): pass def __repr__(self): - args = ', '.join(['%s=%s'%(k,repr(v)) for k, v in self.options.items()]) - return "%s(%s)" % (self.__class__.__name__, args) + args = ' '.join(['%s=%r' % a for a in self.options.items()]) + name = self.__class__.__name__ + return "<%s host='%s:%d' %s>" % (name, self.host, self.port, args) class CGIServer(ServerAdapter): @@ -2486,6 +2487,7 @@ class GeventServer(ServerAdapter): * `fast` (default: False) uses libevent's http server, but has some issues: No streaming, no pipelining, no SSL. """ + gevent = True def run(self, handler): from gevent import wsgi, pywsgi, local if not isinstance(_lctx, local.local): @@ -2495,7 +2497,6 @@ class GeventServer(ServerAdapter): log = None if self.quiet else 'default' wsgi.WSGIServer((self.host, self.port), handler, log=log).serve_forever() - class GunicornServer(ServerAdapter): """ Untested. See http://gunicorn.org/configure.html for options. """ def run(self, handler): @@ -2615,11 +2616,10 @@ def load_app(target): default_app.remove(tmp) # Remove the temporary added default application NORUN = nr_old -_debug = debug -def run(app=None, server='wsgiref', host='127.0.0.1', port=8080, - interval=1, reloader=False, quiet=False, plugins=None, - debug=False, **kargs): - """ Start a server instance. This method blocks until the server terminates. + +class Runner(object): + """ Server launcher and runtime state mashine. You are probably looking + for :func:`run`. :param app: WSGI application or target string supported by :func:`load_app`. (default: :func:`default_app`) @@ -2635,119 +2635,128 @@ def run(app=None, server='wsgiref', host='127.0.0.1', port=8080, :param quiet: Suppress output to stdout and stderr? (default: False) :param options: Options passed to the server adapter. """ - if NORUN: return - if reloader and not os.environ.get('BOTTLE_CHILD'): - try: - lockfile = None - fd, lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock') - os.close(fd) # We only need this file to exist. We never write to it - while os.path.exists(lockfile): - args = [sys.executable] + sys.argv - environ = os.environ.copy() - environ['BOTTLE_CHILD'] = 'true' - environ['BOTTLE_LOCKFILE'] = lockfile - p = subprocess.Popen(args, env=environ) - while p.poll() is None: # Busy wait... - os.utime(lockfile, None) # I am alive! - time.sleep(interval) - if p.poll() != 3: - if os.path.exists(lockfile): os.unlink(lockfile) - sys.exit(p.poll()) - except KeyboardInterrupt: - pass - finally: - if os.path.exists(lockfile): - os.unlink(lockfile) - return - try: - _debug(debug) - app = app or default_app() - if isinstance(app, basestring): - app = load_app(app) - if not callable(app): - raise ValueError("Application is not callable: %r" % app) - - for plugin in plugins or []: - app.install(plugin) - - if server in server_names: - server = server_names.get(server) - if isinstance(server, basestring): - server = load(server) - if isinstance(server, type): - server = server(host=host, port=port, **kargs) - if not isinstance(server, ServerAdapter): - raise ValueError("Unknown or unsupported server: %r" % server) - - server.quiet = server.quiet or quiet - if not server.quiet: - _stderr("Bottle v%s server starting up (using %s)...\n" % (__version__, repr(server))) - _stderr("Listening on http://%s:%d/\n" % (server.host, server.port)) - _stderr("Hit Ctrl-C to quit.\n\n") + STATUS_RELOAD = 3 + STATUS_EXIT = 0 + gevent = False - if reloader: - lockfile = os.environ.get('BOTTLE_LOCKFILE') - bgcheck = FileCheckerThread(lockfile, interval) - with bgcheck: - server.run(app) - if bgcheck.status == 'reload': - sys.exit(3) + def __init__(self, app=None, server='wsgiref', host='127.0.0.1', port=8080, + interval=1, reloader=False, quiet=False, plugins=None, + debug=False, **options): + self.__dict__.update(vars()) + self.debug = self.debug or DEBUG + self.interval = max(1, self.interval) + + def run(self): + if NORUN: return + if os.environ.get('BOTTLE_CHILD'): + self._run_child() + elif self.reloader: + self._run_observer() else: - server.run(app) - except KeyboardInterrupt: - pass - except (SystemExit, MemoryError): - raise - except: - if not reloader: raise - if not getattr(server, 'quiet', quiet): - print_exc() - time.sleep(interval) - sys.exit(3) + self._run_server() + + def _run_server(self): + ''' Actually run the server loop ''' + debug(self.debug) + self.app = self.app or default_app() + if isinstance(self.app, basestring): + self.app = load_app(self.app) + if not callable(self.app): + raise ValueError("Application is not callable: %r" % self.app) + + for plugin in self.plugins or []: + self.app.install(plugin) + + if isinstance(self.server, basestring): + if self.server not in server_names: + raise ValueError("Unknown server adapter: %r" % self.server) + self.server = server_names[self.server] + if isinstance(self.server, type): + self.server = self.server(host=host, port=port, **self.options) + + self.quiet = self.server.quiet = self.quiet or self.server.quiet + if not self.quiet: + _stderr("Bottle v%s server starting up...\n" % __version__) + _stderr(" %r\n" % (self.server, )) + _stderr("Hit Ctrl-C to quit.\n\n") + try: + self.server.run(app) + except KeyboardInterrupt: + pass + def _run_observer(self): + ''' Start and restart the server in a new process.''' + fd, self.lockfile = tempfile.mkstemp(prefix='bottle.', suffix='.lock') + os.close(fd) # We only need this file to exist. We never write to it + while os.path.exists(self.lockfile): + args = [sys.executable] + sys.argv + environ = os.environ.copy() + environ['BOTTLE_CHILD'] = 'true' + environ['BOTTLE_LOCKFILE'] = self.lockfile + try: + p = subprocess.Popen(args, env=environ) + while p.poll() is None: # Busy wait... + os.utime(self.lockfile, None) # I am alive! + self.sleep(self.interval) + if p.poll() != self.STATUS_RELOAD: + sys.exit(p.poll()) + except KeyboardInterrupt: + break + finally: + if os.path.exists(self.lockfile): + os.unlink(self.lockfile) -class FileCheckerThread(threading.Thread): - ''' Interrupt main-thread as soon as a changed module file is detected, - the lockfile gets deleted or gets to old. ''' + def _run_child(self): + ''' Start as a self-terminating server loop.''' + self.is_child = True + try: + self.lockfile = os.environ.get('BOTTLE_LOCKFILE') + self.spawn_reload_checker() + self._run_server() # Supresses SIGINT, no need to catch it here + except (SystemExit, MemoryError): + raise + except: + if not self.quiet: print_exc() + self.reloader_status == 'reload' + finally: + self.terminate_reload_checker() + if self.reloader_status == 'reload': + self.exit(self.STATUS_RELOAD) - def __init__(self, lockfile, interval): - threading.Thread.__init__(self) - self.lockfile, self.interval = lockfile, interval - #: Is one of 'reload', 'error' or 'exit' - self.status = None + def file_checker_loop(self): + self.reload_status = None + self.files_to_check = dict() - def run(self): + # Shortcuts exists = os.path.exists mtime = lambda path: os.stat(path).st_mtime - files = dict() for module in list(sys.modules.values()): path = getattr(module, '__file__', '') if path[-4:] in ('.pyo', '.pyc'): path = path[:-1] - if path and exists(path): files[path] = mtime(path) + if path and exists(path): self.files_to_check[path] = mtime(path) - while not self.status: + while not self.reload_status: if not exists(self.lockfile)\ or mtime(self.lockfile) < time.time() - self.interval - 5: - self.status = 'error' - thread.interrupt_main() - for path, lmtime in list(files.items()): + self.reload_status = 'error' + break + for path, lmtime in list(self.files_to_check.items()): if not exists(path) or mtime(path) > lmtime: - self.status = 'reload' - thread.interrupt_main() + self.reload_status = 'reload' break - time.sleep(self.interval) + self.sleep(self.interval) + + if self.reload_status: + self.interrupt_main() - def __enter__(self): - self.start() - def __exit__(self, exc_type, exc_val, exc_tb): - if not self.status: self.status = 'exit' # silent exit - self.join() - return exc_type is not None and issubclass(exc_type, KeyboardInterrupt) +def run(*args, **kwargs): + Runner(*args, **kwargs).run() +run.__doc__ = Runner.__doc__ |