<previous | top | next> Pyro Manual

7. Features and Guidelines

This chapter discusses some of the more technical features of Pyro, and some guidelines you must follow when developing with Pyro.

Automatic rebinding

Since version 2.0 Pyro has an "auto rebind" feature. This means that - with a little help of yourself - your clients can recover from network errors that kill the connection with the server.

More specifically, when your client detects a problem with the network (SocketClosedError) it can use a special 'hidden' method of the internal Pyro protocol adapter object to request it to reconnect to the server:

yourpyroobject.adapter.rebindURI()
You can supply two arguments if desired, tries and wait. The first is the number of retries that is performed (default: sys.maxint), the second is the delay time between each retry in seconds (default: 1).
NOTES:
  1. If your server crashes, and you want to restart it and let the clients reconnect to it, the server has to be prepared for this feature. It must not rely on any transient internal state to function correctly, because that state is lost when your server is restarted.
  2. Your server must register the objects with the new connectPersistent method of the PyroDaemon. Otherwise the reconnect feature does not work because the client tries to access an invalid URI.
  3. The NS does not have to be the persistent version on disk, as long as it keeps running. All that is needed is that the URI of the objects concerned stay available in the NS, so the server can reuse those URIs when it gets back up.
  4. The client is responsible for detecting a network problem itself. It must call the 'hidden' rebindURI() method of the object's protocol adapter itself if this is the case.
  5. The method call that triggered the auto rebind is likely lost, i.e. it almost certainly has not been executed. You have to call it again explicitly if you want to be sure that it has been executed. However, it might be the case that the server received the call and that the connection was lost after the server executed the call but before returning the result. Calling the method again will result in two executions on the server. This is all usual transaction semantics stuff.
Why isn't this transparent???? Because you have to have control about it when a network problem occurs. Furthermore, only you can decide if your system needs this feature, and if your system can support this feature (see points 1 and 2 above).

Examine the example code provided in the "autoreconnect" example, and Pyro's internal adapter code, to see how the rebind feature works.

Mobile code

Pyro supports the concept of mobile code (albeit with a few limitations). What does this mean? Imagine a Pyro object that accepts other objects as arguments in its methods. It will invoke various methods on these objects as they were passed in from the client. The client can pass any object as an argument to a remote method call. What happens on the server is that as soon as the method call arrives, Pyro needs the Python module that contains the code for the objects that were passed in. If this module is not available, you'd normally get an ImportError. However, Pyro intercepts this and returns a NoModuleError.

Unless you enable PYRO_MOBILE_CODE. Now, Pyro internally returns a special code, which makes the client submit the missing Python code to the server. Pyro then proceeds as if nothing was wrong, i.e. it can now load the previously missing module and call those objects! This happens automatically, you only have to enable mobile code on the server by setting the PYRO_MOBILE_CODE config item to 1!

There is one more thing: loading and running arbitrary code is dangerous. A client might supply a hazardous or buggy program. Therefore you can set a codeValidator for each Pyro object that might load mobile code.

This codeValidator is a function (or callable object) that takes three arguments: the name of the module, the code itself, and the address of the client (usually a (IP,port) tuple). It should return 0 or 1, for 'deny' and 'accept'. Pyro.core.ObjBase, the base class of all Pyro objects, has a setCodeValidator(v) method that you must call with your custom validator function (or callable object). You can set a different validator for each Pyro object that your server has.

LIMITATION: currently it is not possible to use mobile objects from a module in a package or subpackage. You can only use modules that can be imported directly ("import ModuleName").

Have a look at the "agent2" example to see how this all works.

Connection validators

Since version 2.0 Pyro uses so-called new connection validators. These are objects that are called to check whether a Pyro server may or may not accept a new connection that a client tries to make.

In the past, there was only one built-in check; the number of connections was limited to a certain amount. Since version Pyro 2.0 this check is now done by the default connection validator Pyro.protocol.limitingConnValidator. The fun is that you can supply your own validator object, and that you can therefore implement much more complex access checks. For instance, you might want to check if the client's site is authorized to connect. Or perhaps you require a password to connect.

How to implement a custom validator

Look at the "denyhosts" example to see how this may be used.

Multithreading in Pyro servers

Pyro 1.4 introduced preliminary multithreading support in Pyro servers. This means that you can now have a Pyro server that processes multiple remote object invocations in parallel. This is useful in cases where a single invocation takes a long time to complete. Without multithreading, the next invocation has to wait before your server is finished with the current one. With multithreading, each invocation runs in its own thread, and new invocations can be started while the others are still in progress.
More precisely: just before the remote method invocation is done, a new thread is started in which the method call is performed. The processing is done in this thread, so the server can continue receiving new incoming messages.

Of course, a little overhead is introduced. You can see this quite clearly when you are running the "benchmark" example in single- and multithreaded mode. The singlethreaded mode is quite a bit faster. Still, Pyro will default to the multithreaded mode if your system supports it, because usually you'll need Pyro to be able to accept new invocations while others are still in progress. If you want high performance, use the PYRO_MULTITHREADED config item to switch to singlethreaded mode (set it to zero). Pyro will default to single threaded mode if Python threads are not available. Switching to multithreaded mode if Python threads are not available, by setting PYRO_MULTITHREADED to 1, will crash Pyro. Please use Pyro.util.supports_multithreading() to test whether or not your system is able to use multithreading.

So when to use multithreading or not?

There are four situations where you don't want multithreading: All other cases will likely benefit from a multithreaded server. Do some tests to find out what suits your needs better in your specific situation!

The "multithread" example shows what's going on, and how multithreading can help to improve performance and response times.

DNS, IP addresses and firewalls

There are some issues to be aware of, depending on your network configuration.

Usually Pyro will locate its servers and objects using fixed IP numbers encoded in the Pyro URIs. This may or may not be appropriate. For instance, when a machine has an IP number that is only temporary, such as DHCP-leased IP numbers. The machine can get a new -different- IP number while Pyro is still running. URIs with the old IP number are now invalid! Therefore it is possible to tell Pyro to use symbolic DNS hostnames in URIs instead of raw IP numbers. Pyro will then use DNS to look up the actual IP number of the specified host (by name). You can enable this by setting the PYRO_DNS_URI config item to 1. However note that each time Pyro has to look up a host, there is a DNS lookup delay.

Another issue is when you're using Pyro behind a firewall. There is one specific form of firewalling that is addressed in Pyro: simple Network Address Translating firewalls using port forwarding. Let's say you're at 192.168.0.1 (a private address) behind a NAT gateway that's 192.1.1.1. You have port forwarding on the NAT, so that Pyro requests go to the private box. However, with the way that Pyro is set up by default, the daemon will publish URI's as though they come from 192.168.0.1 -- an address unavailable to the rest of the Net. There is no way to have 192.168.0.1 publish URI's as though it were actually 192.1.1.1. Were it not for the extra publishhost parameter for the constructor of Pyro.core.Daemon. When constructing a Pyro Daemon, you can give it a special hostname or IP address that it should use when publishing URIs, via the publishhost parameter. The host parameter still is the "real" hostname of the machine the daemon is running on. When publishhost is not specified (it isn't by default) the value for host is taken. If that isn't specified either (it isn't by default) the hostname of the machine is queried and that is used. In our little example, host should be 192.168.0.1 (or just empty/omitted) and publishhost should be 192.1.1.1, the address of the firewall/gateway.

Usage rules and guidelines

You should follow the guidelines below when using Pyro.
  1. The remote class can't have a remote __init__ method. You should use a regular initialization method that you must call explicitly after binding to the remote object. The __init__ method will only be called on the server side when the object is created.
  2. You have to choose explicitly for the special proxy that will allow direct attribute access, because it is slower than the regular proxy. Direct attribute access will not work with the regular proxy.
  3. The proxy compiler can't yet generate proxies for the classes in a module if it's passed on the command line as a member of a Python package ('Package.module').
  4. You have to use the static proxy for the Name Server, provided in Pyro.naming.NameServerProxy. When you use the Locator, you're safe.
  5. All Python .py source files that contain the code of the objects that are used as parameters in remote method calls, must be available on the client and on the server. Otherwise the server cannot load the implementation code of an object that arrives in a remote method call. Since Pyro 2.0 this is no longer necessary if you enable the mobile code feature. See the "agent2" example.
  6. The class that is actually instantiated in the server should inherit from Pyro.core.ObjBase. You could define a new (probably empty) class in the server that inherits both from Pyro.core.ObjBase and the class that is made remotely available. This approach is used in the example in the next chapter. See also the next item.
  7. You could also use the Delegation pattern instead of subclassing from Pyro.core.ObjBase. This works as follows: create an object of your remote class, create a Pyro.core.ObjBase object, and tell the latter to use the former as delegate:
    ...
    impl = MyRemoteClass()
    obj = Pyro.core.ObjBase()
    obj.delegateTo(impl)
    ...
    
    and you then connect obj to the Daemon.
  8. Note that, because Python doesn't do this automatically, you have to call the __init__ from the base class in the __init__ method -if you have one- of your remote class:
    def __init__(self):
        Pyro.core.ObjBase.__init__(self)
        ...
  9. Funny and unexpected things happen if you try to use a Pyro proxy object in various statements such as while obj:. Like the limitation with direct member access, this is also caused by the way the proxy object is currently implemented. It intercepts each and every method call. And testing for zero-ness or nonzero-ness or coercion are also method calls! (__nonzero__, __coerce__ etc.)
  10. If you create new Pyro objects on the server, and you want them to be accessed from the client, make sure you: