Skip to content

RG-T117 Chat fix, web ui fixes, rabbitmq fix - #456

Merged
ucswift merged 4 commits into
masterfrom
develop
Aug 10, 2026
Merged

RG-T117 Chat fix, web ui fixes, rabbitmq fix#456
ucswift merged 4 commits into
masterfrom
develop

Conversation

@ucswift

@ucswift ucswift commented Aug 10, 2026

Copy link
Copy Markdown
Member

Description

This PR addresses RabbitMQ connection reliability issues, chat functionality, and several web UI bugs.

RabbitMQ Resilience Improvements

Channel exhaustion recovery: The shared RabbitMQ connection can report as open while its channel numbers are exhausted (ChannelAllocationException), making all outbound sends fail silently until a process restart. Outbound and topic providers now detect this exception, force-reset the connection, and retry the publish on a fresh connection.

Consumer watchdog: Inbound event consumers (SignalR eventing worker and CQRS queue processor) die silently when the shared connection is replaced or automatic recovery fails, with nothing restarting them. A watchdog loop now monitors IsConnected() every 500ms and rebuilds consumers after ~10 seconds of continuous disconnect, retrying at most once per minute to avoid spinning during a broker outage.

Duplicate event prevention: Start() calls in both inbound providers now dispose any existing channels before creating new ones. This prevents old consumers from being resurrected by late automatic-recovery events alongside new consumers, which would double-deliver or double-process messages.

Chat Fix

Removed the [FromForm] attribute from the UploadAttachment endpoint's file parameter to fix attachment upload binding.

Web UI Fixes

  • Distribution list forms: Removed the switchInputs function that toggled between internal/external email fields, and corrected validation logic that relied on reading Select2 state (edit list) or incorrectly allowed blank addresses for new lists.
  • Contenteditable crash: jQuery Validation was crashing on blur of contenteditable elements (Quill rich text editors) that lack a name attribute. Validation is now configured to ignore contenteditable elements globally.

Summary by CodeRabbit

  • Security

    • New encrypted payloads use authenticated AES-256-GCM protection while maintaining compatibility with existing encrypted data.
  • Reliability

    • Improved messaging connection recovery automatically detects disconnections, resets connections, and retries publishing or event monitoring.
    • Queue and event processing now recover more safely from startup and channel failures.
  • Bug Fixes

    • Improved validation for list types and email addresses.
    • Fixed validation behavior for hidden and rich-text editor fields.
    • Improved handling of duplicate moderation reports.
    • Attachment uploads continue to support existing validation and storage behavior.

@Resgrid-Bot

This comment has been minimized.

@request-info

request-info Bot commented Aug 10, 2026

Copy link
Copy Markdown

Thanks for opening this, but we'd appreciate a little more information. Could you update it with more details?

