irmen / Pyro4

Pyro 4.x - Python remote objects
http://pyro4.readthedocs.io/
MIT License
715 stars 83 forks source link

Server shutdown timeout #219

Closed Evidlo closed 5 years ago

Evidlo commented 5 years ago

I'd like my server to shut down after N seconds of no client activity. Is there a way to do this?

I'm currently using serveSimple, but I can switch to to the Daemon class if necessary.

My application is a CLI program for accessing an encrypted database. Database decryption is slow, so I fork a server process to keep the database open and communicate with it through a unix socket. The server should shut down 5 minutes after no contact from the client.

irmen commented 5 years ago

There's no reliable way to do this when using the thread pool server type, but when you switch to using the multiplex server you could do this by overriding some methods of the Daemon. For example:

from Pyro4.core import Daemon
from Pyro4.core import expose

class MyDaemon(Daemon):
    def __init__(self):
        super().__init__()
        self.was_active = False

    def clientDisconnect(self, conn):
        self.was_active = True

    def housekeeping(self):
        if self.was_active and len(self.sockets) == 1:
            raise SystemExit("stopped due to no more active clients")

class ServerObject:
    @expose
    def echo(self, message):
        print("got message:", message)
        return "hello"

d = MyDaemon()
uri = d.register(ServerObject, "theserver")
print(uri)
d.requestLoop()
Evidlo commented 5 years ago

Thanks. Here is my final solution:

from Pyro4.core import Daemon
from Pyro4.core import expose
from datetime import datetime

class TimeoutDaemon(Daemon):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.idle = True
        self.last_active = datetime.now()

    def clientDisconnect(self, conn):
        # if the last client disconnected
        if len(self.sockets) == 1:
            self.idle = True
            self.last_active = datetime.now()

    def _handshake(self, conn, denied_reason=None):
        # disable timeout while client connected
        self.idle = False
        return super()._handshake(conn, denied_reason=denied_reason)

    def housekeeping(self):
        # shutdown if idle and idle timeout exceeded
        idle_time = (datetime.now() - self.last_active).seconds
        if self.idle and idle_time > 10:
            self.shutdown()

class ServerObject:
    @expose
    def echo(self, message):
        return "hello"

d = TimeoutDaemon(host='localhost', port=8000)
uri = d.register(ServerObject, "theserver")
d.requestLoop()

For others reading this, the client isn't considered connected until echo is executed.