tomerfiliba-org / rpyc

RPyC (Remote Python Call) - A transparent and symmetric RPC library for python
http://rpyc.readthedocs.org
Other
1.57k stars 243 forks source link

ThreadedServer Signal Handling: Shutting down a server by a remote call accept by service? #555

Open glyh opened 6 months ago

glyh commented 6 months ago

I am aware this is probably not the right place to ask but I can't find any other places to talk about rpyc.

So basically I want some rpc to shutdown the service.

class MyService(rpyc.Service):
    def exposed_shutdown(self):
        # what to do?

def run():
  ThreadedServer(MyService).start()

I have tried:

def exposed_shutdown(self):
    exit(0)
def exposed_shutdown(self):
    # https://rpyc.readthedocs.io/en/latest/_modules/rpyc/utils/server.html#Server.start
    raise EOFError
class SigShutdown(Exception):
    pass

class MyService(rpyc.Service):
    def exposed_shutdown(self):
        raise SigShutdown

def run():
    try:
        ThreadedServer(MyService).start()
    except SigShutdown:
        pass

None of the above works.

I am aware there's close(), but I have no way to pass that function into the Service as to get this function I need to construct a Server first which depends on the Service.

glyh commented 6 months ago

Looks like I need to patch _accept_method, and that is fairly complicated to do, I guess I'll probably stick to kill to just kill the process.

comrumino commented 6 months ago

The reason the above method does not work is that the signal is being absorbed by the thread running the connection to your client (my best guess without testing/breakpoints/debugging myself). Each client will result in their own thread spawn when using ThreadedServer. From a system administration standpoint, it would make more sense to terminate the process using whatever starts your server — if you are using something like systemd it would potentially restart the service if you try doing it this way.

If you want a pure RPyC solution, I would say you would probably want to inherit the threaded server and extend it to do one of the following:

  1. intercept the shutdown request
  2. add support for propagating signals from your thread
  3. Try calling os.exit or sys.exit, but this is dirty and wouldn't exit cleanly.

As for where to ask questions, here is okay. I respond on best effort.

glyh commented 6 months ago

I'll take a look once I have time. In my case the server is a forked subprocess of the client, and the Cilent is a textual TUI app.

comrumino commented 6 months ago

@glyh , iirc, when I did this for an electron desktop in the past, rather than forking the process, try keep the the server as a child process and sending the signal from the client/TUI-app. Of course, there needs to be signal handling for the RPyC server process — if I have time, I will check the docs for an example or try to write one this weekend.

From what I remember, RPyC does not document how to do this very well. There is certainly room for enhancement of RPyC regarding signal handling and related documentation (i.e., the ThreadedServer has many "gotchas" for signal handling).