|
// 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;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.Host.Mef;
using Roslyn.Utilities;
[assembly: DebuggerTypeProxy(typeof(MefLanguageServices.LazyServiceMetadataDebuggerProxy), Target = typeof(ImmutableArray<Lazy<ILanguageService, WorkspaceServiceMetadata>>))]
namespace Microsoft.CodeAnalysis.Host.Mef;
internal sealed class MefLanguageServices : HostLanguageServices
{
private readonly MefWorkspaceServices _workspaceServices;
private readonly string _language;
private readonly ImmutableArray<(Lazy<ILanguageService, LanguageServiceMetadata> lazyService, bool usesFactory)> _services;
private ImmutableDictionary<Type, (Lazy<ILanguageService, LanguageServiceMetadata>? lazyService, bool usesFactory)> _serviceMap
= ImmutableDictionary<Type, (Lazy<ILanguageService, LanguageServiceMetadata>? lazyService, bool usesFactory)>.Empty;
private readonly object _gate = new();
private readonly HashSet<IDisposable> _ownedDisposableServices = new(ReferenceEqualityComparer.Instance);
public MefLanguageServices(
MefWorkspaceServices workspaceServices,
string language)
{
_workspaceServices = workspaceServices;
_language = language;
var hostServices = workspaceServices.HostExportProvider;
var services = hostServices.GetExports<ILanguageService, LanguageServiceMetadata>()
.Select(lz => (lazyService: lz, usesFactory: false));
var factories = hostServices.GetExports<ILanguageServiceFactory, LanguageServiceMetadata>()
.Select(lz => (lazyService: new Lazy<ILanguageService, LanguageServiceMetadata>(() => lz.Value.CreateLanguageService(this), lz.Metadata), usesFactory: true));
_services = [.. services.Concat(factories).Where(lz => lz.lazyService.Metadata.Language == language)];
}
public override HostWorkspaceServices WorkspaceServices => _workspaceServices;
public override string Language => _language;
public override void Dispose()
{
ImmutableArray<IDisposable> disposableServices;
lock (_gate)
{
disposableServices = [.. _ownedDisposableServices];
_ownedDisposableServices.Clear();
}
// Take care to give all disposal parts a chance to dispose even if some parts throw exceptions.
List<Exception>? exceptions = null;
foreach (var service in disposableServices)
{
MefUtilities.DisposeWithExceptionTracking(service, ref exceptions);
}
if (exceptions is not null)
{
throw new AggregateException(CompilerExtensionsResources.Instantiated_parts_threw_exceptions_from_IDisposable_Dispose, exceptions);
}
base.Dispose();
}
public override TLanguageService GetService<TLanguageService>()
{
if (TryGetService<TLanguageService>(static _ => true, out var service))
{
return service;
}
else
{
return default!;
}
}
internal bool TryGetService<TLanguageService>(HostWorkspaceServices.MetadataFilter filter, [MaybeNullWhen(false)] out TLanguageService languageService)
{
if (TryGetService(typeof(TLanguageService), out var lazyService, out var usesFactory)
&& filter(lazyService.Metadata.Data))
{
// MEF language service instances created by a factory are not owned by the MEF catalog or disposed
// when the MEF catalog is disposed. Whenever we are potentially going to create an instance of a
// service provided by a factory, we need to check if the resulting service implements IDisposable. The
// specific conditions here are:
//
// * usesFactory: This is true when the language service is provided by a factory. Services provided
// directly are owned by the MEF catalog so they do not need to be tracked by the workspace.
// * IsValueCreated: This will be false at least once prior to accessing the lazy value. Once the value
// is known to be created, we no longer need to try adding it to _ownedDisposableServices, so we use a
// lock-free fast path.
var checkAddDisposable = usesFactory && !lazyService.IsValueCreated;
languageService = (TLanguageService)lazyService.Value;
if (checkAddDisposable && languageService is IDisposable disposable)
{
lock (_gate)
{
_ownedDisposableServices.Add(disposable);
}
}
return true;
}
else
{
languageService = default;
return false;
}
}
private bool TryGetService(Type serviceType, [NotNullWhen(true)] out Lazy<ILanguageService, LanguageServiceMetadata>? lazyService, out bool usesFactory)
{
if (!_serviceMap.TryGetValue(serviceType, out var service))
{
service = ImmutableInterlocked.GetOrAdd(ref _serviceMap, serviceType, serviceType => LayeredServiceUtilities.PickService(serviceType, _workspaceServices.WorkspaceKind, _services));
}
(lazyService, usesFactory) = (service.lazyService, service.usesFactory);
return lazyService != null;
}
internal sealed class LazyServiceMetadataDebuggerProxy(ImmutableArray<Lazy<ILanguageService, LanguageServiceMetadata>> services)
{
public (string type, string layer)[] Metadata
=> [.. services.Select(s => (s.Metadata.ServiceType, s.Metadata.Layer))];
}
}
|