Skip to content

Pub/sub: overlapping subscription restores cannot register on two servers - #3353

Open
mgravell wants to merge 1 commit into
mainfrom
marc/pubsub-restore-race
Open

mgravell wants to merge 1 commit into
mainfrom
marc/pubsub-restore-race

Conversation

@mgravell

Copy link
Copy Markdown
Member

Fixes #3336.

During a Sentinel failover, ServerEndPoint.OnFullyEstablished (per reconnecting connection) and ConnectionMultiplexer.ReconfigureAsync both restore subscriptions fire-and-forget. A SingleNodeSubscription only records its server when the (P)SUBSCRIBE acknowledgement is processed, so between send and ack a concurrent restore saw the subscription as disconnected and subscribed again — often on a different node (sometimes a replica, depending on selectability mid-failover). The later ack overwrote the recorded endpoint while the earlier registration stayed live on its server, and since message dispatch is by channel only, every publish was delivered twice until that connection happened to drop. (Nice diagnosis by the reporter — confirmed accurate end to end, including their in-process repro test, which failed 8/8 against main.)

Two layers:

  • Claim before send: a pending-claim token (CAS) makes competing restores back off while a subscribe attempt is in flight. The subscribe message itself is no longer fire-and-forget even when the caller is (the caller just doesn't await it — "fire-and-observe"), so every completion — ack, error reply, connection failure, timeout — observably releases the claim, preserving retry liveness. Disconnected pending claims are also dropped in RemoveDisconnectedEndpoints.
  • Reconcile at ack: AddEndpoint becomes TryAdoptEndpoint, which rejects an ack when the subscription is already confirmed on a different connected server; the processor then sends a fire-and-forget unsubscribe pinned to the stray node. This mops up anything the claim cannot prevent (e.g. a timed-out attempt whose ack arrives late).

Multi-node (keyspace) subscriptions always adopt — they want every eligible node — so their behavior is unchanged.

Tests: the reporter's overlap scenario (first ack held, second restore must not subscribe elsewhere) plus the stray-ack cleanup, literal/pattern × normal/F&F × RESP2/RESP3, on the in-process test server. Full suite green locally apart from the known SortedSetScanLarge ZSCAN environment failures (fixed on the 8.12 branch).

The reporter also mentioned a separate reconfiguration-gate change for slow concurrent Sentinel discovery; that is intentionally not part of this PR.

…vers (#3336)

During a failover, OnFullyEstablished (per reconnecting connection) and
ReconfigureAsync both restore subscriptions fire-and-forget; between sending
(P)SUBSCRIBE and its acknowledgement a SingleNodeSubscription recorded no
server, so a concurrent restore would subscribe again - often on a different
node. The later ack then overwrote the recorded endpoint while the earlier
registration stayed live on its server, delivering every message twice until
that connection happened to drop.

Two layers:
- claim before send: a pending-claim token (CAS) makes competing restores back
  off while an attempt is in flight; the subscribe message is sent without
  fire-and-forget (callers that asked for F&F just don't await it) so every
  completion - ack, error reply, connection failure, timeout - observably
  releases the claim, keeping retry liveness.
- reconcile at ack: TryAdoptEndpoint rejects an ack when the subscription is
  already confirmed on a different connected server, and the processor sends a
  fire-and-forget unsubscribe pinned to the stray node - mopping up anything
  the claim cannot prevent (e.g. a timed-out attempt whose ack arrives late).

Regression tests run the reporter's overlap scenario (held first ack, second
restore must not subscribe elsewhere) plus the stray-ack cleanup, per protocol,
on the in-process test server.

This branch has not been deployed

No deployments
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.

Duplicate Pub/Sub messages after Redis Sentinel failover

1 participant