← 输入框|github.com|刷新
Skip to content

fix(pub-sub): guard #subscribing against underflow in resubscribe callbacks - #3476

Merged
nkaradzhov merged 1 commit into
redis:masterfrom
tamil-selvan-k:master
Sep 28, 2026
Merged

nkaradzhov merged 1 commit into
redis:masterfrom
tamil-selvan-k:master

Conversation

@tamil-selvan-k

@tamil-selvan-k tamil-selvan-k commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

flushWaitingForReply / flushAll calls #pubSub.reset() first (zeroing #subscribing to 0) and then rejects the in-flight commands. The deferred reject callbacks in #normalResubscribe and #shardedResubscribe were () => this.#subscribing--, so they drove #subscribing to -1 after reset() had already set it to 0.

Because #updateIsActive() uses this.#subscribing !== 0 as one of its conditions, a counter of -1 caused #isActive to stay true permanently — even after every channel was fully unsubscribed.

Consequences:

  • isPubSubActive returned true with no active subscriptions after the client recovered from a rapid double-disconnect.
  • On RESP2 + Entra ID / credential-rotation setups, the re-auth guard at client/index.ts:856 (!(isPubSubActive && RESP === 2)) silently skipped re-authentication, leaving the connection unauthenticated.
  • Pool lease-return unnecessarily issued a RESET command on every returned client.

Fix: Guard both resolve and reject callbacks in #normalResubscribe and #shardedResubscribe with if (this.#subscribing > 0) so they become no-ops when reset() has already zeroed the counter. Adds a regression test that simulates the double-disconnect path end-to-end.

Fixes the #subscribing underflow introduced by the shared const callback = () => this.#subscribing-- pattern used in both methods.


Checklist

  • Does npm test pass with this change (including linting)?
  • Is the new or changed code fully tested?
  • Is a documentation update included (if this change modifies existing APIs, or introduces new ones)?

Note

Medium Risk
Touches reconnect/resubscribe state that drives isPubSubActive, with downstream effects on re-auth and pool RESET behavior, but the change is a narrow guard plus regression test.

Overview
Fixes a PubSub #subscribing underflow when a connection drops twice while resubscribe commands are still in flight. reset() already sets #subscribing to 0, but deferred resolve/reject callbacks from #normalResubscribe and #shardedResubscribe still decremented the counter, leaving it at -1 and keeping isActive stuck true after all listeners were removed.

The change makes those callbacks no-op unless #subscribing > 0, so #updateIsActive() can clear activity correctly. A new spec walks the double-disconnect → resubscribe → reject → recover → unsubscribe path to lock this in.

Reviewed by Cursor Bugbot for commit 208f727. Bugbot is set up for automated code reviews on this repo. Configure here.

…lbacks

When flushWaitingForReply/flushAll fires, #pubSub.reset() zeroes
#subscribing before rejecting the in-flight commands. The deferred
reject callbacks in #normalResubscribe and #shardedResubscribe then
drove #subscribing below zero, causing #updateIsActive() to keep
#isActive true permanently — even after all channels were unsubscribed.

On RESP2 this silently skipped re-authentication when credentials
rotated after the connection recovered.

Guard both resolve and reject callbacks with if (this.#subscribing > 0)
so they become no-ops when reset() has already zeroed the counter.

@nkaradzhov nkaradzhov left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, LGTM

@nkaradzhov
nkaradzhov merged commit 669ccde into redis:master Sep 28, 2026
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants