@@ -76,15 +76,16 @@ type Options struct {
7676 ReadBufferSize int
7777 WriteBufferSize int
7878
79- PoolFIFO bool
80- PoolSize int
81- DialTimeout time.Duration
82- PoolTimeout time.Duration
83- MinIdleConns int
84- MaxIdleConns int
85- MaxActiveConns int
86- ConnMaxIdleTime time.Duration
87- ConnMaxLifetime time.Duration
79+ PoolFIFO bool
80+ PoolSize int32
81+ DialTimeout time.Duration
82+ PoolTimeout time.Duration
83+ MinIdleConns int32
84+ MaxIdleConns int32
85+ MaxActiveConns int32
86+ ConnMaxIdleTime time.Duration
87+ ConnMaxLifetime time.Duration
88+ PushNotificationsEnabled bool
8889}
8990
9091type lastDialErrorWrap struct {
@@ -103,8 +104,9 @@ type ConnPool struct {
103104 conns []* Conn
104105 idleConns []* Conn
105106
106- poolSize atomic.Int32
107- idleConnsLen int
107+ poolSize atomic.Int32
108+ idleConnsLen atomic.Int32
109+ idleCheckInProgress atomic.Bool
108110
109111 stats Stats
110112 waitDurationNs atomic.Int64
@@ -161,23 +163,29 @@ func (p *ConnPool) RemovePoolHook(hook PoolHook) {
161163}
162164
163165func (p * ConnPool ) checkMinIdleConns () {
166+
167+ if ! p .idleCheckInProgress .CompareAndSwap (false , true ) {
168+ return
169+ }
170+ defer p .idleCheckInProgress .Store (false )
171+
164172 if p .cfg .MinIdleConns == 0 {
165173 return
166174 }
167175
168176 // Only create idle connections if we haven't reached the total pool size limit
169177 // MinIdleConns should be a subset of PoolSize, not additional connections
170- for p .poolSize .Load () < int32 ( p .cfg .PoolSize ) && p .idleConnsLen < p .cfg .MinIdleConns {
178+ for p .poolSize .Load () < p .cfg .PoolSize && p .idleConnsLen . Load () < p .cfg .MinIdleConns {
171179 select {
172180 case p .queue <- struct {}{}:
173181 p .poolSize .Add (1 )
174- p .idleConnsLen ++
182+ p .idleConnsLen . Add ( 1 )
175183 go func () {
176184 defer func () {
177185 if err := recover (); err != nil {
178186 p .connsMu .Lock ()
179187 p .poolSize .Add (- 1 )
180- p .idleConnsLen --
188+ p .idleConnsLen . Add ( - 1 )
181189 p .connsMu .Unlock ()
182190
183191 p .freeTurn ()
@@ -189,7 +197,7 @@ func (p *ConnPool) checkMinIdleConns() {
189197 if err != nil && err != ErrClosed {
190198 p .connsMu .Lock ()
191199 p .poolSize .Add (- 1 )
192- p .idleConnsLen --
200+ p .idleConnsLen . Add ( - 1 )
193201 p .connsMu .Unlock ()
194202 }
195203 p .freeTurn ()
@@ -389,6 +397,7 @@ func (p *ConnPool) getConn(ctx context.Context) (*Conn, error) {
389397 // Process connection using the hooks system
390398 if p .hookManager != nil {
391399 if err := p .hookManager .ProcessOnGet (ctx , cn , false ); err != nil {
400+ log .Printf ("redis: connection pool: failed to process idle connection by hook: %v" , err )
392401 // Failed to process connection, discard it
393402 _ = p .CloseConn (cn )
394403 continue
@@ -490,7 +499,7 @@ func (p *ConnPool) popIdle() (*Conn, error) {
490499 attempts ++
491500
492501 if cn .IsUsable () {
493- p .idleConnsLen --
502+ p .idleConnsLen . Add ( - 1 )
494503 break
495504 }
496505
@@ -543,28 +552,38 @@ func (p *ConnPool) Put(ctx context.Context, cn *Conn) {
543552
544553 // If hooks say to remove the connection, do so
545554 if shouldRemove {
546- p .Remove (ctx , cn , nil )
555+ p .Remove (ctx , cn , errors . New ( "hook requested removal" ) )
547556 return
548557 }
549558
550559 // If processor says not to pool the connection, remove it
551560 if ! shouldPool {
552- p .Remove (ctx , cn , nil )
561+ p .Remove (ctx , cn , errors . New ( "hook requested no pooling" ) )
553562 return
554563 }
555564
556565 if ! cn .pooled {
557- p .Remove (ctx , cn , nil )
566+ p .Remove (ctx , cn , errors . New ( "connection not pooled" ) )
558567 return
559568 }
560569
561570 var shouldCloseConn bool
562571
563572 p .connsMu .Lock ()
564573
565- if p .cfg .MaxIdleConns == 0 || p .idleConnsLen < p .cfg .MaxIdleConns {
566- p .idleConns = append (p .idleConns , cn )
567- p .idleConnsLen ++
574+ if p .cfg .MaxIdleConns == 0 || p .idleConnsLen .Load () < p .cfg .MaxIdleConns {
575+ // unusable conns are expected to become usable at some point (background process is reconnecting them)
576+ // put them at the opposite end of the queue
577+ if ! cn .IsUsable () {
578+ if p .cfg .PoolFIFO {
579+ p .idleConns = append (p .idleConns , cn )
580+ } else {
581+ p .idleConns = append ([]* Conn {cn }, p .idleConns ... )
582+ }
583+ } else {
584+ p .idleConns = append (p .idleConns , cn )
585+ }
586+ p .idleConnsLen .Add (1 )
568587 } else {
569588 p .removeConn (cn )
570589 shouldCloseConn = true
@@ -626,9 +645,9 @@ func (p *ConnPool) Len() int {
626645// IdleLen returns number of idle connections.
627646func (p * ConnPool ) IdleLen () int {
628647 p .connsMu .Lock ()
629- n := p .idleConnsLen
648+ n := p .idleConnsLen . Load ()
630649 p .connsMu .Unlock ()
631- return n
650+ return int ( n )
632651}
633652
634653func (p * ConnPool ) Stats () * Stats {
@@ -680,7 +699,7 @@ func (p *ConnPool) Close() error {
680699 p .conns = nil
681700 p .poolSize .Store (0 )
682701 p .idleConns = nil
683- p .idleConnsLen = 0
702+ p .idleConnsLen . Store ( 0 )
684703 p .connsMu .Unlock ()
685704
686705 return firstErr
@@ -696,12 +715,26 @@ func (p *ConnPool) isHealthyConn(cn *Conn, now time.Time) bool {
696715 return false
697716 }
698717
718+ cn .SetUsedAt (now )
699719 // Check basic connection health
700720 // Use GetNetConn() to safely access netConn and avoid data races
701721 if err := connCheck (cn .getNetConn ()); err != nil {
702- return false
722+ // If there's unexpected data, it might be push notifications (RESP3)
723+ // However, push notification processing is now handled by the client
724+ // before WithReader to ensure proper context is available to handlers
725+ if p .cfg .PushNotificationsEnabled && err == errUnexpectedRead {
726+ // we know that there is something in the buffer, so peek at the next reply type without
727+ // the potential to block
728+ if replyType , err := cn .rd .PeekReplyType (); err == nil && replyType == proto .RespPush {
729+ // For RESP3 connections with push notifications, we allow some buffered data
730+ // The client will process these notifications before using the connection
731+ internal .Logger .Printf (context .Background (), "push: connection has buffered data, likely push notifications - will be processed by client" )
732+ return true // Connection is healthy, client will handle notifications
733+ }
734+ return false // Unexpected data, not push notifications, connection is unhealthy
735+ } else {
736+ return false
737+ }
703738 }
704-
705- cn .SetUsedAt (now )
706739 return true
707740}
0 commit comments