Skip to content

Consistently apply optional cancellation tokens - #5725

Merged
johnsimons merged 1 commit into
john/cancel_part0from
john/cancel_part1
Aug 11, 2026
Merged

johnsimons merged 1 commit into
john/cancel_part0from
john/cancel_part1

Conversation

@johnsimons

Copy link
Copy Markdown
Member

Why

Second step in threading CancellationToken through the platform: the five leaf libraries every host
project depends on (ServiceControl.Infrastructure, ServiceControl.Api, ServiceControl.Hosting,
ServiceControl.DomainEvents, ServiceControl.Infrastructure.Metrics). These sit underneath
ServiceControl, ServiceControl.Audit, ServiceControl.Monitoring and the transports/licensing
projects in the reference graph, so getting their signatures settled first means later work is a
one-line change at each call site instead of a redesign.

What this does

Standardizes CancellationToken parameters to the PS0003/PS0004/PS0017 convention already
enforced elsewhere in the solution: non-private members take CancellationToken cancellationToken = default,
private ones take it required, named cancellationToken everywhere (not token).

Retires 28 of 30 tracked diagnostics; deletes the debt block from four .editorconfig files outright
(Api, Hosting, DomainEvents, Infrastructure.Metrics) and narrows Infrastructure's to a single file.

Two categories of exception:

  • Pragmas where the token can't be added. ServiceControl.Hosting's PermissionPolicyProvider and
    RolesClaimsTransformation implement ASP.NET Core interfaces (IAuthorizationPolicyProvider,
    IClaimsTransformation) that declare no CancellationToken parameter. PS0018 is unsatisfiable there;
    suppressed at the site with justification, not fixed.
  • One deferred file. MessageActionAuditLogExtensions.AuditedOperation takes a Func<Task> callback
    that needs to become Func<CancellationToken, Task>, which touches the lambda at a dozen controller call
    sites that don't have a token yet. Rather than thread CancellationToken.None through as a placeholder,
    it's deferred to the controllers PR; ServiceControl.Infrastructure/.editorconfig is narrowed to
    [Auth/MessageActionAuditLogExtensions.cs] to track just that.

Bugs fixed along the way

  • MetricsReporterHostedService.StopAsync caught OperationCanceledException on its own token, but
    MetricsReporter.Stop() took no token, so nothing could ever throw one — the catch was dead code, and
    shutdown was effectively unbounded. Stop now takes a token and awaits the reporter task via WaitAsync.
  • DomainEvents.Raise logged shutdown-triggered cancellation as "Unexpected error publishing domain event"
    at Error level. It now rethrows OperationCanceledException before the generic handler, so a normal
    shutdown doesn't read as an application error.

Notes for reviewers

  • A couple of PS0020 catch filters read cancellationTokenSource.IsCancellationRequested, which the
    analyzer doesn't recognise (it wants the token, not the source). Fixed as
    .Token.IsCancellationRequested, except in AsyncTimer.Stop where the source is already disposed by
    that point — there the filter uses the caller's token instead, which is also the semantically correct
    one since the timer already swallows its own cancellation internally.
  • Watchdog's startup loop legitimately passes two tokens into the same try (shutdown, plus a linked
    15s startup timeout), so PS0021 is suppressed there with an explanation rather than restructured.

@johnsimons
johnsimons requested a review from rbev August 10, 2026 23:27
@johnsimons johnsimons self-assigned this Aug 10, 2026
This change addresses the cancellation analyzer debt by standardizing `CancellationToken` parameter naming to `cancellationToken` and making them optional with `= default` where appropriate. This allows for the removal of cancellation-related `Particular.Analyzers` suppressions from several `.editorconfig` files. Pragmas are introduced for cases where implementing existing interfaces prevents adding cancellation tokens.
@johnsimons
johnsimons merged commit 9056a49 into master Aug 11, 2026
98 of 99 checks passed
@johnsimons
johnsimons deleted the john/cancel_part1 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