Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New rule reliability/maintainability: Do await incomplete Tasks before returning and implicitly running other code #85175

Closed
TimLovellSmith opened this issue Apr 21, 2023 · 4 comments

Comments

@TimLovellSmith
Copy link
Contributor

TimLovellSmith commented Apr 21, 2023

Approximate match on rule categories to put it in:

Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2260
Reliability: CA9998-CA9999, CA2000-CA2021
MicrosoftCodeAnalysisCorrectness: RS1000-RS1999
MicrosoftCodeAnalysisDesign: RS1000-RS1999
RoslynDiagnosticsMaintainability: RS0000-RS0999

Suggested rule name: await asynchronous methods when returning a Task directly.
Rationale: 'goes-out-of-scope' logic like 'using statement' may run sooner than you expect.

Describe the problem you are trying to solve

The following code is 'clearly a bug waiting to happen' to the eyes of some C# experts.

public Task GetHealthStatusAsync(CancellationToken cancellationToken = default)
{
    using IDisposable disposable = new System.IO.FileStream(); // implicit cleanup at end of scope
    return GetCdsProfileAsync(FromInstrumentationKey(_testIKey), cancellationToken); // return without await
} 

What we want to prevent, is people writing code with signature Task, that calls a Task method, without doing await, but just returning that Task directly, and then having automatic cleanup run because the 'synchronous' scope ends.

i.e. the using statement would dispose the object created even though the chained async call won't be completed yet.

This bug will be non-deterministic based on whether the chained async call actually completes immediately or not.

Describe suggestions on how to achieve the rule

Step 1: filter based on method signature. Rule only analyzes methods that return type Task or ValueTask, but do not use the 'async' keyword to implement an async state machine.
Step 2: filter based on method implementation. Rule only fires for methods that
a) return a Task object which isn't obviously going to complete immediately. Ones that are obviously always going to return a completed task include, 'Task.CompletedTask, Task.FromResult, Task.FromException', or have previously been provably completed using e.g. task.Wait(), task.Result.
b) have code that will run implicitly after the return statement. Because of using statements, finally clauses, or anything similar.

Recommended (automatable) fix:

Convert method to do async/await instead.

Additional context

With kudos to Paul Harrington for giving a talk mentioning this antipattern.

@TimLovellSmith TimLovellSmith changed the title New rule reliability: running code implicitly after returning a possibly incomplete Task is a common source of bugs New rule reliability: Do await incomplete Tasks before returning them and running implicit code Apr 21, 2023
@TimLovellSmith TimLovellSmith changed the title New rule reliability: Do await incomplete Tasks before returning them and running implicit code New rule reliability: Do await incomplete Tasks before returning them and implicitly running other code Apr 21, 2023
@TimLovellSmith TimLovellSmith changed the title New rule reliability: Do await incomplete Tasks before returning them and implicitly running other code New rule reliability: Do await incomplete Tasks before returning and implicitly running other code Apr 21, 2023
@TimLovellSmith TimLovellSmith changed the title New rule reliability: Do await incomplete Tasks before returning and implicitly running other code New rule reliability/maintainability: Do await incomplete Tasks before returning and implicitly running other code Apr 21, 2023
@mavasani mavasani transferred this issue from dotnet/roslyn-analyzers Apr 21, 2023
@ghost ghost added the untriaged New issue has not been triaged by the area owner label Apr 21, 2023
@ghost
Copy link

ghost commented Apr 21, 2023

Tagging subscribers to this area: @dotnet/area-system-threading-tasks
See info in area-owners.md if you want to be subscribed.

Issue Details

Approximate match on rule categories to put it in:

Usage: CA1801, CA1806, CA1816, CA2200-CA2209, CA2211-CA2260
Reliability: CA9998-CA9999, CA2000-CA2021
MicrosoftCodeAnalysisCorrectness: RS1000-RS1999
MicrosoftCodeAnalysisDesign: RS1000-RS1999
RoslynDiagnosticsMaintainability: RS0000-RS0999

Suggested rule name: await asynchronous methods when returning a Task directly.
Rationale: 'goes-out-of-scope' logic like 'using statement' may run sooner than you expect.

Describe the problem you are trying to solve

The following code is 'clearly a bug waiting to happen' to the eyes of some C# experts.

public Task GetHealthStatusAsync(CancellationToken cancellationToken = default)
{
    using IDisposable disposable = new System.IO.FileStream(); // implicit cleanup at end of scope
    return GetCdsProfileAsync(FromInstrumentationKey(_testIKey), cancellationToken); // return without await
} 

What we want to prevent, is people writing code with signature Task, that calls a Task method, without doing await, but just returning that Task directly, and then having automatic cleanup run because the 'synchronous' scope ends.

i.e. the using statement would dispose the object created even though the chained async call won't be completed yet.

This bug will be non-deterministic based on whether the chained async call actually completes immediately or not.

Describe suggestions on how to achieve the rule

Step 1: filter based on method signature. Rule only analyzes methods that return type Task or ValueTask, but do not use the 'async' keyword to implement an async state machine.
Step 2: filter based on method implementation. Rule only fires for methods that
a) return a Task object which isn't obviously going to complete immediately. Ones that are obviously always going to return a completed task include, 'Task.CompletedTask, Task.FromResult, Task.FromException', or have previously been provably completed using e.g. task.Wait(), task.Result.
b) have code that will run implicitly after the return statement. Because of using statements, finally clauses, or anything similar.

Recommended (automatable) fix:

Convert method to do async/await instead.

Additional context

With kudos to Paul Harrington for giving a talk mentioning this antipattern.

Author: TimLovellSmith
Assignees: -
Labels:

area-System.Threading.Tasks, untriaged

Milestone: -

@stephentoub
Copy link
Member

Thanks. How does this differ from #78394?

@TimLovellSmith
Copy link
Contributor Author

@stephentoub Looks like its the same proposal to me, just described differently, thanks for deduplicating!

@stephentoub
Copy link
Member

Duplicate of #78394

@stephentoub stephentoub marked this as a duplicate of #78394 Apr 21, 2023
@stephentoub stephentoub closed this as not planned Won't fix, can't repro, duplicate, stale Apr 21, 2023
@ghost ghost removed the untriaged New issue has not been triaged by the area owner label Apr 21, 2023
@ghost ghost locked as resolved and limited conversation to collaborators May 22, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

2 participants