I have an awkward problem with several scheduled asyncio.futures (scheduled as message writer of a server) when cancelling them on a graceful shutdown. Each active client connection has a session of active writers.
writer = asyncio.gather(
*[self._dispatchMessage(message, c) for c in connections],
loop=self.loop,
return_exceptions=True
)
Each writer future (GatheringFuture) gets stored in a list of active sessions...
connection.sessions = set()
...from which the writer future gets removed once it finished. This is achieved by adding a "done_callback" to the future.
writer.add_done_callback(lambda task: connection.sessions.remove(task))
connection.sessions.add(writer)
Once I gracefully shutdown my server, I loop over the active connections and their sessions and cancel (future.cancel()) them to not end up with pending writer futures.
async def cancel_sessions(connection):
for s in connection.sessions:
s.cancel()
asyncio.wait(
[await cancel_sessions(c) for c in client_connections],
timeout=None
)
This works, but as soon as I have more connections I keep getting asyncio loop exceptions despite catching exceptions greedily for debugging nearly everywhere. For me it looks, that the problem is caused by the "done_callbacks" added to the writer futures (GatheringFuture). It seems I have to remove the call_backs, otherwise I end up with:
Traceback (most recent call last):
File "uvloop/cbhandles.pyx", line 49, in uvloop.loop.Handle._run
File "...server.py", line 353, in <lambda>
writer.add_done_callback(lambda task: connection.sessions.remove(task))
myUtilsAsyncLoopException: Async exception "<_GatheringFuture finished result=[None, None]>"
If I omit this line from my code...
writer.add_done_callback(lambda task: connection.sessions.remove(task))
...I do not get the problems. My question now is how to handle done_callbacks on cancelled asyncio.futures. My impression was, that I don't have to manually remove such callbacks from a future before cancelling it. But it seems that when cancelling a future with a callback, these callbacks might throw exceptions when their related future is cancelled.
I also don't yet understand why this happens only when I have more than 2 connections because I see no big difference in handling them.