Skip to content

Commit ded98ec

Browse files
committed
address comments
1 parent 5574793 commit ded98ec

File tree

6 files changed

+108
-21
lines changed

6 files changed

+108
-21
lines changed

hitless/errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ var (
3737
ErrHandoffInProgress = errors.New("hitless: handoff already in progress")
3838
ErrNoHandoffInProgress = errors.New("hitless: no handoff in progress")
3939
ErrConnectionFailed = errors.New("hitless: failed to establish new connection")
40+
ErrHandoffQueueFull = errors.New("hitless: handoff queue is full, cannot queue new handoff requests - consider increasing HandoffQueueSize or MaxWorkers in configuration")
4041
)
4142

4243
// Dead error variables removed - unused in simplified architecture

hitless/pool_hook.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ func (ph *PoolHook) queueHandoff(conn *pool.Conn) error {
309309

310310
// Ensure we have workers available to handle the load
311311
ph.ensureWorkerAvailable()
312-
return errors.New("queue full")
312+
return ErrHandoffQueueFull
313313
}
314314

315315
// performConnectionHandoffWithPool performs the actual connection handoff with pool for connection removal on failure

internal/pool/pool.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,18 @@ var (
2424
// ErrPoolTimeout timed out waiting to get a connection from the connection pool.
2525
ErrPoolTimeout = errors.New("redis: connection pool timeout")
2626

27-
popAttempts = 10
28-
getAttempts = 3
27+
// popAttempts is the maximum number of attempts to find a usable connection
28+
// when popping from the idle connection pool. This handles cases where connections
29+
// are temporarily marked as unusable (e.g., during hitless upgrades or network issues).
30+
// Value of 10 provides sufficient resilience without excessive overhead.
31+
popAttempts = 10
32+
33+
// getAttempts is the maximum number of attempts to get a connection that passes
34+
// hook validation (e.g., hitless upgrade hooks). This protects against race conditions
35+
// where hooks might temporarily reject connections during cluster transitions.
36+
// Value of 3 balances resilience with performance - most hook rejections resolve quickly.
37+
getAttempts = 3
38+
2939
minTime = time.Unix(-2208988800, 0) // Jan 1, 1900
3040
maxTime = minTime.Add(1<<63 - 1)
3141
noExpiration = maxTime

options.go

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"crypto/tls"
66
"errors"
77
"fmt"
8+
"math"
89
"net"
910
"net/url"
1011
"runtime"
@@ -31,6 +32,17 @@ type Limiter interface {
3132
ReportResult(result error)
3233
}
3334

35+
// safeIntToInt32 safely converts an int to int32, returning an error if overflow would occur.
36+
func safeIntToInt32(value int, fieldName string) (int32, error) {
37+
if value > math.MaxInt32 {
38+
return 0, fmt.Errorf("redis: %s value %d exceeds maximum allowed value %d", fieldName, value, math.MaxInt32)
39+
}
40+
if value < math.MinInt32 {
41+
return 0, fmt.Errorf("redis: %s value %d is below minimum allowed value %d", fieldName, value, math.MinInt32)
42+
}
43+
return int32(value), nil
44+
}
45+
3446
// Options keeps the settings to set up redis connection.
3547
type Options struct {
3648

@@ -648,40 +660,80 @@ func getUserPassword(u *url.URL) (string, string) {
648660
func newConnPool(
649661
opt *Options,
650662
dialer func(ctx context.Context, network, addr string) (net.Conn, error),
651-
) *pool.ConnPool {
663+
) (*pool.ConnPool, error) {
664+
poolSize, err := safeIntToInt32(opt.PoolSize, "PoolSize")
665+
if err != nil {
666+
return nil, err
667+
}
668+
669+
minIdleConns, err := safeIntToInt32(opt.MinIdleConns, "MinIdleConns")
670+
if err != nil {
671+
return nil, err
672+
}
673+
674+
maxIdleConns, err := safeIntToInt32(opt.MaxIdleConns, "MaxIdleConns")
675+
if err != nil {
676+
return nil, err
677+
}
678+
679+
maxActiveConns, err := safeIntToInt32(opt.MaxActiveConns, "MaxActiveConns")
680+
if err != nil {
681+
return nil, err
682+
}
683+
652684
return pool.NewConnPool(&pool.Options{
653685
Dialer: func(ctx context.Context) (net.Conn, error) {
654686
return dialer(ctx, opt.Network, opt.Addr)
655687
},
656688
PoolFIFO: opt.PoolFIFO,
657-
PoolSize: int32(opt.PoolSize),
689+
PoolSize: poolSize,
658690
PoolTimeout: opt.PoolTimeout,
659691
DialTimeout: opt.DialTimeout,
660-
MinIdleConns: int32(opt.MinIdleConns),
661-
MaxIdleConns: int32(opt.MaxIdleConns),
662-
MaxActiveConns: int32(opt.MaxActiveConns),
692+
MinIdleConns: minIdleConns,
693+
MaxIdleConns: maxIdleConns,
694+
MaxActiveConns: maxActiveConns,
663695
ConnMaxIdleTime: opt.ConnMaxIdleTime,
664696
ConnMaxLifetime: opt.ConnMaxLifetime,
665697
ReadBufferSize: opt.ReadBufferSize,
666698
WriteBufferSize: opt.WriteBufferSize,
667699
PushNotificationsEnabled: opt.Protocol == 3,
668-
})
700+
}), nil
669701
}
670702

671703
func newPubSubPool(opt *Options, dialer func(ctx context.Context, network, addr string) (net.Conn, error),
672-
) *pool.PubSubPool {
704+
) (*pool.PubSubPool, error) {
705+
poolSize, err := safeIntToInt32(opt.PoolSize, "PoolSize")
706+
if err != nil {
707+
return nil, err
708+
}
709+
710+
minIdleConns, err := safeIntToInt32(opt.MinIdleConns, "MinIdleConns")
711+
if err != nil {
712+
return nil, err
713+
}
714+
715+
maxIdleConns, err := safeIntToInt32(opt.MaxIdleConns, "MaxIdleConns")
716+
if err != nil {
717+
return nil, err
718+
}
719+
720+
maxActiveConns, err := safeIntToInt32(opt.MaxActiveConns, "MaxActiveConns")
721+
if err != nil {
722+
return nil, err
723+
}
724+
673725
return pool.NewPubSubPool(&pool.Options{
674726
PoolFIFO: opt.PoolFIFO,
675-
PoolSize: int32(opt.PoolSize),
727+
PoolSize: poolSize,
676728
PoolTimeout: opt.PoolTimeout,
677729
DialTimeout: opt.DialTimeout,
678-
MinIdleConns: int32(opt.MinIdleConns),
679-
MaxIdleConns: int32(opt.MaxIdleConns),
680-
MaxActiveConns: int32(opt.MaxActiveConns),
730+
MinIdleConns: minIdleConns,
731+
MaxIdleConns: maxIdleConns,
732+
MaxActiveConns: maxActiveConns,
681733
ConnMaxIdleTime: opt.ConnMaxIdleTime,
682734
ConnMaxLifetime: opt.ConnMaxLifetime,
683735
ReadBufferSize: 32 * 1024,
684736
WriteBufferSize: 32 * 1024,
685737
PushNotificationsEnabled: opt.Protocol == 3,
686-
}, dialer)
738+
}, dialer), nil
687739
}

