Skip to content

Commit 0773d52

Browse files
committed
perf(pool): add fast path for Get/Put to match master performance
Introduced TryTransitionFast() for the hot path (Get/Put operations): - Single CAS operation (same as master's atomic bool) - No waiter notification overhead - No loop through valid states - No error allocation Hot path flow: 1. popIdle(): Try IDLE → IN_USE (fast), fallback to CREATED → IN_USE 2. putConn(): Try IN_USE → IDLE (fast) This matches master's performance while preserving state machine for: - Background operations (handoff/reauth use UNUSABLE state) - State validation (TryTransition still available) - Waiter notification (AwaitAndTransition for blocking) Performance comparison per Get/Put cycle: - Master: 2 atomic CAS operations - State machine (before): 5 atomic operations (2.5x slower) - State machine (after): 2 atomic CAS operations (same as master!) Expected improvement: Restore to baseline ~11,373 ops/sec
1 parent f08338e commit 0773d52

File tree

2 files changed

+35
-15
lines changed

2 files changed

+35
-15
lines changed

internal/pool/conn_state.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,20 @@ func (sm *ConnStateMachine) GetState() ConnState {
121121
return ConnState(sm.state.Load())
122122
}
123123

124+
// TryTransitionFast is an optimized version for the hot path (Get/Put operations).
125+
// It only handles simple IDLE ⇄ IN_USE transitions without waiter notification.
126+
// This is safe because:
127+
// 1. Get/Put don't need to wait for state changes
128+
// 2. Background operations (handoff/reauth) use UNUSABLE state, which this won't match
129+
// 3. If a background operation is in progress (state is UNUSABLE), this fails fast
130+
//
131+
// Returns true if transition succeeded, false otherwise.
132+
// Use this for performance-critical paths where you don't need error details.
133+
func (sm *ConnStateMachine) TryTransitionFast(fromState, targetState ConnState) bool {
134+
// Single CAS operation - as fast as the old atomic bool!
135+
return sm.state.CompareAndSwap(uint32(fromState), uint32(targetState))
136+
}
137+
124138
// TryTransition attempts an immediate state transition without waiting.
125139
// Returns the current state after the transition attempt and an error if the transition failed.
126140
// The returned state is the CURRENT state (after the attempt), not the previous state.

internal/pool/pool.go

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -599,16 +599,25 @@ func (p *ConnPool) popIdle() (*Conn, error) {
599599
}
600600
attempts++
601601

602-
// Try to atomically transition to IN_USE using state machine
603-
// Accept both CREATED (uninitialized) and IDLE (initialized) states
604-
// Use predefined slice to avoid allocation
605-
_, err := cn.GetStateMachine().TryTransition(validFromCreatedOrIdle, StateInUse)
606-
if err == nil {
607-
// Successfully acquired the connection
602+
// Hot path optimization: try fast IDLE → IN_USE transition first
603+
// This is a single CAS operation, as fast as the old atomic bool
604+
sm := cn.GetStateMachine()
605+
if sm.TryTransitionFast(StateIdle, StateInUse) {
606+
// Successfully acquired the connection (common case)
608607
p.idleConnsLen.Add(-1)
609608
break
610609
}
611610

611+
// Fast path failed - connection might be CREATED (uninitialized) or UNUSABLE (handoff/reauth)
612+
// Try CREATED → IN_USE for new connections
613+
if sm.TryTransitionFast(StateCreated, StateInUse) {
614+
// Successfully acquired uninitialized connection
615+
p.idleConnsLen.Add(-1)
616+
break
617+
}
618+
619+
// Connection is in UNUSABLE, INITIALIZING, or other state - skip it
620+
612621
// Connection is not in a valid state (might be UNUSABLE for handoff/re-auth, INITIALIZING, etc.)
613622
// Put it back in the pool and try the next one
614623
if p.cfg.PoolFIFO {
@@ -691,16 +700,13 @@ func (p *ConnPool) putConn(ctx context.Context, cn *Conn, freeTurn bool) {
691700
var shouldCloseConn bool
692701

693702
if p.cfg.MaxIdleConns == 0 || p.idleConnsLen.Load() < p.cfg.MaxIdleConns {
694-
// Try to transition to IDLE state BEFORE adding to pool
695-
// Only transition if connection is still IN_USE (hooks might have changed state)
696-
// This prevents:
697-
// 1. Race condition where another goroutine could acquire a connection that's still in IN_USE state
698-
// 2. Overwriting state changes made by hooks (e.g., IN_USE → UNUSABLE for handoff)
699-
// Use predefined slice to avoid allocation
700-
currentState, err := cn.GetStateMachine().TryTransition(validFromInUse, StateIdle)
701-
if err != nil {
702-
// Hook changed the state (e.g., to UNUSABLE for handoff)
703+
// Hot path optimization: try fast IN_USE → IDLE transition
704+
// This is a single CAS operation, as fast as the old atomic bool
705+
sm := cn.GetStateMachine()
706+
if !sm.TryTransitionFast(StateInUse, StateIdle) {
707+
// Fast path failed - hook might have changed state (e.g., to UNUSABLE for handoff)
703708
// Keep the state set by the hook and pool the connection anyway
709+
currentState := sm.GetState()
704710
internal.Logger.Printf(ctx, "Connection state changed by hook to %v, pooling as-is", currentState)
705711
}
706712

0 commit comments

Comments
 (0)