-
Notifications
You must be signed in to change notification settings - Fork 198
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
Fix incrementalism when suppressed: #9717
Conversation
- Previously when going from unsuppressed -> suppressed we cleared certan caches - This meant that when going from suppressed -> unsuppressed we were always re compiling every razor file - Now instead, we produce nothing *until* the generator is unsuppressed, after which we just say that whatever is in the caches is up to date if we're suppressed. - This ensures no downstream processing takes place when the generator is suppressed, but there is still data to incrementally update when unsuppressed - We still remove the generated files at the last step when suppressed, so there is no output, but don't have to recompile anything to get the output back again.
Baby steps. Thank you for your efforts out there @chsienki & team. 🙏 |
src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/IncrementalValueProviderExtensions.cs
Outdated
Show resolved
Hide resolved
Assert.Empty(eventListener.Events); | ||
|
||
// Now enable the generator and confirm we get the expected output | ||
driver = SetSuppressionState(false); |
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.
From the linked issue I understand this happens during hot reload. Can you share more details when do we suppress and unsuppress the generator there?
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.
By default, in Roslyn the suppression state flag is set. If you never do a hot reload it stays that way, and so the generator never runs. When you do a hot reload/EnC, it modifies the workspace to remove the flag, meaning the generator actually runs.
I had thought that because EnC forks the workspace it stays that way, and never turns it back off again, but it does seem to (hence this bug). So in the hot reload workspace the flag can essentially toggle on and off arbitrarily. This PR makes sure that when that happens we keep the caches full so the next 'on' phase doesn't have to re-create everything from scratch.
@dotnet/razor-compiler for review please |
src/Compiler/Microsoft.NET.Sdk.Razor.SourceGenerators/IncrementalValueProviderExtensions.cs
Outdated
Show resolved
Hide resolved
|
||
var razorSourceGeneratorOptions = analyzerConfigOptions | ||
.Combine(parseOptions) | ||
.Combine(isGeneratorSuppressed) | ||
.SuppressIfNeeded(isGeneratorSuppressed) |
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.
So I imagine this can happen?
- The generator is not suppressed, the inputs (
analyzerConfigOptions
orparseOptions
) are changing and hence we are recomputing the output (razorSourceGeneratorOptions
). All is fine. - The generator is suppressed.
- Some inputs change - but we report the output is up to date since the suppression flag is true.
- The suppression flag changes to false but inputs don't change. Now we still report the output is up to date - but it's not since the options changed in the previous step and we haven't recomputed the output. Seems bad.
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 scenario is handled (we test is here: https://github.com/dotnet/razor/pull/9717/files#diff-21bee42801ceadff30fba484ddb46162fb1af8d819b27b06e2c1ebcd612f4feeR2619)
Some of the inputs all have EmptyOrCachedWhen
on them: that means when the suppression flag changes, we'll re-evaluate them, and realize that one of the items is different.
The other inputs are all used in conjunction with SuppressIfNeeded
that ensures they will also be re-evaluated when that value changes.
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 amended the test to cover the compilation changing too.
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 test adds a document, does that change the inputs here (analyzerConfigOptions
or parseOptions
)? Because I'm talking specifically about SuppressIfNeeded
which seems it won't re-run if the inputs change while the suppression was on - since the lambda comparer inside is returning true while suppressed, then when unsuppressed, it compares the old with the new - but the old could have changed during the suppression, hence the old would be equal to new, but could be different from the state when the suppression was off previously. For example:
- Suppression = off.
- parseOptions = A -> razorSourceGeneratorOptions = A
- parseOptions = B -> razorSourceGeneratorOptions = B
- Suppression = on.
- parseOptions = A -> razorSourceGeneratorOptions = B (since
SuppressIfNeeded
reports "up to date" due to suppression) - Suppression = off -> razorSourceGeneratorOptions = B (since
SuppressIfNeeded
compares the previous value of parseOptions = A with the current value A hence "up to date") - seems wrong, should be changed to A.
Isn't that what could happen?
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.
No, that should still work. Because we combine with the configOptions (which has to change to change suppression) ComputeRazorSourceGeneratorOptions
still runs as needed and updates the options.
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.
Added more to the test to cover that scenario.
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.
Ok, thanks. Although it sounds like the SuppressIfNeeded works only due to the pipeline being constructed in a specific way (eg, here it works only because one of the inputs contains the suppression check). Might be worthwhile to add a warning to the doc comment of SuppressIfNeeded to make sure it's not used incorrectly in the future (if it can really behave as I describe if used on different inputs for example).
…talValueProviderExtensions.cs Co-authored-by: Jan Jones <jan.jones.cz@gmail.com>
Helps (at least for second edit) #7626