mirror of
https://github.com/VictoriaMetrics/VictoriaMetrics.git
synced 2026-07-20 15:49:48 +03:00
lib/httpserver: cancel in-flight requests when graceful shutdown times out
`http.Server.Shutdown()` waits for in-flight requests but never cancels them, so long-lived ones (e.g. VictoriaLogs live tailing) make graceful shutdown timeout, and the resulting `logger.Fatalf` -> `os.Exit` skips the storage flush and loses data. So adding a cancelable `BaseContext` that is canceled once `-http.maxGracefulShutdownDuration` elapses. Updates https://github.com/VictoriaMetrics/VictoriaLogs/issues/1502 Also notes that VictoriaMetrics query execution is deadline-driven and ignores ctx, so it's a no-op there.
This commit is contained in:
@@ -58,10 +58,13 @@ var (
|
||||
|
||||
disableKeepAlive = flag.Bool("http.disableKeepAlive", false, "Whether to disable HTTP keep-alive for incoming connections at -httpListenAddr")
|
||||
disableResponseCompression = flag.Bool("http.disableResponseCompression", false, "Disable compression of HTTP responses to save CPU resources. By default, compression is enabled to save network bandwidth")
|
||||
maxGracefulShutdownDuration = flag.Duration("http.maxGracefulShutdownDuration", 7*time.Second, `The maximum duration for a graceful shutdown of the HTTP server. A highly loaded server may require increased value for a graceful shutdown`)
|
||||
shutdownDelay = flag.Duration("http.shutdownDelay", 0, `Optional delay before http server shutdown. During this delay, the server returns non-OK responses from /health page, so load balancers can route new requests to other servers`)
|
||||
idleConnTimeout = flag.Duration("http.idleConnTimeout", time.Minute, "Timeout for incoming idle http connections")
|
||||
connTimeout = flag.Duration("http.connTimeout", 2*time.Minute, "Incoming connections to -httpListenAddr are closed after the configured timeout. "+
|
||||
maxGracefulShutdownDuration = flag.Duration("http.maxGracefulShutdownDuration", 7*time.Second, "The maximum duration for a graceful shutdown of the HTTP server. "+
|
||||
"During this period the server stops accepting new connections, but it will continue serving existing connections. "+
|
||||
"The remaining in-flight requests are canceled before the deadline, so the shutdown can finish within this duration. "+
|
||||
"A highly loaded server may require increased value for a graceful shutdown")
|
||||
shutdownDelay = flag.Duration("http.shutdownDelay", 0, `Optional delay before http server shutdown. During this delay, the server returns non-OK responses from /health page, so load balancers can route new requests to other servers`)
|
||||
idleConnTimeout = flag.Duration("http.idleConnTimeout", time.Minute, "Timeout for incoming idle http connections")
|
||||
connTimeout = flag.Duration("http.connTimeout", 2*time.Minute, "Incoming connections to -httpListenAddr are closed after the configured timeout. "+
|
||||
"This may help evenly spreading load among a cluster of services behind TCP-level load balancer. Zero value disables closing of incoming connections")
|
||||
|
||||
headerHSTS = flag.String("http.header.hsts", "", "Value for 'Strict-Transport-Security' header, recommended: 'max-age=31536000; includeSubDomains'")
|
||||
@@ -80,6 +83,7 @@ var (
|
||||
type server struct {
|
||||
shutdownDelayDeadline atomic.Int64
|
||||
s *http.Server
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// RequestHandler must serve the given request r and write response to w.
|
||||
@@ -156,7 +160,11 @@ func serve(addr string, rh RequestHandler, idx int, opts ServeOptions) {
|
||||
func serveWithListener(addr string, ln net.Listener, rh RequestHandler, disableBuiltinRoutes bool) {
|
||||
var s server
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
s.s = &http.Server{
|
||||
BaseContext: func(l net.Listener) context.Context {
|
||||
return ctx
|
||||
},
|
||||
|
||||
// Disable http/2, since it doesn't give any advantages for VictoriaMetrics services.
|
||||
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
|
||||
@@ -170,6 +178,7 @@ func serveWithListener(addr string, ln net.Listener, rh RequestHandler, disableB
|
||||
ErrorLog: log.New(&tlsErrorSkipLogger{}, "", 0),
|
||||
}
|
||||
s.s.SetKeepAlivesEnabled(!*disableKeepAlive)
|
||||
s.cancel = cancel
|
||||
if *connTimeout > 0 {
|
||||
s.s.ConnContext = func(ctx context.Context, _ net.Conn) context.Context {
|
||||
timeoutSec := connTimeout.Seconds()
|
||||
@@ -265,8 +274,18 @@ func stop(addr string) error {
|
||||
logger.Infof("Starting shutdown for http server %q", addr)
|
||||
}
|
||||
|
||||
// Cancel in-flight requests shortly before the deadline, reserving up to 2s (or 20%
|
||||
// of the window, whichever is smaller) for them to unwind, so Shutdown returns cleanly
|
||||
// within -http.maxGracefulShutdownDuration instead of timing out and dying via
|
||||
// logger.Fatalf -> os.Exit, which skips the storage flush and loses data.
|
||||
// See https://github.com/VictoriaMetrics/VictoriaLogs/issues/1502
|
||||
cancelInflightAfter := *maxGracefulShutdownDuration - min(*maxGracefulShutdownDuration/5, 2*time.Second)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), *maxGracefulShutdownDuration)
|
||||
defer cancel()
|
||||
|
||||
t := time.AfterFunc(cancelInflightAfter, s.cancel)
|
||||
defer t.Stop()
|
||||
|
||||
if err := s.s.Shutdown(ctx); err != nil {
|
||||
return fmt.Errorf("cannot gracefully shutdown http server at %q in %.3fs; "+
|
||||
"probably, `-http.maxGracefulShutdownDuration` command-line flag value must be increased; error: %s", addr, maxGracefulShutdownDuration.Seconds(), err)
|
||||
|
||||
Reference in New Issue
Block a user