Переглянути джерело

reduce Conn.mu hold times (#1105)

The streamPool is already internally serialized by the Go runtime and
designed for high scalability. But currently retrieval from that pool
is unnecessarily serialized by the Conn.mu instead.

Additionally when the pool is empty, the hold times are even higher,
also this channel allocation for the timeout channel eas in that lock scope.

All of this amounted to enough overhead to make the lock appear in
internal profiles.

Technically the only thing required to be serialized are the accesses to
the Conn.calls map, so limit lock scope to exactly these operations.
Ingo Oeser 7 роки тому
батько
коміт
0b2eb416e2
1 змінених файлів з 11 додано та 10 видалено
  1. 11 10
      conn.go

+ 11 - 10
conn.go

@@ -624,22 +624,23 @@ func (c *Conn) exec(ctx context.Context, req frameWriter, tracer Tracer) (*frame
 	// resp is basically a waiting semaphore protecting the framer
 	// resp is basically a waiting semaphore protecting the framer
 	framer := newFramer(c, c, c.compressor, c.version)
 	framer := newFramer(c, c, c.compressor, c.version)
 
 
-	c.mu.Lock()
-	call := c.calls[stream]
-	if call != nil {
-		c.mu.Unlock()
-		return nil, fmt.Errorf("attempting to use stream already in use: %d -> %d", stream, call.streamID)
-	} else {
-		call = streamPool.Get().(*callReq)
-	}
-	c.calls[stream] = call
-
+	call := streamPool.Get().(*callReq)
 	call.framer = framer
 	call.framer = framer
 	call.timeout = make(chan struct{})
 	call.timeout = make(chan struct{})
 	call.streamID = stream
 	call.streamID = stream
 	call.req = req
 	call.req = req
+
+	c.mu.Lock()
+	existingCall := c.calls[stream]
+	if existingCall == nil {
+		c.calls[stream] = call
+	}
 	c.mu.Unlock()
 	c.mu.Unlock()
 
 
+	if existingCall != nil {
+		return nil, fmt.Errorf("attempting to use stream already in use: %d -> %d", stream, existingCall.streamID)
+	}
+
 	if tracer != nil {
 	if tracer != nil {
 		framer.trace()
 		framer.trace()
 	}
 	}