Twisted Documentation: Using Perspective Broker

# Using Perspective Broker

## Basic Example

The first example to look at is a complete (although somewhat trivial) application. It uses `PBServerFactory()` on the server side, and `PBClientFactory()` on the client side.

Source listing - ../examples/pbsimple.py

    d.addCallback(lambda _: reactor.stop())
    reactor.run()

Source listing - ../examples/pbsimpleclient.py

First we look at the server. This defines an Echoer class (derived from `pb.Root`), with a method called `remote_echo()`. `pb.Root` objects (because of their inheritance of `pb.Referenceable`, described later) can define methods with names of the form `remote_*`; a client which obtains a remote reference to that `pb.Root` object will be able to invoke those methods.

The `pb.Root`-ish object is given to a `pb.PBServerFactory``()`. This is a `Factory` object like any other: the `Protocol` objects it creates for new connections know how to speak the PB protocol. The object you give to `pb.PBServerFactory()` becomes the root object, which simply makes it available for the client to retrieve. The client may only request references to the objects you want to provide it: this helps you implement your security model. Because it is so common to export just a single object (and because a `remote_*` method on that one can return a reference to any other object you might want to give out), the simplest example is one where the `PBServerFactory` is given the root object, and the client retrieves it.

The client side uses `pb.PBClientFactory` to make a connection to a given port. This is a two-step process involving opening a TCP connection to a given host and port and requesting the root object using `.getRootObject()`.

Because `.getRootObject()` has to wait until a network connection has been made and exchange some data, it may take a while, so it returns a Deferred, to which the gotObject() callback is attached. (See the documentation on Deferring Execution for a complete explanation of `Deferred`s). If and when the connection succeeds and a reference to the remote root object is obtained, this callback is run. The first argument passed to the callback is a remote reference to the distant root object. (you can give other arguments to the callback too, see the other parameters for `.addCallback()` and `.addCallbacks()`).

The callback does:

    object.callRemote("echo", "hello network")

which causes the server's `.remote_echo()` method to be invoked. (running `.callRemote("boom")` would cause `.remote_boom()` to be run, etc). Again because of the delay involved, `callRemote()` returns a `Deferred`. Assuming the remote method was run without causing an exception (including an attempt to invoke an unknown method), the callback attached to that `Deferred` will be invoked with any objects that were returned by the remote method call.

In this example, the server's `Echoer` object has a method invoked, exactly as if some code on the server side had done:

    echoer_object.remote_echo("hello network")

and from the definition of `remote_echo()` we see that this just returns the same string it was given: hello network.

From the client's point of view, the remote call gets another `Deferred` object instead of that string. `callRemote()`always returns a `Deferred`. This is why PB is described as a system for translucent remote method calls instead of transparent ones: you cannot pretend that the remote object is really local. Trying to do so (as some other RPC mechanisms do, coughCORBAcough) breaks down when faced with the asynchronous nature of the network. Using Deferreds turns out to be a very clean way to deal with the whole thing.

