Skip to content

Commit 22da2b5

Browse files
committed
address comments
1 parent e846e5a commit 22da2b5

File tree

3 files changed

+356
-20
lines changed

3 files changed

+356
-20
lines changed

.github/copilot-instructions.md

Lines changed: 336 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,336 @@
1+
# GitHub Copilot Instructions for go-redis
2+
3+
This file provides context and guidelines for GitHub Copilot when working with the go-redis codebase.
4+
5+
## Project Overview
6+
7+
go-redis is a Redis client for Go with support for:
8+
- Redis Standalone, Cluster, Sentinel, and Ring topologies
9+
- RESP2 and RESP3 protocols
10+
- Connection pooling and management
11+
- Push notifications (RESP3)
12+
- Hitless upgrades for seamless cluster transitions
13+
- Pub/Sub messaging
14+
- Pipelines and transactions
15+
16+
## Architecture
17+
18+
### Core Components
19+
20+
- **Client Types**: `Client`, `ClusterClient`, `SentinelClient`, `RingClient`
21+
- **Connection Pool**: `internal/pool` package manages connection lifecycle
22+
- **Protocol**: `internal/proto` handles RESP protocol parsing
23+
- **Hitless Upgrades**: `hitless` package provides seamless cluster transitions
24+
- **Push Notifications**: `push` package handles RESP3 push notifications
25+
26+
### Key Packages
27+
28+
- `redis.go` - Main client implementation
29+
- `options.go` - Configuration and client options
30+
- `osscluster.go` - Open source cluster client
31+
- `sentinel.go` - Sentinel failover client
32+
- `ring.go` - Ring (sharding) client
33+
- `internal/pool/` - Connection pool management
34+
- `hitless/` - Hitless upgrade functionality
35+
36+
## Coding Standards
37+
38+
### General Guidelines
39+
40+
1. **Error Handling**: Always handle errors explicitly, prefer descriptive error messages
41+
2. **Context**: Use `context.Context` for cancellation and timeouts
42+
3. **Thread Safety**: All public APIs must be thread-safe
43+
4. **Memory Management**: Minimize allocations, reuse buffers where possible
44+
5. **Testing**: Write comprehensive unit tests, prefer table-driven tests
45+
46+
### Naming Conventions
47+
48+
- Use Go standard naming (camelCase for private, PascalCase for public)
49+
- Interface names should end with `-er` (e.g., `Pooler`, `Cmder`)
50+
- Error variables should start with `Err` (e.g., `ErrClosed`)
51+
- Constants should be grouped and well-documented
52+
53+
### Code Organization
54+
55+
- Keep functions focused and small (prefer < 100 lines)
56+
- Group related functionality in the same file
57+
- Use internal packages for implementation details
58+
- Extract common patterns into helper functions
59+
60+
## Connection Pool Guidelines
61+
62+
### Pool Management
63+
64+
- Connections are managed by `internal/pool/ConnPool`
65+
- Use `pool.Conn` wrapper for Redis connections
66+
- Implement proper connection lifecycle (dial, auth, select DB)
67+
- Handle connection health checks and cleanup
68+
69+
### Pool Hooks
70+
71+
- Use `PoolHook` interface for connection processing
72+
- Hooks are called on `OnGet` and `OnPut` operations
73+
- Support for hitless upgrades through pool hooks
74+
- Maintain backward compatibility when adding hooks
75+
76+
### Connection States
77+
78+
- `IsUsable()` - Connection can be used for commands
79+
- `ShouldHandoff()` - Connection needs handoff during cluster transition
80+
- Proper state management is critical for hitless upgrades
81+
82+
## Hitless Upgrades
83+
84+
### Design Principles
85+
86+
- Seamless connection handoffs during cluster topology changes
87+
- Event-driven architecture with push notifications
88+
- Atomic state management using `sync/atomic`
89+
- Worker pools for concurrent handoff processing
90+
91+
### Key Components
92+
93+
- `HitlessManager` - Orchestrates upgrade operations
94+
- `PoolHook` - Handles connection-level operations
95+
- `NotificationHandler` - Processes push notifications
96+
- Configuration through `hitless.Config`
97+
98+
### Implementation Guidelines
99+
100+
- Use atomic operations for state checks (avoid mutex locks)
101+
- Implement proper timeout handling for handoff operations
102+
- Support retry logic with exponential backoff
103+
- Maintain connection pool integrity during transitions
104+
105+
## Testing Guidelines
106+
107+
### Unit Tests
108+
109+
- Use table-driven tests for multiple scenarios
110+
- Test both success and error paths
111+
- Mock external dependencies (Redis server)
112+
- Verify thread safety with race detection
113+
114+
### Integration Tests
115+
116+
- Separate integration tests from unit tests
117+
- Use real Redis instances when needed
118+
- Test all client types (standalone, cluster, sentinel)
119+
- Verify hitless upgrade scenarios
120+
121+
### Test Structure
122+
123+
```go
124+
func TestFeature(t *testing.T) {
125+
tests := []struct {
126+
name string
127+
input InputType
128+
expected ExpectedType
129+
wantErr bool
130+
}{
131+
// test cases
132+
}
133+
134+
for _, tt := range tests {
135+
t.Run(tt.name, func(t *testing.T) {
136+
// test implementation
137+
})
138+
}
139+
}
140+
```
141+
142+
## Performance Considerations
143+
144+
### Memory Optimization
145+
146+
- Reuse buffers and objects where possible
147+
- Use object pools for frequently allocated types
148+
- Minimize string allocations in hot paths
149+
- Profile memory usage regularly
150+
151+
### Concurrency
152+
153+
- Prefer atomic operations over mutexes for simple state
154+
- Use `sync.Map` for concurrent map access
155+
- Implement proper worker pool patterns
156+
- Avoid blocking operations in hot paths
157+
158+
### Connection Management
159+
160+
- Implement connection pooling efficiently
161+
- Handle connection timeouts properly
162+
- Support connection health checks
163+
- Minimize connection churn
164+
165+
## Common Patterns
166+
167+
### Error Handling
168+
169+
```go
170+
if err != nil {
171+
return fmt.Errorf("operation failed: %w", err)
172+
}
173+
```
174+
175+
### Context Usage
176+
177+
```go
178+
func (c *Client) operation(ctx context.Context) error {
179+
select {
180+
case <-ctx.Done():
181+
return ctx.Err()
182+
default:
183+
// continue with operation
184+
}
185+
}
186+
```
187+
188+
### Configuration Validation
189+
190+
```go
191+
func (opt *Options) validate() error {
192+
if opt.PoolSize <= 0 {
193+
return errors.New("PoolSize must be positive")
194+
}
195+
return nil
196+
}
197+
```
198+
199+
## Documentation Standards
200+
201+
- Use Go doc comments for all public APIs
202+
- Include examples for complex functionality
203+
- Document configuration options thoroughly
204+
- Maintain README.md with usage examples
205+
206+
## Compatibility
207+
208+
- Maintain backward compatibility for public APIs
209+
- Use build tags for version-specific features
210+
- Support multiple Redis versions
211+
- Handle protocol differences gracefully
212+
213+
## Security Considerations
214+
215+
- Validate all user inputs
216+
- Handle authentication securely
217+
- Support TLS connections
218+
- Avoid logging sensitive information
219+
220+
## go-redis Specific Patterns
221+
222+
### Command Interface
223+
224+
All Redis commands implement the `Cmder` interface:
225+
226+
```go
227+
type Cmder interface {
228+
Name() string
229+
FullName() string
230+
Args() []interface{}
231+
String() string
232+
stringArg(int) string
233+
firstKeyPos() int8
234+
SetFirstKeyPos(int8)
235+
readTimeout() *time.Duration
236+
readReply(rd *proto.Reader) error
237+
SetErr(error)
238+
Err() error
239+
}
240+
```
241+
242+
### Client Initialization Pattern
243+
244+
```go
245+
func NewClient(opt *Options) *Client {
246+
if opt == nil {
247+
panic("redis: NewClient nil options")
248+
}
249+
opt.init() // Apply defaults
250+
251+
c := Client{
252+
baseClient: &baseClient{opt: opt},
253+
}
254+
c.init()
255+
256+
// Create pools with error handling
257+
var err error
258+
c.connPool, err = newConnPool(opt, c.dialHook)
259+
if err != nil {
260+
panic(fmt.Errorf("redis: failed to create connection pool: %w", err))
261+
}
262+
263+
return &c
264+
}
265+
```
266+
267+
### Pool Hook Pattern
268+
269+
```go
270+
type PoolHook interface {
271+
OnGet(ctx context.Context, conn *Conn, isNewConn bool) error
272+
OnPut(ctx context.Context, conn *Conn) (shouldPool bool, shouldRemove bool, err error)
273+
}
274+
```
275+
276+
### Atomic State Management
277+
278+
Prefer atomic operations for simple state:
279+
280+
```go
281+
type Manager struct {
282+
closed atomic.Bool
283+
count atomic.Int64
284+
}
285+
286+
func (m *Manager) isClosed() bool {
287+
return m.closed.Load()
288+
}
289+
290+
func (m *Manager) close() {
291+
m.closed.Store(true)
292+
}
293+
```
294+
295+
### Configuration Defaults Pattern
296+
297+
```go
298+
func (opt *Options) init() {
299+
if opt.PoolSize == 0 {
300+
opt.PoolSize = 10 * runtime.GOMAXPROCS(0)
301+
}
302+
if opt.ReadTimeout == 0 {
303+
opt.ReadTimeout = 3 * time.Second
304+
}
305+
// Apply hitless upgrade defaults
306+
opt.HitlessUpgradeConfig = opt.HitlessUpgradeConfig.ApplyDefaultsWithPoolSize(opt.PoolSize)
307+
}
308+
```
309+
310+
### Push Notification Handling
311+
312+
```go
313+
type NotificationProcessor interface {
314+
ProcessPushNotification(ctx context.Context, data []byte) error
315+
RegisterHandler(notificationType string, handler NotificationHandler) error
316+
Close() error
317+
}
318+
```
319+
320+
### Error Definitions
321+
322+
Group related errors in separate files:
323+
324+
```go
325+
// errors.go
326+
var (
327+
ErrClosed = errors.New("redis: client is closed")
328+
ErrPoolExhausted = errors.New("redis: connection pool exhausted")
329+
ErrPoolTimeout = errors.New("redis: connection pool timeout")
330+
)
331+
```
332+
333+
### Panics
334+
Creating the client (NewClient, NewClusterClient, etc.) is the only time when the library can panic.
335+
This includes initialization of the pool, hitless upgrade manager, and other critical components.
336+
Other than that, the library should never panic.

internal/util/convert.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,14 @@ func MustParseFloat(s string) float64 {
2828
}
2929
return f
3030
}
31+
32+
// SafeIntToInt32 safely converts an int to int32, returning an error if overflow would occur.
33+
func SafeIntToInt32(value int, fieldName string) (int32, error) {
34+
if value > math.MaxInt32 {
35+
return 0, fmt.Errorf("redis: %s value %d exceeds maximum allowed value %d", fieldName, value, math.MaxInt32)
36+
}
37+
if value < math.MinInt32 {
38+
return 0, fmt.Errorf("redis: %s value %d is below minimum allowed value %d", fieldName, value, math.MinInt32)
39+
}
40+
return int32(value), nil
41+
}

0 commit comments

Comments
 (0)