Conversation
6f167d7 to
19103ee
Compare
|
I think the sentinel side of this is reasonable and captures the key scenarios (not quite full test coverage, but probably enough), however the slot stuff shares the same feedback from #3185 (and for good reason: they share a commit) |
|
Re: "hash-tag / slot feedback common with #3185" - confirmed, this branch carries #3185's commit ( Pushed the same structural fix as a separate commit ( |
b35e0a6 to
883a63a
Compare
When connecting via Sentinel with AbortOnConnectFail=false, a failed initial connect (unreachable sentinels, or no primary discovered within ConnectTimeout) threw instead of returning the multiplexer, and leaked the internally-created ConnectionMultiplexer instances (sentinel monitor connection and per-retry primary candidates) since nothing disposed them. This now mirrors the non-Sentinel Connect path: AbortOnConnectFail=false returns a usable multiplexer with LastException set and background retry wired up, while AbortOnConnectFail=true still throws but disposes everything first. Fixes StackExchange#2980
… on the switchover, cover recovery - ScheduleSentinelPrimaryReconnect null-checked the timer field and then assigned it. That is now reached from the connect path as well as from ConnectionFailed - and the handler is wired up a few lines before the connect path calls it, on a connection already failing in the background - so two threads could each create a timer. Only the published one can be reached by OnManagedConnectionRestored to be disposed; the loser would fire SwitchPrimary every second for the life of the process with no handle left to stop it. Build first, publish with a compare-exchange, and start only the winner. - The initial switchover swallowed failures only when discovery had *not* succeeded, so a connection that had reached its primary was torn down and the exception thrown at a caller who set AbortOnConnectFail=false precisely to avoid that. The soft-fail promise has to hold however far we got. - Cover the arc the change exists for: nothing listening at connect time, a multiplexer returned rather than an exception, then a sentinel becomes reachable at that address and the connection recovers - and the sentinel address it was seeded with does not linger as a server afterwards. Uses a loopback forwarder so the shared topology is never disturbed.
883a63a to
d3cda43
Compare
Summary
Fixes #2980.
When connecting via Sentinel (
ConnectionMultiplexer.Connect/ConnectAsync/SentinelConnect) withAbortOnConnectFail=false, a failed initial connect — sentinels unreachable, or no primary discovered withinConnectTimeout— threw aRedisConnectionExceptioninstead of returning a multiplexer to the caller, and leaked the internally-createdConnectionMultiplexerinstances (the sentinel monitor connection, and per-retry primary candidates inGetSentinelMasterConnection's loop), since nothing disposed them on the failure path.This didn't match the non-Sentinel
Connect/ConnectImplbehavior, which honorsAbortOnConnectFail=falseby returning the muxer (withLastExceptionset) instead of throwing.Changes in
ConnectionMultiplexer.Sentinel.cs:AbortOnConnectFail=false: no longer throws. Returns a usable multiplexer withLastExceptionset, the same event wiring (ConnectionRestored/ConnectionFailed) and background retry (SwitchPrimary/reconnect timer) as the success path, so callers can attach handlers and let it reconnect once sentinels/primary become reachable — mirroring the non-Sentinel path.AbortOnConnectFail=true: preserves today's throwing behavior and exception messages (no breaking change), but now disposes the sentinel connection and any superseded/failed candidate connections before the exception propagates, instead of leaking them.AbortOnConnectFail.No public API changes.
Test plan
dotnet build(Release) — 0 errors, 0 warningsSentinelConfigTests.cscoveringAbortOnConnectFail=false(syncConnect, asyncConnectAsync, andSentinelConnect) returning a non-null, non-throwing, disconnected multiplexer withLastExceptionset against unreachable sentinel endpoints, plus a regression guard thatAbortOnConnectFail=truestill throws — all against closed loopback ports, no live topology requiredStackExchange.Redis.Testssuite (net10.0, Release) against the docker-compose Redis/Sentinel/Cluster topology: 5880 passed, 0 failed (the live Sentinel integration tests are gated[SkipOnWindows], unaffected by this change)🤖 Generated with Claude Code