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

Make fix-all code action more parallel #73356

Merged
merged 8 commits into from
May 7, 2024

Conversation

CyrusNajmabadi
Copy link
Member

No description provided.

@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner May 6, 2024 21:22
@dotnet-issue-labeler dotnet-issue-labeler bot added Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead labels May 6, 2024
@CyrusNajmabadi CyrusNajmabadi requested a review from ToddGrun May 6, 2024 21:22
IProgress<CodeAnalysisProgress> progressTracker)
{
// First, determine the diagnostics to fix.
var diagnostics = await DetermineDiagnosticsAsync(fixAllContext, progressTracker).ConfigureAwait(false);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method was inlined.

var diagnostics = await DetermineDiagnosticsAsync(fixAllContext, progressTracker).ConfigureAwait(false);

// Second, get the fixes for all the diagnostics, and apply them to determine the new root/text for each doc.
return await GetFixedDocumentsAsync(fixAllContext, progressTracker, diagnostics).ConfigureAwait(false);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this method was inlined.

/// </summary>
private static async Task<ImmutableDictionary<Document, ImmutableArray<Diagnostic>>> DetermineDiagnosticsAsync(FixAllContext fixAllContext, IProgress<CodeAnalysisProgress> progressTracker)
{
using var _ = progressTracker.ItemCompletedScope();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this went away. the caller is responsible for this now.

var workItemCount = fixAllKind == FixAllKind.CodeFix ? 3 : 2;
progressTracker.AddItems(fixAllContexts.Length * workItemCount);
// One work item for each context. Then one final item to for cleaning the solution.
progressTracker.AddItems(fixAllContexts.Length + 1);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the math computation was originally wrong. it was not hte case that we did 3 items for fixes per context, and 2 per refactoring per context. Because this was just broken. i simplified this. the computation of progress length is done here, and the updating happens here as well. this is my preference for progress as having length computation and item-finish calls separated means it's too easy to get wrong.

progressTracker.AddItems(fixAllContexts.Length + 1);

var (dirtySolution, changedRootDocumentIds) = await GetInitialUncleanedSolutionAsync().ConfigureAwait(false);
return await CleanSolutionAsync(dirtySolution, changedRootDocumentIds).ConfigureAwait(false);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

broke into two nested functions to help out there.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

view with whitespace off.

{
Contract.ThrowIfFalse(fixAllContext.Scope is FixAllScope.Document or FixAllScope.Project
or FixAllScope.ContainingMember or FixAllScope.ContainingType);

var cancellationToken = fixAllContext.CancellationToken;

using var _1 = progressTracker.ItemCompletedScope();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we definitely weren't doing 'two' "completed" calls here. so this was entirely busted for progress.

fixAllContext.Scope is FixAllScope.Document or FixAllScope.Project or FixAllScope.ContainingMember or FixAllScope.ContainingType);

var singleContextDocIdToNewRootOrText = await args.getFixedDocumentsAsync(fixAllContext).ConfigureAwait(false);
callback(singleContextDocIdToNewRootOrText);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm likely going to change this to a callback as well, to avoid this intermediary dictionary.

await foreach (var (docId, nodeOrText) in results)
docIdToNewRootOrText[docId] = nodeOrText;

return docIdToNewRootOrText;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for a producerconsumer here anymore. we're not making the intermediary dictionary. we're just calling back into callback with the doc data produced.

// Process all documents in parallel to get the change for each doc.
var documentsAndSpansToFix = await fixAllContext.GetFixAllSpansAsync(cancellationToken).ConfigureAwait(false);

return await ProducerConsumer<(DocumentId, (SyntaxNode? node, SourceText? text))>.RunParallelAsync(
await RoslynParallel.ForEachAsync(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for a producerconsumer here anymore. we're not making the intermediary dictionary. we're just calling back into callback with the doc data produced.

var workItemCount = fixAllKind == FixAllKind.CodeFix ? 3 : 2;
progressTracker.AddItems(fixAllContexts.Length * workItemCount);
// One work item for each context.
progressTracker.AddItems(fixAllContexts.Length);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the math computation was originally wrong. it was not hte case that we did 3 items for fixes per context, and 2 per refactoring per context. Because this was just broken. i simplified this. the computation of progress length is done here, and the updating happens here as well. this is my preference for progress as having length computation and item-finish calls separated means it's too easy to get wrong.

@@ -492,4 +492,7 @@
<data name="Unexpected_value_0_in_DocumentKinds_array" xml:space="preserve">
<value>Unexpected value '{0}' in DocumentKinds array.</value>
</data>
<data name="Cleaning_fixed_documents" xml:space="preserve">
<value>Cleaning fixed documents</value>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i don't love this text. perhaps "Running code cleanup on fixed documents"?

var dirtyDocument = args.dirtySolution.GetRequiredDocument(documentId);
var cleanedDocument = await PostProcessCodeAction.Instance.PostProcessChangesAsync(dirtyDocument, cancellationToken).ConfigureAwait(false);
var cleanedText = await cleanedDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false);
callback((dirtyDocument.Id, cleanedText));
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i have a followup PR that moves this cleanup step to OOP. so we can benefit from .net core execution wins.

@CyrusNajmabadi
Copy link
Member Author

@ToddGrun ptal.

return dirtySolution;

// Clear out the progress so far. We're starting a new progress pass for the final cleanup.
progressTracker.Report(CodeAnalysisProgress.Clear());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

progressTracker.Report(CodeAnalysisProgress.Clear());

Out of curiosity, how is this expressed to the user? Does a bar fill during GetInitialUncleanedSolutionAsync, empty here, and then fill again during this method?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Exactly

Copy link
Contributor

@ToddGrun ToddGrun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:shipit:

@CyrusNajmabadi CyrusNajmabadi merged commit ce1d093 into dotnet:main May 7, 2024
25 checks passed
@CyrusNajmabadi CyrusNajmabadi deleted the parallelFixAll branch May 7, 2024 14:45
@dotnet-policy-service dotnet-policy-service bot added this to the Next milestone May 7, 2024
@CyrusNajmabadi
Copy link
Member Author

@jasonmalinowski For review when you get back.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants