Skip to content

Sonar Bucket C: reliability bugs + dispose-pattern (S1751/S4586/S3881)#189

Merged
blehnen merged 2 commits into
masterfrom
sonar-bucketC-bugs
Jul 8, 2026
Merged

Sonar Bucket C: reliability bugs + dispose-pattern (S1751/S4586/S3881)#189
blehnen merged 2 commits into
masterfrom
sonar-bucketC-bugs

Conversation

@blehnen

@blehnen blehnen commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Fourth SonarCloud sweep (Bucket C) — the correctness/reliability findings, following #184, #186 (Bucket A), #188 (Bucket D).

Correctness bugs (with tests)

  • S4586SchedulerMessageHandler.HandleAsync returned a null Task on its not-handling branch. A null Task NREs when the consumer awaits it (ProcessMessageAsync), landing a stopping-time message in error handling instead of a clean rollback. Now returns a canceled Task, which flows through the existing OperationCanceledException rollback path. (The branch is currently unreachable — ShouldHandle throws or returns true — but the null return was a latent hazard.) Added a regression test asserting a stopping worker surfaces OperationCanceledException.
  • S1751Memory ReceiveMessage.GetMessage wrapped straight-line code in a while(true) that always returned on iteration 1 → unwrapped (no behavior change).
  • S1751Interception.GetInterceptor used a foreach that returned on the first match → FirstOrDefault ?? GetOrAdd.
  • Dead guards — removed 3 Guard.NotNull(CancellationToken) calls (Find*Query); guarding a non-nullable struct against null is a silent no-op (surfaced while evaluating S2955).

S2955 itself (Guard.NotNull value == null on unconstrained T) is left as-is — the current behavior is correct (no-op for value types), and the compliant where T : class fix cascades into ~9 relational retry-decorator files with SimpleInjector open-generic risk not worth a single low-severity finding.

Dispose pattern — S3881 (25 of 27)

Sealed 25 leaf IDisposable classes so they conform (a sealed class needs only a simple Dispose()), plus the cleanups sealing requires: de-virtualize now-pointless Dispose()/Dispose(bool), forward disposing in the ConsumerQueue* overrides (was base.Dispose(true)), demote pointless protected ThrowIfDisposed to private.

Left unsealed (2 still flagged): WaitForEventOrCancel and WorkerBase are base classes — their S3881 fix is a virtual-Dispose(bool) cascade through the wait/worker lifecycle, disproportionately risky for a hygiene rule.

⚠️ API note

