aiozmq icon indicating copy to clipboard operation
aiozmq copied to clipboard

Can no longer close server via RPC with loopless design

Open aknuds1 opened this issue 11 years ago • 10 comments

Before the new loopless design, a client could close a server through an RPC call and receive a response to the call. After switching to the loopless design in 0.5, the server exits, but the client doesn't receive a response.

With the old design, I could ensure that the server responded before closing by scheduling the server.close() call with loop.call_soon. This is no longer sufficient unfortunately.

Server

import asyncio
import aiozmq.rpc

class ServerHandler(aiozmq.rpc.AttrHandler):
    @aiozmq.rpc.method
    def exit(self):
        loop = asyncio.get_event_loop()
        loop.call_soon(self.server.close)
        print('Service shutting down...')


@asyncio.coroutine
def go():
    handler = ServerHandler()
    server = yield from aiozmq.rpc.serve_rpc(
        handler, bind='tcp://127.0.0.1:5555')
    handler.server = server
    yield from server.wait_closed()

asyncio.get_event_loop().run_until_complete(go())

Client

import asyncio
import zmq
import aiozmq.rpc

@asyncio.coroutine
def go():
    client = yield from aiozmq.rpc.connect_rpc(
        connect='tcp://127.0.0.1:5555')
    # Tell zmq not to wait until buffered data is sent after socket is closed
    client.transport.setsockopt(zmq.LINGER, 0)

    ret = yield from client.call.exit()
    client.close()

asyncio.get_event_loop().run_until_complete(go())

aknuds1 avatar Aug 20 '14 11:08 aknuds1

aiozmq never guarantee that behavior. Event with ZmqEventLoop you can get into situation when zmq doesn't send message immediately via buffers overflow for example. Also sudden server close can break processing of other rpc methods.

Maybe there is a way to get future object which can be setted up on sending answer to zmq layer but it requires change to transport API. BTW standard asyncio transports also don't guarantee behaviour requested by you.

So I'm not motivated to do something with the issue.

If you still need to close server by rpc call and your code is safe in terms of multiple semi-parallel calls you can use something like self.loop.call_later(0.01, self.close), while I don't recommend that way in general.

asvetlov avatar Aug 20 '14 11:08 asvetlov

OK, by calling loop.call_later: loop.call_later(0.01, self.close), it works. Why does this work better than call_soon? Is it pure luck that aiozmq is able to respond in 0.01 seconds?

Btw, this close() RPC method is only for testing purposes, after tests I need to shut down the tested server. I would think it'd be generally useful to be able to take down an RPC server in this fashion, although not a production instance.

I think your suggestion regarding a future to be notified when a response is sent is good though. It strikes me as the best solution.

aknuds1 avatar Aug 20 '14 11:08 aknuds1

loop.call_soon() executes callback on next loop iteration, .call_later -- after some timeout (maybe after several loop iterations). That's the difference.

Stopping test server from rpc call after small timeout is totally ok for me. Even 0.001 would be enough -- just let loop to iterate several times instantly for finishing own tasks.

asvetlov avatar Aug 20 '14 12:08 asvetlov

Yeah, I know the difference between call_soon and call_later. It just seems to me that it might be pure luck that call_later works, and call_soon doesn't? I mean, how does one know that the response is sent before f.ex. 0.1 seconds have passed?

aknuds1 avatar Aug 20 '14 12:08 aknuds1

Yes, it's matter of luck.

asvetlov avatar Aug 20 '14 12:08 asvetlov

Right :(

aknuds1 avatar Aug 20 '14 12:08 aknuds1

Probably I can make changes to server .close()/.wait_closed() methods to

  1. Stop receiving incoming rpc requests.
  2. Close rpc server after processing all current requests.
  3. .wait_closed() will return only after steps 1 and 2 are done.

That may help you.

asvetlov avatar Aug 20 '14 13:08 asvetlov

That does sound very useful, if you can make it work. Thanks (in advance)!

aknuds1 avatar Aug 20 '14 13:08 aknuds1

-1 for points 1,2,3 -- I beleive this should be done in test itself (in setUp/tearDown methods) and this would give more control over client and server.

popravich avatar Aug 20 '14 13:08 popravich

I encountered the same issue. Any improvements now?

hotpxl avatar May 19 '16 11:05 hotpxl