Repository navigation
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #3554 +/- ##
===============================================
Coverage 100.00000% 100.00000%
===============================================
Files 128 128
Lines 19475 19497 +22
Branches 1323 1325 +2
===============================================
+ Hits 19475 19497 +22
🚀 New features to boost your workflow:
|
When a cancel scope around an `async with` on an @as_safe_channel generator fires while the generator's own code is running, the body and the generator each raise a Cancelled, and the one that propagated was whichever reached the nursery first, which is effectively random. If the generator had already raised and was still awaiting in its cleanup, its Cancelled carries that exception as __context__, so the exception was missing from the traceback about half the time. A plain `async for` over the same generator always raises the generator's Cancelled. Do the same here: remember a Cancelled that comes out of the generator's code, and raise that one when the group holds nothing but Cancelled. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPZoDDtuKy1bbLLR2nsnmW
…ceptionError Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPZoDDtuKy1bbLLR2nsnmW
`except trio.Cancelled as exc` deletes `exc` at the end of the handler, and the function's later `exc = AssertionError(...)` reuses the name, which mypy rejects. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPZoDDtuKy1bbLLR2nsnmW
1681010 to
61696cc
Compare
|
Hm I'm not that happy about Cancelled actually having state and it being important which one we pick. More generally I don't see why there couldn't be two Cancelleds that matter at this point? Should we make a new Cancelled where the context is a synthesized ExceptionGroup? (I guess it's also true people have been poking at |
Yeah, I don't like it either, but it happens to be the case that a If there's another |
|
Nevermind, I forgot we did some work to make sure only the generator or the body is running at once! I guess it's kind of annoying that this doesn't compose well (only works with |
When a cancel scope around an
async withon an@as_safe_channelgenerator fires while the generator's own code is running, the body and the generator each raise aCancelled. Which one propagated was whichever reached the nursery first, which is effectively random. If the generator had already raised and was still awaiting in its cleanup, itsCancelledcarries that exception as__context__, so the exception was missing from the traceback about half the time (under aTooSlowError, say). A plainasync forover the same generator always keeps it.This remembers a
Cancelledthat comes out ofagen.__anext__(), and raises that one when the nursery's group holds nothing butCancelled. A group raised by the body still propagates whole, as before. The new test uses a shield in the generator's cleanup to pin the order that lost the exception, so it fails reliably without the fix.Why not change
raise_single_exception_from_groupto prefer aCancelledwith a__context__? That would pick the background task'sCancelledin ordinary timeouts where the generator is parked at ayield. ThatCancelledhas aWouldBlockcontext, becauseSemaphore.acquireparks insideexcept WouldBlock, and picking it would drop the consumer's frames from the traceback.