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

Free-threaded project system APIs #26931

Conversation

jasonmalinowski
Copy link
Member

@jasonmalinowski jasonmalinowski commented May 17, 2018

This produces a new free-threaded, well factored API for adding projects to the VisualStudioWorkspace. The core type is the VisualStudioProject which is a free-threaded API that you can use to push information over. CSharpProjectShim, VisualBasicProject and CPSProject each have an instance of VisualStudioProject that they push things through.

This also introduces a proper batching API: any consumer of VisualStudioProject may call CreateBatchScope() which means all future operations are put a single batch that will be applied at once in a single workspace change. Multiple calls to CreateBatchScope just stack: it only applies when all scopes are closed. You could think of the behavior as roughly analogous to an SQL transaction, except with no rollback capabilities. A batch is only per-project: a batch started on one project doesn't impact another project. There is no concept of a cross-project batch, short of just starting two independent batches and completing them at the same time (which creates two workspace changes.)

The inheritance model here is now smaller. CSharpProjectShim and VisualBasicProject inherit from AbstractLegacyProject, but that's it. CPSProject now inherits nothing, and AbstractProject is here purely for TypeScript back-compat until they're moved onto the new APIs. The expectation is F# and TypeScript both move to VisualStudioProject, which we make public in some form. Then AbstractProject will go away.

AbstractLegacyProject (which is used for csproj and vbproj) still is tied to the UI thread, but the ties should now be more limited and better understood. The primary tangles right now are having to fetch a few things from IVsHierarchies, namely some project properties and the folder structure. The plan for now is AbstractLegacyProject-derived projects will still be called on the UI thread, and we'll determine a new API to remove those remaining issues for them (or just move them onto VisualStudioProject.)

The threading guarantee given to all consumers of VisualStudioProject is it can be called from any thread, and no synchronous calls to the UI thread may be done at that time. There are a few places where we asynchronously kick off work to inspect the running document table, but those are async and should never block.

There is a lock hierarchy now instituted: each VisualStudioProject has a lock which it takes when any method is called on it, this is to ensure straightforward synchronization of it's data for things like batch start/stopping, what's in the batch, etc. There is also a lock in VisualStudioWorkspaceImpl that is acquired when actually modifying the workspace, which is done through an internal ApplyChangeToWorkspace method so it can be called from VisualStudioProject more easily. It is permissible for a VisualStudioProject to call ApplyChangeToWorkspace to acquire the VisualStudioWorkspaceImpl lock. It is not permissible for a lock holder of VisualStudioWorkspaceImpl's lock to acquire any project lock. To this end, some project-to-project tracking information is held in VisualStudioWorkspaceImpl to avoid mistakes -- although the data is a "per project" concept, putting it in the project would cause people to take a project lock and risk deadlocks.

@jasonmalinowski jasonmalinowski requested a review from a team as a code owner May 17, 2018 01:39
@jasonmalinowski jasonmalinowski added the PR For Personal Review Only The PR doesn’t require anyone other than the developer to review it. label May 17, 2018
@jasonmalinowski jasonmalinowski self-assigned this May 17, 2018
@CyrusNajmabadi
Copy link
Member

Any interest in eyes? or would you prefer people wait?

@@ -11,6 +11,7 @@
<TargetFramework>net461</TargetFramework>
<RuntimeIdentifier>$(RoslynDesktopRuntimeIdentifier)</RuntimeIdentifier>
<RoslynProjectType>UnitTest</RoslynProjectType>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Copy link
Member

Choose a reason for hiding this comment

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

that's scary :)

Copy link
Member

Choose a reason for hiding this comment

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

Where's your sense of adventure? The unsafer, the better!

Copy link
Member Author

Choose a reason for hiding this comment

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

Our C# library already had unsafe for some fun marshalling hacks we have to do. One thing I was repeatedly being bit by while refactoring this is a lot of unit tests were calling internal members of AbstractProject directly instead of through the interfaces the project system code actually calls through. Fixing this meant our unit tests now have to do some unsafe code to call into the existing unsafe code. 😄

@jasonmalinowski
Copy link
Member Author

@CyrusNajmabadi There's still a few weeks of work planned for this. I created the PR only because there's now enough working again that I suspected there's a chance that integration tests would have passed...

@CyrusNajmabadi
Copy link
Member

@CyrusNajmabadi There's still a few weeks of work planned for this. I created the PR only because there's now enough working again that I suspected there's a chance that integration tests would have passed...

Sounds good. Let me know if/when you'd like eyes!

@jasonmalinowski
Copy link
Member Author

@CyrusNajmabadi Oh, once this is tidied up I'll want a hell of a lot of eyes on it. Right now half your comments would be "why is this important bit commented out right now?" 😄

