3

I have a Pyro4 distributed system with multiple clients connecting to a single server. These clients connect to a remote object, and that object may allocate some resources in the system (virtual devices, in my case).

Once a client disconnects (let's say because of a crash), I need to release those resources. What is the proper way to detect that an specific client has disconnected from an specific object?

I've tried different things:

  • Overriding the Daemon.clientDisconnected method. I get a connection parameter from this method. But I can't correlate that to an object, because I have no access to which remote object that connection refers to.
  • Using Pyro4.current_context in Daemon.clientDisconnected. This doesn't work because that is a thread-local object. That in place, if I have more clients connected than threads in my pool, I get repeated contexts.
  • Using Proxy._pyroAnnotations as in the "usersession" example available by the Pyro4 project, doesn't help me, because again, I get the annotation from the Pyro4.core.current_context.annotations attribute, which shows me wrong annotations when Daemon.clientDisconnected is called (I imagine due to a thread related issues).
  • Using instance_mode="session" and the __del__ method in the remote class (as each client would have a separate instance of the class, so the instance is supposed to be destroyed once the client disconnects). But this relies on the __del__ method, which has some problems as some Python programmers would point out.

I added my current solution as an answer, but I really would like to know if there's a more elegant way of doing this with Pyro4, as this scenario is a recurrent pattern in network programming.

Gustavo Meira
  • 2,875
  • 3
  • 21
  • 33
  • about ``Daemon.clientDisconnected``: a client can access more than a single object over its connection. So it is not really possible to correlate anything to a single object here - the client could have accessed multiple. – Irmen de Jong Sep 23 '17 at 15:28

2 Answers2

2

Pyro 4.63 will probably have some built-in support for this to make it easier to do. You can read about it here http://pyro4.readthedocs.io/en/latest/tipstricks.html#automatically-freeing-resources-when-client-connection-gets-closed and try it out if you clone the current master from Github. Maybe you can take a look and see if that would make your use case simpler?

Irmen de Jong
  • 2,739
  • 1
  • 14
  • 26
  • This sounds like the perfect solution. As soon as get some results I'll accept this answer. Thanks a lot. – Gustavo Meira Sep 29 '17 at 07:42
  • Glad to hear. Please let me know if the results are working for you so I know that this solution is worthwhile to include in this form in the next Pyro release. – Irmen de Jong Sep 29 '17 at 21:13
  • I made some tests, Irmen. But I ended up finding some issues. Not sure if I misinterpreted the docs or if it's really something in the lib. I posted the test scenario in your Github issue tracker. – Gustavo Meira Oct 13 '17 at 10:05
  • I ran the tests in my own system. Seems the perfect solution. Just pay attention to the last notes in the doc section. Thanks @Irmen. – Gustavo Meira Oct 18 '17 at 07:41
  • 1
    Just a small reminder that Pyro 4.63 has been released now, with this new feature. – Irmen de Jong Nov 01 '17 at 20:57
1

I use the Proxy._pyroHandshake attribute as a client ID in the client side and override the Daemon.validateHandshake and Daemon.clientDisconnected. This way, on every new connection I map the handshake data (unique per client) to a connection. But I really wanted to know if there's an elegant way to do that in Pyro4, which is pattern that happens very often in network programming.

Notice that instead of using the Proxy as an attribute of Client, Client can also extends Pyro4.Proxy and use _pyroAnnotations to send the client ID to all the remote calls.

class Client:

    def __init__(self):

        self._client_id = uuid.uuid4()
        self._proxy = Pyro4.Proxy("PYRO:server@127.0.0.1")
        self._proxy._pyroHandshake = self._client_id
        self._proxy._pyroBind()

    def allocate_resource(self, resource_name):
        self._proxy.allocate_resource(self._client_id, resource_name)


class Server:

    def __init__(self):

        self._client_id_by_connection = {}
        self._resources_by_client_id = {}

    def client_connected(self, connection, client_id):

        self._client_id_by_connection[client_id] = connection
        self._resources_by_client_id[client_id] = []

    def client_disconnected(self, connection):

        client_id = self._client_id_by_connection[connection]

        for resource in self._resources_by_client_id[client_id]
            resource.free()

    @Pyro4.expose
    def allocate_resource(self, client_id, resource_name)

        new_resource = Resource(resource_name)
        self._resources_by_client_id[client_id].append(new_resource)

server = Server()
daemon.register(server, objectId="server")
daemon.clientDisconnect = server.client_disconnected
daemon.validateHandshake = server.client_connected
daemon.requestLoop()
Gustavo Meira
  • 2,875
  • 3
  • 21
  • 33