server: keep hijacked reader out of pool (#2184)

When KeepHijackedConns is enabled, the hijacked connection may outlive the
HijackHandler. The wrapper continues reading through the buffered reader
after the handler returns, so returning that reader to the pool can let
another connection reset it while the hijacked connection is still in use.

Keep the buffered reader owned by the escaped hijacked connection in
keep-open mode. Add a regression test that forces reader-pool reuse
and verifies buffered data remains available after the handler returns.
This commit is contained in:
Erik Dubbelboer
2026-04-27 12:28:40 +09:00
committed by GitHub
parent 19b39dd08a
commit 3ac3b24635
2 changed files with 48 additions and 1 deletions
+3 -1
View File
@@ -2667,7 +2667,9 @@ func hijackConnHandler(ctx *RequestCtx, r io.Reader, c net.Conn, s *Server, h Hi
hjc := s.acquireHijackConn(r, c)
h(hjc)
if br, ok := r.(*bufio.Reader); ok {
// When the caller keeps using the hijacked connection after return,
// the buffered reader must remain owned by that escaped connection.
if br, ok := r.(*bufio.Reader); ok && !s.KeepHijackedConns {
releaseReader(s, br)
}
if !s.KeepHijackedConns {
+45
View File
@@ -2933,6 +2933,51 @@ func TestRequestCtxHijackReduceMemoryUsage(t *testing.T) {
})
}
func TestRequestCtxHijackKeepHijackedConnsKeepsReaderOutOfPool(t *testing.T) {
t.Parallel()
s := &Server{
KeepHijackedConns: true,
}
ctx := &RequestCtx{s: s}
firstConn := &readWriter{}
firstConn.r.WriteString("first")
secondConn := &readWriter{}
secondConn.r.WriteString("second")
br := bufio.NewReaderSize(firstConn, 1)
var hijacked net.Conn
hijackConnHandler(ctx, br, firstConn, s, func(c net.Conn) {
hijacked = c
})
if hijacked == nil {
t.Fatal("expected hijacked connection")
}
if v := s.readerPool.Get(); v != nil {
v.(*bufio.Reader).Reset(secondConn)
}
buf := make([]byte, len("first"))
if _, err := io.ReadFull(hijacked, buf); err != nil {
t.Fatalf("unexpected read error from hijacked connection: %v", err)
}
if string(buf) != "first" {
t.Fatalf("unexpected hijacked data %q. Expecting %q", buf, "first")
}
if err := hijacked.Close(); err != nil {
t.Fatalf("unexpected close error from hijacked connection: %v", err)
}
if v := s.readerPool.Get(); v != nil {
t.Fatal("did not expect hijacked reader to be released after close")
}
}
func TestRequestCtxHijackNoResponse(t *testing.T) {
t.Parallel()