Skip to content

Commit 752b0e7

Browse files
Remove idleConns mutex for every request (#1986)
Locking and unlocking a mutex multiple times per request is a major slowdown that we can avoid with clever use of atomics. Before: ``` BenchmarkServerGet100ReqPerConn10KClients-12 84167428 867.7 ns/op ``` After: ``` BenchmarkServerGet100ReqPerConn10KClients-12 187397954 386.3 ns/op ```
1 parent bf3f552 commit 752b0e7

File tree

2 files changed

+43
-27
lines changed

2 files changed

+43
-27
lines changed

server.go

Lines changed: 39 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ type Server struct {
219219

220220
concurrencyCh chan struct{}
221221

222-
idleConns map[net.Conn]time.Time
222+
idleConns map[net.Conn]*atomic.Int64
223223
done chan struct{}
224224

225225
// Server name for sending in response headers.
@@ -2132,6 +2132,26 @@ func (s *Server) serveConn(c net.Conn) (err error) {
21322132
return handler(c)
21332133
}
21342134

2135+
s.idleConnsMu.Lock()
2136+
if s.idleConns == nil {
2137+
s.idleConns = make(map[net.Conn]*atomic.Int64)
2138+
}
2139+
idleConnTime, ok := s.idleConns[c]
2140+
if !ok {
2141+
v := idleConnTimePool.Get()
2142+
if v == nil {
2143+
v = &atomic.Int64{}
2144+
}
2145+
idleConnTime = v.(*atomic.Int64)
2146+
s.idleConns[c] = idleConnTime
2147+
}
2148+
2149+
// Count the connection as Idle after 5 seconds.
2150+
// Same as net/http.Server:
2151+
// https://github.com/golang/go/blob/85d7bab91d9a3ed1f76842e4328973ea75efef54/src/net/http/server.go#L2834-L2836
2152+
idleConnTime.Store(time.Now().Add(time.Second * 5).Unix())
2153+
s.idleConnsMu.Unlock()
2154+
21352155
serverName := s.getServerName()
21362156
connRequestNum := uint64(0)
21372157
connID := nextConnID()
@@ -2207,6 +2227,8 @@ func (s *Server) serveConn(c net.Conn) (err error) {
22072227
if err == nil {
22082228
s.setState(c, StateActive)
22092229

2230+
idleConnTime.Store(0)
2231+
22102232
if s.ReadTimeout > 0 {
22112233
if err = c.SetReadDeadline(time.Now().Add(s.ReadTimeout)); err != nil {
22122234
break
@@ -2485,6 +2507,8 @@ func (s *Server) serveConn(c net.Conn) (err error) {
24852507
err = nil
24862508
break
24872509
}
2510+
2511+
idleConnTime.Store(time.Now().Unix())
24882512
}
24892513

24902514
if br != nil {
@@ -2497,11 +2521,18 @@ func (s *Server) serveConn(c net.Conn) (err error) {
24972521
s.releaseCtx(ctx)
24982522
}
24992523

2524+
s.idleConnsMu.Lock()
2525+
ic, ok := s.idleConns[c]
2526+
if ok {
2527+
idleConnTimePool.Put(ic)
2528+
delete(s.idleConns, c)
2529+
}
2530+
s.idleConnsMu.Unlock()
2531+
25002532
return
25012533
}
25022534

25032535
func (s *Server) setState(nc net.Conn, state ConnState) {
2504-
s.trackConn(nc, state)
25052536
if hook := s.ConnState; hook != nil {
25062537
hook(nc, state)
25072538
}
@@ -2878,36 +2909,17 @@ func (s *Server) writeErrorResponse(bw *bufio.Writer, ctx *RequestCtx, serverNam
28782909
return bw
28792910
}
28802911

2881-
func (s *Server) trackConn(c net.Conn, state ConnState) {
2882-
s.idleConnsMu.Lock()
2883-
switch state {
2884-
case StateIdle:
2885-
if s.idleConns == nil {
2886-
s.idleConns = make(map[net.Conn]time.Time)
2887-
}
2888-
s.idleConns[c] = time.Now()
2889-
case StateNew:
2890-
if s.idleConns == nil {
2891-
s.idleConns = make(map[net.Conn]time.Time)
2892-
}
2893-
// Count the connection as Idle after 5 seconds.
2894-
// Same as net/http.Server:
2895-
// https://github.com/golang/go/blob/85d7bab91d9a3ed1f76842e4328973ea75efef54/src/net/http/server.go#L2834-L2836
2896-
s.idleConns[c] = time.Now().Add(time.Second * 5)
2897-
2898-
default:
2899-
delete(s.idleConns, c)
2900-
}
2901-
s.idleConnsMu.Unlock()
2902-
}
2912+
var idleConnTimePool sync.Pool
29032913

29042914
func (s *Server) closeIdleConns() {
29052915
s.idleConnsMu.Lock()
2906-
now := time.Now()
2907-
for c, t := range s.idleConns {
2908-
if now.Sub(t) >= 0 {
2916+
now := time.Now().Unix()
2917+
for c, ict := range s.idleConns {
2918+
t := ict.Load()
2919+
if t != 0 && now-t >= 0 {
29092920
_ = c.Close()
29102921
delete(s.idleConns, c)
2922+
idleConnTimePool.Put(ict)
29112923
}
29122924
}
29132925
s.idleConnsMu.Unlock()

server_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3832,6 +3832,10 @@ func TestShutdownCloseIdleConns(t *testing.T) {
38323832
if err != nil {
38333833
t.Errorf("unexpected error: %v", err)
38343834
}
3835+
3836+
if _, err := conn.Read(make([]byte, 1)); err == nil {
3837+
t.Fatal("connection not closed")
3838+
}
38353839
}
38363840
}
38373841

0 commit comments

Comments
 (0)