if self._timeout is not None:
self.io_loop.remove_timeout(self._timeout)
self._timeout = self.io_loop.add_timeout(
- time.time() + msecs / 1000.0, self._handle_timeout)
+ self.io_loop.time() + msecs / 1000.0, self._handle_timeout)
def _handle_events(self, fd, events):
"""Called by IOLoop when there is activity on one of our
_current = threading.local()
- def initialize(self, impl):
+ def initialize(self, impl, time_func=None):
self._impl = impl
if hasattr(self._impl, 'fileno'):
set_close_exec(self._impl.fileno())
+ self.time_func = time_func or time.time
self._handlers = {}
self._events = {}
self._callbacks = []
self._run_callback(callback)
if self._timeouts:
- now = time.time()
+ now = self.time()
while self._timeouts:
if self._timeouts[0].callback is None:
# the timeout was cancelled
"""Returns true if this IOLoop is currently running."""
return self._running
+ def time(self):
+ """Returns the current time according to the IOLoop's clock.
+
+ The return value is a floating-point number relative to an
+ unspecified time in the past.
+
+ By default, the IOLoop's time function is `time.time`. However,
+ it may be configured to use e.g. `time.monotonic` instead.
+ Calls to `add_timeout` that pass a number instead of a
+ `datetime.timedelta` should use this function to compute the
+ appropriate time, so they can work no matter what time function
+ is chosen.
+ """
+ return self.time_func()
+
def add_timeout(self, deadline, callback):
"""Calls the given callback at the time deadline from the I/O loop.
Returns a handle that may be passed to remove_timeout to cancel.
- ``deadline`` may be a number denoting a unix timestamp (as returned
- by ``time.time()`` or a ``datetime.timedelta`` object for a deadline
- relative to the current time.
+ ``deadline`` may be a number denoting a time relative to
+ `IOLoop.time`, or a ``datetime.timedelta`` object for a
+ deadline relative to the current time.
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.
"""
- timeout = _Timeout(deadline, stack_context.wrap(callback))
+ timeout = _Timeout(deadline, stack_context.wrap(callback), self)
heapq.heappush(self._timeouts, timeout)
return timeout
# Reduce memory overhead when there are lots of pending callbacks
__slots__ = ['deadline', 'callback']
- def __init__(self, deadline, callback):
+ def __init__(self, deadline, callback, io_loop):
if isinstance(deadline, (int, long, float)):
self.deadline = deadline
elif isinstance(deadline, datetime.timedelta):
- self.deadline = time.time() + _Timeout.timedelta_to_seconds(deadline)
+ self.deadline = io_loop.time() + _Timeout.timedelta_to_seconds(deadline)
else:
raise TypeError("Unsupported deadline %r" % deadline)
self.callback = callback
def start(self):
"""Starts the timer."""
self._running = True
- self._next_timeout = time.time()
+ self._next_timeout = self.io_loop.time()
self._schedule_next()
def stop(self):
def _schedule_next(self):
if self._running:
- current_time = time.time()
+ current_time = self.io_loop.time()
while self._next_timeout <= current_time:
self._next_timeout += self.callback_time / 1000.0
self._timeout = self.io_loop.add_timeout(self._next_timeout, self._run)
from tornado.platform.windows import set_close_exec
else:
from tornado.platform.posix import set_close_exec, Waker
+
+try:
+ # monotime monkey-patches the time module to have a monotonic function
+ # in versions of python before 3.3.
+ import monotime
+except ImportError:
+ pass
+try:
+ from time import monotonic as monotonic_time
+except ImportError:
+ monotonic_time = None
# IReactorTime
def seconds(self):
- return time.time()
+ return self._io_loop.time()
def callLater(self, seconds, f, *args, **kw):
dc = TornadoDelayedCall(self, seconds, f, *args, **kw)
def __init__(self, io_loop, client, request, release_callback,
final_callback, max_buffer_size):
- self.start_time = time.time()
+ self.start_time = io_loop.time()
self.io_loop = io_loop
self.client = client
self.request = request
except Exception, e:
gen_log.warning("uncaught exception", exc_info=True)
self._run_callback(HTTPResponse(self.request, 599, error=e,
- request_time=time.time() - self.start_time,
+ request_time=self.io_loop.time() - self.start_time,
))
if hasattr(self, "stream"):
self.stream.close()
response = HTTPResponse(original_request,
self.code, reason=self.reason,
headers=self.headers,
- request_time=time.time() - self.start_time,
+ request_time=self.io_loop.time() - self.start_time,
buffer=buffer,
effective_url=self.request.url)
self._run_callback(response)
self.io_loop.add_callback(callback)
# Store away the time so we can check if we woke up immediately
self.start_time = time.time()
- self.io_loop.add_timeout(time.time(), schedule_callback)
+ self.io_loop.add_timeout(self.io_loop.time(), schedule_callback)
self.wait()
self.assertAlmostEqual(time.time(), self.start_time, places=2)
self.assertTrue(self.called)
# Allow the close to propagate to the client side of the
# connection. Using add_callback instead of add_timeout
# doesn't seem to work, even with multiple iterations
- self.io_loop.add_timeout(time.time() + 0.01, self.stop)
+ self.io_loop.add_timeout(self.io_loop.time() + 0.01, self.stop)
self.wait()
client.read_bytes(256, self.stop)
data = self.wait()
import sys
from tornado.httpclient import AsyncHTTPClient
from tornado.ioloop import IOLoop
-from tornado.options import define
+from tornado.options import define, options, add_parse_callback
from tornado.test.util import unittest
TEST_MODULES = [
define('httpclient', type=str, default=None,
callback=AsyncHTTPClient.configure)
- define('ioloop', type=str, default=None,
- callback=IOLoop.configure)
+ define('ioloop', type=str, default=None)
+ define('ioloop_time_monotonic', default=False)
+ def configure_ioloop():
+ kwargs = {}
+ if options.ioloop_time_monotonic:
+ from tornado.platform.auto import monotonic_time
+ if monotonic_time is None:
+ raise RuntimeError("monotonic clock not found")
+ kwargs['time_func'] = monotonic_time
+ if options.ioloop or kwargs:
+ IOLoop.configure(options.ioloop, **kwargs)
+ add_parse_callback(configure_ioloop)
import tornado.testing
kwargs = {}
This test makes sure that a second call to wait()
clears the first timeout.
"""
- self.io_loop.add_timeout(time.time() + 0.01, self.stop)
+ self.io_loop.add_timeout(self.io_loop.time() + 0.01, self.stop)
self.wait(timeout=0.02)
- self.io_loop.add_timeout(time.time() + 0.03, self.stop)
+ self.io_loop.add_timeout(self.io_loop.time() + 0.03, self.stop)
self.wait(timeout=0.1)
self.stop()
if self.__timeout is not None:
self.io_loop.remove_timeout(self.__timeout)
- self.__timeout = self.io_loop.add_timeout(time.time() + timeout, timeout_func)
+ self.__timeout = self.io_loop.add_timeout(self.io_loop.time() + timeout, timeout_func)
while True:
self.__running = True
self.io_loop.start()
# Give the client a few seconds to complete a clean shutdown,
# otherwise just close the connection.
self._waiting = self.stream.io_loop.add_timeout(
- time.time() + 5, self._abort)
+ self.stream.io_loop.time() + 5, self._abort)
[tox]
# "-full" variants include optional dependencies, to ensure
# that things work both in a bare install and with all the extras.
-envlist = py27-full, py27-curl, py25-full, py32, pypy, py25, py26, py26-full, py27, py32-utf8, py33, py27-opt, py32-opt, pypy-full
+envlist = py27-full, py27-curl, py25-full, py32, pypy, py25, py26, py26-full, py27, py32-utf8, py33, py27-opt, py32-opt, pypy-full, py27-select, py27-monotonic, py33-monotonic
[testenv]
commands = python -m tornado.test.runtests {posargs:}
twisted>=12.0.0
commands = python -m tornado.test.runtests --ioloop=tornado.ioloop.SelectIOLoop {posargs:}
+[testenv:py27-monotonic]
+basepython = python2.7
+# TODO: remove this url when the pypi page is updated.
+deps =
+ http://pypi.python.org/packages/source/M/Monotime/Monotime-1.0.tar.gz
+ futures
+ pycurl
+ twisted
+commands = python -m tornado.test.runtests --ioloop_time_monotonic {posargs:}
+
[testenv:pypy-full]
# This configuration works with pypy 1.9. pycurl installs ok but
# curl_httpclient doesn't work. Twisted works most of the time, but
# tox doesn't yet know "py33" by default
basepython = python3.3
+[testenv:py33-monotonic]
+basepython = python3.3
+commands = python -m tornado.test.runtests --ioloop_time_monotonic {posargs:}
+
# Python's optimized mode disables the assert statement, so run the
# tests in this mode to ensure we haven't fallen into the trap of relying
# on an assertion's side effects or using them for things that should be
option namespace. The `tornado.options` module's new callback
support now makes it easy to add options from a wrapper script
instead of putting all possible options in `tornado.testing.main`.
+* The `IOLoop` constructor has a new keyword argument ``time_func``,
+ which can be used to set the time function used when scheduling callbacks.
+ This is most useful with the `time.monotonic()` function, introduced
+ in Python 3.3 and backported to older versions via the ``monotime``
+ module. Using a monotonic clock here avoids problems when the system
+ clock is changed.
+* New function `IOLoop.time` returns the current time according to the
+ IOLoop. To use the new monotonic clock functionality, all calls to
+ `IOLoop.add_timeout` must be either pass a `datetime.timedelta` or
+ a time relative to `IOLoop.time`, not `time.time`. (`time.time` will
+ continue to work only as long as the IOLoop's ``time_func`` argument
+ is not used).