Skip to content

Commit b4d0ff1

Browse files
committed
refactor: organize push notification code into separate files
Split push notification implementation into focused, maintainable files for better code organization and easier navigation. Each file now has a clear responsibility and contains related functionality. File Organization: 1. push_notifications.go (Main API): - Push notification constants (MOVING, MIGRATING, etc.) - PushNotificationHandler interface - PushNotificationProcessorInterface - Public API wrappers (PushNotificationRegistry, PushNotificationProcessor) - Main entry point for push notification functionality 2. push_notification_handler_context.go (Context): - PushNotificationHandlerContext interface - pushNotificationHandlerContext concrete implementation - NewPushNotificationHandlerContext constructor - All context-related functionality with concrete type getters 3. push_notification_processor.go (Core Logic): - Registry implementation for handler management - Processor implementation for notification processing - VoidProcessor implementation for RESP2 connections - Core processing logic and notification filtering Benefits: - Clear separation of concerns between files - Easier to navigate and maintain codebase - Focused files with single responsibilities - Better code organization for large codebase - Simplified debugging and testing File Responsibilities: - Main API: Public interfaces and constants - Context: Handler context with concrete type access - Processor: Core processing logic and registry management All functionality remains intact with improved organization. Tests pass and compilation succeeds with the new file structure.
1 parent ec4bf57 commit b4d0ff1

File tree

3 files changed

+326
-311
lines changed

3 files changed

+326
-311
lines changed

push_notification_handler_context.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package redis
2+
3+
import (
4+
"github.com/redis/go-redis/v9/internal/pool"
5+
)
6+
7+
// PushNotificationHandlerContext provides context information about where a push notification was received.
8+
// This interface allows handlers to make informed decisions based on the source of the notification
9+
// with strongly typed access to different client types using concrete types.
10+
type PushNotificationHandlerContext interface {
11+
// GetClient returns the Redis client instance that received the notification.
12+
// Returns nil if no client context is available.
13+
GetClient() interface{}
14+
15+
// GetClusterClient returns the client as a ClusterClient if it is one.
16+
// Returns nil if the client is not a ClusterClient or no client context is available.
17+
GetClusterClient() *ClusterClient
18+
19+
// GetSentinelClient returns the client as a SentinelClient if it is one.
20+
// Returns nil if the client is not a SentinelClient or no client context is available.
21+
GetSentinelClient() *SentinelClient
22+
23+
// GetFailoverClient returns the client as a FailoverClient if it is one.
24+
// Returns nil if the client is not a FailoverClient or no client context is available.
25+
GetFailoverClient() *Client
26+
27+
// GetRegularClient returns the client as a regular Client if it is one.
28+
// Returns nil if the client is not a regular Client or no client context is available.
29+
GetRegularClient() *Client
30+
31+
// GetConnPool returns the connection pool from which the connection was obtained.
32+
// Returns nil if no connection pool context is available.
33+
GetConnPool() interface{}
34+
35+
// GetPubSub returns the PubSub instance that received the notification.
36+
// Returns nil if this is not a PubSub connection.
37+
GetPubSub() *PubSub
38+
39+
// GetConn returns the specific connection on which the notification was received.
40+
// Returns nil if no connection context is available.
41+
GetConn() *pool.Conn
42+
43+
// IsBlocking returns true if the notification was received on a blocking connection.
44+
IsBlocking() bool
45+
}
46+
47+
// pushNotificationHandlerContext is the concrete implementation of PushNotificationHandlerContext interface
48+
type pushNotificationHandlerContext struct {
49+
client interface{}
50+
connPool interface{}
51+
pubSub interface{}
52+
conn *pool.Conn
53+
isBlocking bool
54+
}
55+
56+
// NewPushNotificationHandlerContext creates a new PushNotificationHandlerContext implementation
57+
func NewPushNotificationHandlerContext(client, connPool, pubSub interface{}, conn *pool.Conn, isBlocking bool) PushNotificationHandlerContext {
58+
return &pushNotificationHandlerContext{
59+
client: client,
60+
connPool: connPool,
61+
pubSub: pubSub,
62+
conn: conn,
63+
isBlocking: isBlocking,
64+
}
65+
}
66+
67+
// GetClient returns the Redis client instance that received the notification
68+
func (h *pushNotificationHandlerContext) GetClient() interface{} {
69+
return h.client
70+
}
71+
72+
// GetClusterClient returns the client as a ClusterClient if it is one
73+
func (h *pushNotificationHandlerContext) GetClusterClient() *ClusterClient {
74+
if client, ok := h.client.(*ClusterClient); ok {
75+
return client
76+
}
77+
return nil
78+
}
79+
80+
// GetSentinelClient returns the client as a SentinelClient if it is one
81+
func (h *pushNotificationHandlerContext) GetSentinelClient() *SentinelClient {
82+
if client, ok := h.client.(*SentinelClient); ok {
83+
return client
84+
}
85+
return nil
86+
}
87+
88+
// GetFailoverClient returns the client as a FailoverClient if it is one
89+
func (h *pushNotificationHandlerContext) GetFailoverClient() *Client {
90+
if client, ok := h.client.(*Client); ok {
91+
return client
92+
}
93+
return nil
94+
}
95+
96+
// GetRegularClient returns the client as a regular Client if it is one
97+
func (h *pushNotificationHandlerContext) GetRegularClient() *Client {
98+
if client, ok := h.client.(*Client); ok {
99+
return client
100+
}
101+
return nil
102+
}
103+
104+
// GetConnPool returns the connection pool from which the connection was obtained
105+
func (h *pushNotificationHandlerContext) GetConnPool() interface{} {
106+
return h.connPool
107+
}
108+
109+
// GetPubSub returns the PubSub instance that received the notification
110+
func (h *pushNotificationHandlerContext) GetPubSub() *PubSub {
111+
if pubSub, ok := h.pubSub.(*PubSub); ok {
112+
return pubSub
113+
}
114+
return nil
115+
}
116+
117+
// GetConn returns the specific connection on which the notification was received
118+
func (h *pushNotificationHandlerContext) GetConn() *pool.Conn {
119+
return h.conn
120+
}
121+
122+
// IsBlocking returns true if the notification was received on a blocking connection
123+
func (h *pushNotificationHandlerContext) IsBlocking() bool {
124+
return h.isBlocking
125+
}

push_notification_processor.go

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
package redis
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/redis/go-redis/v9/internal"
8+
"github.com/redis/go-redis/v9/internal/proto"
9+
)
10+
11+
// Registry manages push notification handlers
12+
type Registry struct {
13+
handlers map[string]PushNotificationHandler
14+
protected map[string]bool
15+
}
16+
17+
// NewRegistry creates a new push notification registry
18+
func NewRegistry() *Registry {
19+
return &Registry{
20+
handlers: make(map[string]PushNotificationHandler),
21+
protected: make(map[string]bool),
22+
}
23+
}
24+
25+
// RegisterHandler registers a handler for a specific push notification name
26+
func (r *Registry) RegisterHandler(pushNotificationName string, handler PushNotificationHandler, protected bool) error {
27+
if handler == nil {
28+
return fmt.Errorf("handler cannot be nil")
29+
}
30+
31+
// Check if handler already exists and is protected
32+
if existingProtected, exists := r.protected[pushNotificationName]; exists && existingProtected {
33+
return fmt.Errorf("cannot overwrite protected handler for push notification: %s", pushNotificationName)
34+
}
35+
36+
r.handlers[pushNotificationName] = handler
37+
r.protected[pushNotificationName] = protected
38+
return nil
39+
}
40+
41+
// GetHandler returns the handler for a specific push notification name
42+
func (r *Registry) GetHandler(pushNotificationName string) PushNotificationHandler {
43+
return r.handlers[pushNotificationName]
44+
}
45+
46+
// UnregisterHandler removes a handler for a specific push notification name
47+
func (r *Registry) UnregisterHandler(pushNotificationName string) error {
48+
// Check if handler is protected
49+
if protected, exists := r.protected[pushNotificationName]; exists && protected {
50+
return fmt.Errorf("cannot unregister protected handler for push notification: %s", pushNotificationName)
51+
}
52+
53+
delete(r.handlers, pushNotificationName)
54+
delete(r.protected, pushNotificationName)
55+
return nil
56+
}
57+
58+
// GetRegisteredPushNotificationNames returns all registered push notification names
59+
func (r *Registry) GetRegisteredPushNotificationNames() []string {
60+
names := make([]string, 0, len(r.handlers))
61+
for name := range r.handlers {
62+
names = append(names, name)
63+
}
64+
return names
65+
}
66+
67+
// Processor handles push notifications with a registry of handlers
68+
type Processor struct {
69+
registry *Registry
70+
}
71+
72+
// NewProcessor creates a new push notification processor
73+
func NewProcessor() *Processor {
74+
return &Processor{
75+
registry: NewRegistry(),
76+
}
77+
}
78+
79+
// GetHandler returns the handler for a specific push notification name
80+
func (p *Processor) GetHandler(pushNotificationName string) PushNotificationHandler {
81+
return p.registry.GetHandler(pushNotificationName)
82+
}
83+
84+
// RegisterHandler registers a handler for a specific push notification name
85+
func (p *Processor) RegisterHandler(pushNotificationName string, handler PushNotificationHandler, protected bool) error {
86+
return p.registry.RegisterHandler(pushNotificationName, handler, protected)
87+
}
88+
89+
// UnregisterHandler removes a handler for a specific push notification name
90+
func (p *Processor) UnregisterHandler(pushNotificationName string) error {
91+
return p.registry.UnregisterHandler(pushNotificationName)
92+
}
93+
94+
// ProcessPendingNotifications checks for and processes any pending push notifications
95+
func (p *Processor) ProcessPendingNotifications(ctx context.Context, handlerCtx PushNotificationHandlerContext, rd *proto.Reader) error {
96+
if rd == nil {
97+
return nil
98+
}
99+
100+
for {
101+
// Check if there's data available to read
102+
replyType, err := rd.PeekReplyType()
103+
if err != nil {
104+
// No more data available or error reading
105+
break
106+
}
107+
108+
// Only process push notifications (arrays starting with >)
109+
if replyType != proto.RespPush {
110+
break
111+
}
112+
113+
// Read the push notification
114+
reply, err := rd.ReadReply()
115+
if err != nil {
116+
internal.Logger.Printf(ctx, "push: error reading push notification: %v", err)
117+
break
118+
}
119+
120+
// Convert to slice of interfaces
121+
notification, ok := reply.([]interface{})
122+
if !ok {
123+
continue
124+
}
125+
126+
// Handle the notification directly
127+
if len(notification) > 0 {
128+
// Extract the notification type (first element)
129+
if notificationType, ok := notification[0].(string); ok {
130+
// Skip notifications that should be handled by other systems
131+
if shouldSkipNotification(notificationType) {
132+
continue
133+
}
134+
135+
// Get the handler for this notification type
136+
if handler := p.registry.GetHandler(notificationType); handler != nil {
137+
// Handle the notification
138+
handler.HandlePushNotification(ctx, handlerCtx, notification)
139+
}
140+
}
141+
}
142+
}
143+
144+
return nil
145+
}
146+
147+
// shouldSkipNotification checks if a notification type should be ignored by the push notification
148+
// processor and handled by other specialized systems instead (pub/sub, streams, keyspace, etc.).
149+
func shouldSkipNotification(notificationType string) bool {
150+
switch notificationType {
151+
// Pub/Sub notifications - handled by pub/sub system
152+
case "message", // Regular pub/sub message
153+
"pmessage", // Pattern pub/sub message
154+
"subscribe", // Subscription confirmation
155+
"unsubscribe", // Unsubscription confirmation
156+
"psubscribe", // Pattern subscription confirmation
157+
"punsubscribe", // Pattern unsubscription confirmation
158+
"smessage", // Sharded pub/sub message (Redis 7.0+)
159+
"ssubscribe", // Sharded subscription confirmation
160+
"sunsubscribe": // Sharded unsubscription confirmation
161+
return true
162+
default:
163+
return false
164+
}
165+
}
166+
167+
// VoidProcessor discards all push notifications without processing them
168+
type VoidProcessor struct{}
169+
170+
// NewVoidProcessor creates a new void push notification processor
171+
func NewVoidProcessor() *VoidProcessor {
172+
return &VoidProcessor{}
173+
}
174+
175+
// GetHandler returns nil for void processor since it doesn't maintain handlers
176+
func (v *VoidProcessor) GetHandler(pushNotificationName string) PushNotificationHandler {
177+
return nil
178+
}
179+
180+
// RegisterHandler returns an error for void processor since it doesn't maintain handlers
181+
func (v *VoidProcessor) RegisterHandler(pushNotificationName string, handler PushNotificationHandler, protected bool) error {
182+
return fmt.Errorf("cannot register push notification handler '%s': push notifications are disabled (using void processor)", pushNotificationName)
183+
}
184+
185+
// UnregisterHandler returns an error for void processor since it doesn't maintain handlers
186+
func (v *VoidProcessor) UnregisterHandler(pushNotificationName string) error {
187+
return fmt.Errorf("cannot unregister push notification handler '%s': push notifications are disabled (using void processor)", pushNotificationName)
188+
}
189+
190+
// ProcessPendingNotifications for VoidProcessor does nothing since push notifications
191+
// are only available in RESP3 and this processor is used for RESP2 connections.
192+
// This avoids unnecessary buffer scanning overhead.
193+
func (v *VoidProcessor) ProcessPendingNotifications(ctx context.Context, handlerCtx PushNotificationHandlerContext, rd *proto.Reader) error {
194+
// VoidProcessor is used for RESP2 connections where push notifications are not available.
195+
// Since push notifications only exist in RESP3, we can safely skip all processing
196+
// to avoid unnecessary buffer scanning overhead.
197+
return nil
198+
}

0 commit comments

Comments
 (0)