Search code examples
javascriptpythonnode.jszeromqgreenlets

ZeroRPC python server exceptions when attempting to stop or close


I have quite a bit more code than this so I'm trimming it down to just what seems to be relevant. Per the documented example, I have a python class for ZeroRPC to use:

import zerorpc, sys, signal

class MyClass:
    pass

zpc = 0
if __name == '__main__':
    zpc = zerorpc.Server(MyClass)
    zpc.bind('ipc://./mysocket.sock')
    zpc.run()
    print("zpc stopped"); sys.stdout.flush()

The python script is spawned as a ChildProcess from my Node.js server which listens to stdout and stderr. When the client connection times out, or the server shuts down, I call kill() on the ChildProcess which sends SIGTERM to it.

With just the code above, the 'zpc stopped' never gets captured at the Node.js callback which indicates to me that the ZeroRPC server gets killed somewhere in its run-loop. Also, the socket file still exists indicating the server doesn't close the socket, either. So I figured I would call stop() or close() on the server after capturing SIGTERM:

def sig_handle (signal, frame):
    global zpc
    print("SIGTERM received.") # <-- this does occur
    zpc.stop() # <-- Exception thrown here and at run()
    sys.exit(0)

signal.signal(signal.SIGTERM, sig_handle)

The exceptions are picked up by Node.js through its stderr callback:

Gateway Error:   File "/usr/lib/python2.6/site-packages/zerorpc/core.py", line 178, in stop

Gateway Error:     self._acceptor_task.kill()
  File "/usr/lib64/python2.6/site-packages/gevent/greenlet.py", line 235, in kill

Gateway Error:     waiter.get()
  File "/usr/lib64/python2.6/site-packages/gevent/hub.py", line 568, in get

Gateway Error:     return self.hub.switch()
  File "/usr/lib64/python2.6/site-packages/gevent/hub.py", line 330, in switch
    switch_out()
  File "/usr/lib64/python2.6/site-packages/gevent/hub.py", line 334, in switch_out
    raise AssertionError('Impossible to call blocking function in the event loop callback')
AssertionError: Impossible to call blocking function in the event loop callback

Gateway Error: Traceback (most recent call last):
  File "gateway.py", line 111, in <module>
    zpc.run() 

Gateway Error:   File "/usr/lib/python2.6/site-packages/zerorpc/core.py", line 171, in run
    self._acceptor_task.get()
  File "/usr/lib64/python2.6/site-packages/gevent/greenlet.py", line 258, in get

Gateway Error:     result = self.parent.switch()
  File "/usr/lib64/python2.6/site-packages/gevent/hub.py", line 331, in switch

Gateway Error:     return greenlet.switch(self)
AssertionError: Impossible to call blocking function in the event loop callback

Changing stop() to close() results in the same ultimate set of exceptions. Implementing the same idea in Javascript (Node.js), close() cleans up a running server (and its socket file in the directory) without throwing any exceptions or warnings.

This all leaves me with the question: how does one cleanly stop a ZeroRPC server in Python if not by stop() or close()?


Solution

  • Use gevent.signal() to set your signal handler instead of signal.signal().

    This is because the standard module signal directly maps the UNIX API, which runs the signal handler outside of the gevent eventloop, without any concept of gevent greenlet/coroutine. gevent.signal is integrated with the gevent ioloop, and make sure to execute your signal handler in its own greenlet, in the eventloop.

    Gevent compatible solution:

    import zerorpc, sys, gevent, signal, os
    
    class MyClass:
          pass
    
    if __name__ == '__main__':
      zpc = zerorpc.Server(MyClass)
      zpc.bind('ipc://./mysocket.sock')
      gevent.signal(signal.SIGTERM, zpc.stop)
      zpc.run()
      print("zpc stopped"); sys.stdout.flush()