Skip to content

Commit 78bcfbb

Browse files
committed
perf(pool): eliminate mutex overhead in state machine hot path
The state machine was calling notifyWaiters() on EVERY Get/Put operation, which acquired a mutex even when no waiters were present (the common case). Fix: Use atomic waiterCount to check for waiters BEFORE acquiring mutex. This eliminates mutex contention in the hot path (Get/Put operations). Implementation: - Added atomic.Int32 waiterCount field to ConnStateMachine - Increment when adding waiter, decrement when removing - Check waiterCount atomically before acquiring mutex in notifyWaiters() Performance impact: - Before: mutex lock/unlock on every Get/Put (even with no waiters) - After: lock-free atomic check, only acquire mutex if waiters exist - Expected improvement: ~30-50% for Get/Put operations
1 parent 1b0168d commit 78bcfbb

File tree

1 file changed

+15
-2
lines changed

1 file changed

+15
-2
lines changed

internal/pool/conn_state.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,9 @@ type ConnStateMachine struct {
100100
state atomic.Uint32
101101

102102
// FIFO queue for waiters - only locked during waiter add/remove/notify
103-
mu sync.Mutex
104-
waiters *list.List // List of *waiter
103+
mu sync.Mutex
104+
waiters *list.List // List of *waiter
105+
waiterCount atomic.Int32 // Fast lock-free check for waiters (avoids mutex in hot path)
105106
}
106107

107108
// NewConnStateMachine creates a new connection state machine.
@@ -219,6 +220,7 @@ func (sm *ConnStateMachine) AwaitAndTransition(
219220
// Add to FIFO queue
220221
sm.mu.Lock()
221222
elem := sm.waiters.PushBack(w)
223+
sm.waiterCount.Add(1)
222224
sm.mu.Unlock()
223225

224226
// Wait for state change or timeout
@@ -227,20 +229,29 @@ func (sm *ConnStateMachine) AwaitAndTransition(
227229
// Timeout or cancellation - remove from queue
228230
sm.mu.Lock()
229231
sm.waiters.Remove(elem)
232+
sm.waiterCount.Add(-1)
230233
sm.mu.Unlock()
231234
return sm.GetState(), ctx.Err()
232235
case err := <-w.done:
233236
// Transition completed (or failed)
237+
// Note: waiterCount is decremented in notifyWaiters when waiter is removed
234238
return sm.GetState(), err
235239
}
236240
}
237241

238242
// notifyWaiters checks if any waiters can proceed and notifies them in FIFO order.
239243
// This is called after every state transition.
240244
func (sm *ConnStateMachine) notifyWaiters() {
245+
// Fast path: check atomic counter without acquiring lock
246+
// This eliminates mutex overhead in the common case (no waiters)
247+
if sm.waiterCount.Load() == 0 {
248+
return
249+
}
250+
241251
sm.mu.Lock()
242252
defer sm.mu.Unlock()
243253

254+
// Double-check after acquiring lock (waiters might have been processed)
244255
if sm.waiters.Len() == 0 {
245256
return
246257
}
@@ -261,6 +272,7 @@ func (sm *ConnStateMachine) notifyWaiters() {
261272
if _, valid := w.validStates[currentState]; valid {
262273
// Remove from queue first
263274
sm.waiters.Remove(elem)
275+
sm.waiterCount.Add(-1)
264276

265277
// Use CAS to ensure state hasn't changed since we checked
266278
// This prevents race condition where another thread changes state
@@ -273,6 +285,7 @@ func (sm *ConnStateMachine) notifyWaiters() {
273285
} else {
274286
// State changed - re-add waiter to front of queue and retry
275287
sm.waiters.PushFront(w)
288+
sm.waiterCount.Add(1)
276289
// Continue to next iteration to re-read state
277290
processed = true
278291
break

0 commit comments

Comments
 (0)