From: Ben Darnell Date: Sat, 8 Dec 2012 23:37:45 +0000 (-0500) Subject: Run AsyncHTTPClient callbacks on the IOLoop for a clean stack. X-Git-Tag: v3.0.0~197 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=6d9407f9876f52ad000c159acbc0b3ee330b3846;p=thirdparty%2Ftornado.git Run AsyncHTTPClient callbacks on the IOLoop for a clean stack. Closes #652. --- diff --git a/tornado/simple_httpclient.py b/tornado/simple_httpclient.py index faff83c8f..45a11a2a5 100644 --- a/tornado/simple_httpclient.py +++ b/tornado/simple_httpclient.py @@ -309,7 +309,7 @@ class _HTTPConnection(object): if self.final_callback is not None: final_callback = self.final_callback self.final_callback = None - final_callback(response) + self.io_loop.add_callback(final_callback, response) @contextlib.contextmanager def cleanup(self): diff --git a/tornado/test/httpclient_test.py b/tornado/test/httpclient_test.py index 6cc431715..4513666c8 100644 --- a/tornado/test/httpclient_test.py +++ b/tornado/test/httpclient_test.py @@ -7,12 +7,13 @@ import binascii from contextlib import closing import functools import re +import sys from tornado.escape import utf8 from tornado.httpclient import HTTPRequest, _RequestProxy from tornado.iostream import IOStream from tornado import netutil -from tornado.stack_context import ExceptionStackContext +from tornado.stack_context import ExceptionStackContext, NullContext from tornado.testing import AsyncHTTPTestCase, bind_unused_port from tornado.test.util import unittest from tornado.util import b, bytes_type @@ -289,6 +290,26 @@ Transfer-Encoding: chunked self.assertEqual(response.code, 304) self.assertEqual(response.headers['Content-Length'], '42') + def test_final_callback_stack_context(self): + # The final callback should be run outside of the httpclient's + # stack_context. We want to ensure that there is not stack_context + # between the user's callback and the IOLoop, so monkey-patch + # IOLoop.handle_callback_exception and disable the test harness's + # context with a NullContext. + # Note that this does not apply to secondary callbacks (header + # and streaming_callback), as errors there must be seen as errors + # by the http client so it can clean up the connection. + exc_info = [] + def handle_callback_exception(callback): + exc_info.append(sys.exc_info()) + self.stop() + self.io_loop.handle_callback_exception = handle_callback_exception + with NullContext(): + self.http_client.fetch(self.get_url('/hello'), + lambda response: 1 / 0) + self.wait() + self.assertEqual(exc_info[0][0], ZeroDivisionError) + class RequestProxyTest(unittest.TestCase): def test_request_set(self): proxy = _RequestProxy(HTTPRequest('http://example.com/', diff --git a/website/sphinx/releases/next.rst b/website/sphinx/releases/next.rst index 215fb028f..49468ddcc 100644 --- a/website/sphinx/releases/next.rst +++ b/website/sphinx/releases/next.rst @@ -198,3 +198,5 @@ In progress ``*args, **kwargs`` to pass along to the callback. * When gzip is enabled in a `tornado.web.Application`, appropriate ``Vary: Accept-Encoding`` headers are now sent. +* Fixed a bug in which `SimpleAsyncHTTPClient` callbacks were being run in the + client's ``stack_context``.