Does the Kubernetes API server end a watch stream promptly when the client cancels its context?
0 reputation · 18 Jun 2026, 20:43 UTC
0 reputation · 18 Jun 2026, 20:43 UTC
client-go's watch interfaces take a context.Context, and cancelling it is the documented way to stop a watch. On the wire, though, a watch is one-way server push: once the stream is established there is no in-band cancellation frame, so the server has to infer that the client is gone.
How quickly it infers that appears to depend on the transport. Over HTTP/2 the client can reset the stream; over HTTP/1.1 the server may only notice when it writes the next event, or when the timeout behind --min-request-timeout fires. Watch cache, aggregated API servers, and proxies in the path could each change the picture.
Assume a recent stable cluster (say 1.34–1.37) with matching client-go and default HTTP/2 negotiation. The goal is to decide whether application code can rely on prompt server-side teardown after context cancellation, or whether operators should treat lingering watch handlers as normal and size timeouts accordingly.
35275 reputation · 19 Jun 2026, 03:04 UTC
On HTTP/2 (the default for Kubernetes 1.34–1.37), cancelling the client-go context sends an RST_STREAM frame that the Go http2 server translates into a request-context cancellation. The watch handler stops within milliseconds, without waiting for the next event. On HTTP/1.1 there is no in-band signal; the server only notices when it tries to write the next event or when --min-request-timeout (default 30 minutes) expires. This transport-level difference is the dominant factor; the Kubernetes version in the 1.34–1.37 range does not change the behavior.
context.CancelFunc → the HTTP/2 client sends RST_STREAM (error code CANCEL or NO_ERROR).http.Request.Context() that the API server's watch handler is already observing.select on that context returns, the watch is removed from the watch cache, and the goroutine exits. Typical latency is sub-millisecond to a few milliseconds, bounded only by how quickly the server's event loop processes the cancellation.--min-request-timeout.The watch handler has used request.Context() for cancellation since before 1.34. No release in the 1.34–1.37 window introduced changes to this path. Aggregated API servers and the --watch-cache-sizes flag forward the same request context, so they do not add measurable latency.
If a proxy (cloud LB, kube-proxy in certain modes, service mesh sidecar) terminates HTTP/2 and re-originates HTTP/1.1 to the API server, the prompt RST_STREAM behavior is lost. The API server then behaves as if the client spoke HTTP/1.1. Verify the end-to-end protocol with a packet capture on the API server's secure port.
--vmodule=watch*=5 (or --v=5 filtered to watch logs) to emit timestamped "watch handler started/stopped" lines.context.WithTimeout(ctx, 500*time.Millisecond)).--http2-max-streams-per-connection=1 on the API server to force HTTP/1.1 and observe the longer tail.tcpdump -i any port 6443 -w watch.pcap) and confirm RST_STREAM arrives before the handler-stop log line.Application code can rely on prompt server-side teardown if the full path is HTTP/2. Operators should still size timeouts for the HTTP/1.1 worst case (next event write or --min-request-timeout) unless they have verified HTTP/2 all the way to the API server. Lingering watch handlers after a client cancel are normal on HTTP/1.1 and should not be treated as a leak.
Confirm whether your cluster's ingress path (load balancer, API server --http2-max-streams-per-connection setting, or service mesh) preserves HTTP/2 to the API server. If it does not, plan for HTTP/1.1 teardown latency.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.