From: Ben Darnell Date: Thu, 19 Apr 2012 05:22:06 +0000 (-0700) Subject: The RequestHandler.flush callback should be run even if there is no data. X-Git-Tag: v2.3.0~52 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=351c3223f52124dadb135b629818cd5cec8a4065;p=thirdparty%2Ftornado.git The RequestHandler.flush callback should be run even if there is no data. Also IOStream._try_inline_read needs the same fake-callback change as _handle_read (I can't seem to come up with a good isolated test for this, but it's tickled by the empty-flush test). Closes #492. --- diff --git a/tornado/iostream.py b/tornado/iostream.py index 4e8ce0505..f8fadf689 100644 --- a/tornado/iostream.py +++ b/tornado/iostream.py @@ -362,10 +362,15 @@ class IOStream(object): if self._read_from_buffer(): return self._check_closed() - while True: - if self._read_to_buffer() == 0: - break - self._check_closed() + try: + # See comments in _handle_read about incrementing _pending_callbacks + self._pending_callbacks += 1 + while True: + if self._read_to_buffer() == 0: + break + self._check_closed() + finally: + self._pending_callbacks -= 1 if self._read_from_buffer(): return self._add_io_state(self.io_loop.READ) diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py index f65b1f68b..96b4d662f 100644 --- a/tornado/test/web_test.py +++ b/tornado/test/web_test.py @@ -1,4 +1,5 @@ from __future__ import absolute_import, division, with_statement +from tornado import gen from tornado.escape import json_decode, utf8, to_unicode, recursive_unicode, native_str from tornado.iostream import IOStream from tornado.template import DictLoader @@ -366,6 +367,19 @@ class RedirectHandler(RequestHandler): else: raise Exception("didn't get permanent or status arguments") +class EmptyFlushCallbackHandler(RequestHandler): + @gen.engine + @asynchronous + def get(self): + # Ensure that the flush callback is run whether or not there + # was any output. + yield gen.Task(self.flush) # "empty" flush, but writes headers + yield gen.Task(self.flush) # empty flush + self.write("o") + yield gen.Task(self.flush) # flushes the "o" + yield gen.Task(self.flush) # empty flush + self.finish("k") + class WebTest(AsyncHTTPTestCase, LogTrapTestCase): def get_app(self): @@ -391,6 +405,7 @@ class WebTest(AsyncHTTPTestCase, LogTrapTestCase): url("/flow_control", FlowControlHandler), url("/multi_header", MultiHeaderHandler), url("/redirect", RedirectHandler), + url("/empty_flush", EmptyFlushCallbackHandler), ] return Application(urls, template_loader=loader, @@ -484,6 +499,10 @@ js_embed() response = self.fetch("/redirect?status=307", follow_redirects=False) self.assertEqual(response.code, 307) + def test_empty_flush(self): + response = self.fetch("/empty_flush") + self.assertEqual(response.body, b("ok")) + class ErrorResponseTest(AsyncHTTPTestCase, LogTrapTestCase): def get_app(self): diff --git a/tornado/web.py b/tornado/web.py index c66fa4e02..f71ca1727 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -636,8 +636,7 @@ class RequestHandler(object): self.request.write(headers, callback=callback) return - if headers or chunk: - self.request.write(headers + chunk, callback=callback) + self.request.write(headers + chunk, callback=callback) def finish(self, chunk=None): """Finishes this response, ending the HTTP request."""