redis.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,7 @@ func (c *baseClient) initConn(ctx context.Context, cn *pool.Conn) error {
456456
c.optLock.Unlock()
457457
return fmt.Errorf("failed to enable maintenance notifications: %w", hitlessHandshakeErr)
458458
default: // will handle auto and any other
459+
internal.Logger.Printf(ctx, "hitless: auto mode fallback: hitless upgrades disabled due to handshake failure: %v", hitlessHandshakeErr)
459460
c.opt.HitlessUpgradeConfig.Mode = hitless.MaintNotificationsDisabled
460461
c.optLock.Unlock()
461462
// auto mode, disable hitless upgrades and continue
@@ -562,6 +563,8 @@ func (c *baseClient) assertUnstableCommand(cmd Cmder) bool {
562563
if c.opt.UnstableResp3 {
563564
return true
564565
} else {
566+
// TODO: find the best way to remove the panic and return error here
567+
// The client should not panic when executing a command, only when initializing.
565568
panic("RESP3 responses for this command are disabled because they may still change. Please set the flag UnstableResp3 . See the [README](https://github.com/redis/go-redis/blob/master/README.md) and the release notes for guidance.")
566569
}
567570
default:
@@ -921,8 +924,15 @@ func NewClient(opt *Options) *Client {
921924
opt.PushNotificationProcessor = c.pushProcessor
922925

923926
// Create connection pools
924-
c.connPool = newConnPool(opt, c.dialHook)
925-
c.pubSubPool = newPubSubPool(opt, c.dialHook)
927+
var err error
928+
c.connPool, err = newConnPool(opt, c.dialHook)
929+
if err != nil {
930+
panic(fmt.Errorf("redis: failed to create connection pool: %w", err))
931+
}
932+
c.pubSubPool, err = newPubSubPool(opt, c.dialHook)
933+
if err != nil {
934+
panic(fmt.Errorf("redis: failed to create pubsub pool: %w", err))
935+
}
926936

927937
// Initialize hitless upgrades first if enabled and protocol is RESP3
928938
if opt.HitlessUpgradeConfig != nil && opt.HitlessUpgradeConfig.Mode != hitless.MaintNotificationsDisabled && opt.Protocol == 3 {

sentinel.go

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -475,8 +475,15 @@ func NewFailoverClient(failoverOpt *FailoverOptions) *Client {
475475
// Use void processor by default for RESP2 connections
476476
rdb.pushProcessor = initializePushProcessor(opt)
477477

478-
rdb.connPool = newConnPool(opt, rdb.dialHook)
479-
rdb.pubSubPool = newPubSubPool(opt, rdb.dialHook)
478+
var err error
479+
rdb.connPool, err = newConnPool(opt, rdb.dialHook)
480+
if err != nil {
481+
panic(fmt.Errorf("redis: failed to create connection pool: %w", err))
482+
}
483+
rdb.pubSubPool, err = newPubSubPool(opt, rdb.dialHook)
484+
if err != nil {
485+
panic(fmt.Errorf("redis: failed to create pubsub pool: %w", err))
486+
}
480487

481488
rdb.onClose = rdb.wrappedOnClose(failover.Close)
482489

@@ -552,8 +559,15 @@ func NewSentinelClient(opt *Options) *SentinelClient {
552559
dial: c.baseClient.dial,
553560
process: c.baseClient.process,
554561
})
555-
c.connPool = newConnPool(opt, c.dialHook)
556-
c.pubSubPool = newPubSubPool(opt, c.dialHook)
562+
var err error
563+
c.connPool, err = newConnPool(opt, c.dialHook)
564+
if err != nil {
565+
panic(fmt.Errorf("redis: failed to create connection pool: %w", err))
566+
}
567+
c.pubSubPool, err = newPubSubPool(opt, c.dialHook)
568+
if err != nil {
569+
panic(fmt.Errorf("redis: failed to create pubsub pool: %w", err))
570+
}
557571

558572
return c
559573
}

0 commit comments

Comments
 (0)