Can no longer close server via RPC with loopless design
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())
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.
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.
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.
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?
Yes, it's matter of luck.
Right :(
Probably I can make changes to server .close()/.wait_closed() methods to
- Stop receiving incoming rpc requests.
- Close rpc server after processing all current requests.
-
.wait_closed()will return only after steps 1 and 2 are done.
That may help you.
That does sound very useful, if you can make it work. Thanks (in advance)!
-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.
I encountered the same issue. Any improvements now?