Skip to content

Commit 3d041a1

Browse files
ndyakovofekshenawaLINKIWICgol9Christopher Golling
authored
release: 9.7.1 patch (#3278)
* Add guidance on unstable RESP3 support for RediSearch commands to README (#3177) * Add UnstableResp3 to docs * Add RawVal and RawResult to wordlist * Explain more about SetVal * Add UnstableResp to wordlist * Eliminate redundant dial mutex causing unbounded connection queue contention (#3088) * Eliminate redundant dial mutex causing unbounded connection queue contention * Dialer connection timeouts unit test --------- Co-authored-by: ofekshenawa <[email protected]> * SortByWithCount FTSearchOptions fix (#3201) * SortByWithCount FTSearchOptions fix * FTSearch test fix * Another FTSearch test fix * Another FTSearch test fix --------- Co-authored-by: Christopher Golling <[email protected]> * Fix race condition in clusterNodes.Addrs() (#3219) Resolve a race condition in the clusterNodes.Addrs() method. Previously, the method returned a reference to a string slice, creating the potential for concurrent reads by the caller while the slice was being modified by the garbage collection process. Co-authored-by: Nedyalko Dyakov <[email protected]> * chore: fix some comments (#3226) Signed-off-by: zhuhaicity <[email protected]> Co-authored-by: Nedyalko Dyakov <[email protected]> * fix(aggregate, search): ft.aggregate bugfixes (#3263) * fix: rearange args for ft.aggregate apply should be before any groupby or sortby * improve test * wip: add scorer and addscores * enable all tests * fix ftsearch with count test * make linter happy * Addscores is available in later redisearch releases. For safety state it is available in redis ce 8 * load an apply seem to break scorer and addscores * fix: add unstableresp3 to cluster client (#3266) * fix: add unstableresp3 to cluster client * propagate unstableresp3 * proper test that will ignore error, but fail if client panics * add separate test for clusterclient constructor * fix: flaky ClientKillByFilter test (#3268) * Reinstate read-only lock on hooks access in dialHook (#3225) * use limit when limitoffset is zero (#3275) * remove redis 8 comments * update package versions * use latest golangci-lint * fix(search&aggregate):fix error overwrite and typo #3220 (#3224) * fix (#3220) * LOAD has NO AS param(https://redis.io/docs/latest/commands/ft.aggregate/) * fix typo: WITHCOUT -> WITHCOUNT * fix (#3220): * Compatible with known RediSearch issue in test * fix (#3220) * fixed the calculation bug of the count of load params * test should not include special condition * return errors when they occur --------- Co-authored-by: Nedyalko Dyakov <[email protected]> Co-authored-by: ofekshenawa <[email protected]> * Recognize byte slice for key argument in cluster client hash slot computation (#3049) Co-authored-by: Vladyslav Vildanov <[email protected]> Co-authored-by: ofekshenawa <[email protected]> --------- Signed-off-by: zhuhaicity <[email protected]> Co-authored-by: ofekshenawa <[email protected]> Co-authored-by: LINKIWI <[email protected]> Co-authored-by: Cgol9 <[email protected]> Co-authored-by: Christopher Golling <[email protected]> Co-authored-by: Shawn Wang <[email protected]> Co-authored-by: ZhuHaiCheng <[email protected]> Co-authored-by: herodot <[email protected]> Co-authored-by: Vladyslav Vildanov <[email protected]>
1 parent ed37c33 commit 3d041a1

27 files changed

+365
-72
lines changed

.github/wordlist.txt

+3
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ stunnel
5454
SynDump
5555
TCP
5656
TLS
57+
UnstableResp
5758
uri
5859
URI
5960
url
@@ -62,3 +63,5 @@ RedisStack
6263
RedisGears
6364
RedisTimeseries
6465
RediSearch
66+
RawResult
67+
RawVal

.github/workflows/golangci-lint.yml

+2-4
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,13 @@ on:
1212

1313
permissions:
1414
contents: read
15+
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
1516

1617
jobs:
1718
golangci:
18-
permissions:
19-
contents: read # for actions/checkout to fetch code
20-
pull-requests: read # for golangci/golangci-lint-action to fetch pull requests
2119
name: lint
2220
runs-on: ubuntu-latest
2321
steps:
2422
- uses: actions/checkout@v4
2523
- name: golangci-lint
26-
uses: golangci/golangci-lint-action@v6
24+
uses: golangci/golangci-lint-action@v6.5.0

.golangci.yml

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
run:
2-
concurrency: 8
3-
deadline: 5m
2+
timeout: 5m
43
tests: false

README.md

+15
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,21 @@ rdb := redis.NewClient(&redis.Options{
186186
#### Unstable RESP3 Structures for RediSearch Commands
187187
When integrating Redis with application functionalities using RESP3, it's important to note that some response structures aren't final yet. This is especially true for more complex structures like search and query results. We recommend using RESP2 when using the search and query capabilities, but we plan to stabilize the RESP3-based API-s in the coming versions. You can find more guidance in the upcoming release notes.
188188

189+
To enable unstable RESP3, set the option in your client configuration:
190+
191+
```go
192+
redis.NewClient(&redis.Options{
193+
UnstableResp3: true,
194+
})
195+
```
196+
**Note:** When UnstableResp3 mode is enabled, it's necessary to use RawResult() and RawVal() to retrieve a raw data.
197+
Since, raw response is the only option for unstable search commands Val() and Result() calls wouldn't have any affect on them:
198+
199+
```go
200+
res1, err := client.FTSearchWithArgs(ctx, "txt", "foo bar", &redis.FTSearchOptions{}).RawResult()
201+
val1 := client.FTSearchWithArgs(ctx, "txt", "foo bar", &redis.FTSearchOptions{}).RawVal()
202+
```
203+
189204
## Contributing
190205

191206
Please see [out contributing guidelines](CONTRIBUTING.md) to help us improve this library!

command.go

+2
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,8 @@ func (cmd *baseCmd) stringArg(pos int) string {
167167
switch v := arg.(type) {
168168
case string:
169169
return v
170+
case []byte:
171+
return string(v)
170172
default:
171173
// TODO: consider using appendArg
172174
return fmt.Sprint(v)

commands_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ var _ = Describe("Commands", func() {
217217

218218
killed := client.ClientKillByFilter(ctx, "MAXAGE", "1")
219219
Expect(killed.Err()).NotTo(HaveOccurred())
220-
Expect(killed.Val()).To(SatisfyAny(Equal(int64(2)), Equal(int64(3))))
220+
Expect(killed.Val()).To(BeNumerically(">=", 2))
221221

222222
select {
223223
case <-done:

example/del-keys-without-ttl/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.18
55
replace github.com/redis/go-redis/v9 => ../..
66

77
require (
8-
github.com/redis/go-redis/v9 v9.7.0
8+
github.com/redis/go-redis/v9 v9.7.1
99
go.uber.org/zap v1.24.0
1010
)
1111

example/hll/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.18
44

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

7-
require github.com/redis/go-redis/v9 v9.7.0
7+
require github.com/redis/go-redis/v9 v9.7.1
88

99
require (
1010
github.com/cespare/xxhash/v2 v2.2.0 // indirect

example/lua-scripting/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.18
44

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

7-
require github.com/redis/go-redis/v9 v9.7.0
7+
require github.com/redis/go-redis/v9 v9.7.1
88

99
require (
1010
github.com/cespare/xxhash/v2 v2.2.0 // indirect

example/otel/go.mod

+3-3
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ replace github.com/redis/go-redis/extra/redisotel/v9 => ../../extra/redisotel
99
replace github.com/redis/go-redis/extra/rediscmd/v9 => ../../extra/rediscmd
1010

1111
require (
12-
github.com/redis/go-redis/extra/redisotel/v9 v9.7.0
13-
github.com/redis/go-redis/v9 v9.7.0
12+
github.com/redis/go-redis/extra/redisotel/v9 v9.7.1
13+
github.com/redis/go-redis/v9 v9.7.1
1414
github.com/uptrace/uptrace-go v1.21.0
1515
go.opentelemetry.io/otel v1.22.0
1616
)
@@ -23,7 +23,7 @@ require (
2323
github.com/go-logr/stdr v1.2.2 // indirect
2424
github.com/golang/protobuf v1.5.3 // indirect
2525
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 // indirect
26-
github.com/redis/go-redis/extra/rediscmd/v9 v9.7.0 // indirect
26+
github.com/redis/go-redis/extra/rediscmd/v9 v9.7.1 // indirect
2727
go.opentelemetry.io/contrib/instrumentation/runtime v0.46.1 // indirect
2828
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v0.44.0 // indirect
2929
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.21.0 // indirect

example/redis-bloom/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ go 1.18
44

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

7-
require github.com/redis/go-redis/v9 v9.7.0
7+
require github.com/redis/go-redis/v9 v9.7.1
88

99
require (
1010
github.com/cespare/xxhash/v2 v2.2.0 // indirect

example/scan-struct/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ replace github.com/redis/go-redis/v9 => ../..
66

77
require (
88
github.com/davecgh/go-spew v1.1.1
9-
github.com/redis/go-redis/v9 v9.7.0
9+
github.com/redis/go-redis/v9 v9.7.1
1010
)
1111

1212
require (

extra/rediscensus/go.mod

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ replace github.com/redis/go-redis/v9 => ../..
77
replace github.com/redis/go-redis/extra/rediscmd/v9 => ../rediscmd
88

99
require (
10-
github.com/redis/go-redis/extra/rediscmd/v9 v9.7.0
11-
github.com/redis/go-redis/v9 v9.7.0
10+
github.com/redis/go-redis/extra/rediscmd/v9 v9.7.1
11+
github.com/redis/go-redis/v9 v9.7.1
1212
go.opencensus.io v0.24.0
1313
)
1414

extra/rediscmd/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ replace github.com/redis/go-redis/v9 => ../..
77
require (
88
github.com/bsm/ginkgo/v2 v2.12.0
99
github.com/bsm/gomega v1.27.10
10-
github.com/redis/go-redis/v9 v9.7.0
10+
github.com/redis/go-redis/v9 v9.7.1
1111
)
1212

1313
require (

extra/redisotel/go.mod

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ replace github.com/redis/go-redis/v9 => ../..
77
replace github.com/redis/go-redis/extra/rediscmd/v9 => ../rediscmd
88

99
require (
10-
github.com/redis/go-redis/extra/rediscmd/v9 v9.7.0
11-
github.com/redis/go-redis/v9 v9.7.0
10+
github.com/redis/go-redis/extra/rediscmd/v9 v9.7.1
11+
github.com/redis/go-redis/v9 v9.7.1
1212
go.opentelemetry.io/otel v1.22.0
1313
go.opentelemetry.io/otel/metric v1.22.0
1414
go.opentelemetry.io/otel/sdk v1.22.0

extra/redisprometheus/go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ replace github.com/redis/go-redis/v9 => ../..
66

77
require (
88
github.com/prometheus/client_golang v1.14.0
9-
github.com/redis/go-redis/v9 v9.7.0
9+
github.com/redis/go-redis/v9 v9.7.1
1010
)
1111

1212
require (

hash_commands.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ func (c cmdable) HExpire(ctx context.Context, key string, expiration time.Durati
225225
return cmd
226226
}
227227

228-
// HExpire - Sets the expiration time for specified fields in a hash in seconds.
228+
// HExpireWithArgs - Sets the expiration time for specified fields in a hash in seconds.
229229
// It requires a key, an expiration duration, a struct with boolean flags for conditional expiration settings (NX, XX, GT, LT), and a list of fields.
230230
// The command constructs an argument list starting with "HEXPIRE", followed by the key, duration, any conditional flags, and the specified fields.
231231
// For more information - https://redis.io/commands/hexpire/

options.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,7 @@ type Options struct {
154154
// Add suffix to client name. Default is empty.
155155
IdentitySuffix string
156156

157-
// Enable Unstable mode for Redis Search module with RESP3.
157+
// UnstableResp3 enables Unstable mode for Redis Search module with RESP3.
158158
UnstableResp3 bool
159159
}
160160

osscluster.go

+9-3
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,9 @@ type ClusterOptions struct {
9090
DisableIndentity bool // Disable set-lib on connect. Default is false.
9191

9292
IdentitySuffix string // Add suffix to client name. Default is empty.
93+
94+
// UnstableResp3 enables Unstable mode for Redis Search module with RESP3.
95+
UnstableResp3 bool
9396
}
9497

9598
func (opt *ClusterOptions) init() {
@@ -304,7 +307,8 @@ func (opt *ClusterOptions) clientOptions() *Options {
304307
// much use for ClusterSlots config). This means we cannot execute the
305308
// READONLY command against that node -- setting readOnly to false in such
306309
// situations in the options below will prevent that from happening.
307-
readOnly: opt.ReadOnly && opt.ClusterSlots == nil,
310+
readOnly: opt.ReadOnly && opt.ClusterSlots == nil,
311+
UnstableResp3: opt.UnstableResp3,
308312
}
309313
}
310314

@@ -465,9 +469,11 @@ func (c *clusterNodes) Addrs() ([]string, error) {
465469
closed := c.closed //nolint:ifshort
466470
if !closed {
467471
if len(c.activeAddrs) > 0 {
468-
addrs = c.activeAddrs
472+
addrs = make([]string, len(c.activeAddrs))
473+
copy(addrs, c.activeAddrs)
469474
} else {
470-
addrs = c.addrs
475+
addrs = make([]string, len(c.addrs))
476+
copy(addrs, c.addrs)
471477
}
472478
}
473479
c.mu.RUnlock()

osscluster_test.go

+26
Original file line numberDiff line numberDiff line change
@@ -653,6 +653,32 @@ var _ = Describe("ClusterClient", func() {
653653
Expect(client.Close()).NotTo(HaveOccurred())
654654
})
655655

656+
It("determines hash slots correctly for generic commands", func() {
657+
opt := redisClusterOptions()
658+
opt.MaxRedirects = -1
659+
client := cluster.newClusterClient(ctx, opt)
660+
661+
err := client.Do(ctx, "GET", "A").Err()
662+
Expect(err).To(Equal(redis.Nil))
663+
664+
err = client.Do(ctx, []byte("GET"), []byte("A")).Err()
665+
Expect(err).To(Equal(redis.Nil))
666+
667+
Eventually(func() error {
668+
return client.SwapNodes(ctx, "A")
669+
}, 30*time.Second).ShouldNot(HaveOccurred())
670+
671+
err = client.Do(ctx, "GET", "A").Err()
672+
Expect(err).To(HaveOccurred())
673+
Expect(err.Error()).To(ContainSubstring("MOVED"))
674+
675+
err = client.Do(ctx, []byte("GET"), []byte("A")).Err()
676+
Expect(err).To(HaveOccurred())
677+
Expect(err.Error()).To(ContainSubstring("MOVED"))
678+
679+
Expect(client.Close()).NotTo(HaveOccurred())
680+
})
681+
656682
It("follows node redirection immediately", func() {
657683
// Configure retry backoffs far in excess of the expected duration of redirection
658684
opt := redisClusterOptions()

redis.go

+11-6
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,15 @@ type (
4141
)
4242

4343
type hooksMixin struct {
44-
hooksMu *sync.Mutex
44+
hooksMu *sync.RWMutex
4545

4646
slice []Hook
4747
initial hooks
4848
current hooks
4949
}
5050

5151
func (hs *hooksMixin) initHooks(hooks hooks) {
52-
hs.hooksMu = new(sync.Mutex)
52+
hs.hooksMu = new(sync.RWMutex)
5353
hs.initial = hooks
5454
hs.chain()
5555
}
@@ -151,7 +151,7 @@ func (hs *hooksMixin) clone() hooksMixin {
151151
clone := *hs
152152
l := len(clone.slice)
153153
clone.slice = clone.slice[:l:l]
154-
clone.hooksMu = new(sync.Mutex)
154+
clone.hooksMu = new(sync.RWMutex)
155155
return clone
156156
}
157157

@@ -176,9 +176,14 @@ func (hs *hooksMixin) withProcessPipelineHook(
176176
}
177177

178178
func (hs *hooksMixin) dialHook(ctx context.Context, network, addr string) (net.Conn, error) {
179-
hs.hooksMu.Lock()
180-
defer hs.hooksMu.Unlock()
181-
return hs.current.dial(ctx, network, addr)
179+
// Access to hs.current is guarded by a read-only lock since it may be mutated by AddHook(...)
180+
// while this dialer is concurrently accessed by the background connection pool population
181+
// routine when MinIdleConns > 0.
182+
hs.hooksMu.RLock()
183+
current := hs.current
184+
hs.hooksMu.RUnlock()
185+
186+
return current.dial(ctx, network, addr)
182187
}
183188

184189
func (hs *hooksMixin) processHook(ctx context.Context, cmd Cmder) error {

redis_test.go

+65
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88
"net"
9+
"sync"
910
"testing"
1011
"time"
1112

@@ -633,3 +634,67 @@ var _ = Describe("Hook with MinIdleConns", func() {
633634
}))
634635
})
635636
})
637+
638+
var _ = Describe("Dialer connection timeouts", func() {
639+
var client *redis.Client
640+
641+
const dialSimulatedDelay = 1 * time.Second
642+
643+
BeforeEach(func() {
644+
options := redisOptions()
645+
options.Dialer = func(ctx context.Context, network, addr string) (net.Conn, error) {
646+
// Simulated slow dialer.
647+
// Note that the following sleep is deliberately not context-aware.
648+
time.Sleep(dialSimulatedDelay)
649+
return net.Dial("tcp", options.Addr)
650+
}
651+
options.MinIdleConns = 1
652+
client = redis.NewClient(options)
653+
})
654+
655+
AfterEach(func() {
656+
err := client.Close()
657+
Expect(err).NotTo(HaveOccurred())
658+
})
659+
660+
It("does not contend on connection dial for concurrent commands", func() {
661+
var wg sync.WaitGroup
662+
663+
const concurrency = 10
664+
665+
durations := make(chan time.Duration, concurrency)
666+
errs := make(chan error, concurrency)
667+
668+
start := time.Now()
669+
wg.Add(concurrency)
670+
671+
for i := 0; i < concurrency; i++ {
672+
go func() {
673+
defer wg.Done()
674+
675+
start := time.Now()
676+
err := client.Ping(ctx).Err()
677+
durations <- time.Since(start)
678+
errs <- err
679+
}()
680+
}
681+
682+
wg.Wait()
683+
close(durations)
684+
close(errs)
685+
686+
// All commands should eventually succeed, after acquiring a connection.
687+
for err := range errs {
688+
Expect(err).NotTo(HaveOccurred())
689+
}
690+
691+
// Each individual command should complete within the simulated dial duration bound.
692+
for duration := range durations {
693+
Expect(duration).To(BeNumerically("<", 2*dialSimulatedDelay))
694+
}
695+
696+
// Due to concurrent execution, the entire test suite should also complete within
697+
// the same dial duration bound applied for individual commands.
698+
Expect(time.Since(start)).To(BeNumerically("<", 2*dialSimulatedDelay))
699+
})
700+
})

0 commit comments

Comments
 (0)