Skip to content

Commit 5767a31

Browse files
authored
Merge branch 'master' into ndyakov/CAE-1088-resp3-notification-handlers
2 parents 92b2861 + 1eed165 commit 5767a31

File tree

10 files changed

+389
-22
lines changed

10 files changed

+389
-22
lines changed

.github/wordlist.txt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,4 +73,5 @@ OAuth
7373
Azure
7474
StreamingCredentialsProvider
7575
oauth
76-
entraid
76+
entraid
77+
MiB

.github/workflows/stale-issues.yml

Lines changed: 89 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,97 @@
1-
name: "Close stale issues"
1+
name: "Stale Issue Management"
22
on:
33
schedule:
4-
- cron: "0 0 * * *"
4+
# Run daily at midnight UTC
5+
- cron: "0 0 * * *"
6+
workflow_dispatch: # Allow manual triggering
7+
8+
env:
9+
# Default stale policy timeframes
10+
DAYS_BEFORE_STALE: 365
11+
DAYS_BEFORE_CLOSE: 30
12+
13+
# Accelerated timeline for needs-information issues
14+
NEEDS_INFO_DAYS_BEFORE_STALE: 30
15+
NEEDS_INFO_DAYS_BEFORE_CLOSE: 7
516

6-
permissions: {}
717
jobs:
818
stale:
9-
permissions:
10-
issues: write # to close stale issues (actions/stale)
11-
pull-requests: write # to close stale PRs (actions/stale)
19+
runs-on: ubuntu-latest
20+
steps:
21+
- uses: actions/stale@v9
22+
with:
23+
repo-token: ${{ secrets.GITHUB_TOKEN }}
24+
dry-run: true
25+
26+
# Default stale policy
27+
days-before-stale: ${{ env.DAYS_BEFORE_STALE }}
28+
days-before-close: ${{ env.DAYS_BEFORE_CLOSE }}
29+
30+
# Explicit stale label configuration
31+
stale-issue-label: "stale"
32+
stale-pr-label: "stale"
33+
34+
stale-issue-message: |
35+
This issue has been automatically marked as stale due to inactivity.
36+
It will be closed in 30 days if no further activity occurs.
37+
If you believe this issue is still relevant, please add a comment to keep it open.
38+
39+
close-issue-message: |
40+
This issue has been automatically closed due to inactivity.
41+
If you believe this issue is still relevant, please reopen it or create a new issue with updated information.
42+
43+
# Exclude needs-information issues from this job
44+
exempt-issue-labels: 'no-stale,needs-information'
45+
46+
# Remove stale label when issue/PR becomes active again
47+
remove-stale-when-updated: true
48+
49+
# Apply to pull requests with same timeline
50+
days-before-pr-stale: ${{ env.DAYS_BEFORE_STALE }}
51+
days-before-pr-close: ${{ env.DAYS_BEFORE_CLOSE }}
1252

