Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 23 additions & 7 deletions tornado/simple_httpclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def fetch_impl(self, request, callback):
timeout_handle = self.io_loop.add_timeout(
self.io_loop.time() + min(request.connect_timeout,
request.request_timeout),
functools.partial(self._on_timeout, key))
functools.partial(self._on_timeout, key, "in request queue"))
else:
timeout_handle = None
self.waiting[key] = (request, callback, timeout_handle)
Expand Down Expand Up @@ -167,11 +167,20 @@ def _remove_timeout(self, key):
self.io_loop.remove_timeout(timeout_handle)
del self.waiting[key]

def _on_timeout(self, key):
def _on_timeout(self, key, info=None):
"""Timeout callback of request.

Construct a timeout HTTPResponse when a timeout occurs.

:arg object key: A simple object to mark the request.
:info string key: More detailed timeout information.
"""
request, callback, timeout_handle = self.waiting[key]
self.queue.remove((key, request, callback))

error_message = "Timeout {0}".format(info) if info else "Timeout"
timeout_response = HTTPResponse(
request, 599, error=HTTPError(599, "Timeout"),
request, 599, error=HTTPError(599, error_message),
request_time=self.io_loop.time() - request.start_time)
self.io_loop.add_callback(callback, timeout_response)
del self.waiting[key]
Expand Down Expand Up @@ -229,7 +238,7 @@ def __init__(self, io_loop, client, request, release_callback,
if timeout:
self._timeout = self.io_loop.add_timeout(
self.start_time + timeout,
stack_context.wrap(self._on_timeout))
stack_context.wrap(functools.partial(self._on_timeout, "while connecting")))
self.tcp_client.connect(host, port, af=af,
ssl_options=ssl_options,
max_buffer_size=self.max_buffer_size,
Expand Down Expand Up @@ -284,10 +293,17 @@ def _get_ssl_options(self, scheme):
return ssl_options
return None

def _on_timeout(self):
def _on_timeout(self, info=None):
"""Timeout callback of _HTTPConnection instance.

Raise a timeout HTTPError when a timeout occurs.

:info string key: More detailed timeout information.
"""
self._timeout = None
error_message = "Timeout {0}".format(info) if info else "Timeout"
if self.final_callback is not None:
raise HTTPError(599, "Timeout")
raise HTTPError(599, error_message)

def _remove_timeout(self):
if self._timeout is not None:
Expand All @@ -307,7 +323,7 @@ def _on_connect(self, stream):
if self.request.request_timeout:
self._timeout = self.io_loop.add_timeout(
self.start_time + self.request.request_timeout,
stack_context.wrap(self._on_timeout))
stack_context.wrap(functools.partial(self._on_timeout, "during request")))
if (self.request.method not in self._SUPPORTED_METHODS and
not self.request.allow_nonstandard_methods):
raise KeyError("unknown method %s" % self.request.method)
Expand Down
23 changes: 21 additions & 2 deletions tornado/test/simple_httpclient_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from tornado.httputil import HTTPHeaders, ResponseStartLine
from tornado.ioloop import IOLoop
from tornado.log import gen_log
from tornado.concurrent import Future
from tornado.netutil import Resolver, bind_sockets
from tornado.simple_httpclient import SimpleAsyncHTTPClient
from tornado.test.httpclient_test import ChunkHandler, CountdownHandler, HelloWorldHandler, RedirectHandler
Expand Down Expand Up @@ -237,6 +238,24 @@ def test_see_other_redirect(self):
# request is the original request, is a POST still
self.assertEqual("POST", response.request.method)

@skipOnTravis
def test_connect_timeout(self):
timeout = 0.1
timeout_min, timeout_max = 0.099, 1.0

class TimeoutResolver(Resolver):
def resolve(self, *args, **kwargs):
return Future() # never completes

with closing(self.create_client(resolver=TimeoutResolver())) as client:
client.fetch(self.get_url('/hello'), self.stop,
connect_timeout=timeout)
response = self.wait()
self.assertEqual(response.code, 599)
self.assertTrue(timeout_min < response.request_time < timeout_max,
response.request_time)
self.assertEqual(str(response.error), "HTTP 599: Timeout while connecting")

@skipOnTravis
def test_request_timeout(self):
timeout = 0.1
Expand All @@ -249,7 +268,7 @@ def test_request_timeout(self):
self.assertEqual(response.code, 599)
self.assertTrue(timeout_min < response.request_time < timeout_max,
response.request_time)
self.assertEqual(str(response.error), "HTTP 599: Timeout")
self.assertEqual(str(response.error), "HTTP 599: Timeout during request")
# trigger the hanging request to let it clean up after itself
self.triggers.popleft()()

Expand Down Expand Up @@ -357,7 +376,7 @@ def test_queue_timeout(self):

self.assertEqual(response.code, 599)
self.assertTrue(response.request_time < 1, response.request_time)
self.assertEqual(str(response.error), "HTTP 599: Timeout")
self.assertEqual(str(response.error), "HTTP 599: Timeout in request queue")
self.triggers.popleft()()
self.wait()

Expand Down