Skip to content

Commit

Permalink
Teach OOP how to MEF (#9953)
Browse files Browse the repository at this point in the history
I'm mainly putting this up so @DustinCampbell can tell me what I did
wrong :)

As part of cohosting we're going to need a lot more things to run in
OOP, so its probably best to move away from these hand-constructed
services that it has. This PR introduced MEF, in a reasonably
unsophisticated way, into the procedure so that services can at least
get some things from an `ExportProvider`. Once in that world, obviously
everything works as expected with attributes etc.

Along the way I also found that our telemetry reporting in OOP wasn't
doing anything, so fixed that and moved it to the MEF composition too.
Also improved the `launchSettings.json` file a little, and found it was
previously in the `.gitignore`. This seemed wrong to me, but maybe
@dotnet/razor-compiler will have something different to say about that?
I can always move the compiler one back in if necessary.

Part of #9519
  • Loading branch information
davidwengier committed Feb 21, 2024
2 parents a76f73b + c4fd0aa commit e05abe5
Show file tree
Hide file tree
Showing 12 changed files with 110 additions and 18 deletions.
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
*.user
*.userosscache
*.sln.docstates
launchSettings.json

# Build results
artifacts/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"profiles": {
"SampleApp": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:53331;http://localhost:53332"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@
<ProjectReference Include="..\Microsoft.CodeAnalysis.Razor.Workspaces\Microsoft.CodeAnalysis.Razor.Workspaces.csproj" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\Microsoft.VisualStudio.Editor.Razor\Telemetry\TelemetryReporter.cs" Link="Telemetry\TelemetryReporter.cs" />
</ItemGroup>

<ItemGroup>
<!--
Pinning packages to avoid misaligned reference CI failures.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Composition;
using Microsoft.VisualStudio.Threading;

namespace Microsoft.CodeAnalysis.Remote.Razor;

internal abstract partial class RazorServiceFactoryBase<TService>
{
internal static readonly ImmutableArray<Assembly> RemoteHostAssemblies = [typeof(RazorServiceFactoryBase<TService>).Assembly];

#pragma warning disable VSTHRD012 // Provide JoinableTaskFactory where allowed
private static readonly AsyncLazy<ExportProvider> s_exportProviderLazy = new(GetExportProviderAsync);
#pragma warning restore VSTHRD012 // Provide JoinableTaskFactory where allowed

// Inspired by https://github.com/dotnet/roslyn/blob/25aa74d725e801b8232dbb3e5abcda0fa72da8c5/src/Workspaces/Remote/ServiceHub/Host/RemoteWorkspaceManager.cs#L77
private static async Task<ExportProvider> GetExportProviderAsync()
{
var resolver = Resolver.DefaultInstance;
var discovery = new AttributedPartDiscovery(resolver, isNonPublicSupported: true); // MEFv2 only
var parts = await discovery.CreatePartsAsync(RemoteHostAssemblies).ConfigureAwait(false);
var catalog = ComposableCatalog.Create(resolver).AddParts(parts);

var configuration = CompositionConfiguration.Create(catalog).ThrowOnErrors();
var runtimeComposition = RuntimeComposition.CreateRuntimeComposition(configuration);
var exportProviderFactory = runtimeComposition.CreateExportProviderFactory();
return exportProviderFactory.CreateExportProvider();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
using System.IO;
using System.IO.Pipelines;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Telemetry;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.ServiceHub.Framework;
using Microsoft.ServiceHub.Framework.Services;
using Microsoft.VisualStudio.Composition;
using Nerdbank.Streams;

namespace Microsoft.CodeAnalysis.Remote.Razor;
Expand All @@ -19,10 +19,9 @@ namespace Microsoft.CodeAnalysis.Remote.Razor;
/// <remarks>
/// Implementors of <see cref="IServiceHubServiceFactory" /> (and thus this class) MUST provide a parameterless constructor or ServiceHub will fail to construct them.
/// </remarks>
internal abstract class RazorServiceFactoryBase<TService> : IServiceHubServiceFactory where TService : class
internal abstract partial class RazorServiceFactoryBase<TService> : IServiceHubServiceFactory where TService : class
{
private readonly RazorServiceDescriptorsWrapper _razorServiceDescriptors;
private ITelemetryReporter? _telemetryReporter;

/// <summary>
/// </summary>
Expand All @@ -45,23 +44,23 @@ public Task<object> CreateAsync(
// Dispose the AuthorizationServiceClient since we won't be using it
authorizationServiceClient?.Dispose();

_telemetryReporter = (ITelemetryReporter)hostProvidedServices.GetService(typeof(ITelemetryReporter));

return Task.FromResult((object)Create(stream.UsePipe(), serviceBroker));
return CreateAsync(stream.UsePipe(), serviceBroker);
}

internal TService Create(IDuplexPipe pipe, IServiceBroker serviceBroker)
private async Task<object> CreateAsync(IDuplexPipe pipe, IServiceBroker serviceBroker)
{
var descriptor = _razorServiceDescriptors.GetDescriptorForServiceFactory(typeof(TService));
var serverConnection = descriptor.ConstructRpcConnection(pipe);

var service = CreateService(serviceBroker, _telemetryReporter ?? NoOpTelemetryReporter.Instance);
var exportProvider = await s_exportProviderLazy.GetValueAsync().ConfigureAwait(false);

var service = CreateService(serviceBroker, exportProvider);

serverConnection.AddLocalRpcTarget(service);
serverConnection.StartListening();

return service;
}

protected abstract TService CreateService(IServiceBroker serviceBroker, ITelemetryReporter telemetryReporter);
protected abstract TService CreateService(IServiceBroker serviceBroker, ExportProvider exportProvider);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
// Licensed under the MIT license. See License.txt in the project root for license information.

using System.Collections.Immutable;
using System.Composition;
using Microsoft.AspNetCore.Razor.Serialization;
using Microsoft.AspNetCore.Razor.Utilities;

namespace Microsoft.CodeAnalysis.Remote.Razor;

[Export(typeof(RemoteTagHelperDeltaProvider)), Shared]
internal class RemoteTagHelperDeltaProvider
{
private readonly TagHelperResultCache _resultCache = new();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
using System.Collections.Immutable;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
using Microsoft.AspNetCore.Razor.Language;
using Microsoft.AspNetCore.Razor.PooledObjects;
using Microsoft.AspNetCore.Razor.Serialization;
using Microsoft.AspNetCore.Razor.Telemetry;
using Microsoft.AspNetCore.Razor.Utilities;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.ExternalAccess.Razor.Api;
using Microsoft.ServiceHub.Framework;
using Microsoft.VisualStudio.Composition;

namespace Microsoft.CodeAnalysis.Remote.Razor;

Expand All @@ -20,11 +21,11 @@ internal sealed class RemoteTagHelperProviderService : RazorServiceBase, IRemote
private readonly RemoteTagHelperResolver _tagHelperResolver;
private readonly RemoteTagHelperDeltaProvider _tagHelperDeltaProvider;

internal RemoteTagHelperProviderService(IServiceBroker serviceBroker, ITelemetryReporter telemetryReporter)
internal RemoteTagHelperProviderService(IServiceBroker serviceBroker, ExportProvider exportProvider)
: base(serviceBroker)
{
_tagHelperResolver = new RemoteTagHelperResolver(telemetryReporter);
_tagHelperDeltaProvider = new RemoteTagHelperDeltaProvider();
_tagHelperResolver = exportProvider.GetExportedValue<RemoteTagHelperResolver>().AssumeNotNull();
_tagHelperDeltaProvider = exportProvider.GetExportedValue<RemoteTagHelperDeltaProvider>().AssumeNotNull();
}

public ValueTask<FetchTagHelpersResult> FetchTagHelpersAsync(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using Microsoft.AspNetCore.Razor.Telemetry;
using Microsoft.ServiceHub.Framework;
using Microsoft.VisualStudio.Composition;

namespace Microsoft.CodeAnalysis.Remote.Razor;

Expand All @@ -14,6 +14,6 @@ public RemoteTagHelperProviderServiceFactory()
{
}

protected override IRemoteTagHelperProviderService CreateService(IServiceBroker serviceBroker, ITelemetryReporter telemetryReporter)
=> new RemoteTagHelperProviderService(serviceBroker, telemetryReporter);
protected override IRemoteTagHelperProviderService CreateService(IServiceBroker serviceBroker, ExportProvider exportProvider)
=> new RemoteTagHelperProviderService(serviceBroker, exportProvider);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor;
Expand All @@ -13,6 +14,8 @@

namespace Microsoft.CodeAnalysis.Remote.Razor;

[Export(typeof(RemoteTagHelperResolver)), Shared]
[method: ImportingConstructor]
internal class RemoteTagHelperResolver(ITelemetryReporter telemetryReporter)
{
/// <summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.

using System.Composition;
using Microsoft.AspNetCore.Razor.Telemetry;
using Microsoft.VisualStudio.Telemetry;

namespace Microsoft.CodeAnalysis.Remote.Razor.Telemetry;

[Export(typeof(ITelemetryReporter)), Shared]
internal class OutOfProcessTelemetryReporter : TelemetryReporter
{
public OutOfProcessTelemetryReporter()
: base([TelemetryService.DefaultSession])
{
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"profiles": {
"Razor": {
"commandName": "Project",
"environmentVariables": {
"CPS_DiagnosticRuntime": "1",
"CPS_MetricsCollection": "1",
"ServiceHubTraceLevel": "All",
},
"nativeDebugging": true
},
"Razor (with ServiceHub debugging)": {
"commandName": "Project",
"environmentVariables": {
"CPS_DiagnosticRuntime": "1",
"CPS_MetricsCollection": "1",
"ServiceHubTraceLevel": "All",
"SERVICEHUBDEBUGHOSTONSTARTUP": "ServiceHub.RoslynCodeAnalysisService.exe"
},
"nativeDebugging": true
}
}
}

0 comments on commit e05abe5

Please sign in to comment.