diff --git a/eng/targets/Services.props b/eng/targets/Services.props
index 409f1b282f9a1..c9b59f0c095bf 100644
--- a/eng/targets/Services.props
+++ b/eng/targets/Services.props
@@ -33,6 +33,7 @@
+
diff --git a/src/CodeStyle/Core/CodeFixes/Host/Mef/CodeStyleHostLanguageServices.cs b/src/CodeStyle/Core/CodeFixes/Host/Mef/CodeStyleHostLanguageServices.cs
index 38d1eefe4a63b..340b67afd3947 100644
--- a/src/CodeStyle/Core/CodeFixes/Host/Mef/CodeStyleHostLanguageServices.cs
+++ b/src/CodeStyle/Core/CodeFixes/Host/Mef/CodeStyleHostLanguageServices.cs
@@ -49,7 +49,7 @@ private static ImmutableArray CreateAssemblies(string languageName)
}
return MefHostServices.DefaultAssemblies.Concat(
- MefHostServicesHelpers.LoadNearbyAssemblies(assemblyNames));
+ MefHostServicesHelpers.LoadNearbyAssemblies(assemblyNames.ToImmutableAndClear()));
}
IEnumerable> IMefHostExportProvider.GetExports()
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/ExportProviderBuilderTests.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/ExportProviderBuilderTests.cs
index 7148ec5e9a274..85d5d3d542de9 100644
--- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/ExportProviderBuilderTests.cs
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/ExportProviderBuilderTests.cs
@@ -148,7 +148,7 @@ public ImportType(ExportedType t) { }
private async Task AssertCacheWriteWasAttemptedAsync()
{
- var cacheWriteTask = ExportProviderBuilder.TestAccessor.GetCacheWriteTask();
+ var cacheWriteTask = LanguageServerExportProviderBuilder.TestAccessor.GetCacheWriteTask();
Assert.NotNull(cacheWriteTask);
await cacheWriteTask;
@@ -156,7 +156,7 @@ private async Task AssertCacheWriteWasAttemptedAsync()
private void AssertNoCacheWriteWasAttempted()
{
- var cacheWriteTask2 = ExportProviderBuilder.TestAccessor.GetCacheWriteTask();
+ var cacheWriteTask2 = LanguageServerExportProviderBuilder.TestAccessor.GetCacheWriteTask();
Assert.Null(cacheWriteTask2);
}
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs
index 9667713fc60bc..9215dfc08b82b 100644
--- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer.UnitTests/Utilities/LanguageServerTestComposition.cs
@@ -33,6 +33,7 @@ public static Task CreateExportProviderAsync(
UseStdIo: false);
var extensionManager = ExtensionAssemblyManager.Create(serverConfiguration, loggerFactory);
assemblyLoader = new CustomExportAssemblyLoader(extensionManager, loggerFactory);
- return ExportProviderBuilder.CreateExportProviderAsync(extensionManager, assemblyLoader, devKitDependencyPath, cacheDirectory, loggerFactory);
+
+ return LanguageServerExportProviderBuilder.CreateExportProviderAsync(extensionManager, assemblyLoader, devKitDependencyPath, cacheDirectory, loggerFactory, CancellationToken.None);
}
}
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/ExportProviderBuilder.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/ExportProviderBuilder.cs
deleted file mode 100644
index 6f8f7fbd154a7..0000000000000
--- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/ExportProviderBuilder.cs
+++ /dev/null
@@ -1,229 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Collections.Immutable;
-using System.IO.Hashing;
-using System.Text;
-using Microsoft.CodeAnalysis.LanguageServer.Logging;
-using Microsoft.CodeAnalysis.LanguageServer.Services;
-using Microsoft.CodeAnalysis.Shared.Collections;
-using Microsoft.Extensions.Logging;
-using Microsoft.VisualStudio.Composition;
-using Roslyn.Utilities;
-
-namespace Microsoft.CodeAnalysis.LanguageServer;
-
-internal sealed class ExportProviderBuilder
-{
- // For testing purposes, track the last cache write task.
- private static Task? _cacheWriteTask;
-
- public static async Task CreateExportProviderAsync(
- ExtensionAssemblyManager extensionManager,
- IAssemblyLoader assemblyLoader,
- string? devKitDependencyPath,
- string cacheDirectory,
- ILoggerFactory loggerFactory)
- {
- // Clear any previous cache write task, so that it is easy to discern whether
- // a cache write was attempted.
- _cacheWriteTask = null;
-
- var logger = loggerFactory.CreateLogger();
- var baseDirectory = AppContext.BaseDirectory;
-
- // Load any Roslyn assemblies from the extension directory
- var assemblyPaths = Directory.EnumerateFiles(baseDirectory, "Microsoft.CodeAnalysis*.dll");
- assemblyPaths = assemblyPaths.Concat(Directory.EnumerateFiles(baseDirectory, "Microsoft.ServiceHub*.dll"));
-
- // DevKit assemblies are not shipped in the main language server folder
- // and not included in ExtensionAssemblyPaths (they get loaded into the default ALC).
- // So manually add them to the MEF catalog here.
- if (devKitDependencyPath != null)
- {
- assemblyPaths = assemblyPaths.Concat(devKitDependencyPath);
- }
-
- // Add the extension assemblies to the MEF catalog.
- assemblyPaths = assemblyPaths.Concat(extensionManager.ExtensionAssemblyPaths);
-
- // Get the cached MEF composition or create a new one.
- var exportProviderFactory = await GetCompositionConfigurationAsync([.. assemblyPaths], assemblyLoader, cacheDirectory, logger);
-
- // Create an export provider, which represents a unique container of values.
- // You can create as many of these as you want, but typically an app needs just one.
- var exportProvider = exportProviderFactory.CreateExportProvider();
-
- // Immediately set the logger factory, so that way it'll be available for the rest of the composition
- exportProvider.GetExportedValue().SetFactory(loggerFactory);
-
- // Also add the ExtensionAssemblyManager so it will be available for the rest of the composition.
- exportProvider.GetExportedValue().SetMefExtensionAssemblyManager(extensionManager);
-
- return exportProvider;
- }
-
- private static async Task GetCompositionConfigurationAsync(
- ImmutableArray assemblyPaths,
- IAssemblyLoader assemblyLoader,
- string cacheDirectory,
- ILogger logger)
- {
- // Create a MEF resolver that can resolve assemblies in the extension contexts.
- var resolver = new Resolver(assemblyLoader);
-
- var compositionCacheFile = GetCompositionCacheFilePath(cacheDirectory, assemblyPaths);
-
- // Try to load a cached composition.
- try
- {
- if (File.Exists(compositionCacheFile))
- {
- logger.LogTrace($"Loading cached MEF catalog: {compositionCacheFile}");
-
- CachedComposition cachedComposition = new();
- using FileStream cacheStream = new(compositionCacheFile, FileMode.Open, FileAccess.Read, FileShare.Read, bufferSize: 4096, useAsync: true);
- var exportProviderFactory = await cachedComposition.LoadExportProviderFactoryAsync(cacheStream, resolver);
-
- return exportProviderFactory;
- }
- }
- catch (Exception ex)
- {
- // Log the error, and move on to recover by recreating the MEF composition.
- logger.LogError($"Loading cached MEF composition failed: {ex}");
- }
-
- logger.LogTrace($"Composing MEF catalog using:{Environment.NewLine}{string.Join($" {Environment.NewLine}", assemblyPaths)}.");
-
- var discovery = PartDiscovery.Combine(
- resolver,
- new AttributedPartDiscovery(resolver, isNonPublicSupported: true), // "NuGet MEF" attributes (Microsoft.Composition)
- new AttributedPartDiscoveryV1(resolver));
-
- var catalog = ComposableCatalog.Create(resolver)
- .AddParts(await discovery.CreatePartsAsync(assemblyPaths))
- .WithCompositionService(); // Makes an ICompositionService export available to MEF parts to import
-
- // Assemble the parts into a valid graph.
- var config = CompositionConfiguration.Create(catalog);
-
- // Verify we only have expected errors.
- ThrowOnUnexpectedErrors(config, catalog, logger);
-
- // Try to cache the composition.
- _cacheWriteTask = WriteCompositionCacheAsync(compositionCacheFile, config, logger).ReportNonFatalErrorAsync();
-
- // Prepare an ExportProvider factory based on this graph.
- return config.CreateExportProviderFactory();
- }
-
- private static string GetCompositionCacheFilePath(string cacheDirectory, ImmutableArray assemblyPaths)
- {
- // This should vary based on .NET runtime major version so that as some of our processes switch between our target
- // .NET version and the user's selected SDK runtime version (which may be newer), the MEF cache is kept isolated.
- // This can be important when the MEF catalog records full assembly names such as "System.Runtime, 8.0.0.0" yet
- // we might be running on .NET 7 or .NET 8, depending on the particular session and user settings.
- var cacheSubdirectory = $".NET {Environment.Version.Major}";
-
- return Path.Combine(cacheDirectory, cacheSubdirectory, $"c#-languageserver.{ComputeAssemblyHash(assemblyPaths)}.mef-composition");
-
- static string ComputeAssemblyHash(ImmutableArray assemblyPaths)
- {
- // Ensure AssemblyPaths are always in the same order.
- assemblyPaths = assemblyPaths.Sort();
-
- var assemblies = new StringBuilder();
- foreach (var assemblyPath in assemblyPaths)
- {
- // Include assembly path in the hash so that changes to the set of included
- // assemblies cause the composition to be rebuilt.
- assemblies.Append(assemblyPath);
- // Include the last write time in the hash so that newer assemblies written
- // to the same location cause the composition to be rebuilt.
- assemblies.Append(File.GetLastWriteTimeUtc(assemblyPath).ToString("F"));
- }
-
- var hash = XxHash128.Hash(Encoding.UTF8.GetBytes(assemblies.ToString()));
- // Convert to filename safe base64 string.
- return Convert.ToBase64String(hash).Replace('+', '-').Replace('/', '_').TrimEnd('=');
- }
- }
-
- private static async Task WriteCompositionCacheAsync(string compositionCacheFile, CompositionConfiguration config, ILogger logger)
- {
- try
- {
- await Task.Yield();
-
- if (Path.GetDirectoryName(compositionCacheFile) is string directory)
- {
- Directory.CreateDirectory(directory);
- }
-
- CachedComposition cachedComposition = new();
- var tempFilePath = Path.Combine(Path.GetTempPath(), Path.GetTempFileName());
- using (FileStream cacheStream = new(tempFilePath, FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true))
- {
- await cachedComposition.SaveAsync(config, cacheStream);
- }
-
- File.Move(tempFilePath, compositionCacheFile, overwrite: true);
- }
- catch (Exception ex)
- {
- logger.LogError($"Failed to save MEF cache: {ex}");
- }
- }
-
- private static void ThrowOnUnexpectedErrors(CompositionConfiguration configuration, ComposableCatalog catalog, ILogger logger)
- {
- // Verify that we have exactly the MEF errors that we expect. If we have less or more this needs to be updated to assert the expected behavior.
- // Currently we are expecting the following:
- // "----- CompositionError level 1 ------
- // Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.CodeMapper.CSharpMapCodeService.ctor(service): expected exactly 1 export matching constraints:
- // Contract name: Microsoft.CodeAnalysis.ExternalAccess.Copilot.CodeMapper.ICSharpCopilotMapCodeService
- // TypeIdentityName: Microsoft.CodeAnalysis.ExternalAccess.Copilot.CodeMapper.ICSharpCopilotMapCodeService
- // but found 0.
- // part definition Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.CodeMapper.CSharpMapCodeService
- //
- // Microsoft.CodeAnalysis.ExternalAccess.Pythia.PythiaSignatureHelpProvider.ctor(implementation): expected exactly 1 export matching constraints:
- // Contract name: Microsoft.CodeAnalysis.ExternalAccess.Pythia.Api.IPythiaSignatureHelpProviderImplementation
- // TypeIdentityName: Microsoft.CodeAnalysis.ExternalAccess.Pythia.Api.IPythiaSignatureHelpProviderImplementation
- // but found 0.
- // part definition Microsoft.CodeAnalysis.ExternalAccess.Pythia.PythiaSignatureHelpProvider
- //
- // Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.SemanticSearch.CopilotSemanticSearchQueryExecutor.ctor(workspaceProvider): expected exactly 1 export matching constraints:
- // Contract name: Microsoft.CodeAnalysis.Host.IHostWorkspaceProvider
- // TypeIdentityName: Microsoft.CodeAnalysis.Host.IHostWorkspaceProvider
- // but found 0.
- // part definition Microsoft.CodeAnalysis.ExternalAccess.Copilot.Internal.SemanticSearch.CopilotSemanticSearchQueryExecutor
-
- var erroredParts = configuration.CompositionErrors.FirstOrDefault()?.SelectMany(error => error.Parts).Select(part => part.Definition.Type.Name) ?? [];
- var expectedErroredParts = new string[] { "CSharpMapCodeService", "PythiaSignatureHelpProvider", "CopilotSemanticSearchQueryExecutor" };
- var hasUnexpectedErroredParts = erroredParts.Any(part => !expectedErroredParts.Contains(part));
-
- if (hasUnexpectedErroredParts || !catalog.DiscoveredParts.DiscoveryErrors.IsEmpty)
- {
- try
- {
- catalog.DiscoveredParts.ThrowOnErrors();
- configuration.ThrowOnErrors();
- }
- catch (CompositionFailedException ex)
- {
- // The ToString for the composition failed exception doesn't output a nice set of errors by default, so log it separately
- logger.LogError($"Encountered errors in the MEF composition:{Environment.NewLine}{ex.ErrorsAsString}");
- throw;
- }
- }
- }
-
- internal static class TestAccessor
- {
-#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods
- public static Task? GetCacheWriteTask() => _cacheWriteTask;
-#pragma warning restore VSTHRD200 // Use "Async" suffix for async methods
- }
-}
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServerExportProviderBuilder.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServerExportProviderBuilder.cs
new file mode 100644
index 0000000000000..82eb3cf9d89d7
--- /dev/null
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/LanguageServerExportProviderBuilder.cs
@@ -0,0 +1,120 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections.Immutable;
+using Microsoft.CodeAnalysis.LanguageServer.Logging;
+using Microsoft.CodeAnalysis.LanguageServer.Services;
+using Microsoft.CodeAnalysis.PooledObjects;
+using Microsoft.CodeAnalysis.Remote;
+using Microsoft.Extensions.Logging;
+using Microsoft.VisualStudio.Composition;
+
+namespace Microsoft.CodeAnalysis.LanguageServer;
+
+internal sealed class LanguageServerExportProviderBuilder : ExportProviderBuilder
+{
+ private readonly ILogger _logger;
+
+ // For testing purposes, track the last cache write task.
+ private static Task? s_cacheWriteTask_forTestingPurposesOnly;
+
+ private LanguageServerExportProviderBuilder(
+ ImmutableArray assemblyPaths,
+ Resolver resolver,
+ string cacheDirectory,
+ string catalogPrefix,
+ ILoggerFactory loggerFactory)
+ : base(assemblyPaths, resolver, cacheDirectory, catalogPrefix)
+ {
+ _logger = loggerFactory.CreateLogger();
+ }
+
+ public static async Task CreateExportProviderAsync(
+ ExtensionAssemblyManager extensionManager,
+ IAssemblyLoader assemblyLoader,
+ string? devKitDependencyPath,
+ string cacheDirectory,
+ ILoggerFactory loggerFactory,
+ CancellationToken cancellationToken)
+ {
+ var baseDirectory = AppContext.BaseDirectory;
+
+ // Load any Roslyn assemblies from the extension directory
+ using var _ = ArrayBuilder.GetInstance(out var assemblyPathsBuilder);
+
+ // Don't catch IO exceptions as it's better to fail to build the catalog than give back
+ // a partial catalog that will surely blow up later.
+ assemblyPathsBuilder.AddRange(Directory.EnumerateFiles(baseDirectory, "Microsoft.CodeAnalysis*.dll"));
+ assemblyPathsBuilder.AddRange(Directory.EnumerateFiles(baseDirectory, "Microsoft.ServiceHub*.dll"));
+
+ // DevKit assemblies are not shipped in the main language server folder
+ // and not included in ExtensionAssemblyPaths (they get loaded into the default ALC).
+ // So manually add them to the MEF catalog here.
+ if (devKitDependencyPath != null)
+ assemblyPathsBuilder.Add(devKitDependencyPath);
+
+ // Add the extension assemblies to the MEF catalog.
+ assemblyPathsBuilder.AddRange(extensionManager.ExtensionAssemblyPaths);
+
+ // Create a MEF resolver that can resolve assemblies in the extension contexts.
+ var builder = new LanguageServerExportProviderBuilder(
+ assemblyPathsBuilder.ToImmutableAndClear(),
+ new Resolver(assemblyLoader),
+ cacheDirectory,
+ catalogPrefix: "c#-languageserver",
+ loggerFactory);
+ var exportProvider = await builder.CreateExportProviderAsync(cancellationToken);
+
+ // Also add the ExtensionAssemblyManager so it will be available for the rest of the composition.
+ exportProvider.GetExportedValue().SetMefExtensionAssemblyManager(extensionManager);
+
+ // Immediately set the logger factory, so that way it'll be available for the rest of the composition
+ exportProvider.GetExportedValue().SetFactory(loggerFactory);
+
+ return exportProvider;
+ }
+
+ protected override void LogError(string message)
+ => _logger.LogError(message);
+
+ protected override void LogTrace(string message)
+ => _logger.LogTrace(message);
+
+ protected override Task CreateExportProviderAsync(CancellationToken cancellationToken)
+ {
+ // Clear any previous cache write task, so that it is easy to discern whether
+ // a cache write was attempted.
+ s_cacheWriteTask_forTestingPurposesOnly = null;
+
+ return base.CreateExportProviderAsync(cancellationToken);
+ }
+
+ protected override bool ContainsUnexpectedErrors(IEnumerable erroredParts, ImmutableList partDiscoveryExceptions)
+ {
+ // Verify that we have exactly the MEF errors that we expect. If we have less or more this needs to be updated to assert the expected behavior.
+ var expectedErrorPartsSet = new HashSet(["CSharpMapCodeService", "PythiaSignatureHelpProvider", "CopilotSemanticSearchQueryExecutor"]);
+ var hasUnexpectedErroredParts = erroredParts.Any(part => !expectedErrorPartsSet.Contains(part));
+
+ return hasUnexpectedErroredParts || !partDiscoveryExceptions.IsEmpty;
+ }
+
+ protected override Task WriteCompositionCacheAsync(string compositionCacheFile, CompositionConfiguration config, CancellationToken cancellationToken)
+ {
+ s_cacheWriteTask_forTestingPurposesOnly = base.WriteCompositionCacheAsync(compositionCacheFile, config, cancellationToken);
+
+ return s_cacheWriteTask_forTestingPurposesOnly;
+ }
+
+ protected override void PerformCacheDirectoryCleanup(DirectoryInfo directoryInfo, CancellationToken cancellationToken)
+ {
+ // No cache directory cleanup is needed for the language server.
+ }
+
+ internal static class TestAccessor
+ {
+#pragma warning disable VSTHRD200 // Use "Async" suffix for async methods
+ public static Task? GetCacheWriteTask() => s_cacheWriteTask_forTestingPurposesOnly;
+#pragma warning restore VSTHRD200 // Use "Async" suffix for async methods
+ }
+}
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs
index 56c2d092bf6fe..779bb65eea742 100644
--- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Program.cs
@@ -2,7 +2,6 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System.Collections.Immutable;
using System.CommandLine;
using System.Diagnostics;
using System.IO.Pipes;
@@ -19,7 +18,6 @@
using Microsoft.CodeAnalysis.LanguageServer.StarredSuggestions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Console;
-using Roslyn.Utilities;
using RoslynLog = Microsoft.CodeAnalysis.Internal.Log;
// Setting the title can fail if the process is run without a window, such
@@ -98,7 +96,7 @@ static async Task RunAsync(ServerConfiguration serverConfiguration, Cancellation
var cacheDirectory = Path.Combine(Path.GetDirectoryName(typeof(Program).Assembly.Location)!, "cache");
- using var exportProvider = await ExportProviderBuilder.CreateExportProviderAsync(extensionManager, assemblyLoader, serverConfiguration.DevKitDependencyPath, cacheDirectory, loggerFactory);
+ using var exportProvider = await LanguageServerExportProviderBuilder.CreateExportProviderAsync(extensionManager, assemblyLoader, serverConfiguration.DevKitDependencyPath, cacheDirectory, loggerFactory, cancellationToken);
// LSP server doesn't have the pieces yet to support 'balanced' mode for source-generators. Hardcode us to
// 'automatic' for now.
diff --git a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Services/ExtensionAssemblyManager.cs b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Services/ExtensionAssemblyManager.cs
index d1ca799deec39..7c4b98c63a987 100644
--- a/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Services/ExtensionAssemblyManager.cs
+++ b/src/LanguageServer/Microsoft.CodeAnalysis.LanguageServer/Services/ExtensionAssemblyManager.cs
@@ -7,6 +7,7 @@
using System.Runtime.Loader;
using Microsoft.CodeAnalysis.LanguageServer.StarredSuggestions;
using Microsoft.CodeAnalysis.PooledObjects;
+using Microsoft.CodeAnalysis.Remote;
using Microsoft.Extensions.Logging;
namespace Microsoft.CodeAnalysis.LanguageServer.Services;
diff --git a/src/VisualStudio/Core/Def/Remote/VisualStudioRemoteHostClientProvider.cs b/src/VisualStudio/Core/Def/Remote/VisualStudioRemoteHostClientProvider.cs
index 1f133e65019a9..76e12659cbadb 100644
--- a/src/VisualStudio/Core/Def/Remote/VisualStudioRemoteHostClientProvider.cs
+++ b/src/VisualStudio/Core/Def/Remote/VisualStudioRemoteHostClientProvider.cs
@@ -17,7 +17,10 @@
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Shared.TestHooks;
+using Microsoft.VisualStudio.Settings;
+using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.ServiceBroker;
+using Microsoft.VisualStudio.Shell.Settings;
using Microsoft.VisualStudio.Threading;
using Roslyn.Utilities;
using VSThreading = Microsoft.VisualStudio.Threading;
@@ -31,6 +34,7 @@ internal sealed class Factory : IWorkspaceServiceFactory
{
private readonly VisualStudioWorkspace _vsWorkspace;
private readonly IVsService _brokeredServiceContainer;
+ private readonly IServiceProvider _serviceProvider;
private readonly AsynchronousOperationListenerProvider _listenerProvider;
private readonly RemoteServiceCallbackDispatcherRegistry _callbackDispatchers;
private readonly IGlobalOptionService _globalOptions;
@@ -44,6 +48,7 @@ internal sealed class Factory : IWorkspaceServiceFactory
public Factory(
VisualStudioWorkspace vsWorkspace,
IVsService brokeredServiceContainer,
+ SVsServiceProvider serviceProvider,
AsynchronousOperationListenerProvider listenerProvider,
IGlobalOptionService globalOptions,
IThreadingContext threadingContext,
@@ -52,6 +57,7 @@ public Factory(
_globalOptions = globalOptions;
_vsWorkspace = vsWorkspace;
_brokeredServiceContainer = brokeredServiceContainer;
+ _serviceProvider = serviceProvider;
_listenerProvider = listenerProvider;
_threadingContext = threadingContext;
_callbackDispatchers = new RemoteServiceCallbackDispatcherRegistry(callbackDispatchers);
@@ -79,7 +85,7 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
// If we have a cached vs instance, then we can return that instance since we know they have the same host services.
// Otherwise, create and cache an instance based on vs workspace for future callers with same services.
if (_cachedVSInstance is null)
- _cachedVSInstance = new VisualStudioRemoteHostClientProvider(_vsWorkspace.Services.SolutionServices, _globalOptions, _brokeredServiceContainer, _threadingContext, _listenerProvider, _callbackDispatchers);
+ _cachedVSInstance = new VisualStudioRemoteHostClientProvider(_vsWorkspace.Services.SolutionServices, _globalOptions, _brokeredServiceContainer, _serviceProvider, _threadingContext, _listenerProvider, _callbackDispatchers);
return _cachedVSInstance;
}
@@ -90,14 +96,17 @@ public IWorkspaceService CreateService(HostWorkspaceServices workspaceServices)
private readonly IGlobalOptionService _globalOptions;
private readonly VSThreading.AsyncLazy _lazyClient;
private readonly IVsService _brokeredServiceContainer;
+ private readonly IServiceProvider _serviceProvider;
private readonly AsynchronousOperationListenerProvider _listenerProvider;
private readonly RemoteServiceCallbackDispatcherRegistry _callbackDispatchers;
private readonly TaskCompletionSource _clientCreationSource = new(TaskCreationOptions.RunContinuationsAsynchronously);
+ private readonly IThreadingContext _threadingContext;
private VisualStudioRemoteHostClientProvider(
SolutionServices services,
IGlobalOptionService globalOptions,
IVsService brokeredServiceContainer,
+ IServiceProvider serviceProvider,
IThreadingContext threadingContext,
AsynchronousOperationListenerProvider listenerProvider,
RemoteServiceCallbackDispatcherRegistry callbackDispatchers)
@@ -105,12 +114,14 @@ private VisualStudioRemoteHostClientProvider(
Services = services;
_globalOptions = globalOptions;
_brokeredServiceContainer = brokeredServiceContainer;
+ _serviceProvider = serviceProvider;
_listenerProvider = listenerProvider;
_callbackDispatchers = callbackDispatchers;
+ _threadingContext = threadingContext;
// using VS AsyncLazy here since Roslyn's is not compatible with JTF.
// Our ServiceBroker services may be invoked by other VS components under JTF.
- _lazyClient = new VSThreading.AsyncLazy(CreateHostClientAsync, threadingContext.JoinableTaskFactory);
+ _lazyClient = new VSThreading.AsyncLazy(CreateHostClientAsync, _threadingContext.JoinableTaskFactory);
}
private async Task CreateHostClientAsync()
@@ -122,9 +133,10 @@ private VisualStudioRemoteHostClientProvider(
var configuration =
_globalOptions.GetOption(RemoteHostOptionsStorage.OOPServerGCFeatureFlag) ? RemoteProcessConfiguration.ServerGC : 0;
+ var localSettingsDirectory = new ShellSettingsManager(_serviceProvider).GetApplicationDataFolder(ApplicationDataFolder.LocalSettings);
// VS AsyncLazy does not currently support cancellation:
- var client = await ServiceHubRemoteHostClient.CreateAsync(Services, configuration, _listenerProvider, serviceBroker, _callbackDispatchers, CancellationToken.None).ConfigureAwait(false);
+ var client = await ServiceHubRemoteHostClient.CreateAsync(Services, configuration, localSettingsDirectory, _listenerProvider, serviceBroker, _callbackDispatchers, _threadingContext.DisposalToken).ConfigureAwait(false);
// proffer in-proc brokered services:
_ = brokeredServiceContainer.Proffer(SolutionAssetProvider.ServiceDescriptor, (_, _, _, _) => ValueTaskFactory.FromResult