Skip to content

Commit b8afd7f

Browse files
committed
Merge remote-tracking branch 'upstream/master' into add-metrics
2 parents 22a92e7 + 4edf494 commit b8afd7f

File tree

36 files changed

+629
-85
lines changed

36 files changed

+629
-85
lines changed

.github/wordlist.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,4 @@ oauth
7676
entraid
7777
MiB
7878
KiB
79+
oldstable

.github/workflows/build.yml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,10 @@ jobs:
2222
- "8.2.x" # Redis CE 8.2
2323
- "8.0.x" # Redis CE 8.0
2424
go-version:
25+
- "1.21.x"
2526
- "1.23.x"
26-
- "1.24.x"
27+
- oldstable
28+
- stable
2729

2830
steps:
2931
- name: Set up ${{ matrix.go-version }}
@@ -78,8 +80,10 @@ jobs:
7880
- "8.2.x" # Redis CE 8.2
7981
- "8.0.x" # Redis CE 8.0
8082
go-version:
83+
- "1.21.x"
8184
- "1.23.x"
82-
- "1.24.x"
85+
- oldstable
86+
- stable
8387

8488
steps:
8589
- name: Checkout code

.github/workflows/golangci-lint.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ jobs:
2222
steps:
2323
- uses: actions/checkout@v6
2424
- name: golangci-lint
25-
uses: golangci/golangci-lint-action@v9.1.0
25+
uses: golangci/golangci-lint-action@v9.2.0
2626
with:
2727
verify: true
2828

