]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
IOStream: do not listen for close events if there is no close_callback.
authorBen Darnell <ben@bendarnell.com>
Mon, 12 May 2014 00:23:33 +0000 (20:23 -0400)
committerBen Darnell <ben@bendarnell.com>
Mon, 12 May 2014 00:23:33 +0000 (20:23 -0400)
HTTP1Connection now only registers its close callback when it is done
reading.  This improves performance for synchronous handlers, which no
longer interact with the IOLoop as often.

tornado/http1connection.py
tornado/iostream.py
tornado/test/iostream_test.py
tornado/websocket.py

index a0c96a2f28dd44e8b4ff06782cdd66a439767f8f..b8109eed486ace5ec56d71aeddd503e262e76a56 100644 (file)
@@ -94,7 +94,6 @@ class HTTP1Connection(httputil.HTTPConnection):
         # and after it has been read in the client)
         self._disconnect_on_finish = False
         self._clear_callbacks()
-        self.stream.set_close_callback(self._on_connection_close)
         # Save the start lines after we read or write them; they
         # affect later processing (e.g. 304 responses and HEAD methods
         # have content-length but no bodies)
@@ -196,7 +195,14 @@ class HTTP1Connection(httputil.HTTPConnection):
             if not self._write_finished or self.is_client:
                 need_delegate_close = False
                 delegate.finish()
-            yield self._finish_future
+            # If we're waiting for the application to produce an asynchronous
+            # response, and we're not detached, register a close callback
+            # on the stream (we didn't need one while we were reading)
+            if (not self._finish_future.done() and
+                self.stream is not None and
+                not self.stream.closed()):
+                self.stream.set_close_callback(self._on_connection_close)
+                yield self._finish_future
             if self.is_client and self._disconnect_on_finish:
                 self.close()
             if self.stream is None:
@@ -221,6 +227,8 @@ class HTTP1Connection(httputil.HTTPConnection):
         self._write_callback = None
         self._write_future = None
         self._close_callback = None
+        if self.stream is not None:
+            self.stream.set_close_callback(None)
 
     def set_close_callback(self, callback):
         """Sets a callback that will be run when the connection is closed.
@@ -231,6 +239,9 @@ class HTTP1Connection(httputil.HTTPConnection):
         self._close_callback = stack_context.wrap(callback)
 
     def _on_connection_close(self):
+        # Note that this callback is only registered on the IOStream
+        # when we have finished reading the request and are waiting for
+        # the application to produce its response.
         if self._close_callback is not None:
             callback = self._close_callback
             self._close_callback = None
@@ -240,8 +251,11 @@ class HTTP1Connection(httputil.HTTPConnection):
         self._clear_callbacks()
 
     def close(self):
-        self.stream.close()
+        if self.stream is not None:
+            self.stream.close()
         self._clear_callbacks()
+        if not self._finish_future.done():
+            self._finish_future.set_result(None)
 
     def detach(self):
         """Take control of the underlying stream.
@@ -251,6 +265,7 @@ class HTTP1Connection(httputil.HTTPConnection):
         `.HTTPMessageDelegate.headers_received`.  Intended for implementing
         protocols like websockets that tunnel over an HTTP handshake.
         """
+        self._clear_callbacks()
         stream = self.stream
         self.stream = None
         return stream
index 597516eff75b19888c75c5cfc469312be5af1c79..e5423fe6926945ae19c4a6d7675c29c5bfda5e3b 100644 (file)
@@ -316,6 +316,7 @@ class BaseIOStream(object):
         `StreamClosedError` when the stream is closed.
         """
         self._close_callback = stack_context.wrap(callback)
+        self._maybe_add_error_listener()
 
     def close(self, exc_info=False):
         """Close this stream.
@@ -764,7 +765,8 @@ class BaseIOStream(object):
         if self._state is None or self._state == ioloop.IOLoop.ERROR:
             if self.closed():
                 self._maybe_run_close_callback()
-            elif self._read_buffer_size == 0:
+            elif (self._read_buffer_size == 0 and
+                  self._close_callback is not None):
                 self._add_io_state(ioloop.IOLoop.READ)
 
     def _add_io_state(self, state):
index efe35780788a2d4dceedbb6dea44b9e594055137..baca340af209378978a16ad32bc8e9403b039adf 100644 (file)
@@ -199,9 +199,6 @@ class TestIOStreamMixin(object):
         server, client = self.make_iostream_pair()
         server.write(b'', callback=self.stop)
         self.wait()
-        # As a side effect, the stream is now listening for connection
-        # close (if it wasn't already), but is not listening for writes
-        self.assertEqual(server._state, IOLoop.READ | IOLoop.ERROR)
         server.close()
         client.close()
 
index 66a4fedf9df6c67c67dd7d3edb933b2604ec5a91..218413f8924d7574c4c5f1693f5fd230c3699498 100644 (file)
@@ -118,6 +118,7 @@ class WebSocketHandler(tornado.web.RequestHandler):
         self.open_kwargs = kwargs
 
         self.stream = self.request.connection.detach()
+        self.stream.set_close_callback(self.on_connection_close)
 
         # Upgrade header should be present and should be equal to WebSocket
         if self.request.headers.get("Upgrade", "").lower() != 'websocket':