Skip to content

Add ExcludeFromCancellationTokenAnalysisAttribute to opt out of the CancellationToken rules - #1364

Merged
meziantou merged 2 commits into
mainfrom
feature/ma0040-cancellation-token-suppress-58f1b8
Aug 30, 2026
Merged

Add ExcludeFromCancellationTokenAnalysisAttribute to opt out of the CancellationToken rules#1364
meziantou merged 2 commits into
mainfrom
feature/ma0040-cancellation-token-suppress-58f1b8

Conversation

@meziantou

Copy link
Copy Markdown
Owner

Why

MA0040 (and its siblings MA0032/MA0079/MA0080) reports every call to a method that has a CancellationToken overload. Some methods are legitimately called without a token — cleanup and shutdown paths, telemetry flushes, fire-and-forget writes. Until now the only escapes were #pragma warning disable at every call site, or lowering the severity for a whole file or folder. Neither expresses "this API is fine to call without a token" once, project-wide.

What

A new ExcludeFromCancellationTokenAnalysisAttribute in the Meziantou.Analyzer.Annotations package marks a called method as excluded. No MA0032/MA0040/MA0079/MA0080 diagnostic is reported for the calls to it.

The methods of the current project can be annotated directly:

class Sample
{
    [Meziantou.Analyzer.Annotations.ExcludeFromCancellationTokenAnalysis]
    public static Task FlushAsync() => throw null;
    public static Task FlushAsync(CancellationToken cancellationToken) => throw null;
}

await Sample.FlushAsync(); // No MA0040 diagnostic

The methods of another assembly are annotated at the assembly level, using their XML documentation id or their containing type and name. The parameter types are optional and restrict the exclusion to a single overload:

[assembly: ExcludeFromCancellationTokenAnalysis("M:System.Threading.Channels.ChannelWriter`1.WriteAsync(`0,System.Threading.CancellationToken)")]
[assembly: ExcludeFromCancellationTokenAnalysis(typeof(Sample), "FlushAsync")]
[assembly: ExcludeFromCancellationTokenAnalysis(typeof(Sample), "FlushAsync", typeof(CancellationToken))]

This mirrors the existing ExcludeFromBlockingCallAnalysisAttribute. Its assembly-attribute resolution is extracted from DoNotUseBlockingCallInAsyncContextAnalyzer into a new AnnotationExclusions helper, now shared by both analyzers.

Notes for the reviewer

  • A method matches by its symbol, its OriginalDefinition and its ReducedFrom, so generic methods and extension methods called with the instance syntax are excluded too.
  • The lookup runs only once the analyzer is about to report a diagnostic (after the overload check), and its result is cached per compilation, so the common path is untouched.
  • Like every attribute of the package, it is [Conditional("MEZIANTOU_ANALYZER_ANNOTATIONS")]: the declaration-site form only crosses assembly boundaries when the declaring project defines that symbol. The assembly-level form is the documented answer for third-party APIs.
  • Meziantou.Analyzer.Annotations is bumped to 1.8.0, and its README plus the four rule docs are updated.
  • First commit is unrelated: tests/Meziantou.Analyzer.Test/Helpers/ProjectBuilder.Validation.cs did not compile (CA1859, then CA1836 on AssertNoAnalyzerException). It is fixed separately so it can be cherry-picked or dropped.

Verification

  • 8 new tests in UseAnOverloadThatHasCancellationTokenAnalyzerTests covering both attribute forms, the parameter-types overload filter, MA0032 with no token in scope, await foreach, generic and extension methods, and negative controls.
  • Full suite across all five Roslyn versions: 19012 passed, 0 failed.
  • dotnet run --project src/DocumentationGenerator exits 0.

`AssertNoAnalyzerException` takes the `ConcurrentBag` collected during
the analysis as an `IReadOnlyCollection<T>`, which CA1859 reports as an
error, and `ConcurrentBag.Count` walks the whole bag, which CA1836
reports once the parameter is typed as the concrete type.

The test projects did not compile before this commit.
…Attribute

Some methods are fine to call without a `CancellationToken`, even when
they have an overload that takes one and a token is available in the
scope: cleanup and shutdown paths, telemetry flushes, fire-and-forget
writes. Until now the only ways to silence the diagnostic were
`#pragma warning disable` at every call site, or lowering the severity
for a whole file or folder.

The new `ExcludeFromCancellationTokenAnalysisAttribute` of the
`Meziantou.Analyzer.Annotations` package marks a called method as valid
to call without a token. No MA0032/MA0040/MA0079/MA0080 diagnostic is
reported for the calls to an excluded method.

The methods of the current project can be annotated directly, and the
methods of another assembly at the assembly level, using their XML
documentation id or their containing type and name, optionally with the
parameter types to restrict the exclusion to a single overload. This
mirrors `ExcludeFromBlockingCallAnalysisAttribute`, whose assembly
attribute resolution is extracted to the new `AnnotationExclusions`
helper and shared by both analyzers.

A method matches by its symbol, its original definition, and its
unreduced form, so generic methods and extension methods called with the
instance syntax are excluded too. The lookup runs only once the analyzer
is about to report a diagnostic, and its result is cached per
compilation.
@meziantou
meziantou enabled auto-merge (squash) August 30, 2026 19:43
@meziantou
meziantou merged commit b20a548 into main Aug 30, 2026
13 checks passed
@meziantou
meziantou deleted the feature/ma0040-cancellation-token-suppress-58f1b8 branch August 30, 2026 19:43
This was referenced Aug 30, 2026
This was referenced Aug 31, 2026
meziantou added a commit that referenced this pull request Aug 31, 2026
main added ExcludeFromCancellationTokenAnalysisAttribute (#1364) while this
branch replaced ProjectBuilder with the Roslyn SDK testing harness.

- Keep the deletion of ProjectBuilder.Validation.cs: main only adjusted the
  analyzer exception assert of a file this branch removes with the old harness.
- Convert the eight ExcludedMethod tests main added to the new harness, so the
  new attribute keeps its coverage.
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