@@ -243,6 +243,19 @@ func (c *Client) XReadUntilResult(ctx context.Context, a *redis.XReadArgs) ([]re
243243
244244 for {
245245 cmd := c .XRead (ctx , a )
246+ // Explicitly check for context errors because go-redis v9 does not respect context.Canceled or
247+ // context.DeadlineExceeded unless Options.ContextTimeoutEnabled is set [^1] [^2], which we do not enable.
248+ // If the context is canceled or times out during XRead and there is no data to read,
249+ // XRead will **still** block until the block timeout is reached and
250+ // return redis.Nil instead of the context error. Without this check,
251+ // the function would return redis.Nil, potentially leading to unexpected errors for consumers.
252+ //
253+ // [^1]: https://github.com/redis/go-redis/issues/2556
254+ // [^2]: https://github.com/redis/go-redis/issues/2682
255+ if ctx .Err () != nil {
256+ return nil , ctx .Err ()
257+ }
258+
246259 streams , err := cmd .Result ()
247260 if err != nil {
248261 // We need to retry the XREAD commands in the following situations:
@@ -253,11 +266,10 @@ func (c *Client) XReadUntilResult(ctx context.Context, a *redis.XReadArgs) ([]re
253266 // important to set a block timeout greater than zero for the XREAD commands, see the "a.Block" above.
254267 // However, setting a block timeout means that Go Redis will not retry any errors internally and will
255268 // instead return an I/O timeout error when exceeding the timeout. Thus, we need to handle this here and
256- // retry it again. On the other hand, an I/O timeout could also mean a context.DeadlineExceeded error,
257- // which is not retryable, so we have to check for context termination by ourselves via ctx.Err().
269+ // retry it again.
258270 //
259271 // [^1]: https://github.com/redis/go-redis/issues/2131
260- if ( errors .Is (err , redis .Nil ) || retry .Retryable (err )) && ctx . Err () == nil {
272+ if errors .Is (err , redis .Nil ) || retry .Retryable (err ) {
261273 continue
262274 }
263275
0 commit comments