mirror of
https://github.com/redis/go-redis.git
synced 2026-10-28 16:35:06 +08:00
fix(sentinel): guard typed-nil pool on ctor panic
newConnPool returns a nil *pool.ConnPool for an out-of-range PoolSize; assigning it straight into the pool.Pooler interface field left a typed-nil that closeResources' `!= nil` check treats as present, so the ctor-panic cleanup nil-dereffed inside ConnPool.Close and replaced the intended "failed to create connection pool" panic. Assign through a local after the error check, as NewClient already does. pubSubPool is a concrete *pool.PubSubPool whose nil is caught correctly, but assign it the same way to keep this constructor identical to NewClient. Refs #4002
This commit is contained in:
2 files changed
+42
-3
No files matched your search
@@ -158,3 +158,32 @@ func TestNewFailoverClientPanicClosesPartialClient(t *testing.T) {
|
||||
t.Fatalf("cleanup replaced the construction panic (partial failover Close nil-deref?): recovered=%v (%T)", recovered, recovered)
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewFailoverClientPanicCleanupSurvivesNilPool is the NewFailoverClient twin
|
||||
// of TestNewClientPanicCleanupSurvivesNilPool, pinning the specific bug where the
|
||||
// failover constructor assigned newConnPool's typed-nil straight into the
|
||||
// pool.Pooler field. An out-of-range PoolSize makes newConnPool return (nil, err),
|
||||
// so the field must stay a plain nil (not a typed-nil interface) — otherwise
|
||||
// closeResources' `connPool != nil` guard passes and the panic-cleanup defer
|
||||
// nil-derefs inside ConnPool.Close, replacing the intended "failed to create
|
||||
// connection pool" panic. MinIdleConns is 0 and the sentinel addrs are never
|
||||
// contacted: the panic precedes any dialing.
|
||||
func TestNewFailoverClientPanicCleanupSurvivesNilPool(t *testing.T) {
|
||||
var recovered any
|
||||
func() {
|
||||
defer func() { recovered = recover() }()
|
||||
NewFailoverClient(&FailoverOptions{
|
||||
MasterName: "mymaster",
|
||||
SentinelAddrs: []string{"127.0.0.1:26379"}, // never contacted: panic precedes dialing
|
||||
PoolSize: math.MaxInt32 + 1, // overflows int32 in newConnPool
|
||||
MinIdleConns: 0,
|
||||
})
|
||||
}()
|
||||
if recovered == nil {
|
||||
t.Fatal("expected NewFailoverClient to panic on an out-of-range PoolSize")
|
||||
}
|
||||
err, ok := recovered.(error)
|
||||
if !ok || !strings.Contains(err.Error(), "failed to create connection pool") {
|
||||
t.Fatalf("cleanup replaced the construction panic (typed-nil connPool nil-deref?): recovered=%v (%T)", recovered, recovered)
|
||||
}
|
||||
}
|
||||
+13
-3
@@ -603,15 +603,25 @@ func NewFailoverClient(failoverOpt *FailoverOptions) *Client {
|
||||
mainPoolName := opt.Addr + "_" + uniqueID
|
||||
pubsubPoolName := opt.Addr + "_" + uniqueID + "_pubsub"
|
||||
|
||||
var err error
|
||||
rdb.connPool, err = newConnPool(opt, rdb.dialHook, mainPoolName)
|
||||
// Assign the pool fields only AFTER the error check, mirroring NewClient.
|
||||
// newConnPool returns a nil *pool.ConnPool on error, and assigning that
|
||||
// straight to the pool.Pooler interface field would leave a typed-nil
|
||||
// interface that closeResources treats as present (its != nil check passes),
|
||||
// so the panic-cleanup defer above would nil-deref inside ConnPool.Close and
|
||||
// replace the intended "failed to create connection pool" panic. A local var
|
||||
// keeps the field nil on failure. (pubSubPool is a concrete *pool.PubSubPool
|
||||
// whose nil is caught correctly by the != nil check, but assign it the same
|
||||
// way to keep this constructor identical to NewClient.)
|
||||
connPool, err := newConnPool(opt, rdb.dialHook, mainPoolName)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("redis: failed to create connection pool: %w", err))
|
||||
}
|
||||
rdb.pubSubPool, err = newPubSubPool(opt, rdb.dialHook, pubsubPoolName)
|
||||
rdb.connPool = connPool
|
||||
pubSubPool, err := newPubSubPool(opt, rdb.dialHook, pubsubPoolName)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("redis: failed to create pubsub pool: %w", err))
|
||||
}
|
||||
rdb.pubSubPool = pubSubPool
|
||||
|
||||
// Create the dedicated pipeline pool unconditionally, mirroring NewClient
|
||||
// via the shared buildPipelinePool helper. PipelinePoolSize < 0 opts out.
|
||||
|
||||
Reference in new issue
Block a user