Search code examples
pythonnode.jszeromqgreenlets

Bidirectional ZeroRPC in Python client invoke causes AssertionError


My setup has a Node.js child spawned which creates a 2-way ZeroRPC pair of sessions with a Python object.

The python side is similar to this:

class MyClass:
    def __init__(self, socketpath):
        self.client = zerorpc.Client()
        self.client.connect(socketpath)

    def sendtoclient(self, msg):
        self.client.receiveMessage(msg)

if __name__ == '__main__':
    zpc = zerorpc.Server(MyClass(sys.argv[1]))
    zpc.bind(sys.argv[1] + "_python")
    zpc.run()

The Node.js child client can invoke methods on the Python server, but the client within that server cannot invoke on the Node.js child server without getting an exception:

Traceback (most recent call last):
File "/usr/lib64/python2.6/site-packages/gevent/queue.py", line 271, in _unlock getter.switch(getter)

File "/usr/lib64/python2.6/site-packages/gevent/hub.py", line 534, in switch assert getcurrent() is self.hub, "Can only use Waiter.switch method from the Hub greenlet"

AssertionError: Can only use Waiter.switch method from the Hub greenlet
<callback at 0x3055e90 args=()> failed with AssertionError

Does the client in the Python class need to be spawned as a gevent and then its receiveMessage method invoked when needed? Or is there some other trick I'm overlooking?


Solution

  • After some experimenting, a workable solution came from some examples in Gevent's documentation. The solution I went with is to create a gevent Queue that is populated from the server side and output from a separate Greenlet in a loop. So in the file with my server, I added:

    import gevent
    from gevent.queue import Queue, Empty
    
    messagesOut = Queue()
    def clientWorker(address):
        client = zerorpc.Client()
        if (None != client):
            client.connect(address)
            while True:
                try:
                    messages = messagesOut.get()
                    client.passMessages(messages) # a method on the Node.js-side ZeroRPC server
                except: # Empty could be thrown here if you're interested
                    pass
                finally:
                    gevent.sleep(0)
    

    In MyClass, its initialization changed to retain a reference to the queue as self.outbox (indeed, I could have used global each time I access it). When an async message needs to be sent, MyClass calls self.outbox.put(messages).

    Then down below when the ZeroRPC instances are created, I spawned each:

    if __name__ == '__main__':
        ge_client = gevent.spawn(clientWorker, sys.argv[1] + "_node2python")
        zpc = zerorpc.Server(messagesOut)
        zpc.bind(sys.argv[1] + "_python2node")
        ge_server = gevent.spawn(zpc.run)
        gevent.joinall([ge_client, ge_server]) # up and running here.
    

    Doing the above resolved my problems as a proof-of-concept.