Skip to content

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

Description

@talvaknin744

On Python 3.10, google.adk.agents.ParallelAgent uses _merge_agent_run_pre_3_11() instead of asyncio.TaskGroup. If one sub-agent async generator raises,
_merge_agent_run_pre_3_11() cancels sibling processing tasks in finally but does not await those tasks before ParallelAgent._run_async_impl() closes each sub-agent
generator.

That can raise a second cleanup exception:

RuntimeError: aclose(): asynchronous generator is already running

This can mask or pollute the original error, for example a normal Pydantic validation failure from a structured-output sub-agent.

Environment

  • Python: 3.10.12
  • google-adk: 1.26.0
  • litellm: 1.82.1
  • OS: Linux
  • Agent shape: SequentialAgent containing a ParallelAgent with multiple LlmAgent children using output_schema and output_key

Minimal repro

importasynciofromgoogle.adk.agents.parallel_agentimport_merge_agent_run_pre_3_11asyncdefslow_agent():
try:
awaitasyncio.sleep(10)
yield"slow-event"finally:
awaitasyncio.sleep(0.1)
asyncdeffailing_agent():
awaitasyncio.sleep(0.01)
raiseValueError("simulated sub-agent validation failure")
yield"unreachable"asyncdefmain():
agent_runs= [slow_agent(), failing_agent()]
try:
asyncforeventin_merge_agent_run_pre_3_11(agent_runs):
print("event", event)
exceptExceptionasexc:
print("merge raised:", type(exc).__name__, exc)
fori, ageninenumerate(agent_runs):
try:
awaitagen.aclose()
print("closed", i)
exceptExceptionasexc:
print("close raised:", i, type(exc).__name__, exc)
asyncio.run(main())
## Actual outputmergeraised: ValueErrorsimulatedsub-agentvalidationfailurecloseraised: 0RuntimeErroraclose(): asynchronousgeneratorisalreadyrunningclosed1## Expected behaviorADKshouldpropagatetheoriginalsub-agentfailurecleanlyandclose/cancelsiblingsub-agentrunswithoutraisingaclose(): asynchronousgeneratorisalreadyrunning.
## Suspected root causeIn_merge_agent_run_pre_3_11(), thefinallyblockcancelsprocessingtasks:
finally:
fortaskintasks:
task.cancel()
butdoesnotawaittheircancellation. ThenParallelAgent._run_async_impl() closesthesameasyncgenerators:
finally:
forsub_agent_runinagent_runs:
awaitsub_agent_run.aclose()
Ifataskisstillinsideasyncforonasiblinggenerator, aclose() racesthatstill-runninggenerator.
## Suggested fixAftercancelingtasksin_merge_agent_run_pre_3_11(), awaitthembeforetheparentclosessub-agentgenerators:
finally:
fortaskintasks:
task.cancel()
iftasks:
awaitasyncio.gather(*tasks, return_exceptions=True)
Inalocalsimulation, thispreservedtheoriginalValueErrorandallowedbothasyncgeneratorstoclosecleanly.

Metadata

Metadata

Assignees

Labels

core[Component] This issue is related to the core interface and implementationrequest clarification[Status] The maintainer need clarification or more information from the authorstale[Status] Issues which have been marked inactive since there is no user response

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions