+52
−9
Loading
relayLogStream copied a pod-log follow to the client with a plain flusher.Flush per event. On a client that stays connected but stops reading (its TCP receive window shut), net/http buffers the small "data:" line and only touches the socket at Flush, which then blocks forever inside the write. The select's <-ctx.Done() branch is never reached, because r.Context() cancels on an actual disconnect, not on a stall, so the relay goroutine and its upstream apiserver follow leak for the life of the process. Route every event's write+flush through http.ResponseController with a per-write deadline (writeTimeout, 30s): a stalled flush now returns os.ErrDeadlineExceeded, the error plain http.Flusher.Flush swallows, and the relay abandons the stream so the deferred cancel + src.Close release the follow. SetWriteDeadline and rc.Flush are best-effort: a writer without deadline support (httptest recorder; some HTTP/2 origins) ignores the deadline and behaves exactly as before, so the guard degrades gracefully. This closes the leak the per-principal stream cap only bounded the blast radius of. Verified by a deterministic test with a deadline-aware ResponseWriter whose flush blocks until the deadline; the test times out (fails closed) if the guard is removed.