Skip to content

Tolerate refused CLUSTER SLOTS/NODES during topology updates - #3275

Merged
mgravell merged 2 commits into
mainfrom
marc/3274-tolerate-refused-cluster-topology
Oct 13, 2026
Merged

mgravell merged 2 commits into
mainfrom
marc/3274-tolerate-refused-cluster-topology

Conversation

@mgravell

Copy link
Copy Markdown
Collaborator

Addresses #3274.

Problem

Proxied deployments can identify as a cluster (via HELLO mode, INFO redis_mode, or an answered CLUSTER NODES) while refusing CLUSTER SLOTS with ERR command is not allowed — Redis Enterprise (and so Azure Managed Redis) exposes CLUSTER SLOTS only under the OSS cluster API. The refusal makes await slotsTask in GetEndpointsFromClusterNodes throw RedisServerException, which the outer catch logs as an error ("Encountered error while updating cluster config") — at connect and on every reconfigure, including the periodic topology refresh (default 30 min). The reported stack line (ConnectionMultiplexer.cs:line 2165 in 3.4.0) is exactly that await.

Note the method's own comment already said an error reply for SLOTS should "behave exactly as before" (the pre-4.0 NODES-only path), but the exception fired before that fallback was reached.

An A/B test of the shipped 3.3.1 and 3.4.0 packages against a fake-AMR in-process server showed this path behaves identically in both (same error count at connect and per refresh; only the line number differs, 2162 vs 2165) — so the "works on 3.3.1" aspect of the report is likely about reconfigure frequency (maintenance notifications now auto-enabled for AMR, #3248; dedicated pub/sub connection, #3264) or a server-side change; context requested on the issue.

Fix

A refusal is a posture, not a fault. In GetEndpointsFromClusterNodes, catch RedisServerException per task:

  • CLUSTER SLOTS refused → fall back to the CLUSTER NODES-only path, exactly as for a pre-4.0 server;
  • CLUSTER NODES refused too → no topology view at all; return null quietly.

Both are logged at Information (new LoggerMessage EventIds 128/129). Genuine faults (timeouts, connection drops, parse failures) still flow to the existing error handling.

Tests

ClusterCommandsRefusedUnitTests ([RunPerProtocol]) drives the in-process test server with a subclass refusing CLUSTER SLOTS (and a variant refusing CLUSTER NODES too, while still announcing itself as a cluster):

  • SLOTS refused: endpoint discovery still works from the NODES view alone; no "Encountered error while updating cluster config" is logged; the Information message is.
  • Both refused: connect succeeds, PING works, an explicit later ReconfigureAsync stays quiet.

CapturingLogger is promoted from a private nested class in ConnectFailureRefreshTests to a shared test helper.

Full suite: new tests pass across protocols, all *UnitTests green, Build.csproj -c Release /p:CI=true clean.

Proxied deployments (Azure Managed Redis / Redis Enterprise) can identify
as a cluster while refusing CLUSTER SLOTS ("ERR command is not allowed";
Enterprise exposes SLOTS only under the OSS cluster API). That refusal
surfaced as a logged *error* from GetEndpointsFromClusterNodes at connect
and on every reconfigure, including the periodic topology refresh.

A refusal is a posture, not a fault: catch RedisServerException per task -
SLOTS refused falls back to the NODES-only path (as the method's own
comment always intended), NODES refused means no topology view at all, so
return null; both are logged at Information (EventIds 128/129).

Tests: ClusterCommandsRefusedUnitTests drives the in-process test server
with SLOTS (and SLOTS+NODES) refused, asserting discovery still works and
no error is logged; CapturingLogger promoted from ConnectFailureRefreshTests
to a shared test helper.
SLOTS and NODES are issued as a pair; if one await throws anything other
than a server refusal, the other task is abandoned mid-flight - and both
ride the same connection, so the orphan is likely to fault too. Attach
ObserveErrors at creation so it can never surface as an
UnobservedTaskException in the host. (Pre-existing gap: the bare
sequential awaits had the same shape.)
@mgravell
mgravell merged commit 3679bd0 into main Oct 13, 2026
5 checks passed
@mgravell
mgravell deleted the marc/3274-tolerate-refused-cluster-topology branch October 13, 2026 09:59
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.

1 participant