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 asvetlov
Recipients ajoino, alex.gronholm, asvetlov, chris.jerdonek, gvanrossum, iritkatriel, jab, njs, tinchester, yselivanov
Date 2022-02-20.15:22:29
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <1645370550.11.0.522359208952.issue46771@roundup.psfhosted.org>
In-reply-to
Content
Suppose we have a case when two nested timeouts are reached at the same event loop iteration:

async def asyncio.timeout(1) as cm1:
   async with third_party_cm() as cm2:
       async def asyncio.timeout(1) as cm3:
           async with third_party_cm() as cm4:
               await asyncio.sleep(10)

What exception should be bubbled between outer and inner context manager 'exit' executions?

`sleep()` is interrupted with CancelledError, it is clear (and the only possible solution in asyncio world). `cm4.__aexit__` receives the CancelledError, does the cleanup if required, and re-raises the cancellation.

`cm3.__aexit__` receives the bubbled CancelledError and updates its own state and raises an exception.
The question is: what exception should be raised, CancelledError or TimeoutError?
What exception should see `cm2.__aexit__` code?

After careful thinking, I believe that CancelledError should be re-raised by *inner affected* timeout context managers, the only top-level *affected* context should convert CancelledError and raise TimeoutError.

My reasons for this behavior are:
  A generic asyncio code is usually *ready* for cancellation. If it wants to react to the cancellation event, it caught `asyncio.CancelledError` and reraised it. Also, the asyncio code is cancellation-ready by default because usually `BaseException` is now handled (asyncio.CancelledError is derived from BaseException). TimeoutError is caught by `except Exception` instead, it adds extra difficulty.
  Handling both CancelledError and TimeoutError by *any* asyncio code on async stack unwinding is tedious and error-prone. If we should choose one I bet on CancelledError.
  The inner code ignores timeouts usually (and executes resource cleanup only). That's what CancelledError handling exists for already. If the cleanup differs depending on timeout expiration, `cm3.expired` (name it) can be used as a flag. You can disagree with me here, my opinion is based on my experience of writing asyncio code only.
  The top-level affected timeout context manager should raise TimeoutError because it exists and is used for such things.

Long story short: all *internal affected* timeout context managers should not raise TimeoutError (or it should be configurable and 'off' by default) because `third_party_cm()` should have the same simple implementation whether is it used as `cm2` or `cm4`.

Happy to see your opinions regarding the question, folks!
History
Date User Action Args
2022-02-20 15:22:30asvetlovsetrecipients: + asvetlov, gvanrossum, njs, jab, alex.gronholm, chris.jerdonek, yselivanov, tinchester, iritkatriel, ajoino
2022-02-20 15:22:30asvetlovsetmessageid: <1645370550.11.0.522359208952.issue46771@roundup.psfhosted.org>
2022-02-20 15:22:30asvetlovlinkissue46771 messages
2022-02-20 15:22:29asvetlovcreate