Ticket #1396: callLater.patch.txt

File callLater.patch.txt, 11.0 KB (added by zooko, 7 years ago)
Line 
1Index: 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
95Index: 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:
121Index: 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
173Index: 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
189Index: 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
218Index: 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: