From 60b748bf1a626a798806b55c1e7ac180fe0217ab Mon Sep 17 00:00:00 2001 From: manish Date: Wed, 5 Nov 2025 20:23:45 +0530 Subject: [PATCH] fix(sentinel): handle empty address (#3577) * improvements * linter fixes * prevention on unnecessary allocations in case of bad configuration * Test/Benchmark, old code with safety harness preventing panic --------- Co-authored-by: manish Co-authored-by: Nedyalko Dyakov <1547186+ndyakov@users.noreply.github.com> --- export_test.go | 4 ++ sentinel.go | 9 ++++- sentinel_test.go | 96 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/export_test.go b/export_test.go index c1b77683..97b6179a 100644 --- a/export_test.go +++ b/export_test.go @@ -106,3 +106,7 @@ func (c *ModuleLoadexConfig) ToArgs() []interface{} { func ShouldRetry(err error, retryTimeout bool) bool { return shouldRetry(err, retryTimeout) } + +func JoinErrors(errs []error) string { + return joinErrors(errs) +} diff --git a/sentinel.go b/sentinel.go index f1222a34..6481e1ee 100644 --- a/sentinel.go +++ b/sentinel.go @@ -843,6 +843,11 @@ func (c *sentinelFailover) MasterAddr(ctx context.Context) (string, error) { } } + // short circuit if no sentinels configured + if len(c.sentinelAddrs) == 0 { + return "", errors.New("redis: no sentinels configured") + } + var ( masterAddr string wg sync.WaitGroup @@ -890,10 +895,12 @@ func (c *sentinelFailover) MasterAddr(ctx context.Context) (string, error) { } func joinErrors(errs []error) string { + if len(errs) == 0 { + return "" + } if len(errs) == 1 { return errs[0].Error() } - b := []byte(errs[0].Error()) for _, err := range errs[1:] { b = append(b, '\n') diff --git a/sentinel_test.go b/sentinel_test.go index f332822f..0f0f61eb 100644 --- a/sentinel_test.go +++ b/sentinel_test.go @@ -682,3 +682,99 @@ func compareSlices(t *testing.T, a, b []string, name string) { } } } + +type joinErrorsTest struct { + name string + errs []error + expected string +} + +func TestJoinErrors(t *testing.T) { + tests := []joinErrorsTest{ + { + name: "empty slice", + errs: []error{}, + expected: "", + }, + { + name: "single error", + errs: []error{errors.New("first error")}, + expected: "first error", + }, + { + name: "two errors", + errs: []error{errors.New("first error"), errors.New("second error")}, + expected: "first error\nsecond error", + }, + { + name: "multiple errors", + errs: []error{ + errors.New("first error"), + errors.New("second error"), + errors.New("third error"), + }, + expected: "first error\nsecond error\nthird error", + }, + { + name: "nil slice", + errs: nil, + expected: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := redis.JoinErrors(tt.errs) + if result != tt.expected { + t.Errorf("joinErrors() = %q, want %q", result, tt.expected) + } + }) + } +} + +func BenchmarkJoinErrors(b *testing.B) { + benchmarks := []joinErrorsTest{ + { + name: "empty slice", + errs: []error{}, + expected: "", + }, + { + name: "single error", + errs: []error{errors.New("first error")}, + expected: "first error", + }, + { + name: "two errors", + errs: []error{errors.New("first error"), errors.New("second error")}, + expected: "first error\nsecond error", + }, + { + name: "multiple errors", + errs: []error{ + errors.New("first error"), + errors.New("second error"), + errors.New("third error"), + }, + expected: "first error\nsecond error\nthird error", + }, + { + name: "nil slice", + errs: nil, + expected: "", + }, + } + for _, bm := range benchmarks { + b.Run(bm.name, func(b *testing.B) { + b.ResetTimer() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + result := redis.JoinErrors(bm.errs) + if result != bm.expected { + b.Errorf("joinErrors() = %q, want %q", result, bm.expected) + } + } + }) + }) + } +}