Files
minio/docs/investigations/r8/review/implementation.patch
T
Feng Ruohang 055030ea53 fix(http): honor configured request header deadlines
Signed-off-by: Feng Ruohang <rh@vonng.com>
(cherry picked from commit 0d48d32d7e038ae1ea5966f3d7e0cb86780a6311)
Signed-off-by: Feng Ruohang <rh@vonng.com>
2026-09-16 00:37:56 +08:00

138 lines
5.0 KiB
Diff

diff --git a/cmd/server-main.go b/cmd/server-main.go
index 48ed0f87d..c8a3a19ca 100644
--- a/cmd/server-main.go
+++ b/cmd/server-main.go
@@ -901,6 +901,8 @@ func serverMain(ctx *cli.Context) {
close(globalGridStart)
close(globalLockGridStart)
+ // The HTTP/1 listener preserves absolute header deadlines and renews the
+ // body read/write idle limits, so transfers may outlast IdleTimeout.
httpServer := xhttp.NewServer(getServerListenAddrs()).
UseHandler(setCriticalErrorHandler(corsHandler(handler))).
UseTLSConfig(newTLSConfig(getCert)).
diff --git a/internal/deadlineconn/deadlineconn.go b/internal/deadlineconn/deadlineconn.go
index 95bb43eff..5fa5a1403 100644
--- a/internal/deadlineconn/deadlineconn.go
+++ b/internal/deadlineconn/deadlineconn.go
@@ -34,6 +34,8 @@ type DeadlineConn struct {
net.Conn
readDeadline time.Duration // sets the read deadline on a connection.
readSetAt time.Time
+ readExplicit time.Time // last deadline requested by the caller.
+ readDeadlineStrict bool // idle renewal must not extend readExplicit.
writeDeadline time.Duration // sets the write deadline on a connection.
writeSetAt time.Time
abortReads, abortWrites atomic.Bool // A deadline was set to indicate caller wanted the conn to time out.
@@ -59,17 +61,31 @@ func (c *DeadlineConn) setReadDeadline() {
c.mu.Lock()
defer c.mu.Unlock()
- if c.abortReads.Load() {
+ if c.abortReads.Load() || c.infReads.Load() {
return
}
now := time.Now()
if now.Sub(c.readSetAt) > updateInterval {
- c.Conn.SetReadDeadline(now.Add(c.readDeadline + updateInterval))
+ deadline := now.Add(c.readDeadline + updateInterval)
+ if c.readDeadlineStrict && !c.readExplicit.IsZero() && c.readExplicit.Before(deadline) {
+ deadline = c.readExplicit
+ }
+ c.Conn.SetReadDeadline(deadline)
c.readSetAt = now
}
}
+// SetReadDeadlineStrict controls whether idle renewal may extend a deadline set
+// by SetReadDeadline or SetDeadline. The default is false. Explicit zero and
+// past deadlines retain their disable/cancel semantics in either mode.
+func (c *DeadlineConn) SetReadDeadlineStrict(strict bool) {
+ c.mu.Lock()
+ defer c.mu.Unlock()
+ c.readDeadlineStrict = strict
+ c.readSetAt = time.Time{}
+}
+
func (c *DeadlineConn) setWriteDeadline() {
// Do not set a Write deadline, if upstream wants to cancel all reads.
if c.writeDeadline <= 0 || c.abortWrites.Load() || c.infWrites.Load() {
@@ -115,6 +131,7 @@ func (c *DeadlineConn) SetDeadline(t time.Time) error {
defer c.mu.Unlock()
c.readSetAt = time.Time{}
+ c.readExplicit = t
c.writeSetAt = time.Time{}
c.abortReads.Store(!t.IsZero() && time.Until(t) < 0)
c.abortWrites.Store(!t.IsZero() && time.Until(t) < 0)
@@ -132,6 +149,7 @@ func (c *DeadlineConn) SetReadDeadline(t time.Time) error {
c.abortReads.Store(!t.IsZero() && time.Until(t) < 0)
c.infReads.Store(t.IsZero())
c.readSetAt = time.Time{}
+ c.readExplicit = t
return c.Conn.SetReadDeadline(t)
}
diff --git a/internal/http/listener.go b/internal/http/listener.go
index bc6de3af9..14d34f6ea 100644
--- a/internal/http/listener.go
+++ b/internal/http/listener.go
@@ -70,7 +70,10 @@ func (listener *httpListener) Accept() (conn net.Conn, err error) {
if result.err != nil {
return nil, result.err
}
- return deadlineconn.New(result.conn).WithReadDeadline(listener.opts.IdleTimeout).WithWriteDeadline(listener.opts.IdleTimeout), result.err
+ conn := deadlineconn.New(result.conn).WithReadDeadline(listener.opts.IdleTimeout).WithWriteDeadline(listener.opts.IdleTimeout)
+ // Server.Init switches to rolling reads only after HTTP/1 headers are read.
+ conn.SetReadDeadlineStrict(true)
+ return conn, nil
case <-listener.ctxDoneCh:
}
return nil, syscall.EINVAL
diff --git a/internal/http/server.go b/internal/http/server.go
index 2934fda6c..d9c19a33f 100644
--- a/internal/http/server.go
+++ b/internal/http/server.go
@@ -29,6 +29,7 @@ import (
"time"
"github.com/dustin/go-humanize"
+ "github.com/minio/minio/internal/deadlineconn"
)
var (
@@ -123,6 +124,32 @@ func (srv *Server) Init(listenCtx context.Context, listenErrCallback func(listen
srv.listener = listener
srv.listenerMutex.Unlock()
+ connState := srv.ConnState
+ srv.ConnState = func(conn net.Conn, state http.ConnState) {
+ raw := conn
+ if tlsConn, ok := raw.(*tls.Conn); ok {
+ if tlsConn.ConnectionState().NegotiatedProtocol == "h2" {
+ // HTTP/2 owns its stream deadlines; do not change the connection.
+ raw = nil
+ } else {
+ raw = tlsConn.NetConn()
+ }
+ }
+ if dc, ok := raw.(*deadlineconn.DeadlineConn); ok {
+ switch state {
+ case http.StateNew, http.StateIdle:
+ dc.SetReadDeadlineStrict(true)
+ case http.StateActive:
+ // net/http has finished reading the headers, including buffered
+ // requests. Keep ReadTimeout as a rolling idle limit for uploads.
+ dc.SetReadDeadlineStrict(false)
+ }
+ }
+ if connState != nil {
+ connState(conn, state)
+ }
+ }
+
var l net.Listener = listener
if tlsConfig != nil {
l = tls.NewListener(listener, tlsConfig)