The remote reference object (the one given to `getRootObject()`'s success callback) is an instance the `RemoteReference` class. This means you can use it to invoke methods on the remote object that it refers to. Only instances of `RemoteReference` are eligible for `.callRemote()`. The `RemoteReference` object is the one that lives on the remote side (the client, in this case), not the local side (where the actual object is defined).

In our example, the local object is that `Echoer()` instance, which inherits from `pb.Root`, which inherits from `pb.Referenceable`. It is that `Referenceable` class that makes the object eligible to be available for remote method calls1. If you have an object that is Referenceable, then any client that manages to get a reference to it can invoke any `remote_*` methods they please.

Note:

The only thing they can do is invoke those methods. In particular, they cannot access attributes. From a security point of view, you control what they can do by limiting what the `remote_*` methods can do.

Also note: the other classes like `Referenceable` allow access to other methods, in particular `perspective_*` and `view_*` may be accessed. Don't write local-only methods with these names, because then remote callers will be able to do more than you intended.

Also also note: the other classes like `pb.Copyable`do allow access to attributes, but you control which ones they can see.

You don't have to be a `pb.Root` to be remotely callable, but you do have to be `pb.Referenceable`. (Objects that inherit from `pb.Referenceable` but not from `pb.Root` can be remotely called, but only `pb.Root`-ish objects can be given to the `PBServerFactory`.)

## Complete Example

    from twisted.spread import pb

    class QuoteReader(pb.Root):

        def __init__(self, quoter):
            self.quoter = quoter

        def remote_nextQuote(self):
            return self.quoter.getQuote()

QuoteReader Root object - listings/TwistedQuotes/pbquote.py

For examples of these, we're returning to the TwistedQuotes project discussed in Writing Plugins. To use the examples in this HOWTO, we need to make a TML file to refer to our new set of examples:

    register("Quote of the Day TAP Builder",
             "TwistedQuotes.quotetap2",
             description="""
             Example of a TAP builder module.
             """,
             type="tap",
             tapname="qotd")

Twisted Quotes Plug-in registration - listings/TwistedQuotes/plugins2.tml

The root object for TwistedQuotes is pretty small. The only thing it needs to keep track of for itself is the quoter object.

The QuoteReader publishes one method. By subclassing `Root`, we are declaring that all methods with the `remote_` prefix are remotely accessible.

In order to get this Root published, so that we can actually connect to it, we need to re-visit the TAP building plugin, so we can actually get an Application that has a PBServerFactory listening on a port. (The default port for PB is 8787.)

    from TwistedQuotes import quoteproto    # Protocol and Factory
    from TwistedQuotes import quoters       # "give me a quote" code
    from TwistedQuotes import pbquote       # perspective broker binding

    from twisted.application import service, internet
    from twisted.python import usage        # twisted command-line processing
    from twisted.spread import pb           # Perspective Broker

    class Options(usage.Options):
        optParameters = [["port", "p", 8007,
                          "Port number to listen on for QOTD protocol."],
                         ["static", "s", "An apple a day keeps the doctor away.",
                          "A static quote to display."],
                         ["file", "f", None,
                          "A fortune-format text file to read quotes from."],
                         ["pb", "b", None,
                          "Port to listen with PB server"]]

    def makeService(config):
        svc = service.MultiService()
        if config["file"]:                  # If I was given a "file" option...
            # Read quotes from a file, selecting a random one each time,
            quoter = quoters.FortuneQuoter([config['file']])
        else:                               # otherwise,
            # read a single quote from the command line (or use the default).
            quoter = quoters.StaticQuoter(config['static'])
        port = int(config["port"])          # TCP port to listen on
        factory = quoteproto.QOTDFactory(quoter) # here we create a QOTDFactory
        # Finally, set up our factory, with its custom quoter, to create QOTD
        # protocol instances when events arrive on the specified port.
        pbport = config['pb']               # TCP PB port to listen on
        if pbport:
            pbfact = pb.PBServerFactory(pbquote.QuoteReader(quoter))
            svc.addService(internet.TCPServer(int(pbport), pbfact))
        svc.addService(internet.TCPServer(port, factory))
        return svc

TAP Plugin with PB Quotes support - listings/TwistedQuotes/quotetap2.py

In the TAP builder, all we need to do is create our QuoteReader instance (making sure to pass it our quoter object), give it to a PBServerFactory, and create a TCPServer so that it can listen on a TCP port.

Accessing this through a client is fairly easy, as we use the `pb.PBClientFactory.getRootObject` method.

    from sys import stdout
    from twisted.python import log
    log.discardLogs()
    from twisted.internet import reactor
    from twisted.spread import pb

    def connected(root):
        root.callRemote('nextQuote').addCallbacks(success, failure)

    def success(quote):
        stdout.write(quote + "\n")
        reactor.stop()

    def failure(error):
        stdout.write("Failed to obtain quote.\n")
        reactor.stop()

    factory = pb.PBClientFactory()
    reactor.connectTCP(
        "localhost", # host name
        pb.portno, # port number
        factory, # factory
        )

    factory.getRootObject().addCallbacks(connected, # when we get the root
                                         failure)   # when we can't

    reactor.run() # start the main loop

PB Quotes Client Code - listings/TwistedQuotes/pbquoteclient.py

`pb.PBClientFactory.getRootObject` will handle all the details of waiting for the creation of a connection. It returns a `Deferred`, which will have its callback called when the reactor connects to the remote server and `pb.PBClientFactory` gets the root, and have its `errback` called when the object-connection fails for any reason, whether it was host lookup failure, connection refusal, or some server-side error.

In this example, the `connected` callback should be made when the script is run. Looking at the code, it should be clear that in the event of a connection success, the client will print out a quote and exit. If you start up a server, you can see:

    % mktap qotd --pb 8787
    % twistd -f qotd.tap
    % python -c 'import TwistedQuotes.pbquoteclient'
    An apple a day keeps the doctor away.

The argument to this callback, `root`, is a `RemoteReference`. It represents a reference to the `QuoteReader` object.

`RemoteReference` objects have one method which is their purpose for being: `callRemote`. This method allows you to call a remote method on the object being referred to by the Reference. `RemoteReference.callRemote`, like `pb.PBClientFactory.getRootObject`, returns a `Deferred`. When a response to the method-call being sent arrives, the `Deferred`'s `callback` or `errback` will be made, depending on whether an error occurred in processing the method call.

This introduction to PB does not showcase all of the features that it provides, but hopefully it gives you a good idea of where to get started setting up your own application. Here are some of the other building blocks you can use.

## Passing more references

Here is an example of using `pb.Referenceable` in a second class. The second `Referenceable` object can have remote methods invoked too, just like the first. In this example, the initial root object has a method that returns a reference to the second object.

    #! /usr/bin/python
    from twisted.spread import pb

    class Two(pb.Referenceable):
        def remote_three(self, arg):
            print "Two.three was given", arg

    class One(pb.Root):
        def remote_getTwo(self):
            two = Two()
            print "returning a Two called", two
            return two

    from twisted.internet import reactor
    reactor.listenTCP(8800, pb.PBServerFactory(One()))

Source listing - listings/pb/pb1server.py

    #! /usr/bin/python

    from twisted.spread import pb
    from twisted.internet import reactor

    def main():
        factory = pb.PBClientFactory()
        reactor.connectTCP("localhost", 8800, factory)
        def1 = factory.getRootObject()
        def1.addCallbacks(got_obj1, err_obj1)
        reactor.run()

    def err_obj1(reason):
        print "error getting first object", reason
        reactor.stop()

    def got_obj1(obj1):
        print "got first object:", obj1
        print "asking it to getTwo"
        def2 = obj1.callRemote("getTwo")
        def2.addCallbacks(got_obj2)

    def got_obj2(obj2):
        print "got second object:", obj2
        print "telling it to do three(12)"
        obj2.callRemote("three", 12)

    main()

Source listing - listings/pb/pb1client.py

The root object has a method called `remote_getTwo`, which returns the `Two()` instance. On the client end, the callback gets a `RemoteReference` to that instance. The client can then invoke two's `.remote_three()` method.

You can use this technique to provide access to arbitrary sets of objects. Just remember that any object that might get passed over the wire must inherit from `Referenceable` (or one of the other flavors). If you try to pass a non-Referenceable object (say, by returning one from a `remote_*` method), you'll get an `InsecureJelly` exception2.

## References can come back to you

If your server gives a reference to a client, and then that client gives the reference back to the server, the server will wind up with the same object it gave out originally. The serialization layer watches for returning reference identifiers and turns them into actual objects. You need to stay aware of where the object lives: if it is on your side, you do actual method calls. If it is on the other side, you do `.callRemote()`3.

    #! /usr/bin/python

    from twisted.spread import pb
    from twisted.internet import reactor

    class Two(pb.Referenceable):
        def remote_print(self, arg):
            print "two.print was given", arg

    class One(pb.Root):
        def __init__(self, two):
            #pb.Root.__init__(self)   # pb.Root doesn't implement __init__
            self.two = two
        def remote_getTwo(self):
            print "One.getTwo(), returning my two called", two
            return two
        def remote_checkTwo(self, newtwo):
            print "One.checkTwo(): comparing my two", self.two
            print "One.checkTwo(): against your two", newtwo
            if two == newtwo:
                print "One.checkTwo(): our twos are the same"

    two = Two()
    root_obj = One(two)
    reactor.listenTCP(8800, pb.PBServerFactory(root_obj))
    reactor.run()

Source listing - listings/pb/pb2server.py

    #! /usr/bin/python

    from twisted.spread import pb
    from twisted.internet import reactor

    def main():
        foo = Foo()
        factory = pb.PBClientFactory()
        reactor.connectTCP("localhost", 8800, factory)
        factory.getRootObject().addCallback(foo.step1)
        reactor.run()

    # keeping globals around is starting to get ugly, so we use a simple class
    # instead. Instead of hooking one function to the next, we hook one method
    # to the next.

    class Foo:
        def __init__(self):
            self.oneRef = None

        def step1(self, obj):
            print "got one object:", obj
            self.oneRef = obj
            print "asking it to getTwo"
            self.oneRef.callRemote("getTwo").addCallback(self.step2)

        def step2(self, two):
            print "got two object:", two
            print "giving it back to one"
            print "one is", self.oneRef
            self.oneRef.callRemote("checkTwo", two)

    main()

Source listing - listings/pb/pb2client.py

The server gives a `Two()` instance to the client, who then returns the reference back to the server. The server compares the two given with the two received and shows that they are the same, and that both are real objects instead of remote references.

A few other techniques are demonstrated in `pb2client.py`. One is that the callbacks are are added with `.addCallback` instead of `.addCallbacks`. As you can tell from the Deferred documentation, `.addCallback` is a simplified form which only adds a success callback. The other is that to keep track of state from one callback to the next (the remote reference to the main One() object), we create a simple class, store the reference in an instance thereof, and point the callbacks at a sequence of bound methods. This is a convenient way to encapsulate a state machine. Each response kicks off the next method, and any data that needs to be carried from one state to the next can simply be saved as an attribute of the object.

Remember that the client can give you back any remote reference you've given them. Don't base your zillion-dollar stock-trading clearinghouse server on the idea that you trust the client to give you back the right reference. The security model inherent in PB means that they can only give you back a reference that you've given them for the current connection (not one you've given to someone else instead, nor one you gave them last time before the TCP session went down, nor one you haven't yet given to the client), but just like with URLs and HTTP cookies, the particular reference they give you is entirely under their control.

## References to client-side objects

Anything that's Referenceable can get passed across the wire, in either direction. The client can give a reference to the server, and then the server can use .callRemote() to invoke methods on the client end. This fuzzes the distinction between client and server: the only real difference is who initiates the original TCP connection; after that it's all symmetric.

    #! /usr/bin/python

    from twisted.spread import pb
    from twisted.internet import reactor

    class One(pb.Root):
        def remote_takeTwo(self, two):
            print "received a Two called", two
            print "telling it to print(12)"
            two.callRemote("print", 12)

    reactor.listenTCP(8800, pb.PBServerFactory(One()))
    reactor.run()

Source listing - listings/pb/pb3server.py

    #! /usr/bin/python

    from twisted.spread import pb
    from twisted.internet import reactor

    class Two(pb.Referenceable):
        def remote_print(self, arg):
            print "Two.print() called with", arg

    def main():
        two = Two()
        factory = pb.PBClientFactory()
        reactor.connectTCP("localhost", 8800, factory)
        def1 = factory.getRootObject()
        def1.addCallback(got_obj, two) # hands our 'two' to the callback
        reactor.run()

    def got_obj(obj, two):
        print "got One:", obj
        print "giving it our two"
        obj.callRemote("takeTwo", two)

    main()

Source listing - listings/pb/pb3client.py

In this example, the client gives a reference to its own object to the server. The server then invokes a remote method on the client-side object.

## Raising Remote Exceptions

Everything so far has covered what happens when things go right. What about when they go wrong? The Python Way is to raise an exception of some sort. The Twisted Way is the same.

The only special thing you do is to define your `Exception` subclass by deriving it from `pb.Error`. When any remotely-invokable method (like `remote_*` or `perspective_*`) raises a `pb.Error`-derived exception, a serialized form of that Exception object will be sent back over the wire4. The other side (which did `callRemote`) will have the `errback` callback run with a `Failure` object that contains a copy of the exception object. This `Failure` object can be queried to retrieve the error message and a stack traceback.

`Failure` is a special class, defined in `twisted/python/failure.py`, created to make it easier to handle asynchronous exceptions. Just as exception handlers can be nested, `errback` functions can be chained. If one errback can't handle the particular type of failure, it can be passed along to a errback handler further down the chain.

For simple purposes, think of the `Failure` as just a container for remotely-thrown `Exception` objects. To extract the string that was put into the exception, use its `.getErrorMessage()` method. To get the type of the exception (as a string), look at its `.type` attribute. The stack traceback is available too. The intent is to let the errback function get just as much information about the exception as Python's normal `try:` clauses do, even though the exception occurred in somebody else's memory space at some unknown time in the past.

    #! /usr/bin/python

    from twisted.spread import pb
    from twisted.internet import reactor

    class MyError(pb.Error):
        """This is an Expected Exception. Something bad happened."""
        pass

    class MyError2(Exception):
        """This is an Unexpected Exception. Something really bad happened."""
        pass

    class One(pb.Root):
        def remote_broken(self):
            msg = "fall down go boom"
            print "raising a MyError exception with data '%s'" % msg
            raise MyError(msg)
        def remote_broken2(self):
            msg = "hadda owie"
            print "raising a MyError2 exception with data '%s'" % msg
            raise MyError2(msg)

    def main():
        reactor.listenTCP(8800, pb.PBServerFactory(One()))
        reactor.run()

    if __name__ == '__main__':
        main()

Source listing - listings/pb/exc_server.py

    #! /usr/bin/python

    from twisted.spread import pb
    from twisted.internet import reactor

    def main():
        factory = pb.PBClientFactory()
        reactor.connectTCP("localhost", 8800, factory)
        d = factory.getRootObject()
        d.addCallbacks(got_obj)
        reactor.run()

    def got_obj(obj):
        # change "broken" into "broken2" to demonstrate an unhandled exception
        d2 = obj.callRemote("broken")
        d2.addCallback(working)
        d2.addErrback(broken)

    def working():
        print "erm, it wasn't *supposed* to work.."

    def broken(reason):
        print "got remote Exception"
        # reason should be a Failure (or subclass) holding the MyError exception
        print " .__class__ =", reason.__class__
        print " .getErrorMessage() =", reason.getErrorMessage()
        print " .type =", reason.type
        reactor.stop()

    main()

Source listing - listings/pb/exc_client.py

    % ./exc_client.py
    got remote Exception
     .__class__ = twisted.spread.pb.CopiedFailure
     .getErrorMessage() = fall down go boom
     .type = __main__.MyError
    Main loop terminated.

Oh, and what happens if you raise some other kind of exception? Something that isn't subclassed from `pb.Error`? Well, those are called unexpected exceptions, which make Twisted think that something has really gone wrong. These will raise an exception on the server side. This won't break the connection (the exception is trapped, just like most exceptions that occur in response to network traffic), but it will print out an unsightly stack trace on the server's stderr with a message that says Peer Will Receive PB Traceback, just as if the exception had happened outside a remotely-invokable method. (This message will go the current log target, if `log.startLogging` was used to redirect it). The client will get the same `Failure` object in either case, but subclassing your exception from `pb.Error` is the way to tell Twisted that you expect this sort of exception, and that it is ok to just let the client handle it instead of also asking the server to complain. Look at `exc_client.py` and change it to invoke `broken2()` instead of `broken()` to see the change in the server's behavior.

If you don't add an `errback` function to the `Deferred`, then a remote exception will still send a `Failure` object back over, but it will get lodged in the `Deferred` with nowhere to go. When that `Deferred` finally goes out of scope, the side that did `callRemote` will emit a message about an Unhandled error in Deferred, along with an ugly stack trace. It can't raise an exception at that point (after all, the `callRemote` that triggered the problem is long gone), but it will emit a traceback. So be a good programmer and always add `errback` handlers, even if they are just calls to `log.err`.

## Try/Except blocks and `Failure.trap`

To implement the equivalent of the Python try/except blocks (which can trap particular kinds of exceptions and pass others up to higher-level `try/except` blocks), you can use the `.trap()` method in conjunction with multiple `errback` handlers on the `Deferred`. Re-raising an exception in an `errback` handler serves to pass that new exception to the next handler in the chain. The `trap` method is given a list of exceptions to look for, and will re-raise anything that isn't on the list. Instead of passing unhandled exceptions up to an enclosing `try` block, this has the effect of passing the exception off to later `errback` handlers on the same `Deferred`. The `trap` calls are used in chained errbacks to test for each kind of exception in sequence.

    #! /usr/bin/python

    from twisted.internet import reactor
    from twisted.spread import pb

    class MyException(pb.Error):
        pass

    class One(pb.Root):
        def remote_fooMethod(self, arg):
            if arg == "panic!":
                raise MyException
            return "response"
        def remote_shutdown(self):
            reactor.stop()

    reactor.listenTCP(8800, pb.PBServerFactory(One()))
    reactor.run()

Source listing - listings/pb/trap_server.py

    #! /usr/bin/python

    from twisted.spread import pb, jelly
    from twisted.python import log
    from twisted.internet import reactor

    class MyException(pb.Error): pass
    class MyOtherException(pb.Error): pass

    class ScaryObject:
        # not safe for serialization
        pass

    def worksLike(obj):
        # the callback/errback sequence in class One works just like an
        # asynchronous version of the following:
        try:
            response = obj.callMethod(name, arg)
        except pb.DeadReferenceError:
            print " stale reference: the client disconnected or crashed"
        except jelly.InsecureJelly:
            print " InsecureJelly: you tried to send something unsafe to them"
        except (MyException, MyOtherException):
            print " remote raised a MyException" # or MyOtherException
        except:
            print " something else happened"
        else:
            print " method successful, response:", response

    class One:
        def worked(self, response):
            print " method successful, response:", response
        def check_InsecureJelly(self, failure):
            failure.trap(jelly.InsecureJelly)
            print " InsecureJelly: you tried to send something unsafe to them"
            return None
        def check_MyException(self, failure):
            which = failure.trap(MyException, MyOtherException)
            if which == MyException:
                print " remote raised a MyException"
            else:
                print " remote raised a MyOtherException"
            return None
        def catch_everythingElse(self, failure):
            print " something else happened"
            log.err(failure)
            return None

        def doCall(self, explanation, arg):
            print explanation
            try:
                deferred = self.remote.callRemote("fooMethod", arg)
                deferred.addCallback(self.worked)
                deferred.addErrback(self.check_InsecureJelly)
                deferred.addErrback(self.check_MyException)
                deferred.addErrback(self.catch_everythingElse)
            except pb.DeadReferenceError:
                print " stale reference: the client disconnected or crashed"

        def callOne(self):
            self.doCall("callOne: call with safe object", "safe string")
        def callTwo(self):
            self.doCall("callTwo: call with dangerous object", ScaryObject())
        def callThree(self):
            self.doCall("callThree: call that raises remote exception", "panic!")
        def callShutdown(self):
            print "telling them to shut down"
            self.remote.callRemote("shutdown")
        def callFour(self):
            self.doCall("callFour: call on stale reference", "dummy")

        def got_obj(self, obj):
            self.remote = obj
            reactor.callLater(1, self.callOne)
            reactor.callLater(2, self.callTwo)
            reactor.callLater(3, self.callThree)
            reactor.callLater(4, self.callShutdown)
            reactor.callLater(5, self.callFour)
            reactor.callLater(6, reactor.stop)

    factory = pb.PBClientFactory()
    reactor.connectTCP("localhost", 8800, factory)
    deferred = factory.getRootObject()
    deferred.addCallback(One().got_obj)
    reactor.run()

Source listing - listings/pb/trap_client.py

    % ./trap_client.py
    callOne: call with safe object
     method successful, response: response
    callTwo: call with dangerous object
     InsecureJelly: you tried to send something unsafe to them
    callThree: call that raises remote exception
     remote raised a MyException
    telling them to shut down
    callFour: call on stale reference
     stale reference: the client disconnected or crashed
    %

In this example, `callTwo` tries to send an instance of a locally-defined class through `callRemote`. The default security model implemented by `pb.Jelly` on the remote end will not allow unknown classes to be unserialized (i.e. taken off the wire as a stream of bytes and turned back into an object: a living, breathing instance of some class): one reason is that it does not know which local class ought to be used to create an instance that corresponds to the remote object5. The receiving end of the connection gets to decide what to accept and what to reject. It indicates its disapproval by raising a `pb.InsecureJelly` exception. Because it occurs at the remote end, the exception is returned to the caller asynchronously, so an `errback` handler for the associated `Deferred` is run. That errback receives a `Failure` which wraps the `InsecureJelly`.

Remember that `trap` re-raises exceptions that it wasn't asked to look for. You can only check for one set of exceptions per errback handler: all others must be checked in a subsequent handler. `check_MyException` shows how multiple kinds of exceptions can be checked in a single errback: give a list of exception types to `trap`, and it will return the matching member. In this case, the kinds of exceptions we are checking for (`MyException` and `MyOtherException`) may be raised by the remote end: they inherit from `pb.Error`.

The handler can return `None` to terminate processing of the errback chain (to be precise, it switches to the callback that follows the errback; if there is no callback then processing terminates). It is a good idea to put an errback that will catch everything (no `trap` tests, no possible chance of raising more exceptions, always returns `None`) at the end of the chain. Just as with regular `try: except:` handlers, you need to think carefully about ways in which your errback handlers could themselves raise exceptions. The extra importance in an asynchronous environment is that an exception that falls off the end of the `Deferred` will not be signalled until that `Deferred` goes out of scope, and at that point may only cause a log message (which could even be thrown away if `log.startLogging` is not used to point it at stdout or a log file). In contrast, a synchronous exception that is not handled by any other `except:` block will very visibly terminate the program immediately with a noisy stack trace.

`callFour` shows another kind of exception that can occur while using `callRemote`: `pb.DeadReferenceError`. This one occurs when the remote end has disconnected or crashed, leaving the local side with a stale reference. This kind of exception happens to be reported right away (XXX: is this guaranteed? probably not), so must be caught in a traditional synchronous `try: except pb.DeadReferenceError` block.

Yet another kind that can occur is a `pb.PBConnectionLost` exception. This occurs (asynchronously) if the connection was lost while you were waiting for a `callRemote` call to complete. When the line goes dead, all pending requests are terminated with this exception. Note that you have no way of knowing whether the request made it to the other end or not, nor how far along in processing it they had managed before the connection was lost. XXX: explain transaction semantics, find a decent reference.

## Footnotes

1. There are a few other classes that can bestow this ability, but pb.Referenceable is the easiest to understand; see 'flavors' below for details on the others.
2. This can be overridden, by subclassing one of the Serializable flavors and defining custom serialization code for your class. See Passing Complex Types for details.
3. The binary nature of this local vs. remote scheme works because you cannot give RemoteReferences to a third party. If you could, then your object A could go to B, B could give it to C, C might give it back to you, and you would be hard pressed to tell if the object lived in C's memory space, in B's, or if it was really your own object, tarnished and sullied after being handed down like a really ugly picture that your great aunt owned and which nobody wants but which nobody can bear to throw out. Ok, not really like that, but you get the idea.
4. To be precise, the Failure will be sent if any exception is raised, not just pb.Error-derived ones. But the server will print ugly error messages if you raise ones that aren't derived from pb.Error.

5. The naive approach of simply doing `import SomeClass` to match a remote caller who claims to have an object of type SomeClass could have nasty consequences for some modules that do significant operations in their `__init__` methods (think `telnetlib.Telnet(host='localhost', port='chargen')`, or even more powerful classes that you have available in your server program). Allowing a remote entity to create arbitrary classes in your namespace is nearly equivalent to allowing them to run arbitrary code.

   The `pb.InsecureJelly` exception arises because the class being sent over the wire has not been registered with the serialization layer (known as `jelly`). The easiest way to make it possible to copy entire class instances over the wire is to have them inherit from `pb.Copyable`, and then to use `setUnjellyableForClass(remoteClass, localClass)` on the receiving side. See Passing Complex Types for an example.

Index

Version: 2.0.1