Skip to content

Commit 997ba3a

Browse files
introduce reader deadlines for net.Conn (minio#19023)
Bonus: set "retry-after" header for AWS SDKs if possible to honor them.
1 parent 8e68ff9 commit 997ba3a

12 files changed

+183
-142
lines changed

cmd/api-errors.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"fmt"
2525
"net/http"
2626
"net/url"
27+
"os"
2728
"strconv"
2829
"strings"
2930

@@ -297,7 +298,6 @@ const (
297298
ErrAdminConfigLDAPValidation
298299
ErrAdminConfigIDPCfgNameAlreadyExists
299300
ErrAdminConfigIDPCfgNameDoesNotExist
300-
ErrAdminCredentialsMismatch
301301
ErrInsecureClientRequest
302302
ErrObjectTampered
303303

@@ -1425,11 +1425,6 @@ var errorCodes = errorCodeMap{
14251425
Description: "Unable to perform the requested operation because profiling is not enabled",
14261426
HTTPStatusCode: http.StatusBadRequest,
14271427
},
1428-
ErrAdminCredentialsMismatch: {
1429-
Code: "XMinioAdminCredentialsMismatch",
1430-
Description: "Credentials in config mismatch with server environment variables",
1431-
HTTPStatusCode: http.StatusServiceUnavailable,
1432-
},
14331428
ErrAdminBucketQuotaExceeded: {
14341429
Code: "XMinioAdminBucketQuotaExceeded",
14351430
Description: "Bucket quota exceeded",
@@ -2082,6 +2077,11 @@ func toAPIErrorCode(ctx context.Context, err error) (apiErr APIErrorCode) {
20822077
return ErrNone
20832078
}
20842079

2080+
// Errors that are generated by net.Conn and any context errors must be handled here.
2081+
if errors.Is(err, os.ErrDeadlineExceeded) || errors.Is(err, context.DeadlineExceeded) {
2082+
return ErrRequestTimedout
2083+
}
2084+
20852085
// Only return ErrClientDisconnected if the provided context is actually canceled.
20862086
// This way downstream context.Canceled will still report ErrRequestTimedout
20872087
if contextCanceled(ctx) && errors.Is(ctx.Err(), context.Canceled) {

cmd/api-response.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -946,11 +946,13 @@ func writeSuccessResponseHeadersOnly(w http.ResponseWriter) {
946946

947947
// writeErrorRespone writes error headers
948948
func writeErrorResponse(ctx context.Context, w http.ResponseWriter, err APIError, reqURL *url.URL) {
949-
switch err.Code {
950-
case "SlowDown", "XMinioServerNotInitialized", "XMinioReadQuorum", "XMinioWriteQuorum":
949+
if err.HTTPStatusCode == http.StatusServiceUnavailable {
951950
// Set retry-after header to indicate user-agents to retry request after 120secs.
952951
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
953952
w.Header().Set(xhttp.RetryAfter, "120")
953+
}
954+
955+
switch err.Code {
954956
case "InvalidRegion":
955957
err.Description = fmt.Sprintf("Region does not match; expecting '%s'.", globalSite.Region)
956958
case "AuthorizationHeaderMalformed":

cmd/apierrorcode_string.go

+122-123
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

cmd/common-main.go

+1
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ func buildServerCtxt(ctx *cli.Context, ctxt *serverCtxt) (err error) {
394394
ctxt.UserTimeout = ctx.Duration("conn-user-timeout")
395395
ctxt.ConnReadDeadline = ctx.Duration("conn-read-deadline")
396396
ctxt.ConnWriteDeadline = ctx.Duration("conn-write-deadline")
397+
ctxt.ConnClientReadDeadline = ctx.Duration("conn-client-read-deadline")
397398

398399
ctxt.ShutdownTimeout = ctx.Duration("shutdown-timeout")
399400
ctxt.IdleTimeout = ctx.Duration("idle-timeout")

cmd/globals.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -160,9 +160,10 @@ type serverCtxt struct {
160160
FTP []string
161161
SFTP []string
162162

163-
UserTimeout time.Duration
164-
ConnReadDeadline time.Duration
165-
ConnWriteDeadline time.Duration
163+
UserTimeout time.Duration
164+
ConnReadDeadline time.Duration
165+
ConnWriteDeadline time.Duration
166+
ConnClientReadDeadline time.Duration
166167

167168
ShutdownTimeout time.Duration
168169
IdleTimeout time.Duration

cmd/server-main.go

+9-2
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,12 @@ var ServerFlags = []cli.Flag{
101101
EnvVar: "MINIO_READ_HEADER_TIMEOUT",
102102
Hidden: true,
103103
},
104+
cli.DurationFlag{
105+
Name: "conn-client-read-deadline",
106+
Usage: "custom connection READ deadline for incoming requests",
107+
Hidden: true,
108+
EnvVar: "MINIO_CONN_CLIENT_READ_DEADLINE",
109+
},
104110
cli.DurationFlag{
105111
Name: "conn-read-deadline",
106112
Usage: "custom connection READ deadline",
@@ -351,8 +357,9 @@ func serverHandleCmdArgs(ctxt serverCtxt) {
351357
})
352358

353359
globalTCPOptions = xhttp.TCPOptions{
354-
UserTimeout: int(ctxt.UserTimeout.Milliseconds()),
355-
Interface: ctxt.Interface,
360+
UserTimeout: int(ctxt.UserTimeout.Milliseconds()),
361+
ClientReadTimeout: ctxt.ConnClientReadDeadline,
362+
Interface: ctxt.Interface,
356363
}
357364

358365
// On macOS, if a process already listens on LOCALIPADDR:PORT, net.Listen() falls back

docs/distributed/decom-compressed-sse-s3.sh

+6-1
Original file line numberDiff line numberDiff line change
@@ -147,4 +147,9 @@ if [ $ret -ne 0 ]; then
147147
exit 1
148148
fi
149149

150-
# kill $pid
150+
go install github.com/minio/minio/docs/debugging/s3-check-md5@latest
151+
152+
s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://127.0.0.1:9001/ -bucket bucket2
153+
s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://127.0.0.1:9001/ -bucket versioned
154+
155+
kill $pid

docs/distributed/decom-encrypted-sse-s3.sh

+5
Original file line numberDiff line numberDiff line change
@@ -158,4 +158,9 @@ if [ $ret -ne 0 ]; then
158158
exit 1
159159
fi
160160

161+
go install github.com/minio/minio/docs/debugging/s3-check-md5@latest
162+
163+
s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://127.0.0.1:9001/ -bucket bucket2
164+
s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://127.0.0.1:9001/ -bucket versioned
165+
161166
kill $pid

docs/distributed/decom-encrypted.sh

+5
Original file line numberDiff line numberDiff line change
@@ -144,4 +144,9 @@ if [ "${expected_checksum}" != "${got_checksum}" ]; then
144144
exit 1
145145
fi
146146

147+
go install github.com/minio/minio/docs/debugging/s3-check-md5@latest
148+
149+
s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://127.0.0.1:9001/ -bucket bucket2
150+
s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://127.0.0.1:9001/ -bucket versioned
151+
147152
kill $pid

docs/distributed/decom.sh

+5
Original file line numberDiff line numberDiff line change
@@ -210,4 +210,9 @@ if [ "${expected_checksum}" != "${got_checksum}" ]; then
210210
exit 1
211211
fi
212212

213+
go install github.com/minio/minio/docs/debugging/s3-check-md5@latest
214+
215+
s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://127.0.0.1:9001/ -bucket bucket2
216+
s3-check-md5 -versions -access-key minioadmin -secret-key minioadmin -endpoint http://127.0.0.1:9001/ -bucket versioned
217+
213218
kill $pid

internal/http/listener.go

+11-4
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ import (
2222
"fmt"
2323
"net"
2424
"syscall"
25+
"time"
26+
27+
"github.com/minio/minio/internal/deadlineconn"
2528
)
2629

2730
type acceptResult struct {
@@ -32,6 +35,7 @@ type acceptResult struct {
3235

3336
// httpListener - HTTP listener capable of handling multiple server addresses.
3437
type httpListener struct {
38+
opts TCPOptions
3539
tcpListeners []*net.TCPListener // underlying TCP listeners.
3640
acceptCh chan acceptResult // channel where all TCP listeners write accepted connection.
3741
ctx context.Context
@@ -74,7 +78,8 @@ func (listener *httpListener) Accept() (conn net.Conn, err error) {
7478
select {
7579
case result, ok := <-listener.acceptCh:
7680
if ok {
77-
return result.conn, result.err
81+
return deadlineconn.New(result.conn).
82+
WithReadDeadline(listener.opts.ClientReadTimeout), result.err
7883
}
7984
case <-listener.ctx.Done():
8085
}
@@ -119,9 +124,10 @@ func (listener *httpListener) Addrs() (addrs []net.Addr) {
119124

120125
// TCPOptions specify customizable TCP optimizations on raw socket
121126
type TCPOptions struct {
122-
UserTimeout int // this value is expected to be in milliseconds
123-
Interface string // this is a VRF device passed via `--interface` flag
124-
Trace func(msg string) // Trace when starting.
127+
UserTimeout int // this value is expected to be in milliseconds
128+
ClientReadTimeout time.Duration // When the net.Conn is idle for more than ReadTimeout duration, we close the connection on the client proactively.
129+
Interface string // this is a VRF device passed via `--interface` flag
130+
Trace func(msg string) // Trace when starting.
125131
}
126132

127133
// newHTTPListener - creates new httpListener object which is interface compatible to net.Listener.
@@ -173,6 +179,7 @@ func newHTTPListener(ctx context.Context, serverAddrs []string, opts TCPOptions)
173179
listener = &httpListener{
174180
tcpListeners: tcpListeners,
175181
acceptCh: make(chan acceptResult, len(tcpListeners)),
182+
opts: opts,
176183
}
177184
listener.ctx, listener.ctxCanceler = context.WithCancel(ctx)
178185
if opts.Trace != nil {

internal/http/server.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,12 @@ func (srv *Server) Init(listenCtx context.Context, listenErrCallback func(listen
109109
wrappedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
110110
// If server is in shutdown.
111111
if atomic.LoadUint32(&srv.inShutdown) != 0 {
112-
// To indicate disable keep-alive
112+
// To indicate disable keep-alive, server is shutting down.
113113
w.Header().Set("Connection", "close")
114+
115+
// Add 1 minute retry header, incase-client wants to honor it
116+
w.Header().Set(RetryAfter, "60")
117+
114118
w.WriteHeader(http.StatusServiceUnavailable)
115119
w.Write([]byte(http.ErrServerClosed.Error()))
116120
return

0 commit comments

Comments
 (0)