Chris Jerdonek <chris.jerdo...@gmail.com> added the comment:
Here's a simplification of Marco's snippet to focus the discussion. import asyncio async def job(): # raise RuntimeError('error!') await asyncio.sleep(5) async def main(): task = asyncio.create_task(job()) await asyncio.sleep(1) task.cancel('cancel job') await task if __name__=="__main__": asyncio.run(main()) ---- Running this pre-Python 3.9 gives something like this-- Traceback (most recent call last): File "test.py", line 15, in <module> asyncio.run(main()) File "/.../python3.7/asyncio/runners.py", line 43, in run return loop.run_until_complete(main) File "/.../python3.7/asyncio/base_events.py", line 579, in run_until_complete return future.result() concurrent.futures._base.CancelledError ---- Running this with Python 3.9+ gives something like the following. The difference is that the traceback now starts at the sleep() call: Traceback (most recent call last): File "/.../test.py", line 6, in job await asyncio.sleep(5) File "/.../python3.9/asyncio/tasks.py", line 654, in sleep return await future asyncio.exceptions.CancelledError: cancel job During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/.../test.py", line 12, in main await task asyncio.exceptions.CancelledError During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/.../test.py", line 15, in <module> asyncio.run(main()) File "/.../python3.9/asyncio/runners.py", line 44, in run return loop.run_until_complete(main) File "/.../python3.9/asyncio/base_events.py", line 642, in run_until_complete return future.result() asyncio.exceptions.CancelledError ---- Uncommenting the RuntimeError turns it into this-- Traceback (most recent call last): File "/.../test.py", line 15, in <module> asyncio.run(main()) File "/.../python3.9/asyncio/runners.py", line 44, in run return loop.run_until_complete(main) File "/.../python3.9/asyncio/base_events.py", line 642, in run_until_complete return future.result() File "/.../test.py", line 12, in main await task File "/.../test.py", line 5, in job raise RuntimeError('error!') RuntimeError: error! ---- I agree it would be a lot nicer if the original CancelledError('cancel job') could bubble up just like the RuntimeError does, instead of creating a new CancelledError at each await and chaining it to the previous CancelledError. asyncio's creation of a new CancelledError at each stage predates the PR that added the chaining, so this could be viewed as an evolution of the change that added the chaining. I haven't checked to be sure, but the difference in behavior between CancelledError and other exceptions might be explained by the following lines: https://github.com/python/cpython/blob/3d1ca867ed0e3ae343166806f8ddd9739e568ab4/Lib/asyncio/tasks.py#L242-L250 You can see that for exceptions other than CancelledError, the exception is propagated by calling super().set_exception(exc), whereas with CancelledError, it is propagated by calling super().cancel() again. Maybe this would even be an easy change to make. Instead of asyncio creating a new CancelledError and chaining it to the previous, asyncio can just raise the existing one. For the pure Python implementation at least, it may be as simple as making a change here, inside _make_cancelled_error(): https://github.com/python/cpython/blob/3d1ca867ed0e3ae343166806f8ddd9739e568ab4/Lib/asyncio/futures.py#L135-L142 ---------- _______________________________________ Python tracker <rep...@bugs.python.org> <https://bugs.python.org/issue45390> _______________________________________ _______________________________________________ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com