Skip to content

fix(sender): settle sends when the loop terminates - #170

Closed
Ehsan-hash wants to merge 2 commits into
agentclientprotocol:mainfrom
Ehsan-hash:fix/sender-terminal-state
Closed

Ehsan-hash wants to merge 2 commits into
agentclientprotocol:mainfrom
Ehsan-hash:fix/sender-terminal-state

Conversation

@Ehsan-hash

Copy link
Copy Markdown

Why

When the sender loop fails or is cancelled, concurrent requests can wait forever for a send that will never complete. Keep a terminal sender state so active, queued and later sends all finish.

What changed

  • Propagate the original writer error to outstanding and later sends.
  • Preserve cancellation during shutdown, including cancellation before the sender loop starts.
  • Add sender lifecycle regressions and a two-request regression through Connection.

Scope

Blast Radius

Changes how pending sends terminate after writer failure or shutdown. Existing peer cancellation, stdio and HTTP/WebSocket tests pass.

Verification

  • Unchanged source: 5 failed, 7 passed; fixed focused regressions: 12 passed.
  • Affected RPC, cancellation, recovery and logging tests: 75 passed.
  • make check passes; make test: 413 passed, 1 skipped on Python 3.12. The skipped Gemini integration was not enabled.

Developed and reviewed with AI assistance using Codex. No live model/provider calls were used for validation.

Developed with AI assistance using Codex. Regression tests intentionally fail until the sender lifecycle fix follows.
Retain writer failures for active, queued and later sends. Preserve cancellation during shutdown and reject sends after closure, including cancellation before the sender loop starts.

Developed and reviewed with AI assistance using Codex.
Comment thread src/acp/task/sender.py
self._writer.write(item.payload)
await self._writer.drain()
except asyncio.CancelledError:
self._set_terminal(ConnectionError("Sender closed"), cancel_pending=True)

@frostming frostming Nov 9, 2026 •

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.

Suggested change
self._set_terminal(ConnectionError("Sender closed"), cancel_pending=True)
item.future.cancel()

It seems unnecessarily complicated, what does it do differently than this simple change?
send() needs to listen to a completeness flag of course.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for reviewing this, @frostming. For the active send when the loop is cancelled, I agree that item.future.cancel() is sufficient.

With the terminal flag you mentioned, the remaining cases are cancelling the queued futures too, and detecting cancellation before _loop() starts (its exception handler never runs in that case, which is why there is a task-done callback). The writer-failure path separately preserves the original exception, such as BrokenPipeError, for active, queued and later sends instead of turning it into cancellation.

Those are the cases covered by the sender lifecycle regressions and the two-request connection regression. A simpler version using direct cancellation plus the terminal flag, queue cleanup and pre-start handling could preserve the same behaviour; the helper itself is not required.

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.

ok, then we can add the _on_task_done callback to clear all pending futures.

@Ehsan-hash

Copy link
Copy Markdown
Author

Thanks for the follow-up, @frostming. I'm closing this PR because I can't carry the proposed revision through validation and review. The patch and regression tests remain available if useful.

@Ehsan-hash Ehsan-hash closed this Nov 9, 2026
@frostming

Copy link
Copy Markdown
Contributor

Thanks for the follow-up, @frostming. I'm closing this PR because I can't carry the proposed revision through validation and review. The patch and regression tests remain available if useful.

Thank you, I like simplicity instead of throwing a prompt to the agent and ask it to make the CI green. Codex especially likes to make things complicated. If you look at the effect of this class as a whole, you will find that #190 can accomplish the same thing, and the problem you mentioned does not exist.

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.

2 participants