Skip to content

Upgrade Particular Analyzers and triage new diagnostics - #5724

Merged
johnsimons merged 1 commit into
masterfrom
john/cancel_part0
Aug 11, 2026
Merged

johnsimons merged 1 commit into
masterfrom
john/cancel_part0

Conversation

@johnsimons

@johnsimons johnsimons commented Aug 10, 2026 •

Copy link
Copy Markdown
Member

Why

Custom.Build.props has pinned ParticularAnalyzersVersion to 0.9.0 since 2021, with the comment
"Remove after upgrading to NServiceBus 8.x." We're on NServiceBus 10.2.8. Version 0.9.0 predates
Particular.Analyzers' cancellation-token rules entirely, so the analyzer has been silently unable
to catch missing/unpropagated CancellationToken parameters for years. This is the first step in
threading cancellation through the platform: turn the guardrail back on before touching any call sites.

What this does

  • Removes the version pin. ParticularAnalyzersVersion now resolves 2.1.4 from Directory.Build.props.
  • Fixes the handful of diagnostics that are Error-severity by default and would otherwise break every
    build immediately:
    • PS0023 (DateTime.Now vs .UtcNow) in ActiveLicense.cs — also a latent bug, since the trial-window
      tests already assume UTC.
    • PS0025/PS0027 in ServiceControl.Config (WPF): async void and reference-equality collection
      warnings. Five are legitimate WPF idioms (ICommand.Execute, Application.Startup, view/thread caches)
      and are suppressed at the site with justification. The sixth, ListInstancesViewModel.AddAndRemoveInstances,
      is a genuine bug — it returns at its first await, so RefreshInstances publishes before removals finish —
      and is suppressed with a TODO instead, since fixing it needs the constructor call site restructured.
  • Fixes the remaining non-cancellation warnings the upgrade surfaces: a duplicated structured-logging token
    in ScatterGatherApi, four DateTime→DateTimeOffset casts that are provably UTC, and a rename
    (IHostApplicationBuilderExtensions → HostApplicationBuilderExtensions, unreferenced by name).
  • Adds a .editorconfig debt block to every project still carrying cancellation-rule warnings (62 projects),
    listing only the rules that project actually violates, so each block doubles as that project's remaining
    work. This is a ratchet: a rule can only be deleted from a block once the project has zero violations of it.

What this doesn't do

No CancellationToken propagation yet. That starts in the next PR. This is purely: turn the analyzer back on,
fix what's Error-severity, and record everything else as tracked debt instead of leaving it invisible.

@johnsimons johnsimons self-assigned this Aug 10, 2026
@johnsimons
johnsimons requested a review from rbev August 10, 2026 23:24
using (StartExecuting())
{
OnExecuting();
await ExecuteAsync(obj);

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.

Would it be better to just discard the result to avoid the pragma?
There's no code to resume the end result (abandoned task) is the same

Suggested change
_ = ExecuteAsync(obj);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Not quite equivalent, unfortunately. The await is inside using (StartExecuting()), and that disposable is what flips isExecuting back to false and re-raises CanExecuteChanged. If we discard the task, the using block exits synchronously and the command re-enables itself while the work is still running, so we lose the double-click guard. There's also an exception-handling difference: with async void an unhandled exception is rethrown on the WPF dispatcher, whereas a discarded task becomes a silently unobserved task exception.

NotifyOfPropertyChange(nameof(Instances));
}

// TODO: this is a genuine async void, not an event handler. Because it returns at the first

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.

Similar to my other comment, could you make this async Task and discard the task at the caller?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Agreed it should be async Task, but discarding at the call sites would keep the bug the TODO describes. HandleAsync(RefreshInstances) needs to genuinely await it, otherwise PostRefreshInstances is published before the removals have finished, which is the exact ordering that method's own remarks say must not happen.

The blocker is the other call site in the constructor. The clean fix is to move that call into RxScreen.OnInitialize(), which is awaited properly, and then await at both sites. That's a real behaviour change, so I'd rather not fold it into an analyzer-upgrade PR. I will do a standalone PR to fix it.

using NServiceBus.CustomChecks;
using NServiceBus.Transport.IBMMQ;

#pragma warning disable PS0024 // The I is from IBM, matching NServiceBus.Transport.IBMMQ

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.

Is there a way you can define permitted Acronyms instead of using the pragma?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I looked into it and there's no such option.

Unpins the Particular.Analyzers version, enabling newer rules across the solution.
New warnings introduced by the upgrade are suppressed in `.editorconfig` files
as technical debt, with a plan to address them incrementally.

Includes initial fixes for `DateTime` to `DateTimeOffset` usage, and adds
pragmas for intentional `async void` and reference equality usages identified
by the analyzers.
@johnsimons
johnsimons merged commit 9056a49 into master Aug 11, 2026
97 of 98 checks passed
@johnsimons
johnsimons deleted the john/cancel_part0 branch August 11, 2026 10:37
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.

2 participants