from tornado.concurrent import TracebackFuture, is_future
from tornado.log import app_log, gen_log
from tornado import stack_context
-from tornado.util import Configurable
-from tornado.util import errno_from_exception
+from tornado.util import Configurable, errno_from_exception, timedelta_to_seconds
try:
import signal
"""
return time.time()
- def add_timeout(self, deadline, callback):
+ def add_timeout(self, deadline, callback, *args, **kwargs):
"""Runs the ``callback`` at the time ``deadline`` from the I/O loop.
Returns an opaque handle that may be passed to
``deadline`` may be a number denoting a time (on the same
scale as `IOLoop.time`, normally `time.time`), or a
`datetime.timedelta` object for a deadline relative to the
- current time.
+ current time. Since Tornado 4.0, `call_later` is a more
+ convenient alternative for the relative case since it does not
+ require a timedelta object.
Note that it is not safe to call `add_timeout` from other threads.
Instead, you must use `add_callback` to transfer control to the
`IOLoop`'s thread, and then call `add_timeout` from there.
+
+ Subclasses of IOLoop must implement either `add_timeout` or
+ `call_at`; the default implementations of each will call
+ the other. `call_at` is usually easier to implement, but
+ subclasses that wish to maintain compatibility with Tornado
+ versions prior to 4.0 must use `add_timeout` instead.
+
+ .. versionchanged:: 4.0
+ Now passes through ``*args`` and ``**kwargs`` to the callback.
"""
- raise NotImplementedError()
+ if isinstance(deadline, numbers.Real):
+ return self.call_at(deadline, callback, *args, **kwargs)
+ elif isinstance(deadline, datetime.timedelta):
+ return self.call_at(self.time() + timedelta_to_seconds(deadline),
+ callback, *args, **kwargs)
+ else:
+ raise TypeError("Unsupported deadline %r" % deadline)
+
+ def call_later(self, delay, callback, *args, **kwargs):
+ """Runs the ``callback`` after ``delay`` seconds have passed.
+
+ Returns an opaque handle that may be passed to `remove_timeout`
+ to cancel. Note that unlike the `asyncio` method of the same
+ name, the returned object does not have a ``cancel()`` method.
+
+ See `add_timeout` for comments on thread-safety and subclassing.
+
+ .. versionadded:: 4.0
+ """
+ self.call_at(self.time() + delay, callback, *args, **kwargs)
+
+ def call_at(self, when, callback, *args, **kwargs):
+ """Runs the ``callback`` at the absolute time designated by ``when``.
+
+ ``when`` must be a number using the same reference point as
+ `IOLoop.time`.
+
+ Returns an opaque handle that may be passed to `remove_timeout`
+ to cancel. Note that unlike the `asyncio` method of the same
+ name, the returned object does not have a ``cancel()`` method.
+
+ See `add_timeout` for comments on thread-safety and subclassing.
+
+ .. versionadded:: 4.0
+ """
+ self.add_timeout(when, callback, *args, **kwargs)
def remove_timeout(self, timeout):
"""Cancels a pending timeout.
self._thread_ident = None
self._blocking_signal_threshold = None
self._timeout_counter = itertools.count()
-
+
# Create a pipe that we send bogus data to when we want to wake
# the I/O loop when it is idle
self._waker = Waker()
def time(self):
return self.time_func()
- def add_timeout(self, deadline, callback):
- timeout = _Timeout(deadline, stack_context.wrap(callback), self)
+ def call_at(self, deadline, callback, *args, **kwargs):
+ timeout = _Timeout(
+ deadline,
+ functools.partial(stack_context.wrap(callback), *args, **kwargs),
+ self)
heapq.heappush(self._timeouts, timeout)
return timeout
__slots__ = ['deadline', 'callback', 'tiebreaker']
def __init__(self, deadline, callback, io_loop):
- if isinstance(deadline, numbers.Real):
- self.deadline = deadline
- elif isinstance(deadline, datetime.timedelta):
- now = io_loop.time()
- try:
- self.deadline = now + deadline.total_seconds()
- except AttributeError: # py2.6
- self.deadline = now + _Timeout.timedelta_to_seconds(deadline)
- else:
+ if not isinstance(deadline, numbers.Real):
raise TypeError("Unsupported deadline %r" % deadline)
+ self.deadline = deadline
self.callback = callback
self.tiebreaker = next(io_loop._timeout_counter)
- @staticmethod
- def timedelta_to_seconds(td):
- """Equivalent to td.total_seconds() (introduced in python 2.7)."""
- return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / float(10 ** 6)
-
# Comparison methods to sort by deadline, with object id as a tiebreaker
# to guarantee a consistent ordering. The heapq module uses __le__
# in python2.5, and __lt__ in 2.6+ (sort() and most other comparisons
import datetime
import functools
-# _Timeout is used for its timedelta_to_seconds method for py26 compatibility.
-from tornado.ioloop import IOLoop, _Timeout
+from tornado.ioloop import IOLoop
from tornado import stack_context
+from tornado.util import timedelta_to_seconds
try:
# Import the real asyncio module for py33+ first. Older versions of the
def stop(self):
self.asyncio_loop.stop()
- def add_timeout(self, deadline, callback):
- if isinstance(deadline, (int, float)):
- delay = max(deadline - self.time(), 0)
- elif isinstance(deadline, datetime.timedelta):
- delay = _Timeout.timedelta_to_seconds(deadline)
- else:
- raise TypeError("Unsupported deadline %r", deadline)
- return self.asyncio_loop.call_later(delay, self._run_callback,
- stack_context.wrap(callback))
+ def call_at(self, when, callback, *args, **kwargs):
+ # asyncio.call_at supports *args but not **kwargs, so bind them here.
+ # We do not synchronize self.time and asyncio_loop.time, so
+ # convert from absolute to relative.
+ return self.asyncio_loop.call_later(
+ max(0, when - self.time()), self._run_callback,
+ functools.partial(stack_context.wrap(callback), *args, **kwargs))
def remove_timeout(self, timeout):
timeout.cancel()
import datetime
import functools
+import numbers
import socket
import twisted.internet.abstract
from tornado.netutil import Resolver
from tornado.stack_context import NullContext, wrap
from tornado.ioloop import IOLoop
-
-try:
- long # py2
-except NameError:
- long = int # py3
+from tornado.util import timedelta_to_seconds
@implementer(IDelayedCall)
def stop(self):
self.reactor.crash()
- def add_timeout(self, deadline, callback):
- if isinstance(deadline, (int, long, float)):
+ def add_timeout(self, deadline, callback, *args, **kwargs):
+ # This method could be simplified (since tornado 4.0) by
+ # overriding call_at instead of add_timeout, but we leave it
+ # for now as a test of backwards-compatibility.
+ if isinstance(deadline, numbers.Real):
delay = max(deadline - self.time(), 0)
elif isinstance(deadline, datetime.timedelta):
- delay = tornado.ioloop._Timeout.timedelta_to_seconds(deadline)
+ delay = timedelta_to_seconds(deadline)
else:
raise TypeError("Unsupported deadline %r")
- return self.reactor.callLater(delay, self._run_callback, wrap(callback))
+ return self.reactor.callLater(
+ delay, self._run_callback,
+ functools.partial(wrap(callback), *args, **kwargs))
def remove_timeout(self, timeout):
if timeout.active():
def test_remove_timeout_after_fire(self):
# It is not an error to call remove_timeout after it has run.
- handle = self.io_loop.add_timeout(self.io_loop.time(), self.stop())
+ handle = self.io_loop.add_timeout(self.io_loop.time(), self.stop)
self.wait()
self.io_loop.remove_timeout(handle)
self.io_loop.add_callback(lambda: self.io_loop.add_callback(self.stop))
self.wait()
+ def test_timeout_with_arguments(self):
+ # This tests that all the timeout methods pass through *args correctly.
+ results = []
+ self.io_loop.add_timeout(self.io_loop.time(), results.append, 1)
+ self.io_loop.add_timeout(datetime.timedelta(seconds=0),
+ results.append, 2)
+ self.io_loop.call_at(self.io_loop.time(), results.append, 3)
+ self.io_loop.call_later(0, results.append, 4)
+ self.io_loop.call_later(0, self.stop)
+ self.wait()
+ self.assertEqual(results, [1, 2, 3, 4])
+
def test_close_file_object(self):
"""When a file object is used instead of a numeric file descriptor,
the object should be closed (by IOLoop.close(all_fds=True),