Index: twisted/python/runtime.py =================================================================== --- twisted/python/runtime.py (revision 23668) +++ twisted/python/runtime.py (working copy) @@ -24,22 +24,77 @@ 'org.python.modules.os': 'java', } -_timeFunctions = { - #'win32': time.clock, - 'win32': time.time, - } +try: + import _posix_clock + # Make sure the monotonic clock source exists + _posix_clock.gettime(_posix_clock.CLOCK_MONOTONIC) +except: + _posix_clock = None class Platform: """Gives us information about the platform we're running on""" type = knownPlatforms.get(os.name) - seconds = staticmethod(_timeFunctions.get(type, time.time)) + seconds = time.time + _lastTicks = 0 + _offset = 0 + def __init__(self, name=None): if name is not None: self.type = knownPlatforms.get(name) - self.seconds = _timeFunctions.get(self.type, time.time) + + def monotonicTicks(self): + """Returns the current number of nanoseconds as an integer + since some undefined epoch. The only hard requirement is that + the number returned from this function is always strictly + greater than any other number returned by it. + + Additionally, it is very good if time never skips around (such + as by the user setting their system clock). + + This default implementation doesn't have that property, as it + is based upon the system clock, which can be set forward and + backward in time. An implementation based upon + clock_gettime(CLOCK_MONOTONIC) is used when available. + """ + + cur = int(self.seconds() * 1000000000) + self._offset + if self._lastTicks >= cur: + if self._lastTicks < cur + 10000000: # 0.01s + # Just pretend epsilon more time has gone by. + cur = self._lastTicks + 1 + else: + # If lastSeconds is much larger than cur time, clock + # must've moved backwards! Adjust the offset to keep + # monotonicity. + self._offset += self._lastTicks - cur + + self._lastTicks = cur + return cur + if _posix_clock: + def monotonicTicks2(self): + cur = _posix_clock.gettime(_posix_clock.CLOCK_MONOTONIC) + if self._lastTicks >= cur: + cur += 1 + self._lastTicks = cur + return cur + + monotonicTicks2.__doc__=monotonicTicks.__doc__ + monotonicTicks=monotonicTicks2 + del monotonicTicks2 + + def ticksToTime(self, ticks): + """Returns the time (as returned by time.time) that + corresponds to the given ticks value. If the time epoch + changes via the user setting their system time, + the time value of given ticks may or may not also change. + """ + curticks = self.monotonicTicks() + curtime = time.time() + return (ticks - curticks)/1000000000. + curtime + def isKnown(self): """Do we know about this platform?""" return self.type != None @@ -80,3 +135,5 @@ platform = Platform() platformType = platform.getType() seconds = platform.seconds +monotonicTicks = platform.monotonicTicks +ticksToTime = platform.ticksToTime Index: twisted/python/dist.py =================================================================== --- twisted/python/dist.py (revision 23668) +++ twisted/python/dist.py (working copy) @@ -8,7 +8,7 @@ import sys, os from distutils.command import build_scripts, install_data, build_ext, build_py -from distutils.errors import CompileError +from distutils.errors import CompileError, CCompilerError from distutils import core from distutils.core import Extension @@ -328,6 +328,12 @@ build_ext.build_ext.build_extensions(self) + def build_extension(self, ext): + try: + return build_ext.build_ext.build_extension(self, ext) + except CCompilerError: + sys.stderr.write("NOTE: failed to compile optional extension %s\n" % ext.name) + def _remove_conftest(self): for filename in ("conftest.c", "conftest.o", "conftest.obj"): try: Index: twisted/test/test_internet.py =================================================================== --- twisted/test/test_internet.py (revision 23668) +++ twisted/test/test_internet.py (working copy) @@ -815,11 +815,11 @@ clock.pump(reactor, [0, 1]) - self.assertEquals(callbackTimes[0], 3) + self.assertEquals(callbackTimes[0], 3*10**9) self.assertEquals(callbackTimes[1], None) clock.pump(reactor, [0, 3]) - self.assertEquals(callbackTimes[1], 6) + self.assertEquals(callbackTimes[1], 6*10**9) finally: clock.uninstall() @@ -875,8 +875,7 @@ reactor.callLater(0.2, d.callback, None) return d - testCallLaterOrder.todo = "See bug 1396" - testCallLaterOrder.skip = "Trial bug, todo doesn't work! See bug 1397" + def testCallLaterOrder2(self): # This time destroy the clock resolution so that it fails reliably # even on systems that don't have a crappy clock resolution. @@ -884,19 +883,15 @@ def seconds(): return int(time.time()) - base_original = base.seconds - runtime_original = runtime.seconds - base.seconds = seconds - runtime.seconds = seconds + from twisted.python import runtime + runtime_original = runtime.platform.seconds + runtime.platform.seconds = seconds def cleanup(x): - runtime.seconds = runtime_original - base.seconds = base_original + runtime.platform.seconds = runtime_original return x return maybeDeferred(self.testCallLaterOrder).addBoth(cleanup) - testCallLaterOrder2.todo = "See bug 1396" - testCallLaterOrder2.skip = "Trial bug, todo doesn't work! See bug 1397" def testDelayedCallStringification(self): # Mostly just make sure str() isn't going to raise anything for Index: twisted/topfiles/setup.py =================================================================== --- twisted/topfiles/setup.py (revision 23668) +++ twisted/topfiles/setup.py (working copy) @@ -47,6 +47,11 @@ '-framework','CoreServices', '-framework','Carbon'], condition=lambda builder: sys.platform == "darwin"), + + Extension("twisted.python._posix_clock", + ["twisted/python/_posix_clock.c"], + define_macros=builder.define_macros, + libraries=['rt']), ] # Figure out which plugins to include: all plugins except subproject ones Index: twisted/internet/task.py =================================================================== --- twisted/internet/task.py (revision 23668) +++ twisted/internet/task.py (working copy) @@ -14,6 +14,7 @@ from zope.interface import implements +from twisted.python.runtime import monotonicTicks from twisted.python import reflect from twisted.internet import base, defer @@ -76,7 +77,7 @@ raise ValueError, "interval must be >= 0" self.running = True d = self.deferred = defer.Deferred() - self.starttime = self.clock.seconds() + self.starttime = monotonicTicks() / 1000000000. self._lastTime = self.starttime self.interval = interval if now: @@ -124,6 +125,7 @@ self.call = self.clock.callLater(0, self) return + #fromNow = self.starttime - monotonicTicks() / 1000000000. currentTime = self.clock.seconds() # Find how long is left until the interval comes around again. untilNextTime = (self._lastTime - currentTime) % self.interval Index: twisted/internet/base.py =================================================================== --- twisted/internet/base.py (revision 23668) +++ twisted/internet/base.py (working copy) @@ -27,8 +27,9 @@ from twisted.internet.interfaces import IResolverSimple, IReactorPluggableResolver from twisted.internet.interfaces import IConnector, IDelayedCall from twisted.internet import main, error, abstract, defer, threads -from twisted.python import log, failure, reflect +from twisted.python import log, failure, reflect, components from twisted.python.runtime import seconds as runtimeSeconds, platform, platformType +from twisted.python.runtime import monotonicTicks, platform, ticksToTime from twisted.internet.defer import Deferred, DeferredList from twisted.persisted import styles @@ -79,7 +80,7 @@ @return: The number of seconds after the epoch at which this call is scheduled to be made. """ - return self.time + self.delayed_time + return ticksToTime(self.time + self.delayed_time) def cancel(self): """Unschedule this call @@ -115,7 +116,7 @@ elif self.called: raise error.AlreadyCalled else: - newTime = self.seconds() + secondsFromNow + newTime = monotonicTicks() + int(secondsFromNow * 1000000000) if newTime < self.time: self.delayed_time = 0 self.time = newTime @@ -138,7 +139,7 @@ elif self.called: raise error.AlreadyCalled else: - self.delayed_time += secondsLater + self.delayed_time += int(secondsLater * 1000000000) if self.delayed_time < 0: self.activate_delay() self.resetter(self) @@ -174,7 +175,7 @@ now = self.seconds() L = ["= _seconds >= 0, \ "%s is not greater than or equal to 0 seconds" % (_seconds,) - tple = DelayedCall(self.seconds() + _seconds, _f, args, kw, + tple = DelayedCall(monotonicTicks() + int(_seconds * 1000000000), _f, args, kw, self._cancelCallLater, self._moveCallLaterSooner, seconds=self.seconds) @@ -701,7 +702,7 @@ if not self._pendingTimedCalls: return None - return max(0, self._pendingTimedCalls[0].time - self.seconds()) + return max(0, (self._pendingTimedCalls[0].time - monotonicTicks()) / 1000000000.) def runUntilCurrent(self): @@ -729,7 +730,7 @@ # insert new delayed calls now self._insertNewDelayedCalls() - now = self.seconds() + now = monotonicTicks() while self._pendingTimedCalls and (self._pendingTimedCalls[0].time <= now): call = heappop(self._pendingTimedCalls) if call.cancelled: