From: Ben Darnell Date: Tue, 5 Aug 2014 03:46:03 +0000 (-0400) Subject: Fix a memory leak and test timeout in websockets. X-Git-Tag: v4.1.0b1~118 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=dbab79218ffcd124dbfbd1fa06245582ca9c11db;p=thirdparty%2Ftornado.git Fix a memory leak and test timeout in websockets. In some cases (primarily when prepare() is asynchronous), an HTTP1ServerConnection could be left waiting forever for a connection to finish when that connection has been detached and handed off to a WebSocketHandler. This would manifest as a leak and a timeout in tests as the HTTPServer waited for all its existing connections to finish. Also fix a bug in the test tearDown method that would actually wait forever for connections to finish instead of timing out. Closes #1133. --- diff --git a/tornado/http1connection.py b/tornado/http1connection.py index 2ac139a64..6ed74e2ad 100644 --- a/tornado/http1connection.py +++ b/tornado/http1connection.py @@ -294,6 +294,8 @@ class HTTP1Connection(httputil.HTTPConnection): self._clear_callbacks() stream = self.stream self.stream = None + if not self._finish_future.done(): + self._finish_future.set_result(None) return stream def set_body_timeout(self, timeout): diff --git a/tornado/test/websocket_test.py b/tornado/test/websocket_test.py index f8cd163bd..e1e3ea700 100644 --- a/tornado/test/websocket_test.py +++ b/tornado/test/websocket_test.py @@ -78,6 +78,15 @@ class CloseReasonHandler(TestWebSocketHandler): self.close(1001, "goodbye") +class AsyncPrepareHandler(TestWebSocketHandler): + @gen.coroutine + def prepare(self): + yield gen.moment + + def on_message(self, message): + self.write_message(message) + + class WebSocketBaseTestCase(AsyncHTTPTestCase): @gen.coroutine def ws_connect(self, path, compression_options=None): @@ -107,6 +116,8 @@ class WebSocketTest(WebSocketBaseTestCase): dict(close_future=self.close_future)), ('/error_in_on_message', ErrorInOnMessageHandler, dict(close_future=self.close_future)), + ('/async_prepare', AsyncPrepareHandler, + dict(close_future=self.close_future)), ]) def test_http_request(self): @@ -219,6 +230,15 @@ class WebSocketTest(WebSocketBaseTestCase): self.assertEqual(code, 1001) self.assertEqual(reason, 'goodbye') + @gen_test + def test_async_prepare(self): + # Previously, an async prepare method triggered a bug that would + # result in a timeout on test shutdown (and a memory leak). + ws = yield self.ws_connect('/async_prepare') + ws.write_message('hello') + res = yield ws.read_message() + self.assertEqual(res, 'hello') + @gen_test def test_check_origin_valid_no_path(self): port = self.get_http_port() diff --git a/tornado/testing.py b/tornado/testing.py index b4bfb274a..b3c47a03b 100644 --- a/tornado/testing.py +++ b/tornado/testing.py @@ -395,7 +395,8 @@ class AsyncHTTPTestCase(AsyncTestCase): def tearDown(self): self.http_server.stop() - self.io_loop.run_sync(self.http_server.close_all_connections) + self.io_loop.run_sync(self.http_server.close_all_connections, + timeout=get_async_test_timeout()) if (not IOLoop.initialized() or self.http_client.io_loop is not IOLoop.instance()): self.http_client.close()