Skip to content

agent: set per-request write deadline for blocking queries - #24042

Open
satroutr wants to merge 2 commits into
hashicorp:mainfrom
satroutr:fix/blocking-query-write-deadline
Open

satroutr wants to merge 2 commits into
hashicorp:mainfrom
satroutr:fix/blocking-query-write-deadline

Conversation

@satroutr

@satroutr satroutr commented Oct 15, 2026 •

Copy link
Copy Markdown

What

Set a per-request write deadline for blocking queries so a long-lived, reused HTTP/2 connection is not torn down mid-watch by the global per-connection http.Server.WriteTimeout.

Fixes #24041.

Why

http_config.write_timeout is enforced as a global, per-connection http.Server.WriteTimeout. On a reused HTTP/2 connection carrying back-to-back blocking queries (for example the built-in connect proxy's connect_roots watch), the in-flight stream is reset with INTERNAL_ERROR once write_timeout elapses from connection start — regardless of how long the current request has run. #23267 raised the default to 15m, which only changes the reset cadence; it does not eliminate it for a persistently reused connection. Full root-cause analysis and reproduction are in #24041.

How

In HTTPHandlers.wrap, blocking queries (identified by the ?index param, per parseWait) get a per-request write deadline via http.NewResponseController(resp).SetWriteDeadline(...), bounded by max_query_time (or the request's shorter wait) plus jitter and an rpc_hold_timeout buffer, never shorter than the configured write_timeout. Non-blocking requests keep the global deadline, preserving the Slowloris protections from #22739 (read_header_timeout, idle_timeout).

Testing

  • go build ./agent/ and go vet ./agent/ pass locally (including the new test file).
  • Added TestHTTPWriteTimeout_BlockingQueryNotCutByGlobalTimeout: with write_timeout = "2s", a blocking query on the current index with wait=4s returns valid JSON instead of being reset.
  • A full go test ./agent/ -run TestHTTPWriteTimeout run and maintainer review of the design are still pending — which is why this is opened as a draft.

@satroutr
satroutr marked this pull request as ready for review October 15, 2026 07:47
@satroutr
satroutr requested review from a team as code owners October 15, 2026 07:47
The global http_config.write_timeout is enforced as a per-connection
http.Server.WriteTimeout. On a long-lived, reused HTTP/2 connection
carrying back-to-back blocking queries (for example the built-in connect
proxy's connect_roots watch), the in-flight stream is reset with
INTERNAL_ERROR once write_timeout elapses from connection start,
regardless of how long the current request has actually run. Raising the
default to 15m (hashicorp#23267) only changes the reset cadence; it does not
eliminate it for a persistently reused connection.

Set the write deadline per blocking-query request (identified by the
?index param) based on max_query_time plus jitter and an rpc_hold_timeout
buffer, never shorter than the configured write_timeout, so a long-poll
is bounded by its own duration. Non-blocking requests keep the global
deadline for slow-drain protection, preserving the Slowloris hardening
from hashicorp#22739.

Fixes hashicorp#24041

Signed-off-by: satroutr@cisco.com <satroutr@cisco.com>
@satroutr
satroutr force-pushed the fix/blocking-query-write-deadline branch from d31f4c7 to d1ad091 Compare October 15, 2026 09:29

This branch has not been deployed

No deployments
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.

Global http_config.write_timeout resets long-lived blocking-query HTTP/2 streams (connect_roots watch)

1 participant