Skip to content

[Fix] RMQ event handlers not removed (#4044)#4045

Merged
iancooper merged 10 commits into
BrighterCommand:masterfrom
ravriel:fix-channel-events
Mar 24, 2026
Merged

[Fix] RMQ event handlers not removed (#4044)#4045
iancooper merged 10 commits into
BrighterCommand:masterfrom
ravriel:fix-channel-events

Conversation

@ravriel

@ravriel ravriel commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Fixes #4044 by removing channel event handlers in each event handler.

@CLAassistant

CLAassistant commented Mar 11, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@iancooper iancooper changed the title Fix RMQ event handlers not removed (#4044) [Fix] RMQ event handlers not removed (#4044) Mar 11, 2026
@iancooper iancooper added help wanted 3 - Done Bug .NET Pull requests that update .net code V10.X labels Mar 11, 2026
codescene-delta-analysis[bot]

This comment was marked as outdated.

@iancooper

iancooper commented Mar 11, 2026

Copy link
Copy Markdown
Member

Issues with the Fix

The handlers are removed inside OnPublishSucceeded / OnPublishFailed — before the pending confirmation is processed for the current delivery tag. But more importantly, unsubscribing in the event handler creates a window in which a subsequent SendWithDelayAsync call has already re-registered handlers, and the first event's handler then removes those new registrations. With concurrent sends on the same producer, this can cause missed confirmations.

The root cause is that handlers are registered per-send when they should be registered once per channel. The handlers already use _pendingConfirmations (a ConcurrentDictionary) to correlate delivery tags, so they work correctly for multiple in-flight messages. The fix should be:

  • Subscribe to BasicAcksAsync/BasicNacksAsync once when the channel is created (in EnsureBrokerAsync or after it returns), not on every send.
  • Unsubscribe when the channel is disposed/reset (in ResetConnectionToBrokerAsync or Dispose).

This eliminates the add/remove churn entirely and is safe for concurrent sends.

// In EnsureBrokerAsync (or right after it returns), subscribe once:
if (Channel != null)
{
Channel.BasicAcksAsync += OnPublishSucceeded;
Channel.BasicNacksAsync += OnPublishFailed;
}

// Remove the per-send subscription entirely from SendWithDelayAsync
// Unsubscribe in ResetConnectionToBrokerAsync before creating a new channel

This is simpler, correct for concurrent use, and doesn't require the RegisterChannelEvents method at all.

The PR has no tests verifying the fix. A test that sends N messages and asserts the handler is invoked exactly N times (once per message, not N×(N+1)/2 times) would prevent regression.

@iancooper iancooper left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@ravriel Your idea is right, but it needs adjusting so that it doesn't do this every time

codescene-delta-analysis[bot]

This comment was marked as outdated.

@iancooper

Copy link
Copy Markdown
Member

@ravriel Just nudging in case you missed the feedback

Comment on lines +191 to +203
private void RegisterChannelEvents(bool add)
{
if (add)
{
Channel?.BasicAcksAsync += OnPublishSucceeded;
Channel?.BasicNacksAsync += OnPublishFailed;
}
else
{
Channel?.BasicAcksAsync -= OnPublishSucceeded;
Channel?.BasicNacksAsync -= OnPublishFailed;
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Split it into two methods, register and unregister

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We need to do it in a different place, as we don't want to register/unregister per publish

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@iancooper iancooper left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for adding the test, I think we should link at cleaning up in the Dispose as well, then we are done

{
Log.ErrorTalkingToSocketAsync(s_logger, io, Connection.AmpqUri.GetSanitizedUri());
await ResetConnectionToBrokerAsync(cancellationToken);
Channel?.BasicAcksAsync -= OnPublishSucceeded;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should unsubscribe in Dispose, if not already disposed, since if we exit without an error, we want to remove any subscriptions.

codescene-delta-analysis[bot]

This comment was marked as outdated.

codescene-delta-analysis[bot]

This comment was marked as outdated.

@codescene-delta-analysis codescene-delta-analysis Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Gates Passed
4 Quality Gates Passed

See analysis details in CodeScene

Quality Gate Profile: Clean Code Collective
Install CodeScene MCP: safeguard and uplift AI-generated code. Catch issues early with our IDE extension and CLI tool.

@iancooper iancooper merged commit 9b35ed5 into BrighterCommand:master Mar 24, 2026
24 of 28 checks passed
@iancooper

Copy link
Copy Markdown
Member

Thanks for the contribution @ravriel I am going to get a release out soon

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3 - Done Bug help wanted .NET Pull requests that update .net code V10.X

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug ]RMQ channel event handlers are added again and again for each send

4 participants