lib/httpserver: reduce typical duration for http server graceful shutdown

Previously the duration for graceful shutdown for http server could take more than a minute
because of imporperly set timeouts in setNetworkTimeout.
Now typical duration for graceful shutdown should be reduced to less than 5 seconds.
This commit is contained in:
Aliaksandr Valialkin 2020-05-07 14:10:40 +03:00
parent 6afb25fd08
commit 787fcfba0c
4 changed files with 10 additions and 66 deletions

View File

@ -71,7 +71,6 @@ func Serve(addr string, rh RequestHandler) {
if err != nil { if err != nil {
logger.Fatalf("cannot start http server at %s: %s", addr, err) logger.Fatalf("cannot start http server at %s: %s", addr, err)
} }
setNetworkTimeouts(lnTmp)
ln := net.Listener(lnTmp) ln := net.Listener(lnTmp)
if *tlsEnable { if *tlsEnable {
@ -87,29 +86,18 @@ func Serve(addr string, rh RequestHandler) {
serveWithListener(addr, ln, rh) serveWithListener(addr, ln, rh)
} }
func setNetworkTimeouts(ln *netutil.TCPListener) {
// Set network-level read and write timeouts to reasonable values
// in order to protect from DoS or broken networks.
// Application-level timeouts must be set by the authors of request handlers.
//
// The read timeout limits the life of idle connection
ln.ReadTimeout = time.Minute
ln.WriteTimeout = time.Minute
}
func serveWithListener(addr string, ln net.Listener, rh RequestHandler) { func serveWithListener(addr string, ln net.Listener, rh RequestHandler) {
s := &http.Server{ s := &http.Server{
Handler: gzipHandler(rh), Handler: gzipHandler(rh),
// Disable http/2 // Disable http/2, since it doesn't give any advantages for VictoriaMetrics services.
TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)),
// Do not set ReadTimeout and WriteTimeout here. ReadHeaderTimeout: 5 * time.Second,
// Network-level timeouts are set in setNetworkTimeouts. IdleTimeout: time.Minute,
// Application-level timeouts must be set in the app.
// Do not set IdleTimeout, since it is equivalent to read timeout // Do not set ReadTimeout and WriteTimeout here,
// set in setNetworkTimeouts. // since these timeouts must be controlled by request handlers.
ErrorLog: logger.StdErrorLogger(), ErrorLog: logger.StdErrorLogger(),
} }

View File

@ -43,9 +43,11 @@ func MustStart(addr string, insertHandler func(req *http.Request) error) *Server
func MustServe(ln net.Listener, insertHandler func(req *http.Request) error) *Server { func MustServe(ln net.Listener, insertHandler func(req *http.Request) error) *Server {
h := newRequestHandler(insertHandler) h := newRequestHandler(insertHandler)
hs := &http.Server{ hs := &http.Server{
Handler: h, Handler: h,
ReadTimeout: 30 * time.Second, ReadHeaderTimeout: 5 * time.Second,
WriteTimeout: 10 * time.Second, IdleTimeout: time.Minute,
// Do not set ReadTimeout and WriteTimeout here,
// since these timeouts must be controlled by request handler.
} }
s := &Server{ s := &Server{
s: hs, s: hs,

View File

@ -5,7 +5,6 @@ import (
"io" "io"
"net" "net"
"sync/atomic" "sync/atomic"
"time"
"github.com/VictoriaMetrics/metrics" "github.com/VictoriaMetrics/metrics"
) )
@ -48,29 +47,12 @@ type statConn struct {
closeCalls uint64 closeCalls uint64
readTimeout time.Duration
lastReadTime time.Time
writeTimeout time.Duration
lastWriteTime time.Time
net.Conn net.Conn
cm *connMetrics cm *connMetrics
} }
func (sc *statConn) Read(p []byte) (int, error) { func (sc *statConn) Read(p []byte) (int, error) {
if sc.readTimeout > 0 {
t := time.Now()
if t.Sub(sc.lastReadTime) > sc.readTimeout>>4 {
d := t.Add(sc.readTimeout)
if err := sc.Conn.SetReadDeadline(d); err != nil {
// This error may occur when the client closes the connection before setting the deadline
return 0, err
}
}
}
n, err := sc.Conn.Read(p) n, err := sc.Conn.Read(p)
sc.cm.readCalls.Inc() sc.cm.readCalls.Inc()
sc.cm.readBytes.Add(n) sc.cm.readBytes.Add(n)
@ -85,17 +67,6 @@ func (sc *statConn) Read(p []byte) (int, error) {
} }
func (sc *statConn) Write(p []byte) (int, error) { func (sc *statConn) Write(p []byte) (int, error) {
if sc.writeTimeout > 0 {
t := time.Now()
if t.Sub(sc.lastWriteTime) > sc.writeTimeout>>4 {
d := t.Add(sc.writeTimeout)
if err := sc.Conn.SetWriteDeadline(d); err != nil {
// This error may accour when the client closes the connection before setting the deadline
return 0, err
}
}
}
n, err := sc.Conn.Write(p) n, err := sc.Conn.Write(p)
sc.cm.writeCalls.Inc() sc.cm.writeCalls.Inc()
sc.cm.writtenBytes.Add(n) sc.cm.writtenBytes.Add(n)

View File

@ -49,20 +49,6 @@ func getNetwork() string {
// //
// It also gathers various stats for the accepted connections. // It also gathers various stats for the accepted connections.
type TCPListener struct { type TCPListener struct {
// ReadTimeout is timeout for each Read call on accepted conns.
//
// By default it isn't set.
//
// Set ReadTimeout before calling Accept the first time.
ReadTimeout time.Duration
// WriteTimeout is timeout for each Write call on accepted conns.
//
// By default it isn't set.
//
// Set WriteTimeout before calling Accept the first time.
WriteTimeout time.Duration
net.Listener net.Listener
accepts *metrics.Counter accepts *metrics.Counter
@ -87,9 +73,6 @@ func (ln *TCPListener) Accept() (net.Conn, error) {
} }
ln.conns.Inc() ln.conns.Inc()
sc := &statConn{ sc := &statConn{
readTimeout: ln.ReadTimeout,
writeTimeout: ln.WriteTimeout,
Conn: conn, Conn: conn,
cm: &ln.connMetrics, cm: &ln.connMetrics,
} }