Skip to content

Do not persist winforms data to disk#46391

Merged
CyrusNajmabadi merged 5 commits intodotnet:masterfrom
CyrusNajmabadi:formPerf
Jul 29, 2020
Merged

Do not persist winforms data to disk#46391
CyrusNajmabadi merged 5 commits intodotnet:masterfrom
CyrusNajmabadi:formPerf

Conversation

@CyrusNajmabadi
Copy link
Contributor

There is a possible race condition in this cde (originally documented) that could lead us to persist data to disk that does not match what the host thinks the state of things are. i.e. we compute something is a form, then remote that information over to VS. THe processing of that data on the VS side happens at some point in the future, but we still persist what we think VS knows at that point.

If this gets out of sync (i.e. because a message is lost) user might be in a broken state until they can do something that forces reprocessing.

@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner July 28, 2020 21:01
{
_workQueue = new AsyncBatchingWorkQueue<DesignerAttributeData>(
TimeSpan.FromSeconds(1),
TimeSpan.FromMilliseconds(500),
Copy link
Contributor

Choose a reason for hiding this comment

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

what is the rationale for this change?

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 actually reduce it even more in the next PR. the point of these delays is to allow us to hear about a large set of result from oop at once, and then report them at once to VS in one go. i.e. we don't want to hear about 100 files, and then send 100 calls to update VS (each which might hit the UI thread). Instead, it's better to hear about 100 files, then report it in one go to VS on the UI thread once.

So the goal is simply to give enough time to hear about a lot, but not so long that it feels slow to the user for thigns to update. 250ms seems to work well. It allows for hearing about tons of files, not saturating the UI thread, and providing low latency between hearing about changes and updating the UI with them. Note: my next PR adds a comment on this.

Copy link
Member

Choose a reason for hiding this comment

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

"250ms seems to work well."

The PR is changing it to 500ms though?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

next pr reduces it further :)

Copy link
Member

@jasonmalinowski jasonmalinowski left a comment

Choose a reason for hiding this comment

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

Looks OK and I'll never complain about the removal of the a bunch of storage stuff. A bit worried about the use of ConcurrentDictionary in a place or two where if various members here can be running in parallel strange things might happen.

}

public override Task RemoveProjectAsync(ProjectId projectId, CancellationToken cancellationToken)
public override async Task RemoveProjectAsync(ProjectId projectId, CancellationToken cancellationToken)
Copy link
Member

Choose a reason for hiding this comment

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

Does this also get called if we unload the entire solution? I'm guessing we'll see a project removed notification for each project now but would be good to confirm under a debugger. I know the live unit testing code was running into some 'surprises' here and I wouldn't want us to fall into the same trap.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@mavasani for information here. IN the case of solution-unload, how to incremetnal analyzers hear about the 'removal'? Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

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

Current implementation invokes RemoveDocumentAsync for each document in the solution: http://sourceroslyn.io/#Microsoft.CodeAnalysis.Features/SolutionCrawler/WorkCoordinator.cs,407. It seems it can certainly do batching here and just invoke RemoveProjectAsync for all projects in the solution.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

on interesting. so there's no real point in responding to RemoveProject if i'm also responding to RemoveDocument, correct?

Copy link
Contributor

Choose a reason for hiding this comment

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

We invoke RemoveProject for enqueued project items for which solution.GetProject(id) returns null, i.e. project has been removed and we cannot find the documents for it.

Copy link
Contributor

Choose a reason for hiding this comment

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

Comment on lines +99 to +101
var changedData =
latestData.Where(d => !_documentToLastReportedInformation.TryGetValue(d.document.Id, out var existingInfo) || existingInfo.category != d.data.Category)
.ToImmutableArray();
Copy link
Member

Choose a reason for hiding this comment

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

If the user had a file that we classified, and then edits the code such that ComputeLatestDataAsync no longer returns anything for that file, do we need to report the now lack of data? And clear out the dictionary in that case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we do report the lack of data. specifically, we report changes from whatever we last reported. We don't actually clear out the dictionary because we want to store in teh dictionary the version of the last report, taht way we can ignore future events (that don't chanage hte state). If we remove things from teh dictionary, that is akin to saying "we've never reported anything", and then we will just keep notifying VS of "it's not designable, it's not designable, it's not designable".