53+
stale-pr-message: |
54+
This pull request has been automatically marked as stale due to inactivity.
55+
It will be closed in 30 days if no further activity occurs.
56+
57+
close-pr-message: |
58+
This pull request has been automatically closed due to inactivity.
59+
If you would like to continue this work, please reopen the PR or create a new one.
60+
61+
# Only exclude no-stale PRs (needs-information PRs follow standard timeline)
62+
exempt-pr-labels: 'no-stale'
63+
64+
# Separate job for needs-information issues ONLY with accelerated timeline
65+
stale-needs-info:
1366
runs-on: ubuntu-latest
1467
steps:
15-
- uses: actions/stale@v9
16-
with:
17-
repo-token: ${{ secrets.GITHUB_TOKEN }}
18-
stale-issue-message: 'This issue is marked stale. It will be closed in 30 days if it is not updated.'
19-
stale-pr-message: 'This pull request is marked stale. It will be closed in 30 days if it is not updated.'
20-
days-before-stale: 365
21-
days-before-close: 30
22-
stale-issue-label: "Stale"
23-
stale-pr-label: "Stale"
24-
operations-per-run: 10
25-
remove-stale-when-updated: true
68+
- uses: actions/stale@v9
69+
with:
70+
repo-token: ${{ secrets.GITHUB_TOKEN }}
71+
dry-run: true
72+
73+
# Accelerated timeline for needs-information
74+
days-before-stale: ${{ env.NEEDS_INFO_DAYS_BEFORE_STALE }}
75+
days-before-close: ${{ env.NEEDS_INFO_DAYS_BEFORE_CLOSE }}
76+
77+
# Explicit stale label configuration
78+
stale-issue-label: "stale"
79+
80+
# Only target ISSUES with needs-information label (not PRs)
81+
only-issue-labels: 'needs-information'
82+
83+
stale-issue-message: |
84+
This issue has been marked as stale because it requires additional information
85+
that has not been provided for 30 days. It will be closed in 7 days if the
86+
requested information is not provided.
87+
88+
close-issue-message: |
89+
This issue has been closed because the requested information was not provided within the specified timeframe.
90+
If you can provide the missing information, please reopen this issue or create a new one.
91+
92+
# Disable PR processing for this job
93+
days-before-pr-stale: -1
94+
days-before-pr-close: -1
95+
96+
# Remove stale label when issue becomes active again
97+
remove-stale-when-updated: true

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,18 @@ func main() {
297297
```
298298
299299
300+
### Buffer Size Configuration
301+
302+
go-redis uses 0.5MiB read and write buffers by default for optimal performance. For high-throughput applications or large pipelines, you can customize buffer sizes:
303+
304+
```go
305+
rdb := redis.NewClient(&redis.Options{
306+
Addr: "localhost:6379",
307+
ReadBufferSize: 1024 * 1024, // 1MiB read buffer
308+
WriteBufferSize: 1024 * 1024, // 1MiB write buffer
309+
})
310+
```
311+
300312
### Advanced Configuration
301313
302314
go-redis supports extending the client identification phase to allow projects to send their own custom client identification.

internal/pool/buffer_size_test.go

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
package pool_test
2+
3+
import (
4+
"bufio"
5+
"context"
6+
"net"
7+
"unsafe"
8+
9+
. "github.com/bsm/ginkgo/v2"
10+
. "github.com/bsm/gomega"
11+
12+
"github.com/redis/go-redis/v9/internal/pool"
13+
"github.com/redis/go-redis/v9/internal/proto"
14+
)
15+
16+
var _ = Describe("Buffer Size Configuration", func() {
17+
var connPool *pool.ConnPool
18+
ctx := context.Background()
19+
20+
AfterEach(func() {
21+
if connPool != nil {
22+
connPool.Close()
23+
}
24+
})
25+
26+
It("should use default buffer sizes when not specified", func() {
27+
connPool = pool.NewConnPool(&pool.Options{
28+
Dialer: dummyDialer,
29+
PoolSize: 1,
30+
PoolTimeout: 1000,
31+
})
32+
33+
cn, err := connPool.NewConn(ctx)
34+
Expect(err).NotTo(HaveOccurred())
35+
defer connPool.CloseConn(cn)
36+
37+
// Check that default buffer sizes are used (0.5MiB)
38+
writerBufSize := getWriterBufSizeUnsafe(cn)
39+
readerBufSize := getReaderBufSizeUnsafe(cn)
40+
41+
Expect(writerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 0.5MiB buffer size
42+
Expect(readerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 0.5MiB buffer size
43+
})
44+
45+
It("should use custom buffer sizes when specified", func() {
46+
customReadSize := 32 * 1024 // 32KB
47+
customWriteSize := 64 * 1024 // 64KB
48+
49+
connPool = pool.NewConnPool(&pool.Options{
50+
Dialer: dummyDialer,
51+
PoolSize: 1,
52+
PoolTimeout: 1000,
53+
ReadBufferSize: customReadSize,
54+
WriteBufferSize: customWriteSize,
55+
})
56+
57+
cn, err := connPool.NewConn(ctx)
58+
Expect(err).NotTo(HaveOccurred())
59+
defer connPool.CloseConn(cn)
60+
61+
// Check that custom buffer sizes are used
62+
writerBufSize := getWriterBufSizeUnsafe(cn)
63+
readerBufSize := getReaderBufSizeUnsafe(cn)
64+
65+
Expect(writerBufSize).To(Equal(customWriteSize))
66+
Expect(readerBufSize).To(Equal(customReadSize))
67+
})
68+
69+
It("should handle zero buffer sizes by using defaults", func() {
70+
connPool = pool.NewConnPool(&pool.Options{
71+
Dialer: dummyDialer,
72+
PoolSize: 1,
73+
PoolTimeout: 1000,
74+
ReadBufferSize: 0, // Should use default
75+
WriteBufferSize: 0, // Should use default
76+
})
77+
78+
cn, err := connPool.NewConn(ctx)
79+
Expect(err).NotTo(HaveOccurred())
80+
defer connPool.CloseConn(cn)
81+
82+
// Check that default buffer sizes are used (0.5MiB)
83+
writerBufSize := getWriterBufSizeUnsafe(cn)
84+
readerBufSize := getReaderBufSizeUnsafe(cn)
85+
86+
Expect(writerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 0.5MiB buffer size
87+
Expect(readerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 0.5MiB buffer size
88+
})
89+
90+
It("should use 0.5MiB default buffer sizes for standalone NewConn", func() {
91+
// Test that NewConn (without pool) also uses 0.5MiB defaults
92+
netConn := newDummyConn()
93+
cn := pool.NewConn(netConn)
94+
defer cn.Close()
95+
96+
writerBufSize := getWriterBufSizeUnsafe(cn)
97+
readerBufSize := getReaderBufSizeUnsafe(cn)
98+
99+
Expect(writerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 0.5MiB buffer size
100+
Expect(readerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 0.5MiB buffer size
101+
})
102+
103+
It("should use 0.5MiB defaults even when pool is created directly without buffer sizes", func() {
104+
// Test the scenario where someone creates a pool directly (like in tests)
105+
// without setting ReadBufferSize and WriteBufferSize
106+
connPool = pool.NewConnPool(&pool.Options{
107+
Dialer: dummyDialer,
108+
PoolSize: 1,
109+
PoolTimeout: 1000,
110+
// ReadBufferSize and WriteBufferSize are not set (will be 0)
111+
})
112+
113+
cn, err := connPool.NewConn(ctx)
114+
Expect(err).NotTo(HaveOccurred())
115+
defer connPool.CloseConn(cn)
116+
117+
// Should still get 0.5MiB defaults because NewConnPool sets them
118+
writerBufSize := getWriterBufSizeUnsafe(cn)
119+
readerBufSize := getReaderBufSizeUnsafe(cn)
120+
121+
Expect(writerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 0.5MiB buffer size
122+
Expect(readerBufSize).To(Equal(proto.DefaultBufferSize)) // Default 0.5MiB buffer size
123+
})
124+
})
125+
126+
// Helper functions to extract buffer sizes using unsafe pointers
127+
func getWriterBufSizeUnsafe(cn *pool.Conn) int {
128+
cnPtr := (*struct {
129+
usedAt int64
130+
netConn net.Conn
131+
rd *proto.Reader
132+
bw *bufio.Writer
133+
wr *proto.Writer
134+
// ... other fields
135+
})(unsafe.Pointer(cn))
136+
137+
if cnPtr.bw == nil {
138+
return -1
139+
}
140+
141+
bwPtr := (*struct {
142+
err error
143+
buf []byte
144+
n int
145+
wr interface{}
146+
})(unsafe.Pointer(cnPtr.bw))
147+
148+
return len(bwPtr.buf)
149+
}
150+
151+
func getReaderBufSizeUnsafe(cn *pool.Conn) int {
152+
cnPtr := (*struct {
153+
usedAt int64
154+
netConn net.Conn
155+
rd *proto.Reader
156+
bw *bufio.Writer
157+
wr *proto.Writer
158+
// ... other fields
159+
})(unsafe.Pointer(cn))
160+
161+
if cnPtr.rd == nil {
162+
return -1
163+
}
164+
165+
rdPtr := (*struct {
166+
rd *bufio.Reader
167+
})(unsafe.Pointer(cnPtr.rd))
168+
169+
if rdPtr.rd == nil {
170+
return -1
171+
}
172+
173+
bufReaderPtr := (*struct {
174+
buf []byte
175+
rd interface{}
176+
r, w int
177+
err error
178+
lastByte int
179+
lastRuneSize int
180+
})(unsafe.Pointer(rdPtr.rd))
181+
182+
return len(bufReaderPtr.buf)
183+
}

internal/pool/conn.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,28 @@ type Conn struct {
2828
}
2929

3030
func NewConn(netConn net.Conn) *Conn {
31+
return NewConnWithBufferSize(netConn, proto.DefaultBufferSize, proto.DefaultBufferSize)
32+
}
33+
34+
func NewConnWithBufferSize(netConn net.Conn, readBufSize, writeBufSize int) *Conn {
3135
cn := &Conn{
3236
netConn: netConn,
3337
createdAt: time.Now(),
3438
}
35-
cn.rd = proto.NewReader(netConn)
36-
cn.bw = bufio.NewWriter(netConn)
39+
40+
// Use specified buffer sizes, or fall back to 0.5MiB defaults if 0
41+
if readBufSize > 0 {
42+
cn.rd = proto.NewReaderSize(netConn, readBufSize)
43+
} else {
44+
cn.rd = proto.NewReader(netConn) // Uses 0.5MiB default
45+
}
46+
47+
if writeBufSize > 0 {
48+
cn.bw = bufio.NewWriterSize(netConn, writeBufSize)
49+
} else {
50+
cn.bw = bufio.NewWriterSize(netConn, proto.DefaultBufferSize)
51+
}
52+
3753
cn.wr = proto.NewWriter(cn.bw)
3854
cn.SetUsedAt(time.Now())
3955
return cn

internal/pool/pool.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,13 @@ type Options struct {
7373
ConnMaxIdleTime time.Duration
7474
ConnMaxLifetime time.Duration
7575

76+
7677
// Protocol version for optimization (3 = RESP3 with push notifications, 2 = RESP2 without)
7778
Protocol int
79+
80+
ReadBufferSize int
81+
WriteBufferSize int
82+
7883
}
7984

8085
type lastDialErrorWrap struct {
@@ -230,7 +235,7 @@ func (p *ConnPool) dialConn(ctx context.Context, pooled bool) (*Conn, error) {
230235
return nil, err
231236
}
232237

233-
cn := NewConn(netConn)
238+
cn := NewConnWithBufferSize(netConn, p.cfg.ReadBufferSize, p.cfg.WriteBufferSize)
234239
cn.pooled = pooled
235240
return cn, nil
236241
}

0 commit comments

Comments
 (0)