This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Author gvanrossum
Recipients ajoino, alex.gronholm, asvetlov, chris.jerdonek, gvanrossum, iritkatriel, jab, njs, tinchester, yselivanov
Date 2022-02-20.18:05:57
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1645380357.77.0.497700390339.issue46771@roundup.psfhosted.org>
In-reply-to
Content
The 3rd party context managers are somewhat of a red herring.
These are just try/except or try/finally blocks.

The inner cm (cm4) is irrelevant, it will see CancelledError and presumably that bubbles out. If it does any resource cleanup we can replace it with try/finally for purposes of simplifying the example.

But here's an even simpler example that poses the same question:

async with asyncio.timeout(1) as cm1:
  try:
    async with asyncio.timeout(1) as cm3:
      await asyncio.sleep(10)  # Raises CancelledError
  except TimeoutError:
    print("timed out")

Does this see CancelledError or catch TimeoutError? I had naively expected that it would catch TimeoutError, but then there's no place for the outer cancel scope to have any effect, so I agree that it should indeed see CancelledError, and "timed out" is never printed. The outer cancel scope sees CancelledError and turns it into TimeoutError.

Note that if the outer cancel scope has a longer timeout (which isn't expired yet), the try/except will catch TimeoutError. If it then enters another `await asyncio.sleep(10)` it will be cancelled and the outer cancel scope will raise TimeoutError.

How to implement this behavior? It can be done with the "cancel counter" that I proposed and Tin implemented in https://github.com/python/cpython/pull/31434.

Can it be done with the simpler version (just a cancel-requested bit), without using a nonce? I don't think so -- we don't know in which order the cancel call from the inner and outer cancel scope happen, and if the inner goes first, it cannot be aware of the outer.

So I think the cancel counter is the minimal change needed.

I have one final question, to which I don't have a firm answer yet. In Task.cancel(), if the cancel counter is already nonzero, should it still go ahead and set the must-cancel flag (or pass the cancellation on to `self._fut_waiter` -- I am still not sure what that's for :-( ). I think it only makes a difference if the task being cancelled has already caught a CancelledError (from the first cancel()) and is handling it. If we set must-cancel, then if it uses `await` it will be cancelled again. If we don't set must-cancel, its cleanup is "shielded". **Opinions?**




(PS. There's a typo in Andrew's example -- it should be "async with", not "async def".)
History
Date User Action Args
2022-02-20 18:05:57gvanrossumsetrecipients: + gvanrossum, njs, jab, asvetlov, alex.gronholm, chris.jerdonek, yselivanov, tinchester, iritkatriel, ajoino
2022-02-20 18:05:57gvanrossumsetmessageid: <1645380357.77.0.497700390339.issue46771@roundup.psfhosted.org>
2022-02-20 18:05:57gvanrossumlinkissue46771 messages
2022-02-20 18:05:57gvanrossumcreate