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

Work to unify threading concepts between Workspace and ProjectSystemProjectFactory (part1) #67522

Merged
merged 13 commits into from
Apr 26, 2023

Conversation

CyrusNajmabadi
Copy link
Member

This is some preliminary work to help fix #67510. There is a lot more work to be done, but this creates an initial foundation to safely and incrementally move ProjectSystemProjectFactory and workspace off of having two separate locks that have to be coordinated, to just using a single lock that ensures consistent atomic changes to the workspace across all clients.

The core problem we want to solve is that currently it is possible to corrupt the workspace if it is within a host that both uses ProjectSystemProjectFactory to manipulate the workspace and which attempts to mutate the workspace through any other means. This can happen as the ProjectSystemProjectFactory expects all workspace edits to be done while a particular lock it owns is held. But the host can directly interact with the workspace, changing it out from underneath ProjectSystemProjectFactory while it is in the middle of making a series of changes.

Moving to a consistent change-application mechanism ensures that the workspace changes from the host and ProjectSystemProjectFactory happen atomically, and either mutator only ever sees complete changes from the other.

@dotnet-issue-labeler dotnet-issue-labeler bot added Area-IDE untriaged Issues and PRs which have not yet been triaged by a lead labels Mar 28, 2023
Workspace.ClearDocumentData(documentId);
},
onAfterUpdate: null,
CancellationToken.None).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 change at least moves this specific PSPF to use the workspace helper that makes stateless transformations against the it's current-snapshot and uses optimistic concurrency to try to apply it if nothing else intervened. If anything else did, it will back off and continue trying until it succeeds. Only once it does does it then update the current solution and then calls the other lambdas which can make stateful changes.

The goal is to move everything in this type over to that pattern. HOwever, it is difficult given the disparate patterns used here, as well as that this type is itself then called by other external entities making many changes and expecting them all to be atomic. It's going to take a fair amount of work to untangle everything.

Copy link
Member

Choose a reason for hiding this comment

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

Yep, refactoring this one method at a time is going to be the right approach.

WorkspaceChangeKind.SolutionRemoved,
onBeforeUpdate: (_, _) =>
{
Workspace.ClearOpenDocuments();
Copy link
Member Author

Choose a reason for hiding this comment

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

an example of moving mutation to the point that we know that setting the actual solution succeeded. It was previously possible for these two calls to have interleaving mutations run from some other source

(_, _) => changeKind,
projectId,
documentId,
(_, _) => (changeKind, projectId, documentId),
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 was just a simplifying transformation that made other changes/callers simpler.

Action<Solution, Solution>? onBeforeUpdate,
Action<Solution, Solution>? onAfterUpdate,
CancellationToken cancellationToken)
{
Copy link
Member Author

Choose a reason for hiding this comment

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

general change here is that these helpers became async-capable, with a boolean flag to actually say if they can operate asynchronously or not. Existing sync callers pass 'false', but then know that they will complete synchronously. This is a pattern we use in many places in teh workspace already.

@CyrusNajmabadi CyrusNajmabadi marked this pull request as ready for review March 28, 2023 17:58
@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner March 28, 2023 17:58
@CyrusNajmabadi CyrusNajmabadi changed the title Work to unify threading concepts between Workspace and ProjectSystemProjectFactory Work to unify threading concepts between Workspace and ProjectSystemProjectFactory (part1) Mar 28, 2023
Copy link
Member

@dibarbet dibarbet left a comment

Choose a reason for hiding this comment

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

this seems fine? But I think Jason needs to look at this.

@CyrusNajmabadi
Copy link
Member Author

Agreed. This isn't merging until jason is ok with things.

@CyrusNajmabadi
Copy link
Member Author

@jasonmalinowski ptal :)

@CyrusNajmabadi
Copy link
Member Author

@jasonmalinowski I'd like a review from you on this. If you aren't able to, let me know and i'll merge and you can review at a better time for you later.

@CyrusNajmabadi
Copy link
Member Author

@jasonmalinowski ptal.

@CyrusNajmabadi
Copy link
Member Author

@jasonmalinowski ptal.

1 similar comment
@CyrusNajmabadi
Copy link
Member Author

@jasonmalinowski ptal.

CancellationToken.None);
#pragma warning restore CA2012 // Use ValueTasks correctly

Contract.ThrowIfFalse(valueTask.IsCompleted, "Task must have completed synchronously as we passed 'useAsync: false' to SetCurrentSolutionAsync");
Copy link
Member

Choose a reason for hiding this comment

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

For regular tasks, we wrote an extension method here for asserting this:

public static void VerifyCompleted(this Task task)

Want to write an equivalent "VerifyCompleteAndReturn" form for ValueTask? If nothing else, if you can do it where the you can keep the pragma disables to the helper it might make it look nicer.

Copy link
Member Author

Choose a reason for hiding this comment

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

done. that is nice :)

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.

All comments can be dealt with in follow ups since I know you have more queued behind this.

Comment on lines 243 to 245
// If the accumulator showed no changes, return the old solution. This will ensure that
// SetCurrentSolutionAsync bails out immediately and no further work is done.
return solutionChanges.HasChange ? solutionChanges.Solution : oldSolution;
Copy link
Member

Choose a reason for hiding this comment

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

Was this check needed? I would have assumed that if HasChange returns false, solutionChanges.Solution already equals oldSolution...

Copy link
Member Author

Choose a reason for hiding this comment

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

you are correct. simplified.

Func<Solution, Solution, WorkspaceChangeKind> changeKind,
ProjectId? projectId = null,
DocumentId? documentId = null,
Func<Solution, Solution, (WorkspaceChangeKind changeKind, ProjectId? projectId, DocumentId? documentId)> changeKind,
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if it makes sense just to have the transformation also return the change info too. But that can be another PR.

Copy link
Member Author

Choose a reason for hiding this comment

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

that's a reasonable idea. thanks!

@CyrusNajmabadi CyrusNajmabadi merged commit 28f2063 into dotnet:main Apr 26, 2023
@ghost ghost added this to the Next milestone Apr 26, 2023
@CyrusNajmabadi CyrusNajmabadi deleted the sharedLock2 branch April 26, 2023 15:47
@Cosifne Cosifne modified the milestones: Next, 17.7 P2 May 31, 2023
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.

Remove locking from ProjectSystemProjectFactory (and use Workspace's lock instead).
4 participants