Skip to content

Commit 6ea2e43

Browse files
committed
fix(multidb): autopipeliner-vs-cluster-member lifecycle from review
- a closed autopipeliner no longer blocks AddDatabase(cluster) forever (the cached slot is checked for liveness, matching getOrCreate) - the cluster-member check moved inside the pipeliner build function and AddDatabase holds autopipelinerMu across check+add, so creation and cluster additions are fully serialized
1 parent 561859c commit 6ea2e43

3 files changed

Lines changed: 63 additions & 15 deletions

File tree

multidb.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -461,16 +461,21 @@ func (c *MultiDBClient) ForceActiveIndex(ctx context.Context, index int) error {
461461
}
462462

463463
// AddDatabase implements MultiDBCtrl. Adding a cluster member is refused
464-
// while an autopipeliner instance exists: the cached autopipeliner was built
465-
// without the cluster-specific safeguards and a later failover onto the new
466-
// member would route merged batches around them. Close the autopipeliners
467-
// first, add the member, then recreate them.
464+
// while a live autopipeliner instance exists: the cached autopipeliner was
465+
// built without the cluster-specific safeguards and a later failover onto the
466+
// new member would route merged batches around them. Close the autopipeliners
467+
// first, add the member, then recreate them (closed instances do not block).
468468
func (c *MultiDBClient) AddDatabase(ctx context.Context, cfg MultiDBClientConfig) (int, error) {
469469
if cfg.ClusterOptions != nil {
470+
// Hold autopipelinerMu across the check AND the membership change so
471+
// a concurrent first AutoPipeline call (which creates the pipeliner
472+
// under the same mutex and re-checks membership there) cannot
473+
// interleave between them.
470474
c.autopipelinerMu.Lock()
471-
hasAP := c.autopipeliner != nil || c.asyncAutopipeliner != nil
472-
c.autopipelinerMu.Unlock()
473-
if hasAP {
475+
defer c.autopipelinerMu.Unlock()
476+
hasLiveAP := (c.autopipeliner != nil && !c.autopipeliner.closed.Load()) ||
477+
(c.asyncAutopipeliner != nil && !c.asyncAutopipeliner.closed.Load())
478+
if hasLiveAP {
474479
return -1, errors.New("redis: multidb: close autopipeliners before adding a cluster member database")
475480
}
476481
}

multidb_autopipeline_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,42 @@ func TestMultiDBAutoPipelineRefusesClusterMembers(t *testing.T) {
231231
}
232232
}
233233

234+
func TestMultiDBAddClusterMemberVsAutoPipeliner(t *testing.T) {
235+
db1 := newTestDB("db1", "127.0.0.1:1", 2.0, true)
236+
mdb := newTestMultiDB(t, baseOptions(), db1)
237+
ctx := context.Background()
238+
239+
ap, err := mdb.AutoPipeline()
240+
if err != nil {
241+
t.Fatalf("AutoPipeline: %v", err)
242+
}
243+
244+
clusterCfg := redis.MultiDBClientConfig{
245+
ClusterOptions: &redis.ClusterOptions{Addrs: []string{"127.0.0.1:2"}},
246+
Weight: 1.0,
247+
HealthChecks: []redis.MultiDBHealthCheck{newFakeHealthCheck(true)},
248+
SkipInitialHealthCheck: true,
249+
}
250+
251+
// Live autopipeliner blocks cluster additions.
252+
if _, err := mdb.AddDatabase(ctx, clusterCfg); err == nil {
253+
t.Fatal("AddDatabase(cluster) should be refused while an autopipeliner is live")
254+
}
255+
256+
// A CLOSED autopipeliner must not block forever.
257+
if err := ap.Close(); err != nil {
258+
t.Fatalf("AutoPipeliner.Close: %v", err)
259+
}
260+
if _, err := mdb.AddDatabase(ctx, clusterCfg); err != nil {
261+
t.Fatalf("AddDatabase(cluster) after closing the autopipeliner: %v", err)
262+
}
263+
264+
// And with a cluster member present, new autopipeliners are refused.
265+
if _, err := mdb.AutoPipeline(); err == nil {
266+
t.Error("AutoPipeline should be refused once a cluster member exists")
267+
}
268+
}
269+
234270
func TestMultiDBAutoPipelineRoutesAndFailsOver(t *testing.T) {
235271
db1 := newTestDB("db1", "127.0.0.1:1", 2.0, true)
236272
db2 := newTestDB("db2", "127.0.0.1:2", 1.0, true)

multidb_pipeline.go

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -314,12 +314,17 @@ func (c *MultiDBClient) AutoPipeline() (*AutoPipeliner, error) {
314314
//
315315
// EXPERIMENTAL: this API is subject to change, use with caution.
316316
func (c *MultiDBClient) AutoPipelineWithOptions(config *AutoPipelineOptions) (*AutoPipeliner, error) {
317-
if c.core.hasClusterMember() {
318-
return nil, errMultiDBAutoPipelineCluster
319-
}
317+
// The cluster-member check runs inside the build function, under
318+
// autopipelinerMu: AddDatabase performs its cluster-vs-autopipeliner
319+
// check under the same mutex, so the two cannot interleave.
320320
return getOrCreateAutoPipeliner(c.autopipelinerMu, &c.autopipeliner, &c.autopipelinerClosed, nil, config,
321321
DefaultBlockingAutoPipelineOptions,
322-
func(cfg *AutoPipelineOptions) (*AutoPipeliner, error) { return newAutoPipeliner(c, cfg, true) })
322+
func(cfg *AutoPipelineOptions) (*AutoPipeliner, error) {
323+
if c.core.hasClusterMember() {
324+
return nil, errMultiDBAutoPipelineCluster
325+
}
326+
return newAutoPipeliner(c, cfg, true)
327+
})
323328
}
324329

325330
// AsyncAutoPipeline returns the deferred autopipeliner for this MultiDB
@@ -334,10 +339,12 @@ func (c *MultiDBClient) AsyncAutoPipeline() (*AutoPipeliner, error) {
334339
//
335340
// EXPERIMENTAL: this API is subject to change, use with caution.
336341
func (c *MultiDBClient) AsyncAutoPipelineWithOptions(config *AutoPipelineOptions) (*AutoPipeliner, error) {
337-
if c.core.hasClusterMember() {
338-
return nil, errMultiDBAutoPipelineCluster
339-
}
340342
return getOrCreateAutoPipeliner(c.autopipelinerMu, &c.asyncAutopipeliner, &c.autopipelinerClosed, nil, config,
341343
DefaultAutoPipelineOptions,
342-
func(cfg *AutoPipelineOptions) (*AutoPipeliner, error) { return newAutoPipeliner(c, cfg, false) })
344+
func(cfg *AutoPipelineOptions) (*AutoPipeliner, error) {
345+
if c.core.hasClusterMember() {
346+
return nil, errMultiDBAutoPipelineCluster
347+
}
348+
return newAutoPipeliner(c, cfg, false)
349+
})
343350
}

0 commit comments

Comments
 (0)