Skip to content

Export option persisters via IOptionPersisterProvider#52063

Merged
sharwell merged 8 commits intodotnet:mainfrom
sharwell:persister-provider
Mar 29, 2021
Merged

Export option persisters via IOptionPersisterProvider#52063
sharwell merged 8 commits intodotnet:mainfrom
sharwell:persister-provider

Conversation

@sharwell
Copy link
Contributor

@sharwell sharwell commented Mar 23, 2021

Fixes #37377
Fixes AB#761238

@ghost ghost added the Area-IDE label Mar 23, 2021
@sharwell sharwell marked this pull request as ready for review March 23, 2021 02:37
@sharwell sharwell requested review from a team as code owners March 23, 2021 02:37
@sharwell sharwell force-pushed the persister-provider branch from 1feae75 to 69ac04b Compare March 23, 2021 04:26
@sharwell sharwell force-pushed the persister-provider branch from 69ac04b to fcc29a2 Compare March 23, 2021 16:54
{
// Starting in Dev16, the ILocalRegistry service behind this call is free-threaded, and since the service is offered by msenv.dll can be requested
// without any marshalling (explicit or otherwise) to the UI thread.
threadingContext.ThrowIfNotOnUIThread();
Copy link
Member

Choose a reason for hiding this comment

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

Why add this assert? Was the earlier comment incorrect?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the comment was not supported by public documentation, so I removed it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

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 is why #52063 (comment) is the only sane approach to handling this...

Copy link
Member

Choose a reason for hiding this comment

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

As far as I'm concerned, the comment was the public documentation. 😄

Copy link
Member

Choose a reason for hiding this comment

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

I don't mind the assert now being there given the larger context is now doing the call on the UI thread, but can we keep the comment? Or revise it to explain why the assert is there? I expect this to move around further and having to go through this again would be unfortunate.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Documentation is now more complete

Copy link
Contributor

Choose a reason for hiding this comment

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

the assert feels weird to me. this was made free threaded and communicated to us that way. if we think their documentation should be more sufficient, i woudl prefer we just ask them to do that.

Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi left a comment

Choose a reason for hiding this comment

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

looking for clarity on one bit. otherwise, it looks fine to me. do we need to rope in TS/F#? do they do anything with option persistence?

@sharwell sharwell marked this pull request as draft March 24, 2021 00:44
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.

So I suspect the behavior where the global option service is caching a list, and we drop uncreated ones permanently is going to be broken. If anybody were to fetch the VisualStudioWorkspace prior to our package being loaded (which is totally allowed), then we'll cache an empty list and things will be broken for the rest of that session. A specific thing to test would be opening a CPS based project that doesn't have any saved files open. We won't load our package there (because it's only a perf hit) but the workspace will have been created which will trigger reading of various options at that point. We very much may need to add some code that says if we're running on the UI thread and the persisters aren't created by the package, we still create them synchronously at that point to ensure this doesn't regress something. Otherwise the regression risk here is very high.

More generally it's not clear to me what goodness this is giving here. The bug linked nor PR description doesn't actually make it clear to me which assembly load we're trying to move off the UI thread here -- the MEF components being created at least all exist in the same assembly as RoslynPackage, So my assumption is if this is some other dependency (the workspaces layer? something else?) then I'm not sure how this reliably ensures we don't end up triggering that load somewhere else earlier. If nothing else, I'd appreciate the PR description giving a bit of a background on the internal bug, because the internal bug is pretty opaque.

As far as general direction, I think we're far better off deleting the persister preloading entirely, which I think is achievable but have a few details to still work out. So this work might get deleted soon enough. But since this is written, and it's applying a straightforward refactoring in a consistent way, I can't fault it for making a crappy corner of our codebase a bit less crappy. I'll happily sign off once we know we don't have functional bugs here (see first paragraph.)

(not marking as "request changes" or "approve" just because it went back to draft after my private promise to review it today, and whereas I think it needs changes I think that's probably why it went back to draft.)

@sharwell sharwell force-pushed the persister-provider branch from 8db3bd8 to 6459317 Compare March 24, 2021 16:39
@sharwell sharwell force-pushed the persister-provider branch from 6459317 to b92dc39 Compare March 24, 2021 18:37
@sharwell sharwell marked this pull request as ready for review March 24, 2021 23:25
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.

Looking pretty good just some small requests for docs, a rename to make the workspace threading service clear it's being used for options.

return optionSerializers;
}

ImmutableInterlocked.InterlockedInitialize(
Copy link
Member

Choose a reason for hiding this comment

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

Don't we have an EnsureInitialized helper for this pattern somewhere?

{
// Starting in Dev16, the ILocalRegistry service behind this call is free-threaded, and since the service is offered by msenv.dll can be requested
// without any marshalling (explicit or otherwise) to the UI thread.
threadingContext.ThrowIfNotOnUIThread();
Copy link
Member

Choose a reason for hiding this comment

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

I don't mind the assert now being there given the larger context is now doing the call on the UI thread, but can we keep the comment? Or revise it to explain why the assert is there? I expect this to move around further and having to go through this again would be unfortunate.

}
}

private ImmutableArray<IOptionPersister> GetOptionPersisters()
Copy link
Contributor

Choose a reason for hiding this comment

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

should this become async/cancellable in teh future?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, but it is a much more involved change. I wouldn't expect it to happen unless it became required as a result of some independent observation of a problem.

else
{
return GetOptionPersistersAsync(optionSerializerProviders, cancellationToken).WaitAndGetResult_CanCallOnBackground(cancellationToken);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

instead of getting the service, checking for null, and falling back to WaitAndGetResult here, should we instead export a DefaultWorkspaceThreadingService that has this as its implementation for .Run

Copy link
Contributor Author

@sharwell sharwell Mar 29, 2021

Choose a reason for hiding this comment

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

This is not a workspace service, so we can only export one total instance of it (MEF layering is specific to our workspace and language services). Exporting a default would make it very difficult to override.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

A two-layer indirection alternative exists, which I can implement separately to see if it's preferred.

Copy link
Contributor

@CyrusNajmabadi CyrusNajmabadi left a comment

Choose a reason for hiding this comment

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

I'm ok with this if jason is :)

namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities
{
[Export(typeof(IWorkspaceThreadingService))]
[Shared]
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 style here? I typically keep the Shared attribute on the same line

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 always put one attribute in one set of brackets

namespace Microsoft.VisualStudio.LanguageServices.Implementation.TaskList
{
[Export(typeof(IOptionPersisterProvider))]
internal sealed class CommentTaskTokenSerializerProvider : IOptionPersisterProvider
Copy link
Contributor

Choose a reason for hiding this comment

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

All of these IOptionPersisterProvider's look very similar, can we combine most of this into a common base class?

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 was covered by #52063 (comment). In this case, sharing doesn't bring enough value to cover its own complexity cost.

@sharwell sharwell merged commit 7b9df38 into dotnet:main Mar 29, 2021
@ghost ghost added this to the Next milestone Mar 29, 2021
@sharwell sharwell deleted the persister-provider branch March 29, 2021 22:16
@allisonchou allisonchou modified the milestones: Next, 16.10.P2 Mar 29, 2021
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.

IOptionPersister should be async

6 participants