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()?
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()