Skip to content

fix(agents): await cancelled tasks in _merge_agent_run_pre_3_11 to prevent aclose() RuntimeError#5416

Open
Koushik-Salammagari wants to merge 7 commits intogoogle:mainfrom
Koushik-Salammagari:fix/parallel-agent-pre-311-aclose-error
Open

fix(agents): await cancelled tasks in _merge_agent_run_pre_3_11 to prevent aclose() RuntimeError#5416
Koushik-Salammagari wants to merge 7 commits intogoogle:mainfrom
Koushik-Salammagari:fix/parallel-agent-pre-311-aclose-error

Conversation

@Koushik-Salammagari
Copy link
Copy Markdown

Link to Issue or Description of Change

Fixes #5297

Description

On Python 3.10, ParallelAgent uses _merge_agent_run_pre_3_11 instead of
asyncio.TaskGroup. When a sub-agent raises an exception, the finally block
cancelled all internal tasks with task.cancel() but did not await them.
This left the process_an_agent coroutines still executing their own finally
blocks — which hold references to the sub-agent async generators — when
_run_async_impl subsequently called aclose() on those generators, raising:

RuntimeError: aclose(): asynchronous generator is already running

This secondary error masked the original sub-agent exception (e.g., a
Pydantic validation failure from a structured-output agent).

Fix: add await asyncio.gather(*tasks, return_exceptions=True) after
cancellation so all tasks — including their generator cleanup — complete fully
before the caller can invoke aclose() on the generators.

Changes

  • src/google/adk/agents/parallel_agent.py: one line added to _merge_agent_run_pre_3_11 finally block
  • tests/unittests/agents/test_parallel_agent.py: regression test that directly exercises _merge_agent_run_pre_3_11 with a slow generator + failing generator, then calls aclose() — without the fix this raises RuntimeError

Testing Plan

  • New test test_merge_agent_run_pre_3_11_no_aclose_error_on_failure added to tests/unittests/agents/test_parallel_agent.py
  • All 9 existing parallel agent tests continue to pass
pytest tests/unittests/agents/test_parallel_agent.py -v
======================== 9 passed in 5.46s ===========================

…in thread pool

When RunConfig.tool_thread_pool_config is enabled, _call_tool_in_thread_pool
used None as a sentinel to distinguish "FunctionTool ran in thread pool" from
"non-FunctionTool sync tool, needs async fallback". Because None is also a
valid return value from any FunctionTool whose underlying function has no
explicit return statement (implicit None), the sentinel check failed and
execution fell through to tool.run_async(), invoking the function a second
time silently.

Replace the None sentinel with a dedicated _SYNC_TOOL_RESULT_UNSET object so
that a legitimate None result from a FunctionTool is correctly returned on the
first execution, without triggering the async fallback path.

Fixes google#5284
…ases

Per reviewer feedback: collapse the two near-identical None tests into a
single @pytest.mark.parametrize test, and add falsy-but-not-None cases
(0, '', {}, False) to prove the sentinel is identity-based and does not
mishandle any falsy return value from a FunctionTool.
…event aclose() RuntimeError

On Python 3.10, ParallelAgent uses _merge_agent_run_pre_3_11 instead of
asyncio.TaskGroup. When a sub-agent raises, the finally block cancelled
all tasks but did not await them. This left the internal process_an_agent
coroutines still executing their own finally blocks (which hold references
to the sub-agent async generators) when _run_async_impl subsequently called
aclose() on those generators, raising:

  RuntimeError: aclose(): asynchronous generator is already running

Fix: add asyncio.gather(*tasks, return_exceptions=True) after cancellation
so that all tasks — and their generator cleanup — complete before the caller
can invoke aclose().

Fixes google#5297
@adk-bot adk-bot added the core [Component] This issue is related to the core interface and implementation label Apr 20, 2026
@rohityan rohityan self-assigned this Apr 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core [Component] This issue is related to the core interface and implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ParallelAgent on Python 3.10 can raise RuntimeError: aclose(): asynchronous generator is already running after one sub-agent fails

3 participants