Repository navigation
GH-3950: assert that listeners resume after the connection dies underneath them - #3961
Merged
Merged
Conversation
…neath them The issue's second suggestion. A rejected delivery tag on a busy channel can cost the whole RabbitMQ connection -- RabbitMQ.Client's SessionManager.Lookup does an indexer read on a dictionary it may have just mutated, throws KeyNotFoundException on the receive loop, and escalates that to a library-initiated close (code=541). Wolverine cannot catch it; it is raised on the client's own MainLoop thread. So the only part Wolverine owns is what happens next, and that part was observed to work and asserted nowhere. Kills the connection from the BROKER side through the management API rather than provoking the 541 race. The race is timing dependent and would make a flaky test, and a forced close is indistinguishable from Wolverine's point of view -- an unsolicited shutdown it did not initiate. What is pinned here is the recovery contract, not the client bug. Asserting on ReconnectAttempts alone would be too weak: a recovered CONNECTION does not mean a recovered CONSUMER. ConnectionMonitor has to rebuild every tracked channel agent on recovery, and #3370 is the case where an agent falling out of that list was one connection drop away from being permanently ghosted. So the real assertion is that a message published after the kill is still received, with a baseline send first so a failure cannot be read as "it never worked". Two things measured rather than assumed while writing this, both of which the first attempt got wrong: - /api/connections is fed by RabbitMQ's connection tracking and LAGS the actual TCP connect on 4.x. Locally it was empty at 4s and populated at 8s with the host already up and passing traffic. Reading it once reported "no connection to kill" against a perfectly healthy host, so the lookup polls. - The kill is scoped by a per-test ClientProvidedName, so a run cannot take down a concurrently executing test's connections. Bobcat runs this project across several worker processes against one broker, which makes that a correctness requirement rather than tidiness. Local: the new test 4/4 across repeated isolated runs (~10s each), and the full Wolverine.RabbitMQ.Tests suite 503/503 with the kill running alongside everything else. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JG8Un6iNeyXECKJk3jo5uC
This was referenced Aug 16, 2026
This was referenced Aug 16, 2026
esond
pushed a commit
to esond/wolverine
that referenced
this pull request
Aug 18, 2026
Covers JasperFxGH-3956 (store agnostic document contracts), JasperFxGH-3954 (durability agents no longer assigned to incapable nodes), JasperFxGH-3953 (persistence provider owns the chain's transaction), JasperFxGH-3955 (idle reaper latching durable endpoints), and the three RabbitMQ changes: the community DrainWaitForPrefetch opt-in (JasperFx#3796), its non-terminal-stop follow-up (JasperFx#3960), and the JasperFxGH-3950 pair (JasperFx#3961 coverage, JasperFx#3964 channel quiesce). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JG8Un6iNeyXECKJk3jo5uC
This was referenced Aug 20, 2026
This was referenced Aug 27, 2026
This was referenced Sep 10, 2026
This was referenced Sep 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Addresses suggestion 2 of #3950 — "Confirm Wolverine recovers."
Why
A rejected delivery tag on a busy channel can cost the whole connection:
SessionManager.Lookupdoes an indexer read on a dictionary it may have just mutated, throwsKeyNotFoundExceptionon the receive loop, and RabbitMQ.Client escalates that into a library-initiated close (code=541). Wolverine cannot catch it — it is raised on the client's ownMainLoopthread.So the only part Wolverine owns is what happens next, and as the issue puts it, that part was observed to work and asserted nowhere. This turns it into a guarantee.
What it does
Kills the connection from the broker side via the management API rather than provoking the 541 race. The race is timing-dependent and would make a flaky test, and a forced close is indistinguishable from Wolverine's point of view — an unsolicited shutdown it did not initiate. What's pinned is the recovery contract, not the client bug.
Asserting on
ReconnectAttemptsalone would be too weak: a recovered connection is not a recovered consumer.ConnectionMonitorhas to rebuild every tracked channel agent on recovery, and #3370 is exactly the case where an agent falling out of that list was one drop away from being permanently ghosted. So the real assertion is that a message published after the kill is still received — with a baseline send first, so a failure can't be read as "it never worked".Two things I measured rather than assumed
Both of which my first attempt got wrong, and both now encoded in the test:
/api/connectionslags the actual TCP connect on RabbitMQ 4.x. Measured locally as empty at 4s and populated at 8s, with the host already up and passing traffic. Reading it once reported "no connection to kill" against a perfectly healthy host, so the lookup polls.ClientProvidedName, so a run cannot take down a concurrently executing test's connections. Bobcat runs this project across several worker processes against one broker, which makes that a correctness requirement rather than tidiness.Verification
Wolverine.RabbitMQ.TestsThe suite result matters more than usual here: a test that force-closes broker connections is exactly the kind that can perturb its neighbours, so I ran the whole project rather than the class.
Not included
SessionManager.Lookupdefect upstream to rabbitmq-dotnet-client) — that's an outward-facing action, so I've left it for you to file.🤖 Generated with Claude Code
https://claude.ai/code/session_01JG8Un6iNeyXECKJk3jo5uC