1
0
mirror of https://github.com/redis/go-redis.git synced 2025-07-18 00:20:57 +03:00

fix(peek): non-blocking peek

This commit is contained in:
Nedyalko Dyakov
2025-07-05 06:18:38 +03:00
parent 604c8e313e
commit b23f43c2f1
4 changed files with 41 additions and 10 deletions

View File

@ -58,6 +58,10 @@ func (cn *Conn) SetNetConn(netConn net.Conn) {
cn.bw.Reset(netConn) cn.bw.Reset(netConn)
} }
func (cn *Conn) GetNetConn() net.Conn {
return cn.netConn
}
func (cn *Conn) Write(b []byte) (int, error) { func (cn *Conn) Write(b []byte) (int, error) {
return cn.netConn.Write(b) return cn.netConn.Write(b)
} }

View File

@ -384,6 +384,8 @@ func (p *ConnPool) Put(ctx context.Context, cn *Conn) {
if cn.rd.Buffered() > 0 { if cn.rd.Buffered() > 0 {
// Check if this might be push notification data // Check if this might be push notification data
if p.cfg.Protocol == 3 { if p.cfg.Protocol == 3 {
// we know that there is something in the buffer, so peek at the next reply type without
// the potential to block
if replyType, err := cn.rd.PeekReplyType(); err == nil && replyType == proto.RespPush { if replyType, err := cn.rd.PeekReplyType(); err == nil && replyType == proto.RespPush {
// For push notifications, we allow some buffered data // For push notifications, we allow some buffered data
// The client will process these notifications before using the connection // The client will process these notifications before using the connection
@ -546,6 +548,8 @@ func (p *ConnPool) isHealthyConn(cn *Conn) bool {
// However, push notification processing is now handled by the client // However, push notification processing is now handled by the client
// before WithReader to ensure proper context is available to handlers // before WithReader to ensure proper context is available to handlers
if err == errUnexpectedRead && p.cfg.Protocol == 3 { if err == errUnexpectedRead && p.cfg.Protocol == 3 {
// we know that there is something in the buffer, so peek at the next reply type without
// the potential to block
if replyType, err := cn.rd.PeekReplyType(); err == nil && replyType == proto.RespPush { if replyType, err := cn.rd.PeekReplyType(); err == nil && replyType == proto.RespPush {
// For RESP3 connections with push notifications, we allow some buffered data // For RESP3 connections with push notifications, we allow some buffered data
// The client will process these notifications before using the connection // The client will process these notifications before using the connection

View File

@ -2,6 +2,7 @@ package push
import ( import (
"context" "context"
"time"
"github.com/redis/go-redis/v9/internal" "github.com/redis/go-redis/v9/internal"
"github.com/redis/go-redis/v9/internal/proto" "github.com/redis/go-redis/v9/internal/proto"
@ -51,8 +52,23 @@ func (p *Processor) ProcessPendingNotifications(ctx context.Context, handlerCtx
if rd == nil { if rd == nil {
return nil return nil
} }
conn := handlerCtx.Conn
if conn == nil {
return nil
}
netConn := handlerCtx.Conn.GetNetConn()
if netConn == nil {
return nil
}
for { for {
// Set a short read deadline to check for available data
// otherwise we may block on Peek if there is no data available
err := netConn.SetReadDeadline(time.Now().Add(1))
if err != nil {
return err
}
// Check if there's data available to read // Check if there's data available to read
replyType, err := rd.PeekReplyType() replyType, err := rd.PeekReplyType()
if err != nil { if err != nil {
@ -104,7 +120,7 @@ func (p *Processor) ProcessPendingNotifications(ctx context.Context, handlerCtx
} }
} }
return nil return netConn.SetReadDeadline(time.Time{})
} }
// VoidProcessor discards all push notifications without processing them // VoidProcessor discards all push notifications without processing them
@ -133,12 +149,26 @@ func (v *VoidProcessor) UnregisterHandler(pushNotificationName string) error {
// ProcessPendingNotifications for VoidProcessor does nothing since push notifications // ProcessPendingNotifications for VoidProcessor does nothing since push notifications
// are only available in RESP3 and this processor is used for RESP2 connections. // are only available in RESP3 and this processor is used for RESP2 connections.
// This avoids unnecessary buffer scanning overhead. // This avoids unnecessary buffer scanning overhead.
func (v *VoidProcessor) ProcessPendingNotifications(_ context.Context, _ NotificationHandlerContext, rd *proto.Reader) error { func (v *VoidProcessor) ProcessPendingNotifications(_ context.Context, handlerCtx NotificationHandlerContext, rd *proto.Reader) error {
// read and discard all push notifications // read and discard all push notifications
if rd == nil { if rd == nil {
return nil return nil
} }
conn := handlerCtx.Conn
if conn == nil {
return nil
}
netConn := handlerCtx.Conn.GetNetConn()
if netConn == nil {
return nil
}
for { for {
// Set a short read deadline to check for available data
err := netConn.SetReadDeadline(time.Now().Add(1))
if err != nil {
return err
}
// Check if there's data available to read
replyType, err := rd.PeekReplyType() replyType, err := rd.PeekReplyType()
if err != nil { if err != nil {
// No more data available or error reading // No more data available or error reading
@ -166,7 +196,7 @@ func (v *VoidProcessor) ProcessPendingNotifications(_ context.Context, _ Notific
return nil return nil
} }
} }
return nil return netConn.SetReadDeadline(time.Time{})
} }
// willHandleNotificationInClient checks if a notification type should be ignored by the push notification // willHandleNotificationInClient checks if a notification type should be ignored by the push notification

View File

@ -733,13 +733,6 @@ func (c *baseClient) txPipelineProcessCmds(
} }
if err := cn.WithReader(c.context(ctx), c.opt.ReadTimeout, func(rd *proto.Reader) error { if err := cn.WithReader(c.context(ctx), c.opt.ReadTimeout, func(rd *proto.Reader) error {
// To be sure there are no buffered push notifications, we process them before reading the reply
if err := c.processPendingPushNotificationWithReader(ctx, cn, rd); err != nil {
// Log the error but don't fail the command execution
// Push notification processing errors shouldn't break normal Redis operations
internal.Logger.Printf(ctx, "push: error processing pending notifications before reading reply: %v", err)
}
statusCmd := cmds[0].(*StatusCmd) statusCmd := cmds[0].(*StatusCmd)
// Trim multi and exec. // Trim multi and exec.
trimmedCmds := cmds[1 : len(cmds)-1] trimmedCmds := cmds[1 : len(cmds)-1]