Skip to content

fix(server): default a zero SSE write timeout to 120s - #1686

Merged
wolo-lab merged 3 commits into
google:mainfrom
Vivek1106-04:fix/sse-write-timeout-zero
Oct 4, 2026
Merged

wolo-lab merged 3 commits into
google:mainfrom
Vivek1106-04:fix/sse-write-timeout-zero

Conversation

@Vivek1106-04

@Vivek1106-04 Vivek1106-04 commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Linked issue

2. Describe the change:

Problem:

RuntimeAPIController and AgentEngineAPIController set the write deadline to time.Now().Add(sseTimeout). When the timeout is left at zero (the zero value of adkrest.ServerConfig.SSEWriteTimeout, or 0 passed to agentengine.NewHandler), the deadline has already passed, so every /run_sse response and every Agent Engine query is lost and the client sees EOF. The launcher always passes 120s, so it only hits people who build the server themselves, and the field was undocumented.

Solution:

Treat a zero or negative timeout as 120s, the launcher's --sse-write-timeout default, in both controller constructors (same place an unset Authorizer gets its default). Document the behaviour on ServerConfig.SSEWriteTimeout and agentengine.NewHandler.

I went with a default rather than "zero means no deadline" (as in http.Server.WriteTimeout) to keep the slow-client protection. Happy to switch if maintainers prefer the other.

Behavior change

A server built without a timeout now serves /run_sse and Agent Engine queries with a 120s write deadline instead of failing every request. Servers that set a positive timeout are unchanged.

Testing Plan

Unit Tests:

  • All unit tests pass locally.

With your source change reverted and your tests kept, which test fails?

--- FAIL: TestRunSSEWithoutSSEWriteTimeout (0.00s)
    sse_timeout_integration_test.go:51: POST /run_sse error = Post "http://127.0.0.1:61856/run_sse": EOF
--- FAIL: TestQueryWithoutSSEWriteTimeout (0.00s)
    handler_test.go:93: POST /reasoning_engine error = Post "http://127.0.0.1:61915/reasoning_engine": EOF

Both run against a real httptest.Server, since httptest.ResponseRecorder does not support write deadlines.

With the change, go test -race ./server/... ./cmd/... passes, and gofmt / golangci-lint are clean.

Manual End-to-End (E2E) Tests:

Found while embedding adkrest.NewServer in a small program without setting the timeout: /run_sse returned EOF until SSEWriteTimeout was set.

Checklist

  • I have read the CONTRIBUTING.md document.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • I have added tests that prove my fix is effective or that my feature works.
  • New and existing unit tests pass locally with my changes.
  • I have manually tested my changes end-to-end.
  • Any dependent changes have been merged and published in downstream modules.

adkrest and agentengine set the write deadline to now + the configured
SSE timeout. Left at zero, the deadline was the moment the request
arrived, so every /run_sse response (and every Agent Engine query) was
lost and the client saw EOF.

Treat zero or negative as 120s, the launcher's --sse-write-timeout
default, and document it on ServerConfig.SSEWriteTimeout and
agentengine.NewHandler.

Closes: google#1685

@wolo-lab wolo-lab 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.

Defaulting a zero timeout to 120s is the right fix, and both new tests fail without it. One change before merge: please make a negative timeout mean "no deadline" instead of 120s.

Right now there is no way to turn the deadline off. It is set once when the request arrives, so it caps the whole stream rather than the gap between events, and a long agent run that is still streaming gets cut off at 120s. adk-python sets no timeout on /run_sse, so the same agent runs to completion there. http.Server uses zero for "no deadline", but here zero has to keep meaning the default so that a zero-value ServerConfig works. That leaves a negative value as the natural opt-out, and settling it in this PR avoids changing what a negative value means after release.

What that needs:

  • Constructors: apply the default only when the timeout is == 0, in both NewRuntimeAPIControllerWithConfig and NewAgentEngineAPIController.
  • Handlers: when the timeout is negative, pass a zero time.Time to SetWriteDeadline rather than skipping the call. The web launcher's http.Server has its own WriteTimeout, 15s by default (web.go#L369), and it stays in force unless the handler clears it.
  • Doc comments: update the three that say "zero or negative means 120 seconds" (ServerConfig.SSEWriteTimeout, NewAgentEngineAPIController, agentengine.NewHandler). Please also add one to RuntimeAPIControllerConfig.SSETimeout, which has none, so someone mounting the controller directly can find the default.
  • Tests: add a negative case to each new test, for example by having the test's SetWriteDeadline stub record its argument and asserting it is zero. The current tests don't exercise a negative value at all.

Zero still means the 120s default so a zero-value ServerConfig works.
A negative timeout now passes a zero time to SetWriteDeadline, which
also clears the http.Server's own WriteTimeout, so long agent runs can
stream to completion as they do in adk-python.
@Vivek1106-04

Copy link
Copy Markdown
Contributor Author

@wolo-lab Thanks, that makes sense. Zero now falls back to 120s and a negative value means no deadline: the handlers pass a zero time to SetWriteDeadline, so the server's own WriteTimeout gets cleared too. I updated the three doc comments, added one on RuntimeAPIControllerConfig.SSETimeout, and made both tests table-driven (zero, positive, negative), with a stub that records the deadline that gets set.

@wolo-lab wolo-lab 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.

Thanks, all addressed.

@wolo-lab
wolo-lab merged commit abb1dda into google:main Oct 4, 2026
14 checks passed
wolo-lab added a commit that referenced this pull request Oct 14, 2026
* fix(server): default a zero SSE write timeout to 120s (#1686)

(cherry picked from commit abb1dda)

* fix(adkrest): render nested workflow clusters in graph images (#1724)

(cherry picked from commit 49904c0)

* fix(adkrest): bound /run_live messages, dead peers and concurrent sessions (#1648)

(cherry picked from commit 713449b)

* test(adkrest): pin the 40s live keepalive default as a literal

The default row compared against defaultLiveKeepaliveTimeout itself, so
changing the constant kept the suite green. On 1.x there is no field to
override it, which makes the value part of the behavior.
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.

adkrest/agentengine: zero SSEWriteTimeout makes every response fail

2 participants