-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Filter cancellation exceptions in generator driver #58843
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
Filter cancellation exceptions in generator driver #58843
Conversation
|
@dotnet/roslyn-compiler for review please. |
886503d to
0918664
Compare
| } | ||
| } | ||
| catch (Exception e) | ||
| catch (Exception e) when (ExceptionUtilities.IsNotCancelled(e, cancellationToken)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this missing one causing issues? Do we need this in 17.1?
src/Compilers/Core/Portable/InternalUtilities/ExceptionUtilities.cs
Outdated
Show resolved
Hide resolved
Make all uses take a token Pass the token through from parent wrapper operations.
|
Ping @dotnet/roslyn-compiler for reviews please :) |
| #pragma warning disable CS0618 // ReportIfNonFatalAndCatchUnlessCanceled is obsolete; tracked by https://github.com/dotnet/roslyn/issues/58375 | ||
| catch (Exception e) when (FatalError.ReportIfNonFatalAndCatchUnlessCanceled(e, cancellationToken)) | ||
| #pragma warning restore CS0618 // ReportIfNonFatalAndCatchUnlessCanceled is obsolete | ||
| catch (Exception e) when (!ExceptionUtilities.IsCurrentOperationBeingCancelled(e, cancellationToken)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I'd imagine Initialize can't ever throw a cancellation exception, right? So should the filter just be removed here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Changed
Code changed and brings new concerns.
|
|
||
| var cts = new CancellationTokenSource(); | ||
| var testGenerator = new CallbackGenerator( | ||
| onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver(tag: 0, callback: (a) => { if (a is AssignmentExpressionSyntax){ cts.Cancel(); cts.Token.ThrowIfCancellationRequested(); } })), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This cancellation step is capturing the CancellationTokenSource from the outer context and using it to both cancel and detect cancellation. Is there no way for a receiver to grab the CancellationToken passed into RunGeneratorsAndUpdateCompilation? If so then I would expect the ThrowIfCancellationRequested call to be on that token (make sure we thread it through).
If not how are syntax receivers expected to cancel?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
SyntaxReceivers do not have access to any cancellation tokens. All cancellation exceptions thrown by a syntax receiver are errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unsure what we are testing then here. If user code doesn't have access to the token then they can't exercise this code path. If our internal walk of the syntax throws based on the cancellation token passed to the generator then we shouldn't need a throw here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll adjust it to just throw a new cancellation exception to indicate its not expected to be coming from the driver. Honestly I think the set up is mostly just a copy paste from the execute test.
src/Compilers/CSharp/Test/Semantic/SourceGeneration/GeneratorDriverTests.cs
Outdated
Show resolved
Hide resolved
src/Compilers/Core/Portable/InternalUtilities/ExceptionUtilities.cs
Outdated
Show resolved
Hide resolved
| #pragma warning disable CS0618 // ReportIfNonFatalAndCatchUnlessCanceled is obsolete; tracked by https://github.com/dotnet/roslyn/issues/58375 | ||
| catch (Exception e) when (FatalError.ReportIfNonFatalAndCatchUnlessCanceled(e, token)) | ||
| #pragma warning restore CS0618 // ReportIfNonFatalAndCatchUnlessCanceled is obsolete | ||
| catch (Exception e) when (!ExceptionUtilities.IsCurrentOperationBeingCancelled(e, token)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❔ Are we still going to get NFW reporting if/when these occur inside the IDE
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a separate change we're going to add an API to the GeneratorDriver to let us hook and report these, which is similar to how we do this for analyzers. Having this via an internal only API that's mixed in for fault reporting is a bit funky.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@chsienki Do we have a tracking bug to actually do that?
| /// <param name="cancellationToken">Checked to see if the provided token was cancelled.</param> | ||
| /// <returns><see langword="true"/> if the exception was an <see cref="OperationCanceledException" /> and the token was canceled.</returns> | ||
| internal static bool IsCurrentOperationBeingCancelled(Exception exception, CancellationToken cancellationToken) | ||
| => exception is OperationCanceledException oce && cancellationToken.IsCancellationRequested; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be true when exception and cancellationToken are unrelated instances. Is there a reason you aren't checking && oce.CancellationToken == cancellationToken here?
Effectively I would expect it to be written as such (do the prop check before type check as it's cheaper)
| => exception is OperationCanceledException oce && cancellationToken.IsCancellationRequested; | |
| => | |
| cancellationToken.IsCancellationRequested && | |
| exception is OperationCanceledException oce && | |
| oce.CancellationToken == cancellationToken; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
➡️ The omission of cancellation identity check is intentional. See #58843 (comment).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| => exception is OperationCanceledException oce && cancellationToken.IsCancellationRequested; | |
| => exception is OperationCanceledException && cancellationToken.IsCancellationRequested; |
I think I prefer this better as then there would be no allocation of a new object (as a copy of exception casted to that exception type) needlessly just for this check (increasing performance).
|
|
||
| var cts = new CancellationTokenSource(); | ||
| var testGenerator = new CallbackGenerator( | ||
| onInit: (i) => i.RegisterForSyntaxNotifications(() => new TestSyntaxReceiver(tag: 0, callback: (a) => { if (a is AssignmentExpressionSyntax){ cts.Cancel(); cts.Token.ThrowIfCancellationRequested(); } })), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unsure what we are testing then here. If user code doesn't have access to the token then they can't exercise this code path. If our internal walk of the syntax throws based on the cancellation token passed to the generator then we shouldn't need a throw here.
Original cancellation detection behavior was restored
src/Compilers/CSharp/Test/Semantic/SourceGeneration/SyntaxAwareGeneratorTests.cs
Outdated
Show resolved
Hide resolved
…eGeneratorTests.cs Co-authored-by: Jared Parsons <[email protected]>
Pull Request is not mergeable
| Assert.Single(results.Results); | ||
| Assert.IsType<OperationCanceledException>(results.Results[0].Exception); | ||
| Assert.Equal("Simulated cancellation from external source", results.Results[0].Exception!.Message); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| Assert.Single(results.Results); | |
| Assert.IsType<OperationCanceledException>(results.Results[0].Exception); | |
| Assert.Equal("Simulated cancellation from external source", results.Results[0].Exception!.Message); | |
| var result = Assert.Single(results.Results); | |
| Assert.IsType<OperationCanceledException>(result.Exception); | |
| Assert.Equal("Simulated cancellation from external source", result.Exception!.Message); |
| #pragma warning disable CS0618 // ReportIfNonFatalAndCatchUnlessCanceled is obsolete; tracked by https://github.com/dotnet/roslyn/issues/58375 | ||
| catch (Exception e) when (FatalError.ReportIfNonFatalAndCatchUnlessCanceled(e, token)) | ||
| #pragma warning restore CS0618 // ReportIfNonFatalAndCatchUnlessCanceled is obsolete | ||
| catch (Exception e) when (!ExceptionUtilities.IsCurrentOperationBeingCancelled(e, token)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@chsienki Do we have a tracking bug to actually do that?
|
@jasonmalinowski Filed #59141 to track |
…ess-instance-members * upstream/main: (669 commits) Fix 'hasStaticConstructor' check in MethodCompiler (dotnet#59116) Update dependencies from https://github.com/dotnet/arcade build 20220127.8 (dotnet#59134) Update StreamJsonRpc (dotnet#59073) Resources Filter cancellation exceptions in generator driver (dotnet#58843) Revert "Remove dependency on EditorFeatures from Remote.ServiceHub project (dotnet#59059)" Strings Inline Convert to switch expression Explicitly test empty string case. Fix comment Simplify test code Run all Add tests Delete test generator Add support for specifying server in tests Remove options review feedback Format document after each provider (dotnet#59091) [main] Update dependencies from dotnet/arcade (dotnet#59015) ...
Previously we were using the fatal error handler to do filtering which has been obsoleted via #58094.
Also fixes #58290 by adding a missing filter to the syntax receiver shim.