README.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,12 @@ In `go-redis` we are aiming to support the last three releases of Redis. Current
2121
- [Redis 8.2](https://github.com/redis/redis/8.2/00-RELEASENOTES) - using Redis CE 8.2
2222
- [Redis 8.4](https://github.com/redis/redis/8.4/00-RELEASENOTES) - using Redis CE 8.4
2323

24-
Although the `go.mod` states it requires at minimum `go 1.18`, our CI is configured to run the tests against all three
25-
versions of Redis and latest two versions of Go ([1.23](https://go.dev/doc/devel/release#go1.23.0),
26-
[1.24](https://go.dev/doc/devel/release#go1.24.0)). We observe that some modules related test may not pass with
24+
Although the `go.mod` states it requires at minimum `go 1.21`, our CI is configured to run the tests against all three
25+
versions of Redis and multiple versions of Go ([1.21](https://go.dev/doc/devel/release#go1.21.0),
26+
[1.23](https://go.dev/doc/devel/release#go1.23.0), oldstable, and stable). We observe that some modules related test may not pass with
2727
Redis Stack 7.2 and some commands are changed with Redis CE 8.0.
2828
Although it is not officially supported, `go-redis/v9` should be able to work with any Redis 7.0+.
29-
Please do refer to the documentation and the tests if you experience any issues. We do plan to update the go version
30-
in the `go.mod` to `go 1.24` in one of the next releases.
29+
Please do refer to the documentation and the tests if you experience any issues.
3130

3231
## How do I Redis?
3332

RELEASE-NOTES.md

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,46 @@
11
# Release Notes
22

3+
# 9.18.0-beta.2 (2025-12-09)
4+
5+
## 🚀 Highlights
6+
7+
### Go Version Update
8+
9+
This release updates the minimum required Go version to 1.21. This is part of a gradual migration strategy where the minimum supported Go version will be three versions behind the latest release. With each new Go version release, we will bump the minimum version by one, ensuring compatibility while staying current with the Go ecosystem.
10+
11+
### Stability Improvements
12+
13+
This release includes several important stability fixes:
14+
- Fixed a critical panic in the handoff worker manager that could occur when handling nil errors
15+
- Improved test reliability for Smart Client Handoff functionality
16+
- Fixed logging format issues that could cause runtime errors
17+
18+
## ✨ New Features
19+
20+
- OpenTelemetry metrics improvements for nil response handling ([#3638](https://github.com/redis/go-redis/pull/3638)) by [@fengve](https://github.com/fengve)
21+
22+
## 🐛 Bug Fixes
23+
24+
- Fixed panic on nil error in handoffWorkerManager closeConnFromRequest ([#3633](https://github.com/redis/go-redis/pull/3633)) by [@ccoVeille](https://github.com/ccoVeille)
25+
- Fixed bad sprintf syntax in logging ([#3632](https://github.com/redis/go-redis/pull/3632)) by [@ccoVeille](https://github.com/ccoVeille)
26+
27+
## 🧰 Maintenance
28+
29+
- Updated minimum Go version to 1.21 ([#3640](https://github.com/redis/go-redis/pull/3640)) by [@ndyakov](https://github.com/ndyakov)
30+
- Use Go 1.20 idiomatic string<->byte conversion ([#3435](https://github.com/redis/go-redis/pull/3435)) by [@justinhwang](https://github.com/justinhwang)
31+
- Reduce flakiness of Smart Client Handoff test ([#3641](https://github.com/redis/go-redis/pull/3641)) by [@kiryazovi-redis](https://github.com/kiryazovi-redis)
32+
- Revert PR #3634 (Observability metrics phase1) ([#3635](https://github.com/redis/go-redis/pull/3635)) by [@ofekshenawa](https://github.com/ofekshenawa)
33+
34+
## 👥 Contributors
35+
36+
We'd like to thank all the contributors who worked on this release!
37+
38+
[@justinhwang](https://github.com/justinhwang), [@ndyakov](https://github.com/ndyakov), [@kiryazovi-redis](https://github.com/kiryazovi-redis), [@fengve](https://github.com/fengve), [@ccoVeille](https://github.com/ccoVeille), [@ofekshenawa](https://github.com/ofekshenawa)
39+
40+
---
41+
42+
**Full Changelog**: https://github.com/redis/go-redis/compare/v9.18.0-beta.1...v9.18.0-beta.2
43+
344
# 9.18.0-beta.1 (2025-12-01)
445

546
## 🚀 Highlights

doctests/timeseries_tut_test.go

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,24 @@ package example_commands_test
55
import (
66
"context"
77
"fmt"
8-
"maps"
98
"math"
10-
"slices"
119
"sort"
1210

1311
"github.com/redis/go-redis/v9"
1412
)
1513

1614
// HIDE_END
1715

16+
// mapKeys returns a slice of all keys from the map (Go 1.21 compatible)
17+
// TODO: Once minimum Go version is upgraded to 1.23+, replace with slices.Collect(maps.Keys(m))
18+
func mapKeys[K comparable, V any](m map[K]V) []K {
19+
keys := make([]K, 0, len(m))
20+
for k := range m {
21+
keys = append(keys, k)
22+
}
23+
return keys
24+
}
25+
1826
func ExampleClient_timeseries_create() {
1927
ctx := context.Background()
2028

@@ -417,7 +425,7 @@ func ExampleClient_timeseries_query_multi() {
417425
panic(err)
418426
}
419427

420-
res28Keys := slices.Collect(maps.Keys(res28))
428+
res28Keys := mapKeys(res28)
421429
sort.Strings(res28Keys)
422430

423431
for _, k := range res28Keys {
@@ -457,7 +465,7 @@ func ExampleClient_timeseries_query_multi() {
457465
panic(err)
458466
}
459467

460-
res29Keys := slices.Collect(maps.Keys(res29))
468+
res29Keys := mapKeys(res29)
461469
sort.Strings(res29Keys)
462470

463471
for _, k := range res29Keys {
@@ -505,7 +513,7 @@ func ExampleClient_timeseries_query_multi() {
505513
panic(err)
506514
}
507515

508-
res30Keys := slices.Collect(maps.Keys(res30))
516+
res30Keys := mapKeys(res30)
509517
sort.Strings(res30Keys)
510518

511519
for _, k := range res30Keys {
@@ -550,7 +558,7 @@ func ExampleClient_timeseries_query_multi() {
550558
panic(err)
551559
}
552560

553-
res31Keys := slices.Collect(maps.Keys(res31))
561+
res31Keys := mapKeys(res31)
554562
sort.Strings(res31Keys)
555563

556564
for _, k := range res31Keys {
@@ -857,7 +865,7 @@ func ExampleClient_timeseries_aggmulti() {
857865
panic(err)
858866
}
859867

860-
res44Keys := slices.Collect(maps.Keys(res44))
868+
res44Keys := mapKeys(res44)
861869
sort.Strings(res44Keys)
862870

863871
for _, k := range res44Keys {
@@ -905,7 +913,7 @@ func ExampleClient_timeseries_aggmulti() {
905913
panic(err)
906914
}
907915

908-
res45Keys := slices.Collect(maps.Keys(res45))
916+
res45Keys := mapKeys(res45)
909917
sort.Strings(res45Keys)
910918

911919
for _, k := range res45Keys {

error.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,9 @@ func shouldRetry(err error, retryTimeout bool) bool {
124124
if proto.IsTryAgainError(err) {
125125
return true
126126
}
127+
if proto.IsNoReplicasError(err) {
128+
return true
129+
}
127130

128131
// Fallback to string checking for backward compatibility with plain errors
129132
s := err.Error()
@@ -145,6 +148,9 @@ func shouldRetry(err error, retryTimeout bool) bool {
145148
if strings.HasPrefix(s, "MASTERDOWN ") {
146149
return true
147150
}
151+
if strings.HasPrefix(s, "NOREPLICAS ") {
152+
return true
153+
}
148154

149155
return false
150156
}
@@ -342,6 +348,14 @@ func IsOOMError(err error) bool {
342348
return proto.IsOOMError(err)
343349
}
344350

351+
// IsNoReplicasError checks if an error is a Redis NOREPLICAS error, even if wrapped.
352+
// NOREPLICAS errors occur when not enough replicas acknowledge a write operation.
353+
// This typically happens with WAIT/WAITAOF commands or CLUSTER SETSLOT with synchronous
354+
// replication when the required number of replicas cannot confirm the write within the timeout.
355+
func IsNoReplicasError(err error) bool {
356+
return proto.IsNoReplicasError(err)
357+
}
358+
345359
//------------------------------------------------------------------------------
346360

347361
type timeoutError interface {

error_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ var _ = Describe("error", func() {
4545
proto.ParseErrorReply([]byte("-READONLY You can't write against a read only replica")): true,
4646
proto.ParseErrorReply([]byte("-CLUSTERDOWN The cluster is down")): true,
4747
proto.ParseErrorReply([]byte("-TRYAGAIN Command cannot be processed, please try again")): true,
48-
proto.ParseErrorReply([]byte("-ERR other")): false,
48+
proto.ParseErrorReply([]byte("-NOREPLICAS Not enough good replicas to write")): true,
49+
proto.ParseErrorReply([]byte("-ERR other")): false,
4950
}
5051

5152
for err, expected := range data {

error_wrapping_test.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -239,10 +239,10 @@ func TestErrorWrappingInHookScenario(t *testing.T) {
239239
// TestShouldRetryWithTypedErrors tests that shouldRetry works with typed errors
240240
func TestShouldRetryWithTypedErrors(t *testing.T) {
241241
tests := []struct {
242-
name string
243-
errorMsg string
244-
shouldRetry bool
245-
retryTimeout bool
242+
name string
243+
errorMsg string
244+
shouldRetry bool
245+
retryTimeout bool
246246
}{
247247
{
248248
name: "LOADING error should retry",
@@ -280,6 +280,12 @@ func TestShouldRetryWithTypedErrors(t *testing.T) {
280280
shouldRetry: true,
281281
retryTimeout: false,
282282
},
283+
{
284+
name: "NOREPLICAS error should retry",
285+
errorMsg: "NOREPLICAS Not enough good replicas to write",
286+
shouldRetry: true,
287+
retryTimeout: false,
288+
},
283289
}
284290

285291
for _, tt := range tests {

example/cluster-mget/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module github.com/redis/go-redis/example/cluster-mget
22

3-
go 1.18
3+
go 1.21
44

55
replace github.com/redis/go-redis/v9 => ../..
66

0 commit comments

Comments
 (0)