[Twisted-Python] Deferred style question.

Anthony Baxter anthony at interlink.com.au
Fri Jan 7 04:40:27 EST 2005

Say I have an operation that takes a certain amount of time, and so I 
return a deferred. While the operation is still in progress, another call is 
made for the same operation.

The operation is one of those only-do-once-and-cache-the-results sort of 
things. If an operation is called a second time after the first one is done, 
defer.succeed(previousResult) is returned. If it's called a second time 
while it's still in progress, it returns a deferred that's also triggered with
the result when the operation completes.

Should I just return the same deferred?

Initially I thought 'yes, return the same deferred', but that then relies on 
the user-written callback returning the result that it was passed.

Second thought was 'no, return a new deferred', and return a new deferred and 
install a callback on the original deferred that triggers a new deferred

But then if the user's callback on the original deferred raises an error, the 
second deferred will never be triggered. Or it could simply return another
deferred, and delay the triggering of the second one.

So now I'm leaning towards (on the first call) making two deferreds - one for 
the operation, and one to be returned to the user. Then the operation 
deferred gets a callback that triggers the user's deferred. Then a second 
user call can simply add a new callback to the operation deferred. Any later
calls simply add a callback to the (cached) operation deferred, they'll be 
triggered immediately.

Does this make sense? Sample code to demonstrate what I mean is attached.

Anthony Baxter     <anthony at interlink.com.au>
It's never too late to have a happy childhood.
-------------- next part --------------

from twisted.internet import defer, reactor

# Cache of operations
_cache = {}

def longRunningOperation(value):
    # Stub pointless operation - returns the value passed, after a 2s delay
    opdef = defer.Deferred()
    print "Operation called for", value
    reactor.callLater(2, lambda :opdef.callback(value))
    return opdef

def triggerUserCallback(res, deferred):
    return res

def callOperation(value):
    # Currently not in progress - start it
    if not _cache.has_key(value):
        opdef = longRunningOperation(value)
        _cache[value] = opdef

    userdef = defer.Deferred()
    _cache[value].addCallback(lambda x: triggerUserCallback(x, userdef))
    return userdef

def gotAResult(res):
    print "I got a result!", res

def makeACall():
    print "making a call for 'x'"
    d = callOperation('x')

def startTheFun():
    reactor.callLater(1, makeACall)
    reactor.callLater(4, makeACall)
    reactor.callLater(5, makeACall)

if __name__ == "__main__":
    reactor.callLater(0, startTheFun)

More information about the Twisted-Python mailing list