]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add on_close to HTTPServerConnectionDelegate.
authorBen Darnell <ben@bendarnell.com>
Sat, 26 Apr 2014 18:26:41 +0000 (14:26 -0400)
committerBen Darnell <ben@bendarnell.com>
Sat, 26 Apr 2014 18:26:41 +0000 (14:26 -0400)
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).

tornado/http1connection.py
tornado/httpserver.py
tornado/httputil.py
tornado/testing.py

index a0c125376a6647696dbf95b9350b8c72a0c99b6d..69e8328b71016eaf9277c64cd494b484ad2b5436 100644 (file)
@@ -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)
index 47f3d596980318b69ee61a6fbeac6e4956278c2f..a68afee8e8406880b3855e70b3ea2bad1e44b526 100644 (file)
@@ -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):
index 75a3dbf5fe6a51b32cedaae4191444c883af389a..353e9eb9b225a555b600012542ef0a19f94efda7 100644 (file)
@@ -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):
index 96fdd32b0f4877483c8b24b94176a16a35489f0a..136270fe11cae28d0320777edbbb7ed94704d53a 100644 (file)
@@ -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()