[Twisted-Python] A kinder and more consistent defer.inlineCallbacks

Terry Jones terry at jon.es
Sat Nov 22 12:50:11 EST 2008


Hi Glyph

>>>>> "glyph" == glyph  <glyph at divmod.com> writes:

>> inlineCallbacks appears to have a bug: 'raise' before 'yield' in a
>> generator results in a synchronous exception rather than an errback,
>> although its documentation does not explain this.

glyph> Ugh, scratch that.  No, it doesn't have this bug.

Yes, you're right.

glyph> I did some quick testing and saw some tracebacks, but apparently
glyph> wasn't paying very close attention to them

Nor was I, sorry!

glyph> If func "raises before it gets to its first yield", we get the right
glyph> behavior.  If it just raises and doesn't yield *anywhere*, then it's
glyph> not a generator and that's the same as your other case of
glyph> accidentally-not- returning-a-generator.  Still worth debugging, but
glyph> not as serious.

Agreed.  I also agree with your earlier remarks about dropping the
isinstance(result, GeneratorType). That leaves me with an alternative:

    def altInlineCallbacks(f):
        def unwindGenerator(*args, **kwargs):
            try:
                result = f(*args, **kwargs)
            except Exception, e:
                # f was not a generator.
                return failure.Failure()
            return _inlineCallbacks(None, result, Deferred())
        return mergeFunctionMetadata(f, unwindGenerator)

and still the problem that _inlineCallbacks raises if result doesn't have a
send() method, etc. I'm also not sure of the best way to check for that,
but don't think it should be in the _inlineCallbacks loop.

---

BTW, there is a coding lesson here (at least for me):

The reason I started to think there was a problem with Exceptions,
tracebacks and _inlineCallbacks was from running code like this:

    @inlineCallbacks
    def f():
        try:
            # something
        except Exception:
            # clean up
            raise

When I ran it, the traceback of the Exception that propagated back to the
attached errback would (sometimes) show the exception as having being
triggered in _inlineCallbacks, like this:

    2008/11/22 17:29 +0200 [-]   File "/usr/lib/python2.5/site-packages/twisted/internet/defer.py", line 726, in _inlineCallbacks
    2008/11/22 17:29 +0200 [-]     result = g.send(result)

The problem, as I guess will be obvious, is that my raise was delivering
whatever sys.exc_info returned after the clean-up was done. In my case this
was confusing as although the clean-up code had succeeded, it had called
things that also made use of inlineCallbacks and the exception I was
finally raising, and whose traceback I was printing, was a StopIteration
raised inside *another* call of _inlineCallbacks! So it looked like
_inlineCallbacks was somehow raising instead of sending a failure back to
the errback...

Re-raising the original exception made everything make sense.

Thanks a lot for taking a look.

Terry




More information about the Twisted-Python mailing list