I have an awaitable object implementing a request/reply transaction. If the transaction times out, it will be retried few times before giving up and raising an exception.
Now assume it always times out, because that is the case I have problem with.
When a task starts this operation and then gets cancelled, the retries will continue. This is not what I want. I want to cancel the operation entirely.
I prepared a MCVE and noticed, that a future that the task is waiting for gets cancelled when the task is cancelled. This suits me fine, it could be a base for a solution, but I do not understand why that future gets cancelled and if I can rely on it.
import asyncio
RETRIES = 2
TIMEOUT = 1.0
class ClientRPC:
def __init__(self):
self._reply = None
self._retries = RETRIES
def __await__(self):
self.start()
return self._reply.__await__()
def start(self):
loop = asyncio.get_event_loop()
if self._reply is None:
self._reply = loop.create_future()
loop.call_later(TIMEOUT, self.handle_timeout)
# send a request
print("REQUEST")
def handle_timeout(self):
print("TIMEOUT")
print("future", repr(self._reply._state))
if self._retries > 0:
self._retries -= 1
self.start()
else:
self._reply.set_exception(RuntimeError("Timeout!"))
def handle_reply(self, reply):
# unused in this example
pass
async def client():
transaction = ClientRPC()
try:
reply = await transaction
except asyncio.CancelledError:
print("--CANCELLED--")
async def test():
loop = asyncio.get_event_loop()
task = loop.create_task(client())
await asyncio.sleep(1.5)
task.cancel()
await asyncio.sleep(3)
asyncio.run(test()) # python 3.7+
Output (traceback omitted):
REQUEST TIMEOUT future 'PENDING' REQUEST --CANCELLED-- TIMEOUT future 'CANCELLED' <-- why? REQUEST TIMEOUT future 'CANCELLED' Exception in callback ClientRPC.handle_timeout() handle: asyncio.base_futures.InvalidStateError: invalid state