You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
136 lines
6.5 KiB
136 lines
6.5 KiB
# Copyright (c) 2001 Autonomous Zone Industries
|
|
# Copyright (c) 2002-2009 Zooko Wilcox-O'Hearn
|
|
# This file is part of pyutil; see README.rst for licensing terms.
|
|
|
|
"""
|
|
This module was invented when it was discovered that time.time() can return
|
|
decreasing answers, which was causing scheduled tasks to get executed out of
|
|
order. See python bug report `[ #447945 ] time.time() is not
|
|
non-decreasing',
|
|
http://sourceforge.net/tracker/index.php?func=detail&aid=447945&group_id=5470&atid=105470
|
|
http://mail.python.org/pipermail/python-list/2001-August/thread.html#58296
|
|
|
|
After posting that bug report, I figured out that this isn't really a bug,
|
|
but a misunderstanding about the semantics of gettimeofday(). gettimeofday()
|
|
relies on the hardware clock, which is supposed to reflect the "real" time
|
|
i.e. the position and orientation of our planet with regard to our sun. But
|
|
the hardware clock gets adjusted, either for skew (because hardware clocks
|
|
always run a little faster or a little slower than they ought), or in order to
|
|
sync up with another clock e.g. through NTP. So it isn't really a bug in the
|
|
underlying platform (except perhaps a bug in the lack of a prominent warning
|
|
in the documentation), but if you depend on a monotonically increasing
|
|
timestamps, you need to use IncreasingTimer.time() instead of the Python
|
|
standard library's time.time(). --Zooko 2001-08-04
|
|
"""
|
|
|
|
import time as standardtime
|
|
|
|
# Here is a global reference to an IncreasingTimer.
|
|
# This singleton global IncreasingTimer instance gets created at module load time.
|
|
timer = None
|
|
|
|
class IncreasingTimer:
|
|
def __init__(self, inittime=None):
|
|
"""
|
|
@param inittime starting time (in seconds) or None in which case it
|
|
will be initialized to standardtime.time()
|
|
"""
|
|
if inittime is None:
|
|
inittime = standardtime.time()
|
|
self.lasttime = inittime # This stores the most recent answer that we returned from time().
|
|
self.delta = 0 # We add this to the result from the underlying standardtime.time().
|
|
|
|
# How big of an increment do we need to add in order to make the new float greater than the old float?
|
|
trye = 1.0
|
|
while (self.lasttime + trye) > self.lasttime:
|
|
olde = trye
|
|
trye = trye / 2.0
|
|
self._EPSILON = olde
|
|
|
|
def time(self):
|
|
"""
|
|
This returns the current time as a float, with as much precision as
|
|
the underlying Python interpreter can muster. In addition, successive
|
|
calls to time() always return bigger numbers. (standardtime.time()
|
|
can sometimes return the same or even a *smaller* number!)
|
|
|
|
On the other hand, calling time() is a bit slower than calling
|
|
standardtime.time(), so you might want to avoid it inside tight loops
|
|
and deal with decreasing or identical answers yourself.
|
|
|
|
Now by definition you cannot "reset" this clock to an earlier state.
|
|
This means that if you start a Python interpreter and instantiate an
|
|
IncreasingTimer, and then you subsequently realize that your
|
|
computer's clock was set to next year, and you set it back to the
|
|
correct year, that subsequent calls to standardtime.time() will return
|
|
a number indicating this year and IncreasingTimer.time() will continue
|
|
to return a number indicating next year. Therefore, you should use
|
|
the answers from IncreasingTimer.time() in such a way that the only
|
|
things you depend on are correctness in the relative *order* of two
|
|
times, (and, with the following caveat, the relative *difference*
|
|
between two times as well), not the global "correctness" of the times
|
|
with respect to the rest of the world.
|
|
|
|
The caveat is that if the underlying answers from standardtime.time()
|
|
jump *forward*, then this *does* distort the relative difference
|
|
between two answers from IncreasingTimer.time(). What
|
|
IncreasingTimer.time() does is if the underlying clock goes
|
|
*backwards*, then IncreasingTimer.time() still returns successively
|
|
higher numbers. Then if the underlying clock jumps *forwards*,
|
|
IncreasingTimer.time() also jumps forward the same amount. A weird
|
|
consequence of this is that if you were to set your system clock to
|
|
point to 10 years ago, and call:
|
|
|
|
t1 = increasingtimer.time()
|
|
|
|
and then set your system clock back to the present, and call:
|
|
|
|
t2 = increasingtimer.time()
|
|
|
|
, then there would be a 10-year difference between t2 and t1.
|
|
|
|
In practice, adjustments to the underlying system time are rarely that
|
|
drastic, and for some systems (e.g. Mnet's DoQ, for which this module
|
|
was invented) it doesn't matter anyway if time jumps forward.
|
|
|
|
Another note: Brian Warner has pointed out that there is another
|
|
caveat, which is due to there being a delay between successive calls
|
|
to IncreasingTimer.time(). When the underlying clock jumps backward,
|
|
then events which were scheduled before the jump and scheduled to go
|
|
off after the jump may be delayed by at most d, where d is the delay
|
|
between the two successive calls to IncreasingTimer which spanned the
|
|
jump.
|
|
|
|
@singlethreaded You must guarantee that you never have more than one
|
|
thread in this function at a time.
|
|
"""
|
|
t = standardtime.time() + self.delta
|
|
lasttime = self.lasttime
|
|
|
|
if t <= lasttime:
|
|
self.delta = self.delta + (lasttime - t) + self._EPSILON
|
|
t = lasttime + self._EPSILON
|
|
|
|
# TODO: If you were sure that you could generate a bigger float in one
|
|
# pass, you could change this `while' to an `if' and optimize out a
|
|
# test.
|
|
while t <= lasttime:
|
|
# We can get into here only if self._EPSILON is too small to make
|
|
# # the time float "tick over" to a new higher value. So we
|
|
# (permanently) # double self._EPSILON.
|
|
# TODO: Is doubling epsilon the best way to quickly get a
|
|
# minimally bigger float?
|
|
self._EPSILON = self._EPSILON * 2.0
|
|
|
|
# Delta, having smaller magnitude than t, can be incremented by
|
|
# more than t was incremented. (Up to the old epsilon more.)
|
|
# That's OK.
|
|
self.delta = self.delta + self._EPSILON
|
|
t = t + self._EPSILON
|
|
|
|
self.lasttime = t
|
|
return t
|
|
|
|
# create the global IncreasingTimer instance and `time' function
|
|
timer = IncreasingTimer()
|
|
time = timer.time
|
|
|