Repository navigation
Conversation
Documentation build overview
166 files changed ·
|
OsmnvAslan
left a comment
There was a problem hiding this comment.
I checked this out locally: test_base_events passes, and the two new tests fail on main as described. The move into _get_exception_context() is a straight refactor of the existing lookup.
One behaviour change worth documenting: a custom handler used to run directly in the task's context, so a ContextVar.set() inside the handler was visible to the task afterwards. With ctx.copy() that write is now discarded (I confirmed both behaviours with a small script on main and on this branch). I think that's the right call, but a sentence in the versionchanged note would avoid surprising anyone relying on it.
|
Thanks for checking it locally. Added in 340c4e6: the versionchanged note now says that a ContextVar.set() made inside the handler is no longer visible to the task afterwards, since the handler runs in a copy of the context. |
| The handler now runs in a copy of that context. Previously, reporting | ||
| an exception from inside the task's own context raised | ||
| :exc:`RuntimeError` and the handler was skipped. Because the handler |
There was a problem hiding this comment.
Small accuracy nit: on main the handler isn't really skipped silently. The RuntimeError from ctx.run() is caught and passed to default_exception_handler() as "Unhandled error in exception handler", with the original context nested under 'context'. Maybe something like:
The handler now runs in a copy of that context. Previously, reporting
an exception from inside the task's own context failed with
:exc:`RuntimeError`, and the handler was not called; the failure was
logged as an unhandled error in the exception handler instead.| .. versionchanged:: next | ||
|
|
||
| When no exception handler is set, :meth:`call_exception_handler` | ||
| runs this method in a copy of the :class:`contextvars.Context` of | ||
| the task or handle where the exception originated, as for a custom | ||
| handler. |
There was a problem hiding this comment.
This note describes how call_exception_handler() behaves rather than default_exception_handler() itself (calling loop.default_exception_handler() directly is unaffected). Consider moving it under loop.call_exception_handler instead, e.g. "When no custom handler is set, the default handler now runs in a copy of the context of the task or handle where the exception originated."
| self.loop.run_until_complete, self.loop.subprocess_shell, | ||
| asyncio.SubprocessProtocol, 'exit 0', bufsize=4096) | ||
|
|
||
| def test_default_exc_handler_runs_in_task_context(self): |
There was a problem hiding this comment.
This covers the 'task' and 'handle' keys, but not 'future', which is the path the issue's repro actually goes through: "Task exception was never retrieved" is reported from the task finalizer with 'future': task. Could you add that case here too, e.g.
self.loop.call_exception_handler({
'message': 'msg', 'future': task, 'exception': ValueError()})(and extend the expected list accordingly). An end-to-end variant along the lines of the issue's repro (a failing task dropped without retrieving its exception, then support.gc_collect()) would also be nice, but the direct call is enough.
Fixes gh-159303.
With no custom handler set,
call_exception_handler()calleddefault_exception_handler()in the caller's context. gh-96704 made a custom handler run in the task's or handle's context, and its PR left the default handler for later. This moves the lookup into a helper and uses it for both.Both handlers run in
ctx.copy(). Without the copy, reporting from inside the task itself ('task': asyncio.current_task()) raisesRuntimeError: cannot enter context, and a custom handler is silently skipped today.Three tests in
test_base_events:The docs note both changes. The issue's repro now prints
req-1twice../python -m test --fast-ci -j8 test_asynciopasses.Prepared with AI assistance (Claude), then reviewed and tested before submission.