Move boiler plate logging and error handling into the service

This commit is contained in:
David Wengier 2024-05-16 19:32:47 +10:00
Родитель 8de1a27532
Коммит 36eeb135c4
7 изменённых файлов: 85 добавлений и 116 удалений

Просмотреть файл

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
@ -9,5 +10,5 @@ namespace Microsoft.CodeAnalysis.Razor.Remote;
internal interface IRemoteClientProvider
{
Task<RazorRemoteHostClient?> TryGetClientAsync(CancellationToken cancellationToken);
ValueTask<TResult?> TryInvokeAsync<TService, TResult>(Solution solution, Func<TService, RazorPinnedSolutionInfoWrapper, CancellationToken, ValueTask<TResult>> invocation, CancellationToken cancellationToken) where TService : class;
}

Просмотреть файл

@ -1,7 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
@ -35,7 +34,7 @@ internal class CohostLinkedEditingRangeEndpoint(IRemoteClientProvider remoteClie
protected override bool RequiresLSPSolution => true;
public Registration? GetRegistration(VSInternalClientCapabilities clientCapabilities, DocumentFilter[] filter, RazorCohostRequestContext requestContext)
{
{
if (clientCapabilities.TextDocument?.LinkedEditingRange?.DynamicRegistration == true)
{
return new Registration
@ -58,35 +57,18 @@ internal class CohostLinkedEditingRangeEndpoint(IRemoteClientProvider remoteClie
{
var razorDocument = context.TextDocument.AssumeNotNull();
var remoteClient = await _remoteClientProvider.TryGetClientAsync(cancellationToken).ConfigureAwait(false);
if (remoteClient is null)
{
_logger.LogWarning($"Couldn't get remote client");
return null;
}
var linkedRanges = await _remoteClientProvider.TryInvokeAsync<IRemoteLinkedEditingRangeService, LinePositionSpan[]?>(
razorDocument.Project.Solution,
(service, solutionInfo, cancellationToken) => service.GetRangesAsync(solutionInfo, razorDocument.Id, request.Position.ToLinePosition(), cancellationToken),
cancellationToken).ConfigureAwait(false);
try
if (linkedRanges is [{ } span1, { } span2])
{
var data = await remoteClient.TryInvokeAsync<IRemoteLinkedEditingRangeService, LinePositionSpan[]?>(
razorDocument.Project.Solution,
(service, solutionInfo, cancellationToken) => service.GetRangesAsync(solutionInfo, razorDocument.Id, request.Position.ToLinePosition(), cancellationToken),
cancellationToken).ConfigureAwait(false);
if (data.Value is { } linkedRanges && linkedRanges.Length == 2)
return new LinkedEditingRanges
{
var ranges = new Range[2] { linkedRanges[0].ToRange(), linkedRanges[1].ToRange() };
return new LinkedEditingRanges
{
Ranges = ranges,
WordPattern = LinkedEditingRangeHelper.WordPattern
};
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error calling remote");
return null;
Ranges = [span1.ToRange(), span2.ToRange()],
WordPattern = LinkedEditingRangeHelper.WordPattern
};
}
return null;

Просмотреть файл

@ -73,40 +73,24 @@ internal sealed class CohostSemanticTokensRangeEndpoint(
var razorDocument = context.TextDocument.AssumeNotNull();
var span = request.Range.ToLinePositionSpan();
var remoteClient = await _remoteClientProvider.TryGetClientAsync(cancellationToken).ConfigureAwait(false);
var colorBackground = _clientSettingsManager.GetClientSettings().AdvancedSettings.ColorBackground;
if (remoteClient is null)
var correlationId = Guid.NewGuid();
using var _ = _telemetryReporter.TrackLspRequest(Methods.TextDocumentSemanticTokensRangeName, RazorLSPConstants.CohostLanguageServerName, correlationId);
var tokens = await _remoteClientProvider.TryInvokeAsync<IRemoteSemanticTokensService, int[]?>(
razorDocument.Project.Solution,
(service, solutionInfo, cancellationToken) => service.GetSemanticTokensDataAsync(solutionInfo, razorDocument.Id, span, colorBackground, correlationId, cancellationToken),
cancellationToken).ConfigureAwait(false);
if (tokens is not null)
{
_logger.LogWarning($"Couldn't get remote client");
return null;
}
try
{
var colorBackground = _clientSettingsManager.GetClientSettings().AdvancedSettings.ColorBackground;
var correlationId = Guid.NewGuid();
using var _ = _telemetryReporter.TrackLspRequest(Methods.TextDocumentSemanticTokensRangeName, RazorLSPConstants.CohostLanguageServerName, correlationId);
var data = await remoteClient.TryInvokeAsync<IRemoteSemanticTokensService, int[]?>(
razorDocument.Project.Solution,
(service, solutionInfo, cancellationToken) => service.GetSemanticTokensDataAsync(solutionInfo, razorDocument.Id, span, colorBackground, correlationId, cancellationToken),
cancellationToken).ConfigureAwait(false);
if (data.Value is not { } tokens)
{
return null;
}
return new SemanticTokens
{
Data = tokens
};
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error calling remote");
return null;
}
return null;
}
}

Просмотреть файл

@ -66,29 +66,20 @@ internal class CohostUriPresentationEndpoint(
{
var razorDocument = context.TextDocument.AssumeNotNull();
var remoteClient = await _remoteClientProvider.TryGetClientAsync(cancellationToken).ConfigureAwait(false);
if (remoteClient is null)
{
_logger.LogWarning($"Couldn't get remote client");
return null;
}
var data = await _remoteClientProvider.TryInvokeAsync<IRemoteUriPresentationService, TextChange?>(
razorDocument.Project.Solution,
(service, solutionInfo, cancellationToken) => service.GetPresentationAsync(solutionInfo, razorDocument.Id, request.Range.ToLinePositionSpan(), request.Uris, cancellationToken),
cancellationToken).ConfigureAwait(false);
try
// If we got a response back, then either Razor or C# wants to do something with this, so we're good to go
if (data is { } textChange)
{
var data = await remoteClient.TryInvokeAsync<IRemoteUriPresentationService, TextChange?>(
razorDocument.Project.Solution,
(service, solutionInfo, cancellationToken) => service.GetPresentationAsync(solutionInfo, razorDocument.Id, request.Range.ToLinePositionSpan(), request.Uris, cancellationToken),
cancellationToken).ConfigureAwait(false);
var sourceText = await razorDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);
// If we got a response back, then either Razor or C# wants to do something with this, so we're good to go
if (data.Value is { } textChange)
return new WorkspaceEdit
{
var sourceText = await razorDocument.GetTextAsync(cancellationToken).ConfigureAwait(false);
return new WorkspaceEdit
DocumentChanges = new TextDocumentEdit[]
{
DocumentChanges = new TextDocumentEdit[]
{
new TextDocumentEdit
{
TextDocument = new()
@ -97,14 +88,8 @@ internal class CohostUriPresentationEndpoint(
},
Edits = [textChange.ToTextEdit(sourceText)]
}
}
};
}
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error calling remote");
return null;
}
};
}
// If we didn't get anything from Razor or Roslyn, lets ask Html what they want to do

Просмотреть файл

@ -28,25 +28,11 @@ internal sealed class HtmlDocumentPublisher(
private readonly TrackingLSPDocumentManager _documentManager = documentManager as TrackingLSPDocumentManager ?? throw new InvalidOperationException("Expected TrackingLSPDocumentManager");
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<HtmlDocumentPublisher>();
public async Task<string?> GetHtmlSourceFromOOPAsync(TextDocument document, CancellationToken cancellationToken)
public Task<string?> GetHtmlSourceFromOOPAsync(TextDocument document, CancellationToken cancellationToken)
{
var client = await _remoteClientProvider.TryGetClientAsync(cancellationToken).ConfigureAwait(false);
if (client is null)
{
_logger.LogError($"Couldn't get remote client for html document generation for {document.FilePath}. Html document contents will be stale");
return null;
}
if (cancellationToken.IsCancellationRequested)
{
return null;
}
var htmlText = await client.TryInvokeAsync<IRemoteHtmlDocumentService, string?>(document.Project.Solution,
return _remoteClientProvider.TryInvokeAsync<IRemoteHtmlDocumentService, string>(document.Project.Solution,
(service, solutionInfo, ct) => service.GetHtmlDocumentTextAsync(solutionInfo, document.Id, ct),
cancellationToken).ConfigureAwait(false);
return htmlText.Value;
cancellationToken).AsTask();
}
public async Task PublishAsync(TextDocument document, string htmlText, CancellationToken cancellationToken)

Просмотреть файл

@ -68,14 +68,6 @@ internal class OutOfProcTagHelperResolver(
protected virtual async ValueTask<ImmutableArray<TagHelperDescriptor>> ResolveTagHelpersOutOfProcessAsync(Project workspaceProject, IProjectSnapshot projectSnapshot, CancellationToken cancellationToken)
{
var remoteClient = await _remoteClientProvider.TryGetClientAsync(cancellationToken);
if (remoteClient is null)
{
// Could not resolve
return default;
}
if (!_resultCache.TryGetId(workspaceProject.Id, out var lastResultId))
{
lastResultId = -1;
@ -83,19 +75,19 @@ internal class OutOfProcTagHelperResolver(
var projectHandle = new ProjectSnapshotHandle(workspaceProject.Id, projectSnapshot.Configuration, projectSnapshot.RootNamespace);
var deltaResult = await remoteClient.TryInvokeAsync<IRemoteTagHelperProviderService, TagHelperDeltaResult>(
var deltaResult = await _remoteClientProvider.TryInvokeAsync<IRemoteTagHelperProviderService, TagHelperDeltaResult>(
workspaceProject.Solution,
(service, solutionInfo, innerCancellationToken) =>
service.GetTagHelpersDeltaAsync(solutionInfo, projectHandle, lastResultId, innerCancellationToken),
cancellationToken);
if (!deltaResult.HasValue)
if (deltaResult is null)
{
return default;
}
// Apply the delta we received to any cached checksums for the current project.
var checksums = ProduceChecksumsFromDelta(workspaceProject.Id, lastResultId, deltaResult.Value);
var checksums = ProduceChecksumsFromDelta(workspaceProject.Id, lastResultId, deltaResult);
using var _1 = ArrayBuilderPool<TagHelperDescriptor>.GetPooledObject(out var tagHelpers);
using var _2 = ArrayBuilderPool<Checksum>.GetPooledObject(out var checksumsToFetch);
@ -118,18 +110,18 @@ internal class OutOfProcTagHelperResolver(
if (checksumsToFetch.Count > 0)
{
// There are checksums that we don't have cached tag helpers for, so we need to fetch them from OOP.
var fetchResult = await remoteClient.TryInvokeAsync<IRemoteTagHelperProviderService, FetchTagHelpersResult>(
var fetchResult = await _remoteClientProvider.TryInvokeAsync<IRemoteTagHelperProviderService, FetchTagHelpersResult>(
workspaceProject.Solution,
(service, solutionInfo, innerCancellationToken) =>
service.FetchTagHelpersAsync(solutionInfo, projectHandle, checksumsToFetch.DrainToImmutable(), innerCancellationToken),
cancellationToken);
if (!fetchResult.HasValue)
if (fetchResult is null)
{
return default;
}
var fetchedTagHelpers = fetchResult.Value.TagHelpers;
var fetchedTagHelpers = fetchResult.TagHelpers;
if (fetchedTagHelpers.IsEmpty)
{
// If we didn't receive any tag helpers, something catastrophic happened in the Roslyn OOP

Просмотреть файл

@ -1,10 +1,14 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT license. See License.txt in the project root for license information.
using System;
using System.ComponentModel.Composition;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Razor.Telemetry;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.ExternalAccess.Razor;
using Microsoft.CodeAnalysis.Razor.Logging;
using Microsoft.CodeAnalysis.Razor.Protocol;
using Microsoft.CodeAnalysis.Razor.Remote;
using Microsoft.CodeAnalysis.Razor.SemanticTokens;
@ -18,17 +22,52 @@ internal sealed class RemoteClientProvider(
IWorkspaceProvider workspaceProvider,
LanguageServerFeatureOptions languageServerFeatureOptions,
IClientCapabilitiesService clientCapabilitiesService,
ISemanticTokensLegendService semanticTokensLegendService)
ISemanticTokensLegendService semanticTokensLegendService,
ITelemetryReporter telemetryReporter,
ILoggerFactory loggerFactory)
: IRemoteClientProvider
{
private readonly IWorkspaceProvider _workspaceProvider = workspaceProvider;
private readonly LanguageServerFeatureOptions _languageServerFeatureOptions = languageServerFeatureOptions;
private readonly IClientCapabilitiesService _clientCapabilitiesService = clientCapabilitiesService;
private readonly ISemanticTokensLegendService _semanticTokensLegendService = semanticTokensLegendService;
private readonly ITelemetryReporter _telemetryReporter = telemetryReporter;
private readonly ILogger _logger = loggerFactory.GetOrCreateLogger<RemoteClientProvider>();
private bool _isInitialized;
private bool _isLSPInitialized;
public async Task<RazorRemoteHostClient?> TryGetClientAsync(CancellationToken cancellationToken)
public async ValueTask<TResult?> TryInvokeAsync<TService, TResult>(Solution solution, Func<TService, RazorPinnedSolutionInfoWrapper, CancellationToken, ValueTask<TResult>> invocation, CancellationToken cancellationToken)
where TService : class
{
var client = await TryGetClientAsync(cancellationToken).ConfigureAwait(false);
if (client is null)
{
_logger.LogError($"Couldn't get remote client for {typeof(TService).Name} service");
_telemetryReporter.ReportEvent("OOPClientFailure", Severity.Normal, new Property("service", typeof(TService).FullName));
return default;
}
if (cancellationToken.IsCancellationRequested)
{
return default;
}
try
{
var result = await client.TryInvokeAsync(solution, invocation, cancellationToken).ConfigureAwait(false);
return result.Value;
}
catch (Exception ex)
{
_logger.LogError(ex, $"Error calling remote method for {typeof(TService).Name} service, invocation: ${invocation.ToString()}");
_telemetryReporter.ReportFault(ex, "Exception calling remote method for {service}", typeof(TService).FullName);
return default;
}
}
private async Task<RazorRemoteHostClient?> TryGetClientAsync(CancellationToken cancellationToken)
{
var workspace = _workspaceProvider.GetWorkspace();