Ticket #3245: testdef.py

File testdef.py, 9.1 KB (added by Mekk, 8 years ago)

Primitive test (comment/uncomment Deferred/SlotsDeferred in main to switch between original and slots version)

Line 
1from twisted.internet.defer import timeout, Deferred
2
3class SlotsDeferred(object):
4    __slots__ = ['debug', 'callbacks', 'called', 'paused', 'result', 'timeoutCall', '_runningCallbacks', '_debugInfo']
5
6    # Normal Deferred follow
7    called = 0
8    paused = 0
9    timeoutCall = None
10    _debugInfo = None
11
12    # Are we currently running a user-installed callback?  Meant to prevent
13    # recursive running of callbacks when a reentrant call to add a callback is
14    # used.
15    _runningCallbacks = False
16
17    # Keep this class attribute for now, for compatibility with code that
18    # sets it directly.
19    debug = False
20
21    def __init__(self):
22        self.callbacks = []
23        if self.debug:
24            self._debugInfo = DebugInfo()
25            self._debugInfo.creator = traceback.format_stack()[:-1]
26
27    def addCallbacks(self, callback, errback=None,
28                     callbackArgs=None, callbackKeywords=None,
29                     errbackArgs=None, errbackKeywords=None):
30        """Add a pair of callbacks (success and error) to this Deferred.
31
32        These will be executed when the 'master' callback is run.
33        """
34        assert callable(callback)
35        assert errback == None or callable(errback)
36        cbs = ((callback, callbackArgs, callbackKeywords),
37               (errback or (passthru), errbackArgs, errbackKeywords))
38        self.callbacks.append(cbs)
39
40        if self.called:
41            self._runCallbacks()
42        return self
43
44    def addCallback(self, callback, *args, **kw):
45        """Convenience method for adding just a callback.
46
47        See L{addCallbacks}.
48        """
49        return self.addCallbacks(callback, callbackArgs=args,
50                                 callbackKeywords=kw)
51
52    def addErrback(self, errback, *args, **kw):
53        """Convenience method for adding just an errback.
54
55        See L{addCallbacks}.
56        """
57        return self.addCallbacks(passthru, errback,
58                                 errbackArgs=args,
59                                 errbackKeywords=kw)
60
61    def addBoth(self, callback, *args, **kw):
62        """Convenience method for adding a single callable as both a callback
63        and an errback.
64
65        See L{addCallbacks}.
66        """
67        return self.addCallbacks(callback, callback,
68                                 callbackArgs=args, errbackArgs=args,
69                                 callbackKeywords=kw, errbackKeywords=kw)
70
71    def chainDeferred(self, d):
72        """Chain another Deferred to this Deferred.
73
74        This method adds callbacks to this Deferred to call d's callback or
75        errback, as appropriate. It is merely a shorthand way of performing
76        the following::
77
78            self.addCallbacks(d.callback, d.errback)
79
80        When you chain a deferred d2 to another deferred d1 with
81        d1.chainDeferred(d2), you are making d2 participate in the callback
82        chain of d1. Thus any event that fires d1 will also fire d2.
83        However, the converse is B{not} true; if d2 is fired d1 will not be
84        affected.
85        """
86        return self.addCallbacks(d.callback, d.errback)
87
88    def callback(self, result):
89        """Run all success callbacks that have been added to this Deferred.
90
91        Each callback will have its result passed as the first
92        argument to the next; this way, the callbacks act as a
93        'processing chain'. Also, if the success-callback returns a Failure
94        or raises an Exception, processing will continue on the *error*-
95        callback chain.
96        """
97        assert not isinstance(result, Deferred)
98        self._startRunCallbacks(result)
99
100
101    def errback(self, fail=None):
102        """Run all error callbacks that have been added to this Deferred.
103
104        Each callback will have its result passed as the first
105        argument to the next; this way, the callbacks act as a
106        'processing chain'. Also, if the error-callback returns a non-Failure
107        or doesn't raise an Exception, processing will continue on the
108        *success*-callback chain.
109
110        If the argument that's passed to me is not a failure.Failure instance,
111        it will be embedded in one. If no argument is passed, a failure.Failure
112        instance will be created based on the current traceback stack.
113
114        Passing a string as `fail' is deprecated, and will be punished with
115        a warning message.
116        """
117        if not isinstance(fail, failure.Failure):
118            fail = failure.Failure(fail)
119
120        self._startRunCallbacks(fail)
121
122
123    def pause(self):
124        """Stop processing on a Deferred until L{unpause}() is called.
125        """
126        self.paused = self.paused + 1
127
128
129    def unpause(self):
130        """Process all callbacks made since L{pause}() was called.
131        """
132        self.paused = self.paused - 1
133        if self.paused:
134            return
135        if self.called:
136            self._runCallbacks()
137
138    def _continue(self, result):
139        self.result = result
140        self.unpause()
141
142    def _startRunCallbacks(self, result):
143        if self.called:
144            if self.debug:
145                if self._debugInfo is None:
146                    self._debugInfo = DebugInfo()
147                extra = "\n" + self._debugInfo._getDebugTracebacks()
148                raise AlreadyCalledError(extra)
149            raise AlreadyCalledError
150        if self.debug:
151            if self._debugInfo is None:
152                self._debugInfo = DebugInfo()
153            self._debugInfo.invoker = traceback.format_stack()[:-2]
154        self.called = True
155        self.result = result
156        if self.timeoutCall:
157            try:
158                self.timeoutCall.cancel()
159            except:
160                pass
161
162            del self.timeoutCall
163        self._runCallbacks()
164
165    def _runCallbacks(self):
166        if self._runningCallbacks:
167            # Don't recursively run callbacks
168            return
169        if not self.paused:
170            while self.callbacks:
171                item = self.callbacks.pop(0)
172                callback, args, kw = item[
173                    isinstance(self.result, failure.Failure)]
174                args = args or ()
175                kw = kw or {}
176                try:
177                    self._runningCallbacks = True
178                    try:
179                        self.result = callback(self.result, *args, **kw)
180                    finally:
181                        self._runningCallbacks = False
182                    if isinstance(self.result, Deferred):
183                        # note: this will cause _runCallbacks to be called
184                        # recursively if self.result already has a result.
185                        # This shouldn't cause any problems, since there is no
186                        # relevant state in this stack frame at this point.
187                        # The recursive call will continue to process
188                        # self.callbacks until it is empty, then return here,
189                        # where there is no more work to be done, so this call
190                        # will return as well.
191                        self.pause()
192                        self.result.addBoth(self._continue)
193                        break
194                except:
195                    self.result = failure.Failure()
196
197        if isinstance(self.result, failure.Failure):
198            self.result.cleanFailure()
199            if self._debugInfo is None:
200                self._debugInfo = DebugInfo()
201            self._debugInfo.failResult = self.result
202        else:
203            if self._debugInfo is not None:
204                self._debugInfo.failResult = None
205
206    def setTimeout(self, seconds, timeoutFunc=timeout, *args, **kw):
207        """Set a timeout function to be triggered if I am not called.
208
209        @param seconds: How long to wait (from now) before firing the
210        timeoutFunc.
211
212        @param timeoutFunc: will receive the Deferred and *args, **kw as its
213        arguments.  The default timeoutFunc will call the errback with a
214        L{TimeoutError}.
215        """
216        warnings.warn(
217            "Deferred.setTimeout is deprecated.  Look for timeout "
218            "support specific to the API you are using instead.",
219            DeprecationWarning, stacklevel=2)
220
221        if self.called:
222            return
223        assert not self.timeoutCall, "Don't call setTimeout twice on the same Deferred."
224
225        from twisted.internet import reactor
226        self.timeoutCall = reactor.callLater(
227            seconds,
228            lambda: self.called or timeoutFunc(self, *args, **kw))
229        return self.timeoutCall
230
231    def __str__(self):
232        cname = self.__class__.__name__
233        if hasattr(self, 'result'):
234            return "<%s at %s  current result: %r>" % (cname, hex(unsignedID(self)),
235                                                       self.result)
236        return "<%s at %s>" % (cname, hex(unsignedID(self)))
237    __repr__ = __str__
238
239if __name__ == "__main__":
240    #Cls = Deferred
241    Cls = SlotsDeferred
242    count = 250000
243    print "Testing %s, creating %d instances on each step" % (Cls.__name__, count)
244    import os
245    pid = os.getpid()
246    cmd = "ps -F %d" % pid
247    a = [Cls() for _ in xrange(count)]
248    os.system(cmd)
249    b = [Cls() for _ in xrange(count)]
250    os.system(cmd)
251    c = [Cls() for _ in xrange(count)]
252    os.system(cmd)