Copy link
Member

Choose a reason for hiding this comment

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

Ah got it; all documents being queried still get added to the dictionary no matter what. Makes sense.


tasks.Add(ComputeDesignerAttributeDataAsync(
storage, projectVersion, designerCategoryType, document, cancellationToken));
// If we don't have a path for this document, we cant proceed with it.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// If we don't have a path for this document, we cant proceed with it.
// If we don't have a path for this document, we can't proceed with it.

Comment on lines +136 to +137
if (_documentToLastReportedInformation.TryGetValue(document.Id, out var existingInfo) &&
existingInfo.projectVersion == projectVersion)
Copy link
Member

Choose a reason for hiding this comment

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

What's the threading guarantees here? Can RemoveDocumentAsync earlier in the file be running at the same time as this? Is there some risk that we'd remove the document, then process it here, and add it back?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it cannot be running at the same time. all the incremental-analyzer entrypoints are serialized. the only concurrency is from me spitting out tons of tasks (that i when-all) in one of those incremental-analyzer entrypoints.

@mavasani to confirm.

Copy link
Contributor

Choose a reason for hiding this comment

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

all the incremental-analyzer entrypoints are serialized

Yes, that is correct

Comment on lines 155 to 157
// We either haven't computed the designer info, or our data was out of date. We need
// So recompute here. Figure out what the current category is, and if that's different
// from what we previously stored.
Copy link
Member

Choose a reason for hiding this comment

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

Comment seems stale -- should be deleted now?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

it's still accurate. we have to comptue this and compare against whatever we last reported. teh difference now is that we keep track of what we reported in memory instead of on disk.

Copy link
Member

Choose a reason for hiding this comment

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

Well, it's still correct, but previously that function was doing the storage comparison. Now this function is blissfully ignorant of how it's being used -- does the comment move elsewhere then?

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'll revise in next pr.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

revised in next pr.

@CyrusNajmabadi
Copy link
Contributor Author

Looks OK and I'll never complain about the removal of the a bunch of storage stuff. A bit worried about the use of ConcurrentDictionary in a place or two where if various members here can be running in parallel strange things might happen.

So a core benefit of IncrementalAnalyzer is that it makes its requests to us serially. I do think i could just make this a Dictionary. however, then i have to be very careful about my own reads/writes in the tasks i spin off to process all docs in a project in parallel.

@jasonmalinowski
Copy link
Member

@CyrusNajmabadi Sounds good.

@CyrusNajmabadi CyrusNajmabadi merged commit f4a419b into dotnet:master Jul 29, 2020
@ghost ghost added this to the Next milestone Jul 29, 2020
@CyrusNajmabadi CyrusNajmabadi deleted the formPerf branch July 29, 2020 23:37

private readonly IPersistentStorageService _storageService;
/// <summary>
/// Keep track of the last information we reported. We will avoid notifying the host if we recompute and these
Copy link
Member

Choose a reason for hiding this comment

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

host [](start = 89, length = 4)

Should this rather say "the client", not "the host"?

// easy it would be to get out of that state (just edit the file).

await PersistLatestInfosAsync(project.Solution, projectVersion, latestInfos, cancellationToken).ConfigureAwait(false);
// Now, keep track of what we've reported to the host so we won't report unchanged files in the future.
Copy link
Member

Choose a reason for hiding this comment

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

host [](start = 61, length = 4)

Client?

JoeRobich added a commit that referenced this pull request Aug 4, 2020
This reverts commit f4a419b, reversing
changes made to 23e5b1c.
RikkiGibson added a commit that referenced this pull request Aug 4, 2020
This reverts commit f4a419b, reversing
changes made to 23e5b1c.
CyrusNajmabadi added a commit to CyrusNajmabadi/roslyn that referenced this pull request Aug 6, 2020
RikkiGibson added a commit that referenced this pull request Aug 7, 2020
Revert "Revert "Merge pull request #46391 from CyrusNajmabadi/formPerf""
@RikkiGibson RikkiGibson modified the milestones: Next, 16.8.P2 Aug 11, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants