[issue45390] asyncio.Task doesn't propagate CancelledError() exception correctly.
Marco Pagliaricci added the comment: Andrew, many thanks for your time, solving this issue. I think your solution is the best to fix this little problem and I agree with you on backporting. My Best Regards, and thanks again. Marco On Thu, Feb 17, 2022 at 10:29 AM Andrew Svetlov wrote: > > Andrew Svetlov added the comment: > > I have a pull request for the issue. > It doesn't use `Future.set_exception()` but creates a new CancelledError() > with propagated message. > The result is the same, except raised exceptions are not comparable by > `is` check. > As a benefit, `_cancelled_exc` works with the patch, exc.__context__ is > correctly set. > > The patch is not backported because it changes existing behavior a little. > I'd like to avoid a situation when third-party code works with Python > 3.11+, 3.10.3+, and 3.9.11+ only. > > -- > > ___ > Python tracker > <https://bugs.python.org/issue45390> > ___ > -- ___ Python tracker <https://bugs.python.org/issue45390> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue45390] asyncio.Task doesn't propagate CancelledError() exception correctly.
New submission from Marco Pagliaricci : I've spotted a little bug in how asyncio.CancelledError() exception is propagated inside an asyncio.Task. Since python 3.9 the asyncio.Task.cancel() method has a new 'msg' parameter, that will create an asyncio.CancelledError(msg) exception incorporating that message. The exception is successfully propagated to the coroutine the asyncio.Task is running, so the coroutine successfully gets raised an asyncio.CancelledError(msg) with the specified message in asyncio.Task.cancel(msg) method. But, once the asyncio.Task is cancelled, is impossible to retrieve that original asyncio.CancelledError(msg) exception with the message, because it seems that *a new* asyncio.CancelledError() [without the message] is raised when asyncio.Task.result() or asyncio.Task.exception() methods are called. I have the feeling that this is just wrong, and that the original message specified in asyncio.Task.cancel(msg) should be propagated even also asyncio.Task.result() is called. I'm including a little snippet of code that clearly shows this bug. I'm using python 3.9.6, in particular: Python 3.9.6 (default, Aug 21 2021, 09:02:49) [GCC 10.2.1 20210110] on linux -- components: asyncio files: task_bug.py messages: 403294 nosy: asvetlov, pagliaricci.m, yselivanov priority: normal severity: normal status: open title: asyncio.Task doesn't propagate CancelledError() exception correctly. type: behavior versions: Python 3.9 Added file: https://bugs.python.org/file50328/task_bug.py ___ Python tracker <https://bugs.python.org/issue45390> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue45390] asyncio.Task doesn't propagate CancelledError() exception correctly.
Marco Pagliaricci added the comment: OK, I see your point. But I still can't understand one thing and I think it's very confusing: 1) if you see my example, inside the job() coroutine, I get correctly cancelled with an `asyncio.CancelledError` exception containing my message. 2) Now: if I re-raise the asyncio.CancelledError as-is, I lose the message, if I call the `asyncio.Task.exception()` function. 3) If I raise a *new* asyncio.CancelledError with a new message, inside the job() coroutine's `except asyncio.CancelledError:` block, I still lose the message if I call `asyncio.Task.exception()`. 4) But if I raise another exception, say `raise ValueError("TEST")`, always from the `except asyncio.CancelledError:` block of the job() coroutine, I *get* the message! I get `ValueError("TEST")` by calling `asyncio.Task.exception()`, while I don't with the `asyncio.CancelledError()` one. Is this really wanted? Sorry, but I still find this a lot confusing. Shouldn't it be better to return from the `asyncio.Task.exception()` the old one (containing the message) ? Or, otherwise, create a new instance of the exception for *ALL* the exception classes? Thank you for your time, My Best Regards, M. On Thu, Oct 7, 2021 at 10:25 AM Thomas Grainger wrote: > > Thomas Grainger added the comment: > > afaik this is intentional https://bugs.python.org/issue31033 > > -- > nosy: +graingert > > ___ > Python tracker > <https://bugs.python.org/issue45390> > ___ > -- ___ Python tracker <https://bugs.python.org/issue45390> ___ ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue45390] asyncio.Task doesn't propagate CancelledError() exception correctly.
Marco Pagliaricci added the comment: Chris, I'm attaching to this e-mail the code I'm referring to. As you can see, in line 10, I re-raise the asyncio.CancelledError exception with a message "TEST". That message is lost, due to the reasons we've talked about. My point is that, if we substitute that line 10, with the commented line 11, and we comment the line 10, so we raise a ValueError("TEST") exception, as you can see, the message "TEST" is NOT LOST. I just find this counter-intuitive, and error-prone. AT LEAST should be very well specified in the docs. Regards, M. On Sat, Oct 9, 2021 at 2:51 PM Chris Jerdonek wrote: > > Chris Jerdonek added the comment: > > > 2) Now: if I re-raise the asyncio.CancelledError as-is, I lose the > message, > if I call the `asyncio.Task.exception()` function. > > Re-raise asyncio.CancelledError where? (And what do you mean by > "re-raise"?) Call asyncio.Task.exception() where? This isn't part of your > example, so it's not clear what you mean exactly. > > -- > > ___ > Python tracker > <https://bugs.python.org/issue45390> > ___ > -- Added file: https://bugs.python.org/file50333/task_bug.py ___ Python tracker <https://bugs.python.org/issue45390> ___import asyncio async def job(): print("job(): START...") try: await asyncio.sleep(5.0) except asyncio.CancelledError as e: print("job(): CANCELLED!", e) #raise asyncio.CancelledError("TEST") raise ValueError("TEST") print("job(): DONE.") async def cancel_task_after(task, time): try: await asyncio.sleep(time) except asyncio.CancelledError: print("cancel_task_after(): CANCELLED!") except Exception as e: print("cancel_task_after(): Exception!", e.__class__.__name__, e) task.cancel("Hello!") async def main(): task = asyncio.create_task(job()) # RUN/CANCEL task. try: await asyncio.gather(task, cancel_task_after(task, 1.0)) except asyncio.CancelledError as e: print("In running task, we encountered a cancellation! Excpetion message is: ", e) except Exception as e: print("In running task, we got a generic Exception:", e.__class__.__name__, e) # GET result. try: result = task.result() except asyncio.CancelledError as e: print("Task has been cancelled, exception message is: ", e) except Exception as e: print("Task raised generic exception", e.__class__.__name__, e) else: print("Task result is: ", result) if __name__=="__main__": asyncio.run(main()) ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com
[issue45390] asyncio.Task doesn't propagate CancelledError() exception correctly.
Marco Pagliaricci added the comment: Chris, ok, I have modified the snippet of code to better show what I mean. Still here, the message of the CancelledError exception is lost, but if I comment line 10, and uncomment line 11, so I throw a ValueError("TEST"), that "TEST" string will be printed, so the message is not lost. Again, I just find this behavior very counter-intuitive, and should be VERY WELL documented in the docs. Thanks, M. On Sat, Oct 9, 2021 at 3:06 PM Chris Jerdonek wrote: > > Chris Jerdonek added the comment: > > I still don't see you calling asyncio.Task.exception() in your new > attachment... > > -- > > ___ > Python tracker > <https://bugs.python.org/issue45390> > ___ > -- Added file: https://bugs.python.org/file50334/task_bug.py ___ Python tracker <https://bugs.python.org/issue45390> ___import asyncio async def job(): print("job(): START...") try: await asyncio.sleep(5.0) except asyncio.CancelledError as e: print("job(): CANCELLED!", e) raise asyncio.CancelledError("TEST") #raise ValueError("TEST") print("job(): DONE.") async def cancel_task_after(task, time): try: await asyncio.sleep(time) except asyncio.CancelledError: print("cancel_task_after(): CANCELLED!") except Exception as e: print("cancel_task_after(): Exception!", e.__class__.__name__, e) task.cancel("Hello!") async def main(): task = asyncio.create_task(job()) # RUN/CANCEL task. try: await asyncio.gather(task, cancel_task_after(task, 1.0)) except asyncio.CancelledError as e: try: task_exc = task.exception() except BaseException as be: task_exc = be print("In running task, we encountered a cancellation! Excpetion message is: ", e) print(" ^ Task exc is:", task_exc.__class__.__name__, task_exc) except Exception as e: print("In running task, we got a generic Exception:", e.__class__.__name__, e) # GET result. try: result = task.result() except asyncio.CancelledError as e: print("Task has been cancelled, exception message is: ", e) except Exception as e: try: task_exc = task.exception() except BaseException as be: task_exc = be print("Task raised generic exception", e.__class__.__name__, e) print(" ^ Task exc is:", task_exc.__class__.__name__, task_exc) else: print("Task result is: ", result) if __name__=="__main__": asyncio.run(main()) ___ Python-bugs-list mailing list Unsubscribe: https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com