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