[Twisted-Python] A pseudo-deferred class that can be canceled

Terry Jones terry at jon.es
Mon Jan 4 21:22:53 EST 2010


I just wrote the below for fun.  It's untested :-)

It's a class that you initialize with a callable (f), and which gives you
back a deferred (d) that will fire when f fires. Alternately, you can fire
d yourself by calling 'callback' or 'errback' on the class instance and
passing a value.  That value is returned via d.

The reason this is useful is that normally when you call a function that
returns a deferred, you can't easily "cancel" the deferred because it is
made and controlled by the function you called. The callback or errback on
the deferred is (very likely) going to be called at some point.  OTOH, in
the class below you get to "cancel" the deferred by triggering it yourself.
If you fire d in this way, then when the original deferred fires (if ever),
its result is ignored.

I don't know why defer.Deferred.setTimeout is deprecated, but I guess it's
partly to do with this control issue. If a timeout function you add to a
deferred calls its errback (the default behavior), the deferred is still
nevertheless going to be called by the code that created it. That code
doesn't know and shouldn't have to check if a timeout happened from the POV
of its caller. The setTimeout approach also only allows one timeout. With
the below, any code can call the callback/errback function at any time. If
you do want to cancel the deferred based simply on a timeout, you can do
this (with as many different timeouts as you like - only the earliest will
have any effect, supposing the original deferred still hasn't fired):

   cd = CancellableDeferred(func)
   deferred = cd.call()
   reactor.callLater(5, cd.errback, None)

I post this partly because it seems cute, but more because it seems like
issue that's probably been solved by many people. Is there a general
utility in Twisted for doing this? A better way?

Terry

----

from twisted.internet import defer


class CancellableDeferred(object):
    def __init__(self, f, *args, **kw):
        self._f = f
        self._args = args
        self._kw = kw
        self._calld = None
        self.called = False

    def _fire(self, result):
        if not self.called:
            self.called = True
            self._calld.chainDeferred(self._resultd)

    def call(self):
        if self._calld:
            raise Exception('Already called.')
        self._resultd = defer.Deferred()
        self._calld = defer.maybeDeferred(self.f, *self._args, **self._kw)
        self._calld.addBoth(self._fire)
        return self._resultd

    def callback(self, value=None):
        if not self.called:
            self.called = True
            self._resultd.callback(value)

    def errback(self, value=None):
        if not self.called:
            self.called = True
            self._resultd.errback(value)



More information about the Twisted-Python mailing list