Export option persisters via IOptionPersisterProvider#52063
Export option persisters via IOptionPersisterProvider#52063sharwell merged 8 commits intodotnet:mainfrom
Conversation
1feae75 to
69ac04b
Compare
Closes AB#761238
69ac04b to
fcc29a2
Compare
| { | ||
| // 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(); |
There was a problem hiding this comment.
Why add this assert? Was the earlier comment incorrect?
There was a problem hiding this comment.
the comment was not supported by public documentation, so I removed it
There was a problem hiding this comment.
Also note that the implementation itself is not intended for use on background threads:
https://devdiv.visualstudio.com/DevDiv/_git/VS?path=%2Fsrc%2Fenv%2Fshell%2FPackageFramework%2FCurrent%2FShell%2FVSRegistry.cs&version=GBmain&line=63&lineEnd=64&lineStartColumn=1&lineEndColumn=1&lineStyle=plain&_a=contents
There was a problem hiding this comment.
I think the trick with this one is it's special cased:
There was a problem hiding this comment.
This is why #52063 (comment) is the only sane approach to handling this...
There was a problem hiding this comment.
As far as I'm concerned, the comment was the public documentation. 😄
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Documentation is now more complete
There was a problem hiding this comment.
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.
...lStudio/Core/Def/Implementation/Options/RoamingVisualStudioProfileOptionPersisterProvider.cs
Show resolved
Hide resolved
CyrusNajmabadi
left a comment
There was a problem hiding this comment.
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?
jasonmalinowski
left a comment
There was a problem hiding this comment.
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.)
...lStudio/Core/Def/Implementation/Options/RoamingVisualStudioProfileOptionPersisterProvider.cs
Show resolved
Hide resolved
8db3bd8 to
6459317
Compare
6459317 to
b92dc39
Compare
jasonmalinowski
left a comment
There was a problem hiding this comment.
Looking pretty good just some small requests for docs, a rename to make the workspace threading service clear it's being used for options.
...lStudio/Core/Def/Implementation/Options/RoamingVisualStudioProfileOptionPersisterProvider.cs
Show resolved
Hide resolved
src/Workspaces/Core/Portable/Shared/Utilities/IWorkspaceThreadingService.cs
Show resolved
Hide resolved
| return optionSerializers; | ||
| } | ||
|
|
||
| ImmutableInterlocked.InterlockedInitialize( |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
should this become async/cancellable in teh future?
There was a problem hiding this comment.
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); | ||
| } |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
A two-layer indirection alternative exists, which I can implement separately to see if it's preferred.
CyrusNajmabadi
left a comment
There was a problem hiding this comment.
I'm ok with this if jason is :)
| namespace Microsoft.CodeAnalysis.Editor.Shared.Utilities | ||
| { | ||
| [Export(typeof(IWorkspaceThreadingService))] | ||
| [Shared] |
There was a problem hiding this comment.
What is the style here? I typically keep the Shared attribute on the same line
There was a problem hiding this comment.
I always put one attribute in one set of brackets
| namespace Microsoft.VisualStudio.LanguageServices.Implementation.TaskList | ||
| { | ||
| [Export(typeof(IOptionPersisterProvider))] | ||
| internal sealed class CommentTaskTokenSerializerProvider : IOptionPersisterProvider |
There was a problem hiding this comment.
All of these IOptionPersisterProvider's look very similar, can we combine most of this into a common base class?
There was a problem hiding this comment.
This was covered by #52063 (comment). In this case, sharing doesn't bring enough value to cover its own complexity cost.
Fixes #37377
Fixes AB#761238