[ProducesResponseType(StatusCodes.Status401Unauthorized)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public async Task<ActionResult<ChatAttachmentUploadedResult>> UploadAttachment(string channelId, string messageId, [FromForm] IFormFile file, CancellationToken cancellationToken)
public async Task<ActionResult<ChatAttachmentUploadedResult>> UploadAttachment(string channelId, string messageId, IFormFile file, CancellationToken cancellationToken)
@coderabbitai

coderabbitai Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@ucswift, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 32 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c1f52f6f-209b-4f62-b54f-c9c820baf815

📥 Commits

Reviewing files that changed from the base of the PR and between 6f53198 and e661342.

📒 Files selected for processing (2)
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundEventProvider.cs
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundQueueProvider.cs
📝 Walkthrough

Walkthrough

The changes add AES-256-GCM encryption with legacy CBC decryption, RabbitMQ channel and connection recovery, connection monitoring with restart limits, and targeted moderation, attachment, and client-side validation fixes.

Changes

RabbitMQ recovery

Layer / File(s) Summary
Inbound channel lifecycle
Core/Resgrid.Model/Providers/IRabbitInboundEventProvider.cs, Providers/Resgrid.Providers.Bus.Rabbit/RabbitConnection.cs, Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundEventProvider.cs, Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundQueueProvider.cs
Inbound providers expose IsConnected(), dispose stale channels before startup, verify connections, and clean up partial channel state.
Connection reset and publishing recovery
Providers/Resgrid.Providers.Bus.Rabbit/RabbitConnection.cs, Providers/Resgrid.Providers.Bus.Rabbit/RabbitOutboundQueueProvider.cs, Providers/Resgrid.Providers.Bus.Rabbit/RabbitTopicProvider.cs
Publishing paths use extracted helpers and retry once after ChannelAllocationException by calling ForceResetAsync.
Connection monitoring and restart
Web/Resgrid.Web.Eventing/Worker.cs, Workers/Resgrid.Workers.Console/Tasks/QueuesProcessorTask.cs
Workers monitor connectivity, restart after 20 failed checks, and limit restart attempts to once per 60 seconds.

Authenticated encryption

Layer / File(s) Summary
Encryption format and decryption routing
Core/Resgrid.Services/EncryptionService.cs
New payloads use AES-256-GCM with random nonces, authentication tags, the enc2: prefix, and Base64 encoding. Unprefixed payloads continue to use legacy CBC decryption.

Application validation and error handling

Layer / File(s) Summary
Application behavior corrections
Core/Resgrid.Services/ModerationService.cs, Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs, Web/Resgrid.Web/wwwroot/js/app/internal/dlist/*, Web/Resgrid.Web/wwwroot/js/app/internal/resgrid.user.js
Recovered duplicate reports no longer log errors. Attachment binding, list email validation, and jQuery Validation handling receive targeted corrections.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant EventWorker
  participant RabbitInboundEventProvider
  participant RabbitConnection
  participant RabbitOutboundQueueProvider
  EventWorker->>RabbitInboundEventProvider: Start
  EventWorker->>RabbitInboundEventProvider: IsConnected
  RabbitInboundEventProvider->>RabbitConnection: VerifyAndCreateClients
  RabbitOutboundQueueProvider->>RabbitConnection: ForceResetAsync on channel allocation failure
  RabbitOutboundQueueProvider->>RabbitOutboundQueueProvider: retry publishing once
Loading

Possibly related PRs

  • Resgrid/Core#322: Shared RabbitMQ connection-reset changes affect RabbitConnection and RabbitTopicProvider.
  • Resgrid/Core#408: Related RabbitMQ connection and channel lifecycle handling.
  • Resgrid/Core#409: Related semaphore-based connection handling and reset behavior.

Suggested reviewers: github-actions

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately identifies the main chat, web UI, and RabbitMQ changes in the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch develop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

/// numbers are exhausted (ChannelAllocationException) — which the IsOpen guards can never
/// detect. Raises ConnectionReset so cached declaration state is cleared.
/// </summary>
public static async Task ForceResetAsync()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Performance high

Race condition in VerifyAndCreateClients: the _connection == null check (line 31) lacks a re-check inside the semaphore block, allowing concurrent send threads recovering from ChannelAllocationException to simultaneously create new IConnection instances and exhaust the broker's connection limit. Adding an inner _connection == null re-check inside the critical section ensures only the first thread creates the connection.

if (_connection == null)
{
    await _semaphore.WaitAsync();
    try
    {
        if (_connection == null) // re-check inside the lock: a prior holder may have already created it
        {
            _factory = new ConnectionFactory() { ... };
            _connection = await _factory.CreateConnectionAsync(clientName);
        }
    }
Prompt for LLM

File Providers/Resgrid.Providers.Bus.Rabbit/RabbitConnection.cs:

Line 248:

Race condition in `VerifyAndCreateClients`: the `_connection == null` check (line 31) lacks a re-check inside the semaphore block, allowing concurrent send threads recovering from `ChannelAllocationException` to simultaneously create new `IConnection` instances and exhaust the broker's connection limit. Adding an inner `_connection == null` re-check inside the critical section ensures only the first thread creates the connection.

Suggested Code:

if (_connection == null)
{
    await _semaphore.WaitAsync();
    try
    {
        if (_connection == null) // re-check inside the lock: a prior holder may have already created it
        {
            _factory = new ConnectionFactory() { ... };
            _connection = await _factory.CreateConnectionAsync(clientName);
        }
    }

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

}
catch (Exception ex)
{
Logging.LogException(ex);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Insufficient error context: the catch block logs only the raw exception without identifying the failing channel or the disposal operation. Pass a descriptive message and structured fields, such as Logging.LogException(ex, "DisposeChannelsAsync", new { ChannelType = channel?.GetType().Name }), to capture essential diagnostic data.

Kody rule violation: Include error context in structured logs

Prompt for LLM

File Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundQueueProvider.cs:

Line 670:

Insufficient error context: the catch block logs only the raw exception without identifying the failing channel or the disposal operation. Pass a descriptive message and structured fields, such as `Logging.LogException(ex, "DisposeChannelsAsync", new { ChannelType = channel?.GetType().Name })`, to capture essential diagnostic data.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

if (connection != null)
return await PublishAsync(queueName, message, durable, expiration, requirePublisherConfirmation);
}
catch (RabbitMQ.Client.Exceptions.ChannelAllocationException ex)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Code duplication: the entire ChannelAllocationException recovery block is duplicated verbatim in SendMessagesWithConfirmation at line 260. Extract the log-reset-retry pattern into a reusable named function like RetryAfterResetAsync(Func<Task<bool>> retryOp, Exception ex) and call it from both catch blocks.

Kody rule violation: Extract duplicated logic into functions

Prompt for LLM

File Providers/Resgrid.Providers.Bus.Rabbit/RabbitOutboundQueueProvider.cs:

Line 156:

Code duplication: the entire `ChannelAllocationException` recovery block is duplicated verbatim in `SendMessagesWithConfirmation` at line 260. Extract the log-reset-retry pattern into a reusable named function like `RetryAfterResetAsync(Func<Task<bool>> retryOp, Exception ex)` and call it from both catch blocks.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

}
}
else if ($('#Type').select2('data').text === 'Internal') {
else if (typeof currentType !== 'undefined' && currentType === 'Internal') {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Magic string vulnerability: the literal 'Internal' is error-prone because typos or casing changes will not be caught at compile time. Define a constants object or enum in a shared module and compare against ListType.Internal.

Kody rule violation: Use enums instead of magic strings

Prompt for LLM

File Web/Resgrid.Web/wwwroot/js/app/internal/dlist/resgrid.dlists.editlist.js:

Line 66:

Magic string vulnerability: the literal `'Internal'` is error-prone because typos or casing changes will not be caught at compile time. Define a constants object or enum in a shared module and compare against `ListType.Internal`.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

try
{
_logger.LogWarning($"{Name}: Queue consumers disconnected; restarting queue monitoring.");
await queue.Start("QueueProcessor-CQRS");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Duplicated magic string: repeating the "QueueProcessor-CQRS" queue name risks drift if it is renamed and obscures the canonical value location. Define a single constant in a shared location and use it for both the initial Start and restart calls.

Kody rule violation: Centralize string constants

Prompt for LLM

File Workers/Resgrid.Workers.Console/Tasks/QueuesProcessorTask.cs:

Line 85:

Duplicated magic string: repeating the `"QueueProcessor-CQRS"` queue name risks drift if it is renamed and obscures the canonical value location. Define a single constant in a shared location and use it for both the initial Start and restart calls.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.


disconnectedChecks++;

if (disconnectedChecks >= 20 && (DateTime.UtcNow - lastRestartAttemptUtc) >= TimeSpan.FromSeconds(60))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Magic numbers in condition: inlined literals 20 and 60 encode operational thresholds that are prone to inconsistent modifications and misinterpretation. Hoist these values to named constants like DisconnectThresholdChecks and RestartCooldown to clarify their operational meaning.

Kody rule violation: Replace magic numbers with named constants

Prompt for LLM

File Workers/Resgrid.Workers.Console/Tasks/QueuesProcessorTask.cs:

Line 78:

Magic numbers in condition: inlined literals `20` and `60` encode operational thresholds that are prone to inconsistent modifications and misinterpretation. Hoist these values to named constants like `DisconnectThresholdChecks` and `RestartCooldown` to clarify their operational meaning.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
Web/Resgrid.Web.Eventing/Worker.cs (1)

88-94: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Route the watchdog messages through Resgrid.Framework.Logging.

These two new messages report a silent-outage condition and its recovery. Console.WriteLine output is not captured by the framework logging pipeline, so the signal is lost in production. The coding guidelines require the Resgrid.Framework.Logging static methods for logging.

♻️ Proposed change
-					Console.WriteLine("Eventing Worker: Rabbit consumer disconnected; restarting event monitoring.");
+					Resgrid.Framework.Logging.LogError("Eventing Worker: Rabbit consumer disconnected; restarting event monitoring.");
 					await StartProviderAsync();
 
 					if (_rabbitInboundEventProvider.IsConnected())
 					{
 						disconnectedChecks = 0;
-						Console.WriteLine("Eventing Worker: Event monitoring restarted.");
+						Resgrid.Framework.Logging.LogInfo("Eventing Worker: Event monitoring restarted.");
 					}

As per coding guidelines: "Use Resgrid.Framework.Logging static methods for logging: LogException(), LogError(), LogInfo(), LogDebug()".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Web/Resgrid.Web.Eventing/Worker.cs` around lines 88 - 94, Replace the two
watchdog Console.WriteLine calls in the Rabbit consumer restart flow with the
appropriate Resgrid.Framework.Logging static method, using LogInfo for the
disconnect/restart and recovery messages while preserving their existing text
and control flow.

Source: Coding guidelines

Workers/Resgrid.Workers.Console/Tasks/QueuesProcessorTask.cs (1)

66-74: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Replace Thread.Sleep with await Task.Delay in this async loop.

ProcessAsync is async, and the loop now runs for the whole life of the task. Thread.Sleep(500) blocks a thread pool thread continuously and it does not observe _cancellationToken, so shutdown waits up to 500 ms per iteration.

♻️ Proposed change
 			while (!_cancellationToken.IsCancellationRequested)
 			{
-				Thread.Sleep(500);
+				try
+				{
+					await Task.Delay(500, _cancellationToken);
+				}
+				catch (OperationCanceledException)
+				{
+					break;
+				}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Workers/Resgrid.Workers.Console/Tasks/QueuesProcessorTask.cs` around lines 66
- 74, In ProcessAsync, replace the blocking Thread.Sleep(500) inside the
cancellation loop with an awaited Task.Delay that uses _cancellationToken,
preserving the 500 ms interval and allowing prompt cancellation during shutdown.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Providers/Resgrid.Providers.Bus.Rabbit/RabbitConnection.cs`:
- Around line 248-277: Update VerifyAndCreateClients to re-check _connection
after acquiring _semaphore and return the existing connection when another
caller has already initialized it. Only create and assign a new
ConnectionFactory and connection while the semaphore is held and _connection
remains null, preventing concurrent reset callers from leaking duplicate
connections.

In `@Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundQueueProvider.cs`:
- Around line 44-50: Update Start and its StartMonitoring startup flow so any
exception during channel creation or consumer registration disposes all newly
created channels via DisposeChannelsAsync before propagating the failure. Ensure
the failed startup leaves the provider’s connection state reporting false,
preventing partially initialized channels from being treated as healthy.

---

Nitpick comments:
In `@Web/Resgrid.Web.Eventing/Worker.cs`:
- Around line 88-94: Replace the two watchdog Console.WriteLine calls in the
Rabbit consumer restart flow with the appropriate Resgrid.Framework.Logging
static method, using LogInfo for the disconnect/restart and recovery messages
while preserving their existing text and control flow.

In `@Workers/Resgrid.Workers.Console/Tasks/QueuesProcessorTask.cs`:
- Around line 66-74: In ProcessAsync, replace the blocking Thread.Sleep(500)
inside the cancellation loop with an awaited Task.Delay that uses
_cancellationToken, preserving the 500 ms interval and allowing prompt
cancellation during shutdown.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 4757be48-3c47-4a8d-aaae-bfdf0a8a7bf1

📥 Commits

Reviewing files that changed from the base of the PR and between 9fd5bbe and 05796ba.

⛔ Files ignored due to path filters (2)
  • Tests/Resgrid.Tests/Bootstrapper.cs is excluded by !**/Tests/**
  • Tests/Resgrid.Tests/Services/EncryptionServiceTests.cs is excluded by !**/Tests/**
📒 Files selected for processing (14)
  • Core/Resgrid.Model/Providers/IRabbitInboundEventProvider.cs
  • Core/Resgrid.Services/EncryptionService.cs
  • Core/Resgrid.Services/ModerationService.cs
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitConnection.cs
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundEventProvider.cs
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundQueueProvider.cs
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitOutboundQueueProvider.cs
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitTopicProvider.cs
  • Web/Resgrid.Web.Eventing/Worker.cs
  • Web/Resgrid.Web.Services/Controllers/v4/ChatController.cs
  • Web/Resgrid.Web/wwwroot/js/app/internal/dlist/resgrid.dlists.editlist.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/dlist/resgrid.dlists.newlist.js
  • Web/Resgrid.Web/wwwroot/js/app/internal/resgrid.user.js
  • Workers/Resgrid.Workers.Console/Tasks/QueuesProcessorTask.cs

Comment thread Providers/Resgrid.Providers.Bus.Rabbit/RabbitConnection.cs
@Resgrid-Bot

This comment has been minimized.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Providers/Resgrid.Providers.Bus.Rabbit/RabbitConnection.cs (1)

259-273: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Serialize every _connection mutation through _semaphore, not only the reset path.

ForceResetAsync establishes _semaphore as the owner of _connection and _factory. Two other paths in this file still write those fields without holding the semaphore:

  • Lines 23-29 in VerifyAndCreateClients dispose the connection and null both fields.
  • Lines 297-307 in CreateConnection do the same.

This allows a lost write. Thread A enters ForceResetAsync, captures connection, and nulls the fields. Thread B is already past Line 23 with the old reference and is awaiting DisposeAsync(). Thread C then creates a fresh connection through VerifyAndCreateClients. Thread B resumes and executes _connection = null at Line 26, which discards the live connection that thread C created. That connection is never disposed and every later caller rebuilds another one. The same interleaving also double-disposes the old IConnection.

Move the stale-connection detection into a semaphore-protected helper and reuse it from all three call sites.

🔒️ Proposed fix: route the stale-connection teardown through the same lock
+		/// <summary>
+		/// Clears the shared connection under the semaphore when the supplied reference is still the
+		/// current one. Returns the connection to dispose, or null when another caller already replaced it.
+		/// </summary>
+		private static async Task<IConnection> ClearIfCurrentAsync(IConnection expected)
+		{
+			await _semaphore.WaitAsync();
+
+			try
+			{
+				if (!ReferenceEquals(_connection, expected))
+					return null;
+
+				_connection = null;
+				_factory = null;
+			}
+			finally
+			{
+				_semaphore.Release();
+			}
+
+			return expected;
+		}
+
 		public static async Task ForceResetAsync()
 		{
-			IConnection connection;
-
-			await _semaphore.WaitAsync();
-			try
-			{
-				connection = _connection;
-				_connection = null;
-				_factory = null;
-			}
-			finally
-			{
-				_semaphore.Release();
-			}
+			var connection = await ClearIfCurrentAsync(_connection);

Then apply the helper at the two lock-free sites (outside the selected range):

// VerifyAndCreateClients, replacing lines 23-29
var stale = _connection;
if (stale != null && !stale.IsOpen)
{
    var cleared = await ClearIfCurrentAsync(stale);

    if (cleared != null)
    {
        RaiseConnectionReset();

        try
        {
            await cleared.DisposeAsync();
        }
        catch (Exception ex)
        {
            Logging.LogException(ex);
        }
    }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Providers/Resgrid.Providers.Bus.Rabbit/RabbitConnection.cs` around lines 259
- 273, Serialize all mutations of _connection and _factory through _semaphore by
adding a ClearIfCurrentAsync helper that locks, verifies the supplied connection
is still the current reference, clears both fields, and returns the cleared
connection only when successful. Replace the lock-free stale-connection teardown
in VerifyAndCreateClients and CreateConnection with this helper, preserving
reset notification and disposal only for the returned connection; update
ForceResetAsync to reuse the same protected clearing behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundQueueProvider.cs`:
- Around line 86-95: Update the startup catch around StartMonitoring to
specifically handle ChannelAllocationException, dispose the provider channels,
call RabbitConnection.ForceResetAsync(), then rethrow so watchdog retries create
a fresh connection; preserve existing cleanup and rethrow behavior for other
exceptions, and add the RabbitMQ.Client.Exceptions import if needed.

---

Outside diff comments:
In `@Providers/Resgrid.Providers.Bus.Rabbit/RabbitConnection.cs`:
- Around line 259-273: Serialize all mutations of _connection and _factory
through _semaphore by adding a ClearIfCurrentAsync helper that locks, verifies
the supplied connection is still the current reference, clears both fields, and
returns the cleared connection only when successful. Replace the lock-free
stale-connection teardown in VerifyAndCreateClients and CreateConnection with
this helper, preserving reset notification and disposal only for the returned
connection; update ForceResetAsync to reuse the same protected clearing
behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: f1408bb0-6c72-4565-9c24-42be81633dc0

📥 Commits

Reviewing files that changed from the base of the PR and between 05796ba and 6f53198.

📒 Files selected for processing (4)
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitConnection.cs
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundEventProvider.cs
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundQueueProvider.cs
  • Workers/Resgrid.Workers.Console/Tasks/QueuesProcessorTask.cs
🚧 Files skipped from review as they are similar to previous changes (2)
  • Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundEventProvider.cs
  • Workers/Resgrid.Workers.Console/Tasks/QueuesProcessorTask.cs

try
{
_factory = new ConnectionFactory() { HostName = ServiceBusConfig.RabbitHostname2, UserName = ServiceBusConfig.RabbitUsername, Password = ServiceBusConfig.RabbbitPassword };
_factory = new ConnectionFactory() { HostName = ServiceBusConfig.RabbitHostname, UserName = ServiceBusConfig.RabbitUsername, Password = ServiceBusConfig.RabbbitPassword };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules low

Duplicated logic across the connection-factory creation and CreateConnectionAsync sequence (lines 46-47, 57-58, 68-69) complicates maintenance if factory options change. Extract a helper such as static async Task<bool> TryConnectAsync(string hostname, string clientName) to build the factory and attempt connections.

Kody rule violation: Extract duplicated logic into functions

Prompt for LLM

File Providers/Resgrid.Providers.Bus.Rabbit/RabbitConnection.cs:

Line 46:

Duplicated logic across the connection-factory creation and `CreateConnectionAsync` sequence (lines 46-47, 57-58, 68-69) complicates maintenance if factory options change. Extract a helper such as `static async Task<bool> TryConnectAsync(string hostname, string clientName)` to build the factory and attempt connections.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

// IsConnected() would report healthy and the host watchdog would never rebuild.
// Tear the channel down (nulled field makes IsConnected() false) and rethrow for
// the caller's retry path.
await DisposeChannelAsync();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Unhandled exception in await DisposeChannelAsync() inside the catch block masks the original StartMonitoring exception and prevents the rethrow from executing. Wrap the cleanup in its own try/catch to handle secondary errors, then throw; the original exception.

Kody rule violation: Handle async operations with proper error handling

Prompt for LLM

File Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundEventProvider.cs:

Line 59:

Unhandled exception in `await DisposeChannelAsync()` inside the catch block masks the original `StartMonitoring` exception and prevents the rethrow from executing. Wrap the cleanup in its own try/catch to handle secondary errors, then `throw;` the original exception.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

// A failed initial start (broker briefly down at boot, partial startup torn down by
// the provider) must not kill this task before the watchdog loop below exists —
// the loop sees IsConnected() == false and retries.
Resgrid.Framework.Logging.LogException(ex);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Insufficient logging context in Resgrid.Framework.Logging.LogException(ex) catch blocks prevents proper failure correlation. Include structured fields for the operation name and queue identifier using an equivalent overload, such as Resgrid.Framework.Logging.LogException("QueueProcessor start failed", ex, new { Queue = "QueueProcessor-CQRS", Operation = "queue.Start" }).

Kody rule violation: Include error context in structured logs

Prompt for LLM

File Workers/Resgrid.Workers.Console/Tasks/QueuesProcessorTask.cs:

Line 65:

Insufficient logging context in `Resgrid.Framework.Logging.LogException(ex)` catch blocks prevents proper failure correlation. Include structured fields for the operation name and queue identifier using an equivalent overload, such as `Resgrid.Framework.Logging.LogException("QueueProcessor start failed", ex, new { Queue = "QueueProcessor-CQRS", Operation = "queue.Start" })`.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

@Resgrid-Bot

Resgrid-Bot commented Aug 10, 2026

Copy link
Copy Markdown

Code Review Completed! 🔥

The code review was successfully completed based on your current configurations.

Kody Guide: Usage and Configuration
Interacting with Kody
  • Request a Review: Ask Kody to review your PR manually by adding a comment with the @kody start-review command at the root of your PR.

  • Validate Business Logic: Ask Kody to validate your code against business rules by adding a comment with the @kody -v business-logic command.

  • Provide Feedback: Help Kody learn and improve by reacting to its comments with a 👍 for helpful suggestions or a 👎 if improvements are needed.

Current Kody Configuration
Review Options

The following review options are enabled or disabled:

Options Enabled
Bug
Performance
Security
Business Logic

Access your configuration settings here.

// watchdog's retry would get the same connection back forever — reset it so the
// next Start builds a fresh one.
if (ex is RabbitMQ.Client.Exceptions.ChannelAllocationException)
await RabbitConnection.ForceResetAsync();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Unhandled exception vulnerability exists in the catch block because RabbitConnection.ForceResetAsync() lacks its own try/catch. If ForceResetAsync throws, the exception escapes the handler and crashes the caller; wrap the await in a nested try/catch and log any secondary failure before returning false.

Kody rule violation: Handle async operations with proper error handling

Prompt for LLM

File Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundEventProvider.cs:

Line 106:

Unhandled exception vulnerability exists in the catch block because `RabbitConnection.ForceResetAsync()` lacks its own try/catch. If `ForceResetAsync` throws, the exception escapes the handler and crashes the caller; wrap the await in a nested try/catch and log any secondary failure before returning false.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

// watchdog's retry would get the same connection back forever — reset it so the
// next Start builds a fresh one.
if (ex is RabbitMQ.Client.Exceptions.ChannelAllocationException)
await RabbitConnection.ForceResetAsync();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Unhandled exception risk in ForceResetAsync exposes the caller to unhandled network I/O failures propagating from the catch block with no context. Wrap the reset call in a dedicated try/catch, log the secondary exception with context (clientName, operation 'ForceReset'), and continue to return false.

Kody rule violation: Add try-catch blocks for external calls

Prompt for LLM

File Providers/Resgrid.Providers.Bus.Rabbit/RabbitInboundEventProvider.cs:

Line 106:

Unhandled exception risk in `ForceResetAsync` exposes the caller to unhandled network I/O failures propagating from the catch block with no context. Wrap the reset call in a dedicated try/catch, log the secondary exception with context (clientName, operation 'ForceReset'), and continue to return false.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

@ucswift

ucswift commented Aug 10, 2026

Copy link
Copy Markdown
Member Author

Approve

@github-actions github-actions 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.

This PR is approved.

@ucswift
ucswift merged commit a7e7984 into master Aug 10, 2026
15 of 18 checks passed
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.

3 participants