Skip to content

fix(inner): drop the prompt future from _pending on every ACP-family turn exit (acp/goose/qwen) - #4370

Open
abhay-codes07 wants to merge 2 commits into
omnigent-ai:mainfrom
abhay-codes07:fix/acp-run-turn-pending-future-leak
Open

fix(inner): drop the prompt future from _pending on every ACP-family turn exit (acp/goose/qwen)#4370
abhay-codes07 wants to merge 2 commits into
omnigent-ai:mainfrom
abhay-codes07:fix/acp-run-turn-pending-future-leak

Conversation

@abhay-codes07

Copy link
Copy Markdown
Contributor

Summary

Self-discovered resource leak in the generic ACP executor.

AcpExecutor.run_turn registers its session/prompt request future in self._pending[req_id] (so the stdout reader can resolve it), but _read_stdout removes the entry only when it matches a response (fut = self._pending.pop(msg_id)). Two exit paths never get that match:

  • Timeout — the agent goes silent, the loop hits its deadline, yields an ExecutorError, and returns. req_id is left in _pending.
  • EOF / reader error_read_stdout resolves the future with set_exception(...) (broadcast over _pending.values()) without popping, so run_turn's fut.result() raises and it returns with req_id still present.

On a long-lived ACP session (one process, many turns) each silent/failed turn leaves one stale, already-resolved future in _pending — a slow memory leak, and a late response for an old id could resolve a stale future. _rpc already guards its own timeout (self._pending.pop(req_id, None)); run_turn did not.

Fix: wrap the prompt loop in try/finally that pops req_id on every exit (timeout, EOF/reader-error, normal completion, or an abandoned generator). The happy path is unchanged — the reader still pops on the matching response and the finally is then a no-op.

Test Plan

Added test_run_turn_timeout_does_not_leak_pending_future to tests/inner/test_acp_executor.py: drives a turn whose response never arrives (stubbed session, no reader) and asserts _pending == {} afterward.

OMNIGENT_SKIP_WEB_UI=true uv run pytest tests/inner/test_acp_executor.py -q -k leak_pending
# 1 passed

Verified it fails without the finally (the future lingers) and passes with it. Full file: 34 passed (the 2 test_end_to_end_* fake-agent e2e tests fail identically on clean main in this non-POSIX dev env — unrelated). ruff check + ruff format --check clean.

Demo

N/A. Internal resource-cleanup fix, no user-visible surface.

Type of change

  • Bug fix
  • Feature
  • UI / frontend change
  • Refactor / chore
  • Docs
  • Test / CI
  • Breaking change

Test coverage

  • Unit tests added / updated
  • Integration tests added / updated
  • E2E tests added / updated
  • Manual verification completed
  • Existing tests cover this change
  • Not applicable

Changelog

Fixed a slow memory leak in the ACP executor: a silent or failed turn no longer leaves its request future behind in the pending-request map.

`AcpExecutor.run_turn` registers its `session/prompt` request future in
`self._pending[req_id]`, but the stdout reader removes it only when it
matches a RESPONSE. On the timeout path, and when the reader resolves the
future with an exception (EOF / reader error) rather than a match, nothing
removes `req_id` -- so `self._pending` accumulates one stale entry per
silent or failed turn on a long-lived ACP session (a slow memory leak; a
late response could also resolve a stale future). `_rpc` already guards its
own timeout this way; `run_turn` did not.

Wrap the prompt loop in try/finally that pops `req_id`, so every exit path
(timeout, EOF/reader-error, normal completion, or an abandoned turn) leaves
`_pending` clean. The happy path is unchanged: the reader still pops on the
matching response, and the finally pop is then a no-op.

Regression test drives a turn whose response never arrives and asserts
`_pending` is empty afterward (fails without the finally).

Signed-off-by: abhay-codes07 <abhaysingh0293@gmail.com>
Copilot AI lite review requested due to automatic review settings August 7, 2026 18:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@github-actions github-actions Bot added the size/M Pull request size: M label Aug 7, 2026
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

@abhay-codes07 Thanks for the PR! It doesn't reference an issue yet.

We require an issue for every PR, so the work can be prioritized before it's reviewed. Add one to the description:

  • Closes #123 if this PR finishes the issue. That links it, gives your PR the issue's priority, and closes the issue when this merges. You can also link it from the Development section of the sidebar.
  • Part of #123 if this is one step towards it. Related to, Towards, and Refs work the same way, and leave the issue open.

No issue exists for this yet? Open one first, then reference it. That's how we track what's worth doing, and it's usually quicker than it sounds. Note a reference has to point at an issue: naming another PR doesn't count.

The only exceptions are changes with no user-visible behaviour: pure Refactor / chore, Docs, or Test / CI work. If that's genuinely what this is, check that box under Type of change. Anything that fixes a bug, adds a feature, or changes the UI needs an issue, even when it also touches docs or tests.

See CONTRIBUTING.md for the full policy.

No action is taken beyond this comment.

@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

@abhay-codes07 This PR is a Bug fix, Feature, or UI / frontend change but the Demo section is missing or only contains a placeholder.

These change types require a screenshot or screen recording so reviewers can see the new behaviour without checking out the branch. Please update the Demo section with:

  • A screenshot or screen recording of the change, or
  • A link to a hosted video or GIF showing the new behaviour.

Use N/A only when the change has no user-visible effect whatsoever (e.g. a pure refactor or test-only change). If that's the case, uncheck the relevant type box and check Refactor / chore or Test / CI instead.

@github-actions github-actions Bot added the needs-demo PR needs a demo screenshot or recording label Aug 7, 2026
…cutors

GooseExecutor and QwenExecutor are the vendor-specific ACP executors the
generic AcpExecutor was factored from, and they carry the identical
run_turn leak: the session/prompt future is registered in
self._pending[req_id] but only popped by the stdout reader on a matched
response, so the timeout / EOF paths leak one stale future per silent or
failed turn on a long-lived session. Wrapped each prompt loop in the same
try/finally that drops req_id on every exit, and added the matching
timeout regression test to both suites (each fails without the finally).

Signed-off-by: abhay-codes07 <abhaysingh0293@gmail.com>
@github-actions github-actions Bot added size/XL Pull request size: XL and removed size/M Pull request size: M labels Aug 8, 2026
@abhay-codes07 abhay-codes07 changed the title fix(inner): drop the prompt future from _pending on every ACP turn exit fix(inner): drop the prompt future from _pending on every ACP-family turn exit (acp/goose/qwen) Aug 8, 2026
@abhay-codes07

Copy link
Copy Markdown
Contributor Author

Extended the fix in a second commit to GooseExecutor and QwenExecutor — the vendor-specific ACP executors this generic one was factored from. Both carry the identical run_turn leak (session/prompt future registered in _pending[req_id], popped only on a matched response, leaked on the timeout / EOF paths). Same try/finally cleanup + a matching timeout regression test in each suite (each fails without the finally). All three ACP-family executors are now consistent with _rpc's cleanup.

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

Labels

needs-demo PR needs a demo screenshot or recording size/XL Pull request size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants