[Twisted-Python] Perspective broker and inlineCallbacks exception handling problem

Terry Jones terry at jon.es
Mon Jan 5 12:15:11 EST 2009


Hi Maik

I'm not an expert on inlineCallbacks, but I know enough to give something
of an answer.

> I am developing a basic client/server application based on twisted.
> For communication purposes I am using perspective broker. I have
> problems with inlineCallbacks and remotely raised errors:
> 
> My client code looks like this (pseudo code):
> 
> class Client:
>   @defer.inlineCallbacks
>   def login(self, prms):
>     self.factory = pb.PBClientFactory()
>     reactor.connect(host, port)
>     self.perspective = yield self.factory.login()
>     self.services = yield self.perspective.callRemote('getClientServices')
> 
> When using this class in application, I want to handle errors raised remotely:
> 
> @defer.inlineCallbacks
> def main():
>   client = Client()
>   try:
>     yield client.login(prms)
>   except Exception, e:
>     print 'Handled exception:', e
> 
> However, no matter what is raised on remote side, except block is
> never used.

The first thing to say is that you shouldn't send us pseudo code. It will
help you understand what's going on if you write a small example and play
with it. And that will help us help you because we'll be able to see
exactly what's going on and run your code ourselves.

Without a real example, it's hard to give a concrete answer. There are
quite a few subtleties involved in using inlineCallbacks, and you're using
it twice.

Below is some working code that has something like the calling pattern you
seem to be trying to set up. If I cause _login to raise an exception, it IS
propagated all the way back to the except Exception clause.


> But if I change the code to plain except, exception is handled:
> 
> @defer.inlineCallbacks
> def main():
>   client = Client()
>   try:
>     client.login(prms)
>   except:
>     print 'Some error occurred'

Note that in the above snippet, you're using inlineCallbacks but you're not
yielding anything. If you try calling this main() function, it will give
you an exception: Failure instance: Traceback: <type
'exceptions.AttributeError'>: 'NoneType' object has no attribute 'send'

That's just an example of the subtleties (and it's not so sublte in this
case), and underlines why you should send us real code.

Are you actually trying to call main()?


> If I rewrite this function using classic deferred style, it works as
> excepted (prints error with the correct type):
> 
> def eb_failure(failure):
>   print type(failure), failure
> 
> def cb_success(result):
>   print 'OK', result
> 
> def main():
>   client = Client()
>   d = client.login(prms)
>   d.addCallbacks(cb_success, eb_failure)

Right. But that code is very different from what's going on behind the
scenes when you use inlineCallbacks.

> I am purposely raising errors derived from pb.Error. Using the
> inlineCallbacks version I can only inspect remote errors with
> sys.exc_info(), and that results in a plain string object, which
> includes the classname of the original error like
> 'myerror.MyAuthenticationError'

How do you raise the error? Let's see the code.

> What am I doing wrong? Is there a way to resolve this?

Sure. But we need to know more first.

Below is some code that might be useful.  It's also worth trying to figure
out how inlineCallbacks does its magic.

Terry

---

#!/usr/bin/python

from twisted.internet import defer, reactor

def _login(d):
    try:
        hello
    except Exception:
        d.errback()
    else:
        d.callback(True)

def login():
    d = defer.Deferred()
    reactor.callLater(1, _login, d)
    return d

class Client:
    @defer.inlineCallbacks
    def login(self, prms):
        yield login()

@defer.inlineCallbacks
def main():
    client = Client()
    try:
        yield client.login(33)
    except Exception, e:
        print 'Handled exception:', e

def ok(result):
    print "ok", result

def nok(failure):
    print "nok", failure

def stop(_):
    reactor.stop()

def go():
    x = main()
    x.addCallback(ok)
    x.addErrback(nok)
    x.addBoth(stop)

if __name__ == '__main__':
    reactor.callLater(0, go)
    reactor.run()




More information about the Twisted-Python mailing list