Sealing includes 11 public classes (ConsumerQueue, DataStorage, MetricsNet, DashboardApiClient, LiteDbConnection, …). This is source-breaking for external code that subclasses them (rare — they're used via interfaces). Explicitly chosen. No changelog/version bump (matches the #186/#188 mechanical-cleanup precedent).

Verification

  • Full solution builds clean, 0 warnings / 0 errors (net10.0 + net8.0)
  • All unit suites green (core 915 incl. new scheduler test, Memory, LiteDb, SQLite, SqlServer, PostgreSQL, RelationalDatabase)
  • Memory integration consumer/producer lifecycle green (exercises the sealed worker/queue/dispose paths)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved cancellation handling so message processing now returns a canceled task/exception instead of a null result in stopping scenarios.
    • Fixed disposal behavior in consumer and worker components to behave more reliably during shutdown.
    • Updated in-memory message retrieval to stop immediately when cancellation is already requested.
  • Tests

    • Added coverage for worker-stopping cancellation behavior.

blehnen and others added 2 commits July 8, 2026 13:12
- S4586: SchedulerMessageHandler.HandleAsync returned a null Task on the
  (currently unreachable) not-handling branch; a null Task would NRE when the
  consumer awaits it (ProcessMessageAsync), landing the message in error
  handling instead of a clean rollback. Return a canceled Task, which flows
  through the existing OperationCanceledException rollback path. Added a
  regression test asserting the stopping-worker path surfaces
  OperationCanceledException.
- S1751: Memory ReceiveMessage.GetMessage wrapped straight-line logic in a
  while(true) that always returned on the first iteration — unwrapped (no
  behavior change).
- S1751: Interception.GetInterceptor used a foreach that returned on the first
  match — replaced with FirstOrDefault ?? GetOrAdd.
- Removed 3 dead Guard.NotNull(CancellationToken) calls (Find*Query) — guarding
  a non-nullable struct against null is a silent no-op.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Seal 25 leaf classes flagged by S3881 so they conform to the dispose pattern
(a sealed class needs only a simple Dispose()), and clean up the dispose
members exposed by sealing:
- de-virtualize Dispose()/Dispose(bool) that became pointless in sealed types
- forward 'disposing' in the ConsumerQueue* overrides (was base.Dispose(true))
- demote now-pointless protected ThrowIfDisposed to private

Left unsealed (still flagged): WaitForEventOrCancel and WorkerBase are base
classes; their S3881 fix is a virtual-Dispose(bool) cascade through the
wait/worker lifecycle, out of scope for a mechanical hygiene pass.

Behavior-preserving: full solution builds clean (net10+net8), all unit suites
green, Memory integration consumer/producer lifecycle green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ceff5476-1a0d-40e6-80f8-7225d9986dff

📥 Commits

Reviewing files that changed from the base of the PR and between da00726 and f31bd27.

📒 Files selected for processing (29)
  • Source/DotNetWorkQueue.Dashboard.Client/DashboardApiClient.cs
  • Source/DotNetWorkQueue.Dashboard.Client/DashboardConsumerClient.cs
  • Source/DotNetWorkQueue.Tests/TaskScheduling/MessageHandlerTests.cs
  • Source/DotNetWorkQueue.Transport.LiteDB/Basic/LiteDbConnection.cs
  • Source/DotNetWorkQueue.Transport.SQLite/Basic/SqLiteHoldConnection.cs
  • Source/DotNetWorkQueue.Transport.Shared/Basic/Query/FindErrorMessagesToDeleteQuery.cs
  • Source/DotNetWorkQueue.Transport.Shared/Basic/Query/FindExpiredMessagesToDeleteQuery.cs
  • Source/DotNetWorkQueue.Transport.Shared/Basic/Query/FindMessagesToResetByHeartBeatQuery.cs
  • Source/DotNetWorkQueue/Factory/ContainerFactory.cs
  • Source/DotNetWorkQueue/Interceptors/Interception.cs
  • Source/DotNetWorkQueue/Messages/MessageContext.cs
  • Source/DotNetWorkQueue/Metrics/Decorator/IMessageMethodHandlingDecorator.cs
  • Source/DotNetWorkQueue/Metrics/Decorator/IQueueCreationDecorator.cs
  • Source/DotNetWorkQueue/Metrics/Net/MetricsContextNet.cs
  • Source/DotNetWorkQueue/Metrics/Net/MetricsNet.cs
  • Source/DotNetWorkQueue/Metrics/Net/TimerContextNet.cs
  • Source/DotNetWorkQueue/Metrics/Net/TimerNet.cs
  • Source/DotNetWorkQueue/Metrics/NoOp/MetricsNoOp.cs
  • Source/DotNetWorkQueue/Queue/ConsumerMethodQueue.cs
  • Source/DotNetWorkQueue/Queue/ConsumerQueue.cs
  • Source/DotNetWorkQueue/Queue/ConsumerQueueAsync.cs
  • Source/DotNetWorkQueue/Queue/HeartBeatScheduler.cs
  • Source/DotNetWorkQueue/Queue/WorkerWaitForEventOrCancel.cs
  • Source/DotNetWorkQueue/TaskScheduling/SchedulerMessageHandler.cs
  • Source/DotNetWorkQueue/TaskScheduling/WaitForEventOrCancelThreadPool.cs
  • Source/DotNetWorkQueue/Trace/Decorator/MessageMethodHandlingDecorator.cs
  • Source/DotNetWorkQueue/Trace/Decorator/ProducerMethodJobQueueDecorator.cs
  • Source/DotNetWorkQueue/Transport/Memory/Basic/DataStorage.cs
  • Source/DotNetWorkQueue/Transport/Memory/Basic/Message/ReceiveMessage.cs
💤 Files with no reviewable changes (3)
  • Source/DotNetWorkQueue.Transport.Shared/Basic/Query/FindMessagesToResetByHeartBeatQuery.cs
  • Source/DotNetWorkQueue.Transport.Shared/Basic/Query/FindErrorMessagesToDeleteQuery.cs
  • Source/DotNetWorkQueue.Transport.Shared/Basic/Query/FindExpiredMessagesToDeleteQuery.cs

📝 Walkthrough

Walkthrough

This PR marks many public and internal classes across the codebase as sealed to prevent inheritance, tightens several Dispose/ThrowIfDisposed methods to private, changes Dispose(bool) calls to forward the disposing argument instead of hardcoding true, changes SchedulerMessageHandler.HandleAsync to return a canceled Task instead of null when the worker is stopping (with a new test), removes redundant Guard.NotNull checks in three query constructors, simplifies an interceptor lookup with FirstOrDefault, and rewrites ReceiveMessage.GetMessage from a retry loop to a single-pass check.

Changes

Sealing, Disposal, and Cancellation Fixes

Layer / File(s) Summary
Worker-stopping cancellation returns canceled task
Source/DotNetWorkQueue/TaskScheduling/SchedulerMessageHandler.cs, Source/DotNetWorkQueue.Tests/TaskScheduling/MessageHandlerTests.cs
HandleAsync now returns Task.FromCanceled(new CancellationToken(true)) instead of null when ShouldHandle is false; a new test verifies OperationCanceledException is thrown when the worker is stopping and rollback is supported.
Disposal contract tightening
Source/DotNetWorkQueue.Transport.SQLite/Basic/SqLiteHoldConnection.cs, Source/DotNetWorkQueue/Queue/WorkerWaitForEventOrCancel.cs, Source/DotNetWorkQueue/TaskScheduling/WaitForEventOrCancelThreadPool.cs, Source/DotNetWorkQueue/Queue/ConsumerMethodQueue.cs, Source/DotNetWorkQueue/Queue/ConsumerQueue.cs, Source/DotNetWorkQueue/Queue/ConsumerQueueAsync.cs
Classes are sealed while Dispose/ThrowIfDisposed helpers are narrowed to private; Dispose(bool disposing) implementations now forward the disposing argument to base.Dispose(disposing) instead of always passing true.
Sealing remaining classes
Source/DotNetWorkQueue.Dashboard.Client/*, Source/DotNetWorkQueue.Transport.LiteDB/Basic/LiteDbConnection.cs, Source/DotNetWorkQueue/Factory/ContainerFactory.cs, Source/DotNetWorkQueue/Messages/MessageContext.cs, Source/DotNetWorkQueue/Metrics/..., Source/DotNetWorkQueue/Queue/HeartBeatScheduler.cs, Source/DotNetWorkQueue/Trace/Decorator/*, Source/DotNetWorkQueue/Transport/Memory/Basic/DataStorage.cs
Numerous unrelated classes are marked sealed to prevent further inheritance, with no other logic changes.
Query constructor guard removal
Source/DotNetWorkQueue.Transport.Shared/Basic/Query/FindErrorMessagesToDeleteQuery.cs, ...FindExpiredMessagesToDeleteQuery.cs, ...FindMessagesToResetByHeartBeatQuery.cs
The DotNetWorkQueue.Validation using directive and Guard.NotNull validation are removed from three query constructors; the cancellation token is assigned directly to Cancellation.
Interceptor lookup and message receive simplification
Source/DotNetWorkQueue/Interceptors/Interception.cs, Source/DotNetWorkQueue/Transport/Memory/Basic/Message/ReceiveMessage.cs
GetInterceptor uses FirstOrDefault in place of a Where/foreach loop; GetMessage is rewritten from a retry loop to a single cancellation check plus one GetNextMessage call.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

  • blehnen/DotNetWorkQueue#186: Same disposed-guard and Dispose contract adjustments in Queue/WorkerWaitForEventOrCancel.cs and TaskScheduling/WaitForEventOrCancelThreadPool.cs.

Poem

Sealed up tight, no more subclass sprawl,
A rabbit hops through disposal calls,
Cancel tokens now do their true job right,
No null surprises hopping through the night. 🐇
Guards removed, loops made lean and true,
This warren's code is fresh and new!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the reliability and dispose-pattern fixes covered by the PR.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

@sonarqubecloud

sonarqubecloud Bot commented Jul 8, 2026

Copy link
Copy Markdown

@codecov

codecov Bot commented Jul 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 85.71429% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 87.54%. Comparing base (44c10f4) to head (f31bd27).
⚠️ Report is 4 commits behind head on master.

Files with missing lines Patch % Lines
...ource/DotNetWorkQueue/Interceptors/Interception.cs 50.00% 0 Missing and 1 partial ⚠️
...orkQueue/TaskScheduling/SchedulerMessageHandler.cs 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #189      +/-   ##
==========================================
+ Coverage   87.53%   87.54%   +0.01%     
==========================================
  Files        1023     1023              
  Lines       33621    33614       -7     
  Branches     2803     2803              
==========================================
  Hits        29429    29429              
+ Misses       3329     3324       -5     
+ Partials      863      861       -2     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@blehnen blehnen merged commit 4484fc6 into master Jul 8, 2026
7 checks passed
@blehnen blehnen deleted the sonar-bucketC-bugs branch July 8, 2026 21:20
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