fix(tools): cancel sibling tools on child-originated cancellation - #7180
Anusha0501 wants to merge 2 commits into
Conversation
asyncio.CancelledError is a BaseException, so _gather_or_cancel only tore down siblings on Exception. A parallel tool that raised CancelledError or cancelled itself left sibling tasks running after the batch returned. Fixes google#7172
|
Thanks for putting together the fix and for crediting the reporter's Event handshake in the testing section. One attribution correction: the “Maintainer diagnosis on #7172” paragraph refers to my investigation as the issue reporter, rather than a maintainer response. The root-cause analysis, deterministic reproducer, and candidate My local probes also cover |
google#7172 reproduced child-originated cancellation on run_async SSE and run_live BIDI as well as non-streaming run_async. Parametrize the Runner tests across those three modes so the batch-ownership invariant holds on each path.
|
@jaywang172 Thanks for the correction, and for the original diagnosis, reproducer, and candidate repair on #7172. The additional-context wording is now Reporter's diagnosis and candidate repair from #7172 (@jaywang172). I also added the SSE and That should keep the batch-ownership invariant on those paths in this PR, so a follow-up fix PR is not needed. Further cases you still want to add are welcome as review comments or a follow-up on this branch. |
Please ensure you have read the contribution guide before creating a pull request.
Link to Issue or Description of Change
1. Link to an existing issue (if applicable):
2. Or, if no issue exists, describe the change:
Problem:
When one tool in a parallel batch is cancelled (it raises
asyncio.CancelledError, or it cancels its own task),_gather_or_canceldoes not tear down the rest of the batch.asyncio.CancelledErroris aBaseException, so the existingexcept Exceptionpath never runs.asyncio.gather()also does not cancel siblings when a child is cancelled. The publicRunneriterator can therefore finish while a sibling tool is still executing and can still perform a side effect.Solution:
Catch
(Exception, asyncio.CancelledError)in_gather_or_canceland reuse the existing cancel-unfinished-tasks / await-cleanup / re-raise path. Cancellation stays cancellation; it is not converted into a tool error.Testing Plan
Unit Tests:
Added:
test_gather_or_cancel_cancels_siblings_on_cancelled_errortest_gather_or_cancel_cancels_siblings_when_child_cancels_itselftest_parallel_function_call_cancels_siblings_on_cancelled_error(parametrized:run_async, SSE,run_live)test_parallel_function_call_cancels_siblings_when_tool_cancels_itself(parametrized:run_async, SSE,run_live)The Runner-level tests use the reporter's Event handshake from #7172 and assert the sibling is cancelled and not still pending when the iterator returns. They now cover non-streaming
run_async,run_asyncwith SSE, andrun_liveBIDI, matching the #7172 matrix.Manual End-to-End (E2E) Tests:
Covered by the Runner-level unit tests above (
MockModel, no live model API). Those reproduce the #7172 matrix forCancelledErrorand self-cancel acrossrun_async, SSE, andrun_live.Checklist
Additional context
Reporter's diagnosis and candidate repair from #7172 (@jaywang172):
_gather_or_cancelcatchesExceptiononly;CancelledErrorbypasses sibling cleanup. Caller-cancel of the whole batch already works becausegathercancels children when the gather wait itself is cancelled. This PR only covers child-originated cancellation.