@jcouv jcouv added the Area-IDE label May 21, 2018
@jasonmalinowski jasonmalinowski force-pushed the free-threaded-project-system-api branch 2 times, most recently from 0a0f0f4 to 68565ad Compare May 29, 2018 20:31
@jasonmalinowski jasonmalinowski force-pushed the free-threaded-project-system-api branch 3 times, most recently from 685b619 to 8e10b6b Compare June 14, 2018 17:43
@jasonmalinowski jasonmalinowski force-pushed the free-threaded-project-system-api branch from 8e10b6b to 51bd27a Compare August 17, 2018 19:16
@jasonmalinowski jasonmalinowski requested a review from a team as a code owner August 17, 2018 19:16
@jasonmalinowski jasonmalinowski force-pushed the free-threaded-project-system-api branch from 51bd27a to 7c66439 Compare August 17, 2018 20:45
// Get options from the ruleset file, if any, first. That way project-specific
// options can override them.
ReportDiagnostic? ruleSetGeneralDiagnosticOption = null;
/*
Copy link
Member

Choose a reason for hiding this comment

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

Work in progress?

@jasonmalinowski jasonmalinowski force-pushed the free-threaded-project-system-api branch 8 times, most recently from 7742961 to 3c5e96d Compare August 27, 2018 20:40
@jasonmalinowski jasonmalinowski force-pushed the free-threaded-project-system-api branch 2 times, most recently from e8b21f6 to 597cd1b Compare August 30, 2018 23:32
We initialize AssemblyName when we create the project with the
project system name (because the workspace requires we have something).
But ocne we get the real path, we need to use the real name.
The existing shim constructor we had wasn't being used -- I'm not sure
if this is me accidentally refactoring after I had it working, or
they changed their code more recently. In either case, it's easy enough
to add another compat constructor.

var projectFilePath = hierarchy.GetProjectFilePath();

if (projectFilePath != null && !File.Exists(projectFilePath))
Copy link
Member

Choose a reason for hiding this comment

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

if (projectFilePath != null && !File.Exists(projectFilePath)) [](start = 12, length = 61)

Assuming this is a workaround for something that will go away one day?

Copy link
Member Author

Choose a reason for hiding this comment

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

It's the workaround we've had for 7 years, apparently. (It just wasn't very obvious that the workaround indeed existed.) Hopefully AbstractLegacyProject goes away later in the Dev16 wave.

Copy link
Member

@tmat tmat left a comment

Choose a reason for hiding this comment

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

:shipit:

There's nothing left but shims at this point, so nothing actually
requires it. Previously something must have been creating it by
accident but we aren't anymore, and F# trying to use it would cause
issues.
F# was requesting the ID, which we'd throw on by accident.
@jasonmalinowski
Copy link
Member Author

jasonmalinowski commented Oct 16, 2018

@dotnet-bot retest this please. (This was affected by the Jenkins outage yesterday.)

We stopped populating this, which breaks various things like Live Unit
Testing and some of our persistence stuff.
We were adding with project unique name, but removing with the
DisplayName, which could change over time. I've renamed the map
to clarify exactly which "name" is being used.

Right now I'm being lazy and just using a foreach across the
ImmutableDictionary rather than converting it to a bidirectional
map. If this ever becomes a perf problem, it's easy to fix.
This meant some of the StubProjects we create for compatibility shims
(which derive from AbstractProject) will now have this set. Otherwise
we were passing back null, which didn't end well.
The underlying process of removing a document already handled this,
this was an unnecessary block. It was already the case that you could
call OnProjectRemoved to remove the entire project that had open files,
so this didn't really avoid badness in the first place.
I had implemented ProjectTracker.GetProject to always return shim
projects for F#, but TypeScript expects they can call that, and then
immediately pass that project to RemoveProject. So for TypeScript
projects, we'll keep returning the real thing.
@jasonmalinowski jasonmalinowski force-pushed the free-threaded-project-system-api branch from 3eb6a3e to df4adf6 Compare October 16, 2018 23:41
jasonmalinowski and others added 9 commits October 17, 2018 15:12
I should have overriden this from the start -- the ExplicitBinPath
didn't give you a way to specify you really did want it to be null.
A few places we might try to remove something that we shouldn't.
This would have never worked: we would have been closing documents
after we had already removed them.
Two bugs being fixed here:

1. When we remove a document in a non-batch scenario, we close any
   information related to the open document. If we were in a batch
   scenario, the SetCurrentSolution we do to apply the new Solution
   snapshot didn't go through that. Now we do.
2. UpdateSolutionForBatch wasn't calling the delegates given; this would
   have meant that additional files being added in a batch would have
   been added a regular source files.
…read

Since we're checking the RDT asynchronously, we need a way for
integration tests to confirm that.
We might disconnect/reconnect on background threads now, so
be ready for that.
This test was broken prior to my change: it never waited for CPS to
have completed design time builds before it asserted that everything
was done. But before, there was no window where the UI thread was idle
and the file wasn't associated with Miscellaneous Files. Now that we're
more async, I think it is the case where it can be, and that trips
things up.
This adds an API for the project system to notify us about files that
can generate .cs files in the workspace. The rest of the implementation
is still coming but this unblocks the project system side of things.
@jasonmalinowski jasonmalinowski merged commit c20eae1 into dotnet:dev16.0.x Oct 19, 2018
@Pilchie
Copy link
Member

Pilchie commented Oct 19, 2018

GitHub needs reactions on merge events. So happy to see this merged!!!!!!!!!

koleffc

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.

8 participants