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

Fix incrementalism when suppressed: #9717

Merged
merged 5 commits into from
Jan 17, 2024

Conversation

chsienki
Copy link
Contributor

  • Previously when going from unsuppressed -> suppressed we cleared certain 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.

Helps (at least for second edit) #7626

- 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.
@chsienki chsienki added the area-compiler Umbrella for all compiler issues label Dec 16, 2023
@chsienki chsienki added this to the 17.9 P3 milestone Dec 16, 2023
@chsienki chsienki self-assigned this Dec 16, 2023
@chsienki chsienki requested a review from a team as a code owner December 16, 2023 00:28
@Mike-E-angelo
Copy link

Baby steps. Thank you for your efforts out there @chsienki & team. 🙏

Assert.Empty(eventListener.Events);

// Now enable the generator and confirm we get the expected output
driver = SetSuppressionState(false);
Copy link
Member

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?

Copy link
Contributor Author

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.

@chsienki
Copy link
Contributor Author

@dotnet/razor-compiler for review please


var razorSourceGeneratorOptions = analyzerConfigOptions
.Combine(parseOptions)
.Combine(isGeneratorSuppressed)
.SuppressIfNeeded(isGeneratorSuppressed)
Copy link
Member

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?

  1. The generator is not suppressed, the inputs (analyzerConfigOptions or parseOptions) are changing and hence we are recomputing the output (razorSourceGeneratorOptions). All is fine.
  2. The generator is suppressed.
  3. Some inputs change - but we report the output is up to date since the suppression flag is true.
  4. 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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Member

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:

  1. Suppression = off.
  2. parseOptions = A -> razorSourceGeneratorOptions = A
  3. parseOptions = B -> razorSourceGeneratorOptions = B
  4. Suppression = on.
  5. parseOptions = A -> razorSourceGeneratorOptions = B (since SuppressIfNeeded reports "up to date" due to suppression)
  6. 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?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Member

@jjonescz jjonescz Jan 17, 2024

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).

@chsienki chsienki merged commit cd0eeb4 into dotnet:main Jan 17, 2024
12 checks passed
@chsienki chsienki deleted the incremental_suppression_fix branch January 17, 2024 23:35
@ghost ghost modified the milestones: 17.9 P3, Next Jan 17, 2024
@Cosifne Cosifne modified the milestones: Next, 17.10 P1 Jan 30, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-compiler Umbrella for all compiler issues
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants