From 5fdbeef505bbe1bc4716165dee7f1cbdb5093f66 Mon Sep 17 00:00:00 2001 From: Aliaksandr Valialkin Date: Fri, 15 Apr 2016 13:47:01 +0300 Subject: [PATCH] Server optimization: reduce the number of SetReadDeadline/SetWriteDeadline calls for keep-alive connections, since they don't scale properly. See https://github.com/golang/go/issues/15133 for details --- server.go | 99 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 66 insertions(+), 33 deletions(-) diff --git a/server.go b/server.go index 53d5d90..a9d9f82 100644 --- a/server.go +++ b/server.go @@ -1341,16 +1341,20 @@ func (s *Server) serveConn(c net.Conn) error { connRequestNum := uint64(0) ctx := s.acquireCtx(c) + ctx.connTime = connTime var ( br *bufio.Reader bw *bufio.Writer - ) - var ( + err error - connectionClose bool - isHTTP11 bool timeoutResponse *Response hijackHandler HijackHandler + + lastReadDeadlineTime time.Time + lastWriteDeadlineTime time.Time + + connectionClose bool + isHTTP11 bool ) for { ctx.id++ @@ -1358,19 +1362,10 @@ func (s *Server) serveConn(c net.Conn) error { ctx.time = currentTime if s.ReadTimeout > 0 || s.MaxKeepaliveDuration > 0 { - readTimeout := s.ReadTimeout - if s.MaxKeepaliveDuration > 0 { - connTimeout := s.MaxKeepaliveDuration - currentTime.Sub(connTime) - if connTimeout <= 0 { - err = ErrKeepaliveTimeout - break - } - if connTimeout < readTimeout { - readTimeout = connTimeout - } - } - if err = c.SetReadDeadline(currentTime.Add(readTimeout)); err != nil { - panic(fmt.Sprintf("BUG: error in SetReadDeadline(%s): %s", readTimeout, err)) + lastReadDeadlineTime = s.updateReadDeadline(c, ctx, lastReadDeadlineTime) + if lastReadDeadlineTime.IsZero() { + err = ErrKeepaliveTimeout + break } } @@ -1466,22 +1461,7 @@ func (s *Server) serveConn(c net.Conn) error { } if s.WriteTimeout > 0 || s.MaxKeepaliveDuration > 0 { - writeTimeout := s.WriteTimeout - if s.MaxKeepaliveDuration > 0 { - connTimeout := s.MaxKeepaliveDuration - time.Since(connTime) - if connTimeout <= 0 { - // MaxKeepAliveDuration exceeded, but let's try sending response anyway - // in 100ms with 'Connection: close' header. - ctx.SetConnectionClose() - connTimeout = 100 * time.Millisecond - } - if connTimeout < writeTimeout { - writeTimeout = connTimeout - } - } - if err = c.SetWriteDeadline(time.Now().Add(writeTimeout)); err != nil { - panic(fmt.Sprintf("BUG: error in SetWriteDeadline(%s): %s", writeTimeout, err)) - } + lastWriteDeadlineTime = s.updateWriteDeadline(c, ctx, lastWriteDeadlineTime) } // Verify Request.Header.connectionCloseFast() again, @@ -1554,6 +1534,59 @@ func (s *Server) serveConn(c net.Conn) error { return err } +func (s *Server) updateReadDeadline(c net.Conn, ctx *RequestCtx, lastDeadlineTime time.Time) time.Time { + readTimeout := s.ReadTimeout + currentTime := ctx.time + if s.MaxKeepaliveDuration > 0 { + connTimeout := s.MaxKeepaliveDuration - currentTime.Sub(ctx.connTime) + if connTimeout <= 0 { + return zeroTime + } + if connTimeout < readTimeout { + readTimeout = connTimeout + } + } + + // Optimization: update read deadline only if more than 25% + // of the last read deadline exceeded. + // See https://github.com/golang/go/issues/15133 for details. + if currentTime.Sub(lastDeadlineTime) > (readTimeout >> 2) { + if err := c.SetReadDeadline(currentTime.Add(readTimeout)); err != nil { + panic(fmt.Sprintf("BUG: error in SetReadDeadline(%s): %s", readTimeout, err)) + } + lastDeadlineTime = currentTime + } + return lastDeadlineTime +} + +func (s *Server) updateWriteDeadline(c net.Conn, ctx *RequestCtx, lastDeadlineTime time.Time) time.Time { + writeTimeout := s.WriteTimeout + if s.MaxKeepaliveDuration > 0 { + connTimeout := s.MaxKeepaliveDuration - time.Since(ctx.connTime) + if connTimeout <= 0 { + // MaxKeepAliveDuration exceeded, but let's try sending response anyway + // in 100ms with 'Connection: close' header. + ctx.SetConnectionClose() + connTimeout = 100 * time.Millisecond + } + if connTimeout < writeTimeout { + writeTimeout = connTimeout + } + } + + // Optimization: update write deadline only if more than 25% + // of the last write deadline exceeded. + // See https://github.com/golang/go/issues/15133 for details. + currentTime := time.Now() + if currentTime.Sub(lastDeadlineTime) > (writeTimeout >> 2) { + if err := c.SetWriteDeadline(currentTime.Add(writeTimeout)); err != nil { + panic(fmt.Sprintf("BUG: error in SetWriteDeadline(%s): %s", writeTimeout, err)) + } + lastDeadlineTime = currentTime + } + return lastDeadlineTime +} + func hijackConnHandler(r io.Reader, c net.Conn, s *Server, h HijackHandler) { hjc := s.acquireHijackConn(r, c)