| 1 | Index: twisted/python/runtime.py |
|---|
| 2 | =================================================================== |
|---|
| 3 | --- twisted/python/runtime.py (revision 23668) |
|---|
| 4 | +++ twisted/python/runtime.py (working copy) |
|---|
| 5 | @@ -24,22 +24,77 @@ |
|---|
| 6 | 'org.python.modules.os': 'java', |
|---|
| 7 | } |
|---|
| 8 | |
|---|
| 9 | -_timeFunctions = { |
|---|
| 10 | - #'win32': time.clock, |
|---|
| 11 | - 'win32': time.time, |
|---|
| 12 | - } |
|---|
| 13 | +try: |
|---|
| 14 | + import _posix_clock |
|---|
| 15 | + # Make sure the monotonic clock source exists |
|---|
| 16 | + _posix_clock.gettime(_posix_clock.CLOCK_MONOTONIC) |
|---|
| 17 | +except: |
|---|
| 18 | + _posix_clock = None |
|---|
| 19 | |
|---|
| 20 | class Platform: |
|---|
| 21 | """Gives us information about the platform we're running on""" |
|---|
| 22 | |
|---|
| 23 | type = knownPlatforms.get(os.name) |
|---|
| 24 | - seconds = staticmethod(_timeFunctions.get(type, time.time)) |
|---|
| 25 | + seconds = time.time |
|---|
| 26 | |
|---|
| 27 | + _lastTicks = 0 |
|---|
| 28 | + _offset = 0 |
|---|
| 29 | + |
|---|
| 30 | def __init__(self, name=None): |
|---|
| 31 | if name is not None: |
|---|
| 32 | self.type = knownPlatforms.get(name) |
|---|
| 33 | - self.seconds = _timeFunctions.get(self.type, time.time) |
|---|
| 34 | + |
|---|
| 35 | + def monotonicTicks(self): |
|---|
| 36 | + """Returns the current number of nanoseconds as an integer |
|---|
| 37 | + since some undefined epoch. The only hard requirement is that |
|---|
| 38 | + the number returned from this function is always strictly |
|---|
| 39 | + greater than any other number returned by it. |
|---|
| 40 | + |
|---|
| 41 | + Additionally, it is very good if time never skips around (such |
|---|
| 42 | + as by the user setting their system clock). |
|---|
| 43 | + |
|---|
| 44 | + This default implementation doesn't have that property, as it |
|---|
| 45 | + is based upon the system clock, which can be set forward and |
|---|
| 46 | + backward in time. An implementation based upon |
|---|
| 47 | + clock_gettime(CLOCK_MONOTONIC) is used when available. |
|---|
| 48 | + """ |
|---|
| 49 | + |
|---|
| 50 | + cur = int(self.seconds() * 1000000000) + self._offset |
|---|
| 51 | + if self._lastTicks >= cur: |
|---|
| 52 | + if self._lastTicks < cur + 10000000: # 0.01s |
|---|
| 53 | + # Just pretend epsilon more time has gone by. |
|---|
| 54 | + cur = self._lastTicks + 1 |
|---|
| 55 | + else: |
|---|
| 56 | + # If lastSeconds is much larger than cur time, clock |
|---|
| 57 | + # must've moved backwards! Adjust the offset to keep |
|---|
| 58 | + # monotonicity. |
|---|
| 59 | + self._offset += self._lastTicks - cur |
|---|
| 60 | + |
|---|
| 61 | + self._lastTicks = cur |
|---|
| 62 | + return cur |
|---|
| 63 | |
|---|
| 64 | + if _posix_clock: |
|---|
| 65 | + def monotonicTicks2(self): |
|---|
| 66 | + cur = _posix_clock.gettime(_posix_clock.CLOCK_MONOTONIC) |
|---|
| 67 | + if self._lastTicks >= cur: |
|---|
| 68 | + cur += 1 |
|---|
| 69 | + self._lastTicks = cur |
|---|
| 70 | + return cur |
|---|
| 71 | + |
|---|
| 72 | + monotonicTicks2.__doc__=monotonicTicks.__doc__ |
|---|
| 73 | + monotonicTicks=monotonicTicks2 |
|---|
| 74 | + del monotonicTicks2 |
|---|
| 75 | + |
|---|
| 76 | + def ticksToTime(self, ticks): |
|---|
| 77 | + """Returns the time (as returned by time.time) that |
|---|
| 78 | + corresponds to the given ticks value. If the time epoch |
|---|
| 79 | + changes via the user setting their system time, |
|---|
| 80 | + the time value of given ticks may or may not also change. |
|---|
| 81 | + """ |
|---|
| 82 | + curticks = self.monotonicTicks() |
|---|
| 83 | + curtime = time.time() |
|---|
| 84 | + return (ticks - curticks)/1000000000. + curtime |
|---|
| 85 | + |
|---|
| 86 | def isKnown(self): |
|---|
| 87 | """Do we know about this platform?""" |
|---|
| 88 | return self.type != None |
|---|
| 89 | @@ -80,3 +135,5 @@ |
|---|
| 90 | platform = Platform() |
|---|
| 91 | platformType = platform.getType() |
|---|
| 92 | seconds = platform.seconds |
|---|
| 93 | +monotonicTicks = platform.monotonicTicks |
|---|
| 94 | +ticksToTime = platform.ticksToTime |
|---|
| 95 | Index: twisted/python/dist.py |
|---|
| 96 | =================================================================== |
|---|
| 97 | --- twisted/python/dist.py (revision 23668) |
|---|
| 98 | +++ twisted/python/dist.py (working copy) |
|---|
| 99 | @@ -8,7 +8,7 @@ |
|---|
| 100 | |
|---|
| 101 | import sys, os |
|---|
| 102 | from distutils.command import build_scripts, install_data, build_ext, build_py |
|---|
| 103 | -from distutils.errors import CompileError |
|---|
| 104 | +from distutils.errors import CompileError, CCompilerError |
|---|
| 105 | from distutils import core |
|---|
| 106 | from distutils.core import Extension |
|---|
| 107 | |
|---|
| 108 | @@ -328,6 +328,12 @@ |
|---|
| 109 | build_ext.build_ext.build_extensions(self) |
|---|
| 110 | |
|---|
| 111 | |
|---|
| 112 | + def build_extension(self, ext): |
|---|
| 113 | + try: |
|---|
| 114 | + return build_ext.build_ext.build_extension(self, ext) |
|---|
| 115 | + except CCompilerError: |
|---|
| 116 | + sys.stderr.write("NOTE: failed to compile optional extension %s\n" % ext.name) |
|---|
| 117 | + |
|---|
| 118 | def _remove_conftest(self): |
|---|
| 119 | for filename in ("conftest.c", "conftest.o", "conftest.obj"): |
|---|
| 120 | try: |
|---|
| 121 | Index: twisted/test/test_internet.py |
|---|
| 122 | =================================================================== |
|---|
| 123 | --- twisted/test/test_internet.py (revision 23668) |
|---|
| 124 | +++ twisted/test/test_internet.py (working copy) |
|---|
| 125 | @@ -815,11 +815,11 @@ |
|---|
| 126 | |
|---|
| 127 | clock.pump(reactor, [0, 1]) |
|---|
| 128 | |
|---|
| 129 | - self.assertEquals(callbackTimes[0], 3) |
|---|
| 130 | + self.assertEquals(callbackTimes[0], 3*10**9) |
|---|
| 131 | self.assertEquals(callbackTimes[1], None) |
|---|
| 132 | |
|---|
| 133 | clock.pump(reactor, [0, 3]) |
|---|
| 134 | - self.assertEquals(callbackTimes[1], 6) |
|---|
| 135 | + self.assertEquals(callbackTimes[1], 6*10**9) |
|---|
| 136 | finally: |
|---|
| 137 | clock.uninstall() |
|---|
| 138 | |
|---|
| 139 | @@ -875,8 +875,7 @@ |
|---|
| 140 | reactor.callLater(0.2, d.callback, None) |
|---|
| 141 | return d |
|---|
| 142 | |
|---|
| 143 | - testCallLaterOrder.todo = "See bug 1396" |
|---|
| 144 | - testCallLaterOrder.skip = "Trial bug, todo doesn't work! See bug 1397" |
|---|
| 145 | + |
|---|
| 146 | def testCallLaterOrder2(self): |
|---|
| 147 | # This time destroy the clock resolution so that it fails reliably |
|---|
| 148 | # even on systems that don't have a crappy clock resolution. |
|---|
| 149 | @@ -884,19 +883,15 @@ |
|---|
| 150 | def seconds(): |
|---|
| 151 | return int(time.time()) |
|---|
| 152 | |
|---|
| 153 | - base_original = base.seconds |
|---|
| 154 | - runtime_original = runtime.seconds |
|---|
| 155 | - base.seconds = seconds |
|---|
| 156 | - runtime.seconds = seconds |
|---|
| 157 | + from twisted.python import runtime |
|---|
| 158 | + runtime_original = runtime.platform.seconds |
|---|
| 159 | + runtime.platform.seconds = seconds |
|---|
| 160 | |
|---|
| 161 | def cleanup(x): |
|---|
| 162 | - runtime.seconds = runtime_original |
|---|
| 163 | - base.seconds = base_original |
|---|
| 164 | + runtime.platform.seconds = runtime_original |
|---|
| 165 | return x |
|---|
| 166 | return maybeDeferred(self.testCallLaterOrder).addBoth(cleanup) |
|---|
| 167 | |
|---|
| 168 | - testCallLaterOrder2.todo = "See bug 1396" |
|---|
| 169 | - testCallLaterOrder2.skip = "Trial bug, todo doesn't work! See bug 1397" |
|---|
| 170 | |
|---|
| 171 | def testDelayedCallStringification(self): |
|---|
| 172 | # Mostly just make sure str() isn't going to raise anything for |
|---|
| 173 | Index: twisted/topfiles/setup.py |
|---|
| 174 | =================================================================== |
|---|
| 175 | --- twisted/topfiles/setup.py (revision 23668) |
|---|
| 176 | +++ twisted/topfiles/setup.py (working copy) |
|---|
| 177 | @@ -47,6 +47,11 @@ |
|---|
| 178 | '-framework','CoreServices', |
|---|
| 179 | '-framework','Carbon'], |
|---|
| 180 | condition=lambda builder: sys.platform == "darwin"), |
|---|
| 181 | + |
|---|
| 182 | + Extension("twisted.python._posix_clock", |
|---|
| 183 | + ["twisted/python/_posix_clock.c"], |
|---|
| 184 | + define_macros=builder.define_macros, |
|---|
| 185 | + libraries=['rt']), |
|---|
| 186 | ] |
|---|
| 187 | |
|---|
| 188 | # Figure out which plugins to include: all plugins except subproject ones |
|---|
| 189 | Index: twisted/internet/task.py |
|---|
| 190 | =================================================================== |
|---|
| 191 | --- twisted/internet/task.py (revision 23668) |
|---|
| 192 | +++ twisted/internet/task.py (working copy) |
|---|
| 193 | @@ -14,6 +14,7 @@ |
|---|
| 194 | |
|---|
| 195 | from zope.interface import implements |
|---|
| 196 | |
|---|
| 197 | +from twisted.python.runtime import monotonicTicks |
|---|
| 198 | from twisted.python import reflect |
|---|
| 199 | |
|---|
| 200 | from twisted.internet import base, defer |
|---|
| 201 | @@ -76,7 +77,7 @@ |
|---|
| 202 | raise ValueError, "interval must be >= 0" |
|---|
| 203 | self.running = True |
|---|
| 204 | d = self.deferred = defer.Deferred() |
|---|
| 205 | - self.starttime = self.clock.seconds() |
|---|
| 206 | + self.starttime = monotonicTicks() / 1000000000. |
|---|
| 207 | self._lastTime = self.starttime |
|---|
| 208 | self.interval = interval |
|---|
| 209 | if now: |
|---|
| 210 | @@ -124,6 +125,7 @@ |
|---|
| 211 | self.call = self.clock.callLater(0, self) |
|---|
| 212 | return |
|---|
| 213 | |
|---|
| 214 | + #fromNow = self.starttime - monotonicTicks() / 1000000000. |
|---|
| 215 | currentTime = self.clock.seconds() |
|---|
| 216 | # Find how long is left until the interval comes around again. |
|---|
| 217 | untilNextTime = (self._lastTime - currentTime) % self.interval |
|---|
| 218 | Index: twisted/internet/base.py |
|---|
| 219 | =================================================================== |
|---|
| 220 | --- twisted/internet/base.py (revision 23668) |
|---|
| 221 | +++ twisted/internet/base.py (working copy) |
|---|
| 222 | @@ -27,8 +27,9 @@ |
|---|
| 223 | from twisted.internet.interfaces import IResolverSimple, IReactorPluggableResolver |
|---|
| 224 | from twisted.internet.interfaces import IConnector, IDelayedCall |
|---|
| 225 | from twisted.internet import main, error, abstract, defer, threads |
|---|
| 226 | -from twisted.python import log, failure, reflect |
|---|
| 227 | +from twisted.python import log, failure, reflect, components |
|---|
| 228 | from twisted.python.runtime import seconds as runtimeSeconds, platform, platformType |
|---|
| 229 | +from twisted.python.runtime import monotonicTicks, platform, ticksToTime |
|---|
| 230 | from twisted.internet.defer import Deferred, DeferredList |
|---|
| 231 | from twisted.persisted import styles |
|---|
| 232 | |
|---|
| 233 | @@ -79,7 +80,7 @@ |
|---|
| 234 | @return: The number of seconds after the epoch at which this call is |
|---|
| 235 | scheduled to be made. |
|---|
| 236 | """ |
|---|
| 237 | - return self.time + self.delayed_time |
|---|
| 238 | + return ticksToTime(self.time + self.delayed_time) |
|---|
| 239 | |
|---|
| 240 | def cancel(self): |
|---|
| 241 | """Unschedule this call |
|---|
| 242 | @@ -115,7 +116,7 @@ |
|---|
| 243 | elif self.called: |
|---|
| 244 | raise error.AlreadyCalled |
|---|
| 245 | else: |
|---|
| 246 | - newTime = self.seconds() + secondsFromNow |
|---|
| 247 | + newTime = monotonicTicks() + int(secondsFromNow * 1000000000) |
|---|
| 248 | if newTime < self.time: |
|---|
| 249 | self.delayed_time = 0 |
|---|
| 250 | self.time = newTime |
|---|
| 251 | @@ -138,7 +139,7 @@ |
|---|
| 252 | elif self.called: |
|---|
| 253 | raise error.AlreadyCalled |
|---|
| 254 | else: |
|---|
| 255 | - self.delayed_time += secondsLater |
|---|
| 256 | + self.delayed_time += int(secondsLater * 1000000000) |
|---|
| 257 | if self.delayed_time < 0: |
|---|
| 258 | self.activate_delay() |
|---|
| 259 | self.resetter(self) |
|---|
| 260 | @@ -174,7 +175,7 @@ |
|---|
| 261 | |
|---|
| 262 | now = self.seconds() |
|---|
| 263 | L = ["<DelayedCall %s [%ss] called=%s cancelled=%s" % ( |
|---|
| 264 | - id(self), self.time - now, self.called, self.cancelled)] |
|---|
| 265 | + id(self), (self.time - monotonicTicks()) / 1000000000., self.called, self.cancelled)] |
|---|
| 266 | if func is not None: |
|---|
| 267 | L.extend((" ", func, "(")) |
|---|
| 268 | if self.args: |
|---|
| 269 | @@ -640,7 +641,7 @@ |
|---|
| 270 | assert callable(_f), "%s is not callable" % _f |
|---|
| 271 | assert sys.maxint >= _seconds >= 0, \ |
|---|
| 272 | "%s is not greater than or equal to 0 seconds" % (_seconds,) |
|---|
| 273 | - tple = DelayedCall(self.seconds() + _seconds, _f, args, kw, |
|---|
| 274 | + tple = DelayedCall(monotonicTicks() + int(_seconds * 1000000000), _f, args, kw, |
|---|
| 275 | self._cancelCallLater, |
|---|
| 276 | self._moveCallLaterSooner, |
|---|
| 277 | seconds=self.seconds) |
|---|
| 278 | @@ -701,7 +702,7 @@ |
|---|
| 279 | if not self._pendingTimedCalls: |
|---|
| 280 | return None |
|---|
| 281 | |
|---|
| 282 | - return max(0, self._pendingTimedCalls[0].time - self.seconds()) |
|---|
| 283 | + return max(0, (self._pendingTimedCalls[0].time - monotonicTicks()) / 1000000000.) |
|---|
| 284 | |
|---|
| 285 | |
|---|
| 286 | def runUntilCurrent(self): |
|---|
| 287 | @@ -729,7 +730,7 @@ |
|---|
| 288 | # insert new delayed calls now |
|---|
| 289 | self._insertNewDelayedCalls() |
|---|
| 290 | |
|---|
| 291 | - now = self.seconds() |
|---|
| 292 | + now = monotonicTicks() |
|---|
| 293 | while self._pendingTimedCalls and (self._pendingTimedCalls[0].time <= now): |
|---|
| 294 | call = heappop(self._pendingTimedCalls) |
|---|
| 295 | if call.cancelled: |
|---|