From: Ben Darnell Date: Sat, 26 Apr 2014 18:26:41 +0000 (-0400) Subject: Add on_close to HTTPServerConnectionDelegate. X-Git-Tag: v4.0.0b1~91^2~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=41597204500ed564d226e9d5302ea96594cd43d1;p=thirdparty%2Ftornado.git Add on_close to HTTPServerConnectionDelegate. Add close_all_connections method to HTTPServer for testing cleanup. On Python versions before 3.4, the GC has problems with generators, so the previous approach of closing all the file descriptors and leave the rest to the GC no longer works. (Only Python 3.3 prints the uncollectable garbage warnings, but the problem is present in earlier versions). --- diff --git a/tornado/http1connection.py b/tornado/http1connection.py index a0c125376..69e8328b7 100644 --- a/tornado/http1connection.py +++ b/tornado/http1connection.py @@ -478,30 +478,44 @@ class HTTP1ServerConnection(object): params = HTTP1ConnectionParameters() self.params = params self.context = context + self._serving_future = None + + @gen.coroutine + def close(self): + self.stream.close() + # Block until the serving loop is done, but ignore any exceptions + # (start_serving is already responsible for logging them). + try: + yield self._serving_future + except Exception: + pass def start_serving(self, delegate): assert isinstance(delegate, httputil.HTTPServerConnectionDelegate) + self._serving_future = self._server_request_loop(delegate) # Register the future on the IOLoop so its errors get logged. - self.stream.io_loop.add_future( - self._server_request_loop(delegate), - lambda f: f.result()) + self.stream.io_loop.add_future(self._serving_future, + lambda f: f.result()) @gen.coroutine def _server_request_loop(self, delegate): - while True: - conn = HTTP1Connection(self.stream, False, - self.params, self.context) - request_delegate = delegate.start_request(conn) - try: - ret = yield conn.read_response(request_delegate) - except iostream.StreamClosedError: - return - except Exception: - # TODO: this is probably too broad; it would be better to - # wrap all delegate calls in something that writes to app_log, - # and then errors that reach this point can be gen_log. - app_log.error("Uncaught exception", exc_info=True) - conn.close() - return - if not ret: - return + try: + while True: + conn = HTTP1Connection(self.stream, False, + self.params, self.context) + request_delegate = delegate.start_request(self, conn) + try: + ret = yield conn.read_response(request_delegate) + except iostream.StreamClosedError: + return + except Exception: + # TODO: this is probably too broad; it would be better to + # wrap all delegate calls in something that writes to app_log, + # and then errors that reach this point can be gen_log. + app_log.error("Uncaught exception", exc_info=True) + conn.close() + return + if not ret: + return + finally: + delegate.on_close(self) diff --git a/tornado/httpserver.py b/tornado/httpserver.py index 47f3d5969..a68afee8e 100644 --- a/tornado/httpserver.py +++ b/tornado/httpserver.py @@ -31,6 +31,7 @@ from __future__ import absolute_import, division, print_function, with_statement import socket from tornado.http1connection import HTTP1ServerConnection, HTTP1ConnectionParameters +from tornado import gen from tornado import httputil from tornado import iostream from tornado import netutil @@ -154,16 +155,28 @@ class HTTPServer(TCPServer, httputil.HTTPServerConnectionDelegate): TCPServer.__init__(self, io_loop=io_loop, ssl_options=ssl_options, max_buffer_size=max_buffer_size, read_chunk_size=chunk_size) + self._connections = set() + + @gen.coroutine + def close_all_connections(self): + while self._connections: + # Peek at an arbitrary element of the set + conn = next(iter(self._connections)) + yield conn.close() def handle_stream(self, stream, address): context = _HTTPRequestContext(stream, address, self.conn_params.protocol) conn = HTTP1ServerConnection( stream, self.conn_params, context) + self._connections.add(conn) conn.start_serving(self) - def start_request(self, connection): - return _ServerRequestAdapter(self, connection) + def start_request(self, server_conn, request_conn): + return _ServerRequestAdapter(self, request_conn) + + def on_close(self, server_conn): + self._connections.remove(server_conn) class _HTTPRequestContext(object): diff --git a/tornado/httputil.py b/tornado/httputil.py index 75a3dbf5f..353e9eb9b 100644 --- a/tornado/httputil.py +++ b/tornado/httputil.py @@ -438,9 +438,12 @@ class HTTPOutputException(Exception): class HTTPServerConnectionDelegate(object): - def start_request(self, connection): + def start_request(self, server_conn, request_conn): raise NotImplementedError() + def on_close(self, server_conn): + pass + class HTTPMessageDelegate(object): def headers_received(self, start_line, headers): diff --git a/tornado/testing.py b/tornado/testing.py index 96fdd32b0..136270fe1 100644 --- a/tornado/testing.py +++ b/tornado/testing.py @@ -388,6 +388,7 @@ class AsyncHTTPTestCase(AsyncTestCase): def tearDown(self): self.http_server.stop() + self.io_loop.run_sync(self.http_server.close_all_connections) if (not IOLoop.initialized() or self.http_client.io_loop is not IOLoop.instance()): self.http_client.close()