New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Non-working error handler when creating a task with assigning a variable #84020
Comments
#This example does not work due to assigning the task to a variable import asyncio
import logging
def handle_exception(loop, context):
msg = context.get("exception", context["message"])
logging.error("Caught exception: %s", msg)
async def test():
await asyncio.sleep(1)
raise Exception("Crash.")
def main():
loop = asyncio.get_event_loop()
loop.set_exception_handler(handle_exception)
# if removed "task = " - exception handler will work.
task = loop.create_task(test())
try:
loop.run_forever()
finally:
loop.close()
if __name__ == "__main__":
main() |
# This line doesn`t work
task = loop.create_task(test())
# This line works
loop.create_task(test()) |
Can reproduce also on 3.8. Another version that "works" (raises the exception) is task = loop.create_task(test())
del task Suggests there's something going on with reference counting or garbage collection. In the version that "doesn't work", the task exception only appears in the custom exception handler when the loop is stopped, not before. I've added a log message to show each second that passes, and the loop is stopped after 5 seconds: import asyncio
import logging
def handle_exception(loop, context):
msg = context.get("exception", context["message"])
logging.error("Caught exception: %s", msg)
async def test():
await asyncio.sleep(1)
raise Exception("Crash.")
def second_logger(loop, count) -> int:
logging.warning(count)
loop.call_later(1, lambda count=count: second_logger(loop, count + 1))
def main():
loop = asyncio.get_event_loop()
loop.call_later(1, lambda: second_logger(loop, 0))
loop.call_later(5, loop.stop)
loop.set_exception_handler(handle_exception)
task = loop.create_task(test())
try:
loop.run_forever()
finally:
loop.close()
if __name__ == "__main__":
main() OUTPUT: $ py -3.8 -u bpo-issue39839.py
WARNING:root:0
WARNING:root:1
WARNING:root:2
WARNING:root:3
WARNING:root:4
ERROR:root:Caught exception: Crash. |
I took a look at this. Basically, the reason the exception handler isn't firing when the task is still in scope is that the exception handler is only a handler of "last resort." You can see that the exception handler is called inside Task.__del__ here: Lines 167 to 176 in 3764069
The reason to do it this way is that if the Task is still in scope, there's still a chance that the caller might want to handle the exception themselves, e.g. by awaiting on the Task themselves. It's only when all references to the task go away that you know that the Task's exceptions can no longer be handled manually. Maybe this would be worth clarifying somewhere in the Error Handling API docs: |
The exception handler is only called if the task was garbage collected and is pending. This usually happens when a task was created but there is no reference to it. It is documented at https://docs.python.org/3/library/asyncio-task.html#asyncio.create_task |
Ironically this "bug" reported in this issue is actually (although the OP never quite comes out and says it) that the task raises an exception but that exception is never reported. Following the advice that we recently added to the docs (basically "hold on to the task") causes the "bug". If you disregard the advice, not assigning the task to a variable, the exception handler is called (unless the task gets GC'ed before it is scheduled to run, which is rare but possible since you didn't hang on to it :-). Maybe we need to add another piece of advice: await the task in order to get an exception reported. However, the best advice is to use TaskGroup, which propagates exceptions from all tasks (since it awaits all of them). Also use asyncio.run(), which reports exceptions propagated to the main() coroutine. |
Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.
Show more details
GitHub fields:
bugs.python.org fields:
The text was updated successfully, but these errors were encountered: