Add more docs about Future exception logging.
if that package was available and fall back to the thread-unsafe
implementation if it was not.
+ .. versionchanged:: 4.1
+ If a `.Future` contains an error but that error is never observed
+ (by calling ``result()``, ``exception()``, or ``exc_info()``),
+ a stack trace will be logged when the `.Future` is garbage collected.
+ This normally indicates an error in the application, but in cases
+ where it results in undesired logging it may be necessary to
+ suppress the logging by ensuring that the exception is observed:
+ ``f.add_done_callback(lambda f: f.exception())``.
"""
def __init__(self):
self._done = False
# If the initial synchronous part of f() raised an exception,
# go ahead and raise it to the caller directly without waiting
# for them to inspect the Future.
- #
- # "Consume" the exception from the future so it will not be logged
- # as uncaught.
- future.exception()
- raise_exc_info(exc_info)
+ future.result()
# If the caller passed in a callback, schedule it to be called
# when the future resolves. It is important that this happens
from tornado.concurrent import Future, TracebackFuture, is_future, chain_future
from tornado.ioloop import IOLoop
+from tornado.log import app_log
from tornado import stack_context
return fut
-def with_timeout(timeout, future, io_loop=None):
+def with_timeout(timeout, future, io_loop=None, quiet_exceptions=()):
"""Wraps a `.Future` in a timeout.
Raises `TimeoutError` if the input future does not complete before
`.IOLoop.add_timeout` (i.e. a `datetime.timedelta` or an absolute time
relative to `.IOLoop.time`)
+ If the wrapped `.Future` fails after it has timed out, the exception
+ will be logged unless it is of a type contained in ``quiet_exceptions``
+ (which may be an exception type or a sequence of types).
+
Currently only supports Futures, not other `YieldPoint` classes.
.. versionadded:: 4.0
+
+ .. versionchanged:: 4.1
+ Added the ``quiet_exceptions`` argument and the logging of unhandled
+ exceptions.
"""
# TODO: allow yield points in addition to futures?
# Tricky to do with stack_context semantics.
chain_future(future, result)
if io_loop is None:
io_loop = IOLoop.current()
+ def error_callback(future):
+ try:
+ future.result()
+ except Exception as e:
+ if not isinstance(e, quiet_exceptions):
+ app_log.error("Exception in Future %r after timeout",
+ future, exc_info=True)
def timeout_callback():
result.set_exception(TimeoutError("Timeout"))
- future.add_done_callback(lambda f: f.exception())
+ # In case the wrapped future goes on to fail, log it.
+ future.add_done_callback(error_callback)
timeout_handle = io_loop.add_timeout(
timeout, timeout_callback)
if isinstance(future, Future):
header_data = yield gen.with_timeout(
self.stream.io_loop.time() + self.params.header_timeout,
header_future,
- io_loop=self.stream.io_loop)
+ io_loop=self.stream.io_loop,
+ quiet_exceptions=iostream.StreamClosedError)
except gen.TimeoutError:
self.close()
raise gen.Return(False)
try:
yield gen.with_timeout(
self.stream.io_loop.time() + self._body_timeout,
- body_future, self.stream.io_loop)
+ body_future, self.stream.io_loop,
+ quiet_exceptions=iostream.StreamClosedError)
except gen.TimeoutError:
gen_log.info("Timeout reading body from %s",
self.context)
self._pending_write.add_done_callback(self._finish_request)
def _on_write_complete(self, future):
- future.exception()
+ exc = future.exception()
+ if exc is not None and not isinstance(exc, iostream.StreamClosedError):
+ future.result()
if self._write_callback is not None:
callback = self._write_callback
self._write_callback = None
return future
except:
if future is not None:
+ # Ensure that the future doesn't log an error because its
+ # failure was never examined.
future.add_done_callback(lambda f: f.exception())
raise
return future
# trapped in the Future it returns (which we are ignoring here).
# However, that shouldn't happen because _execute has a blanket
# except handler, and we cannot easily access the IOLoop here to
- # call add_future.
+ # call add_future (because of the requirement to remain compatible
+ # with WSGI)
f = self.handler._execute(transforms, *self.path_args, **self.path_kwargs)
- f.add_done_callback(lambda f: f.exception()) # XXX
+ f.add_done_callback(lambda f: f.exception())
# If we are streaming the request body, then execute() is finished
# when the handler has prepared to receive the body. If not,
# it doesn't matter when execute() finishes (so we return None)