[Twisted-Python] Inline callbacks: generating the same Deferred at multiple places

Glyph glyph at twistedmatrix.com
Fri Feb 22 15:17:52 EST 2013


On Feb 22, 2013, at 8:30 AM, Christopher Armstrong <radix at twistedmatrix.com> wrote:

> I think it's a reasonable change to make, and I don't foresee any problems with it, so I think it's fine to submit a bug about it. But I do question the architecture that needs to make use of it. I would probably avoid scenarios like that in my own code.

I disagree; the behavior of result consumption is intentional - although it could be better documented.  Changing it would very definitely be incompatible (<http://twistedmatrix.com/trac/wiki/CompatibilityPolicy>); this is possible, of course, if the deprecation/migration is worth it, but the behavior being requested here would be worse in a number of ways.

If we re-populated the result, every failed Deferred yielded by an inlineCallbacks function would log its traceback twice: once when the unhandled exception propagated out of the inlineCallbacks function causing its Deferred to fail, and once when the unhandled exception propagated from the yielded Deferred itself, since nothing would have consumed it when that Deferred would be GC'd.

Speaking of GC, similarly, any large objects in Deferred results processed by inlineCallbacks functions would live longer, and continue participating in any reference cycles they're part of, possibly causing memory leaks, or at least, longer collection times and less favorable memory usage behavior, especially in long-lived processes.

Basically, you can't treat a Deferred as an event broadcaster.  It isn't one.  It's a single-shot representation of an asynchronous result, which a single consumer can consume with its current value, possibly yielding a new value.  Some consumers can be diligent about not modifying the Deferred's state, so that it can be passed on down the chain, but inlineCallbacks can never be such a consumer: since each inlineCallbacks-decorated function generates its own Deferred return value, it is naturally the terminal consumer of any Deferreds that it yields, and should clear out their results.

Ultimately, _every_ Deferred ought to have a terminal consumer, that takes the result and does useful work with it - persists it, shows it in some UI to a user - rather than continuing to pass it along.  Since 'yield x' is not sufficiently expressive to say what else to do with 'x' and what state to leave it in, we must assume that the intention of a coroutine is to take the value and do some work with it.  Any inlineCallbacks function which wants to express its intent more precisely can do this, instead:

    def fork(d):
        d2 = Deferred()
        def fire(x):
            d2.callback(x)
            return x
        d.addBoth(fire)
        return d2

    @inlineCallbacks
    def foo():
        result = yield fork(somethingAsync())

Maybe putting that function in Twisted (this is not the first time it's come up) would be a useful addition.

-glyph

-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://twistedmatrix.com/pipermail/twisted-python/attachments/20130222/d5e8a29f/attachment.htm 


More information about the Twisted-Python mailing list