tornado: use time.monotonic() where available.
In ioloop.add_timeout(), we still support adding timeouts using time.time(),
but we actually convert them to time.monotonic() before running. If you
don't explicitly set monotonic=False when calling add_timeout(), you'll get
a warning when that happens.
But mostly, you should really be passing in a datetime.timedelta object,
because almost always you want to use relative time instead of absolute
time.
Change-Id: I5e84f219ed00cf597f651c7df50648a200582675
diff --git a/tornado/curl_httpclient.py b/tornado/curl_httpclient.py
index 95958c1..753ccd1 100644
--- a/tornado/curl_httpclient.py
+++ b/tornado/curl_httpclient.py
@@ -20,10 +20,10 @@
import cStringIO
import collections
+import datetime
import logging
import pycurl
import threading
-import time
from tornado import httputil
from tornado import ioloop
@@ -31,6 +31,7 @@
from tornado.escape import utf8
from tornado.httpclient import HTTPRequest, HTTPResponse, HTTPError, AsyncHTTPClient, main
+from tornado.util import monotime
class CurlAsyncHTTPClient(AsyncHTTPClient):
@@ -110,7 +111,7 @@
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)
+ datetime.timedelta(milliseconds=msecs), self._handle_timeout)
def _handle_events(self, fd, events):
"""Called by IOLoop when there is activity on one of our
@@ -202,7 +203,7 @@
"buffer": cStringIO.StringIO(),
"request": request,
"callback": callback,
- "curl_start_time": time.time(),
+ "curl_start_time": monotime(),
}
# Disable IPv6 to mitigate the effects of this bug
# on curl versions <= 7.21.0
@@ -248,7 +249,7 @@
info["callback"](HTTPResponse(
request=info["request"], code=code, headers=info["headers"],
buffer=buffer, effective_url=effective_url, error=error,
- request_time=time.time() - info["curl_start_time"],
+ request_time=monotime() - info["curl_start_time"],
time_info=time_info))
except Exception:
self.handle_callback_exception(info["callback"])
diff --git a/tornado/database.py b/tornado/database.py
index 982c5db..0f6d17a 100644
--- a/tornado/database.py
+++ b/tornado/database.py
@@ -23,6 +23,8 @@
import logging
import time
+from tornado.util import monotime
+
try:
import MySQLdb.constants
import MySQLdb.converters
@@ -79,7 +81,7 @@
self._db = None
self._db_args = args
- self._last_use_time = time.time()
+ self._last_use_time = monotime()
try:
self.reconnect()
except Exception:
@@ -195,9 +197,9 @@
# case by preemptively closing and reopening the connection
# if it has been idle for too long (7 hours by default).
if (self._db is None or
- (time.time() - self._last_use_time > self.max_idle_time)):
+ (monotime() - self._last_use_time > self.max_idle_time)):
self.reconnect()
- self._last_use_time = time.time()
+ self._last_use_time = monotime()
def _cursor(self):
self._ensure_connected()
diff --git a/tornado/httpclient.py b/tornado/httpclient.py
index 0fcc943..a4a3728 100644
--- a/tornado/httpclient.py
+++ b/tornado/httpclient.py
@@ -34,13 +34,12 @@
import calendar
import email.utils
import httplib
-import time
import weakref
from tornado.escape import utf8
from tornado import httputil
from tornado.ioloop import IOLoop
-from tornado.util import import_object, bytes_type
+from tornado.util import import_object, bytes_type, monotime
class HTTPClient(object):
@@ -319,7 +318,7 @@
self.allow_ipv6 = allow_ipv6
self.client_key = client_key
self.client_cert = client_cert
- self.start_time = time.time()
+ self.start_time = monotime()
class HTTPResponse(object):
diff --git a/tornado/httpserver.py b/tornado/httpserver.py
index 5667ba1..b51f6f0 100644
--- a/tornado/httpserver.py
+++ b/tornado/httpserver.py
@@ -29,14 +29,13 @@
import Cookie
import logging
import socket
-import time
from tornado.escape import utf8, native_str, parse_qs_bytes
from tornado import httputil
from tornado import iostream
from tornado.netutil import TCPServer
from tornado import stack_context
-from tornado.util import b, bytes_type
+from tornado.util import b, bytes_type, monotime
try:
import ssl # Python 2.6+
@@ -395,7 +394,7 @@
self.host = host or self.headers.get("Host") or "127.0.0.1"
self.files = files or {}
self.connection = connection
- self._start_time = time.time()
+ self._start_time = monotime()
self._finish_time = None
self.path, sep, self.query = uri.partition('?')
@@ -431,7 +430,7 @@
def finish(self):
"""Finishes this HTTP request on the open connection."""
self.connection.finish()
- self._finish_time = time.time()
+ self._finish_time = monotime()
def full_url(self):
"""Reconstructs the full URL for this request."""
@@ -440,7 +439,7 @@
def request_time(self):
"""Returns the amount of time it took for this request to execute."""
if self._finish_time is None:
- return time.time() - self._start_time
+ return monotime() - self._start_time
else:
return self._finish_time - self._start_time
diff --git a/tornado/ioloop.py b/tornado/ioloop.py
index 3e14229..730d9ba 100644
--- a/tornado/ioloop.py
+++ b/tornado/ioloop.py
@@ -47,6 +47,7 @@
signal = None
from tornado.platform.auto import set_close_exec, Waker
+from tornado.util import monotime
class IOLoop(object):
@@ -271,7 +272,7 @@
self._run_callback(callback)
if self._timeouts:
- now = time.time()
+ now = monotime()
while self._timeouts:
if self._timeouts[0].callback is None:
# the timeout was cancelled
@@ -366,7 +367,7 @@
"""Returns true if this IOLoop is currently running."""
return self._running
- def add_timeout(self, deadline, callback):
+ def add_timeout(self, deadline, callback, monotonic=None):
"""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.
@@ -378,8 +379,15 @@
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.
+
+ Set monotonic=False if deadline is from time.time(), or monotonic=True
+ if it comes from tornado.util.monotime(). If deadline is a
+ datetime.timedelta, you can omit the monotonic flag. For backward
+ compatibility, an unspecified monotonic flag acts like monotonic=False
+ but prints a warning.
"""
- timeout = _Timeout(deadline, stack_context.wrap(callback))
+ timeout = _Timeout(deadline, stack_context.wrap(callback),
+ monotonic=monotonic)
heapq.heappush(self._timeouts, timeout)
return timeout
@@ -441,11 +449,18 @@
# Reduce memory overhead when there are lots of pending callbacks
__slots__ = ['deadline', 'callback']
- def __init__(self, deadline, callback):
+ def __init__(self, deadline, callback, monotonic):
if isinstance(deadline, (int, long, float)):
- self.deadline = deadline
+ if monotonic:
+ self.deadline = deadline
+ else:
+ if hasattr(time, 'monotonic'):
+ import inspect
+ logging.warning('non-monotonic time _Timeout() created at %s:%d',
+ inspect.stack()[2][1], inspect.stack()[2][2])
+ self.deadline = deadline - time.time() + monotime()
elif isinstance(deadline, datetime.timedelta):
- self.deadline = time.time() + _Timeout.timedelta_to_seconds(deadline)
+ self.deadline = monotime() + _Timeout.timedelta_to_seconds(deadline)
else:
raise TypeError("Unsupported deadline %r" % deadline)
self.callback = callback
@@ -485,7 +500,7 @@
def start(self):
"""Starts the timer."""
self._running = True
- self._next_timeout = time.time()
+ self._next_timeout = monotime()
self._schedule_next()
def stop(self):
@@ -506,10 +521,11 @@
def _schedule_next(self):
if self._running:
- current_time = time.time()
+ current_time = monotime()
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)
+ self._timeout = self.io_loop.add_timeout(self._next_timeout,
+ self._run, monotonic=True)
class _EPoll(object):
diff --git a/tornado/platform/twisted.py b/tornado/platform/twisted.py
index 044c333..a3a69a6 100644
--- a/tornado/platform/twisted.py
+++ b/tornado/platform/twisted.py
@@ -48,7 +48,6 @@
import functools
import logging
-import time
from twisted.internet.posixbase import PosixReactorBase
from twisted.internet.interfaces import \
@@ -62,6 +61,7 @@
import tornado.ioloop
from tornado.stack_context import NullContext
from tornado.ioloop import IOLoop
+from tornado.util import monotime
class TornadoDelayedCall(object):
@@ -78,7 +78,8 @@
self._func = functools.partial(f, *args, **kw)
self._time = self._reactor.seconds() + seconds
self._timeout = self._reactor._io_loop.add_timeout(self._time,
- self._called)
+ self._called,
+ monotonic=True)
self._active = True
def _called(self):
@@ -145,7 +146,7 @@
# IReactorTime
def seconds(self):
- return time.time()
+ return monotime()
def callLater(self, seconds, f, *args, **kw):
dc = TornadoDelayedCall(self, seconds, f, *args, **kw)
diff --git a/tornado/simple_httpclient.py b/tornado/simple_httpclient.py
index 80b3fb0..dfc064d 100644
--- a/tornado/simple_httpclient.py
+++ b/tornado/simple_httpclient.py
@@ -6,7 +6,7 @@
from tornado.httputil import HTTPHeaders
from tornado.iostream import IOStream, SSLIOStream
from tornado import stack_context
-from tornado.util import b
+from tornado.util import b, monotime
import base64
import collections
@@ -18,7 +18,6 @@
import re
import socket
import sys
-import time
import urlparse
import zlib
@@ -127,7 +126,7 @@
def __init__(self, io_loop, client, request, release_callback,
final_callback, max_buffer_size):
- self.start_time = time.time()
+ self.start_time = monotime()
self.io_loop = io_loop
self.client = client
self.request = request
@@ -221,7 +220,7 @@
if timeout:
self._timeout = self.io_loop.add_timeout(
self.start_time + timeout,
- self._on_timeout)
+ self._on_timeout, monotonic=True)
self.stream.set_close_callback(self._on_close)
self.stream.connect(sockaddr,
functools.partial(self._on_connect, parsed,
@@ -230,7 +229,7 @@
def _on_timeout(self):
self._timeout = None
self._run_callback(HTTPResponse(self.request, 599,
- request_time=time.time() - self.start_time,
+ request_time=monotime() - self.start_time,
error=HTTPError(599, "Timeout")))
self.stream.close()
@@ -241,7 +240,7 @@
if self.request.request_timeout:
self._timeout = self.io_loop.add_timeout(
self.start_time + self.request.request_timeout,
- self._on_timeout)
+ self._on_timeout, monotonic=True)
if (self.request.validate_cert and
isinstance(self.stream, SSLIOStream)):
match_hostname(self.stream.socket.getpeercert(),
@@ -324,7 +323,7 @@
except Exception, e:
logging.warning("uncaught exception", exc_info=True)
self._run_callback(HTTPResponse(self.request, 599, error=e,
- request_time=time.time() - self.start_time,
+ request_time=monotime() - self.start_time,
))
if hasattr(self, "stream"):
self.stream.close()
@@ -332,7 +331,7 @@
def _on_close(self):
self._run_callback(HTTPResponse(
self.request, 599,
- request_time=time.time() - self.start_time,
+ request_time=monotime() - self.start_time,
error=HTTPError(599, "Connection closed")))
def _on_headers(self, data):
@@ -431,7 +430,7 @@
buffer = BytesIO(data) # TODO: don't require one big string?
response = HTTPResponse(original_request,
self.code, headers=self.headers,
- request_time=time.time() - self.start_time,
+ request_time=monotime() - self.start_time,
buffer=buffer,
effective_url=self.request.url)
self._run_callback(response)
diff --git a/tornado/test/iostream_test.py b/tornado/test/iostream_test.py
index 5aa1d9b..cf26e9f 100644
--- a/tornado/test/iostream_test.py
+++ b/tornado/test/iostream_test.py
@@ -5,10 +5,10 @@
from tornado.testing import AsyncHTTPTestCase, LogTrapTestCase, get_unused_port
from tornado.util import b
from tornado.web import RequestHandler, Application
+import datetime
import errno
import socket
import sys
-import time
class HelloHandler(RequestHandler):
@@ -222,7 +222,7 @@
# 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(datetime.timedelta(seconds=0.01), self.stop)
self.wait()
client.read_bytes(256, self.stop)
data = self.wait()
diff --git a/tornado/test/testing_test.py b/tornado/test/testing_test.py
index 1de20df..783d0df 100644
--- a/tornado/test/testing_test.py
+++ b/tornado/test/testing_test.py
@@ -1,8 +1,8 @@
#!/usr/bin/env python
from __future__ import absolute_import, division, with_statement
+import datetime
import unittest
-import time
from tornado.testing import AsyncTestCase, LogTrapTestCase
@@ -20,9 +20,9 @@
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(datetime.timedelta(seconds=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(datetime.timedelta(seconds=0.03), self.stop)
self.wait(timeout=0.1)
diff --git a/tornado/testing.py b/tornado/testing.py
index fccdb86..00ec4b4 100644
--- a/tornado/testing.py
+++ b/tornado/testing.py
@@ -34,10 +34,10 @@
from tornado.stack_context import StackContext, NullContext
from tornado.util import raise_exc_info
import contextlib
+import datetime
import logging
import signal
import sys
-import time
import unittest
_next_port = 10000
@@ -186,7 +186,7 @@
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(datetime.timedelta(seconds=timeout), timeout_func)
while True:
self.__running = True
with NullContext():
diff --git a/tornado/util.py b/tornado/util.py
index e19ca90..6f60911 100644
--- a/tornado/util.py
+++ b/tornado/util.py
@@ -3,6 +3,26 @@
from __future__ import absolute_import, division, with_statement
+try:
+ # You can get the monotime module from:
+ # http://pypi.python.org/pypi/Monotime/
+ import monotime
+except ImportError:
+ pass
+import time
+try:
+ # python 3.3 has time.monotonic(), or the monotime module might have
+ # added it.
+ monotime_impl = time.monotonic
+except AttributeError:
+ import logging
+ logging.warning("time.monotonic() not available; using time.time()")
+ monotime_impl = time.time
+# wrap monotime_impl so that monotime_impl can be reassigned in unit tests
+def monotime():
+ return monotime_impl()
+
+
class ObjectDict(dict):
"""Makes a dictionary behave like an object."""
def __getattr__(self, name):
diff --git a/tornado/websocket.py b/tornado/websocket.py
index 266b114..7a1c2cc 100644
--- a/tornado/websocket.py
+++ b/tornado/websocket.py
@@ -21,11 +21,11 @@
# Author: Jacob Kristhammar, 2010
import array
+import datetime
import functools
import hashlib
import logging
import struct
-import time
import base64
import tornado.escape
import tornado.web
@@ -433,7 +433,7 @@
self.stream.close()
elif self._waiting is None:
self._waiting = self.stream.io_loop.add_timeout(
- time.time() + 5, self._abort)
+ datetime.timedelta(seconds=5), self._abort)
class WebSocketProtocol13(WebSocketProtocol):
@@ -651,4 +651,4 @@
# 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)
+ datetime.timedelta(seconds=5), self._abort)
diff --git a/tornado/wsgi.py b/tornado/wsgi.py
index e0b11d3..2084d69 100644
--- a/tornado/wsgi.py
+++ b/tornado/wsgi.py
@@ -35,7 +35,6 @@
import httplib
import logging
import sys
-import time
import tornado
import urllib
@@ -43,7 +42,7 @@
from tornado import httputil
from tornado import web
from tornado.escape import native_str, utf8, parse_qs_bytes
-from tornado.util import b
+from tornado.util import b, monotime
try:
from io import BytesIO # python 3
@@ -156,7 +155,7 @@
else:
logging.warning("Invalid multipart/form-data")
- self._start_time = time.time()
+ self._start_time = monotime()
self._finish_time = None
def supports_http_1_1(self):
@@ -183,7 +182,7 @@
def request_time(self):
"""Returns the amount of time it took for this request to execute."""
if self._finish_time is None:
- return time.time() - self._start_time
+ return monotime() - self._start_time
else:
return self._finish_time - self._start_time