Skip to content

fix(a2a): abort the in-flight HTTP exchange when a streaming call is cancelled (issue #277) - #336

Merged
yuluo-yx merged 1 commit into
agentic-ai-java:mainfrom
konlue:fix/a2a-stream-cancel
Oct 30, 2026
Merged

yuluo-yx merged 1 commit into
agentic-ai-java:mainfrom
konlue:fix/a2a-stream-cancel

Conversation

@konlue

@konlue konlue commented Oct 29, 2026

Copy link
Copy Markdown
Contributor

Describe what this PR does / why we need it

Fixes #277: cancelling a streaming A2A call only stopped event delivery. The producer runs a synchronous HttpClient5 exchange on a bounded-elastic worker, so when the remote peer had already sent response headers and then stayed silent, the cancellation was never observed: the worker remained blocked in readLine(), and the connection was only closed once execute returned. Cancelling therefore neither released the bulkhead thread nor the connection.

Does this pull request fix one issue?

Fixes #277

Describe how you did it

  • The subscription cancellation is now bound to the transport: sink.onCancel(...) calls HttpPost#cancel() (aborts the exchange when the request is already executing) and closes the CloseableHttpClient created for this invocation (covers the window before the request is bound to an execution and unblocks a reader whose peer went quiet). Both steps are idempotent, and the client is per-invocation, so no shared pool or shared client is ever closed.
  • A sink.isCancelled() check right before execute avoids starting a request that was cancelled while the producer was still being set up.
  • Cancellation is no longer attributed to the transport: span success/error marking is guarded by !sink.isCancelled(); non-cancellation failures keep the existing error text and done terminal (unchanged contract, cf. [BUG][OPT-22] A2A 传输与解析失败应传播结构化错误而非正常文本完成 #213). Cancelled runs emit neither.
  • No overall read timeout is introduced: SSE streams are long-lived by design, and a fixed read timeout would kill healthy streams. Cancellation is the active boundary.

How was it tested / verified?

New A2aStreamCancellationTest (offline, local com.sun.net.httpserver fixture):

  1. Silent event stream (one comment line, then no bytes): the worker is confirmed to be in the HTTP read stack after subscription and must leave it within 2s of dispose();
  2. Cancel before response headers arrive: same requirement;
  3. Cancel after one chunk: no error text and no done response may be appended;
  4. Isolation: cancelling a silent run must not disturb a concurrent run, which still receives its chunk and its done response.

Red before the fix (test run on pristine 04c5686fe): Tests run: 4, Failures: 3 — every cancellation case keeps the worker blocked. Green after: Tests run: 4, Failures: 0; module regression shows no new failures beyond the pre-existing Windows-only ones; checkstyle passes.

Additional notes

@yuluo-yx yuluo-yx left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

lgtm

@yuluo-yx
yuluo-yx merged commit b7094ee into agentic-ai-java:main Oct 30, 2026
7 checks passed
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][P1] A2A 流取消应中止阻塞的 HTTP 读取并释放连接与工作线程

2 participants