Skip to content

fix(runners): close shared toolsets only from the last runner - #7045

Open
claxman wants to merge 1 commit into
google:mainfrom
claxman:fix/4045-shared-toolset-refcount
Open

fix(runners): close shared toolsets only from the last runner#7045
claxman wants to merge 1 commit into
google:mainfrom
claxman:fix/4045-shared-toolset-refcount

Conversation

@claxman

@claxman claxman commented Sep 7, 2026

Copy link
Copy Markdown

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):

Problem:
Runner.close() always calls toolset.close() for every toolset on the agent. Two Runners sharing one LlmAgent with a CountingToolset printed after_first_close 1 then after_second_close 2. The first close already released the toolset while the second runner was still open. Parallel AgentTool runs on agents that share a toolset hit the same path (await runner.close() after each nested run).

Solution:
Each Runner is a weak holder of the toolsets it collected. close() removes that holder and only calls toolset.close() when no live runner remains. A Runner dropped without close() falls out of the set, so it cannot pin the toolset open. close() also re-collects toolsets so one appended after construct is still released. AgentTool.run_async closes its nested runner in a finally so a raising nested run still unwinds on the same task.

Testing Plan

Unit Tests:

  • I have added or updated unit tests for my change.
  • All unit tests pass locally.
PYTHONPATH=src .venv/bin/python -m pytest -q tests/unittests/test_runners.py::TestRunnerWithPlugins::test_shared_toolset_stays_open_until_last_runner_closes tests/unittests/test_runners.py::TestRunnerWithPlugins::test_single_runner_still_closes_its_toolset tests/unittests/test_runners.py::TestRunnerWithPlugins::test_dropped_runner_does_not_pin_shared_toolset tests/unittests/test_runners.py::TestRunnerWithPlugins::test_late_added_toolset_is_closed tests/unittests/tools/test_agent_tool.py::test_parallel_agent_tools_do_not_close_shared_toolset_early tests/unittests/tools/test_agent_tool.py::test_failed_agent_tool_still_closes_shared_toolset

6 passed.

With runners.py and agent_tool.py checked out from b0180620, four of those six fail (test_shared_toolset_stays_open_until_last_runner_closes, test_single_runner_still_closes_its_toolset, test_parallel_agent_tools_do_not_close_shared_toolset_early, test_failed_agent_tool_still_closes_shared_toolset). The dropped-runner and late-added tests pass on main (every Runner.close() already closed the toolset) and guard the WeakSet / re-collect paths.

.venv/bin/python -m mypy src/google/adk/runners.py: Success, no issues.

Manual End-to-End (E2E) Tests:

Not run. The CountingToolset runners and the parallel AgentTool test cover the two cases in the issue.

Additional context

Claim: #4045 (comment)

#4046 added an AgentToolManager singleton and was closed on 2026-05-12 after requested changes stayed open.

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules.

Runner.close always released every toolset on the agent, so the first
of two parallel runners or AgentTools tore down a still-used toolset.
Holders are a WeakSet of Runners so a dropped runner cannot pin the
toolset. AgentTool closes the nested runner in a finally.

Fixes google#4045
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] When executing Runners(of which agent contains stateful toolset) in parallel, toolset is being released when only one of them has finished

2 participants