| File: NavigateTo\INavigateToSearcherHost.cs | Web Access |
| Project: src\roslyn\src\Features\Core\Portable\Microsoft.CodeAnalysis.Features.csproj (Microsoft.CodeAnalysis.Features) |
// 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.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.Host; using Microsoft.CodeAnalysis.Remote; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Shared.TestHooks; namespace Microsoft.CodeAnalysis.NavigateTo; /// <summary> /// Host interface abstracting over all the external functionality the <see cref="NavigateToSearcher"/> needs. This /// provide an easy entry point for swapping out functionality of the host, including for testing purposes. /// </summary> internal interface INavigateToSearcherHost { INavigateToSearchService? GetNavigateToSearchService(Project project); /// <summary> /// Returns the fully loaded state for both the project system and the remote host. /// </summary> ValueTask<bool> IsFullyLoadedAsync(CancellationToken cancellationToken); } internal interface IWorkspaceNavigateToSearcherHostService : IWorkspaceService { ValueTask<bool> IsFullyLoadedAsync(CancellationToken cancellationToken); } internal sealed class DefaultNavigateToSearchHost( Solution solution, IAsynchronousOperationListener asyncListener, CancellationToken disposalToken) : INavigateToSearcherHost { private readonly Solution _solution = solution; private readonly IAsynchronousOperationListener _asyncListener = asyncListener; private readonly CancellationToken _disposalToken = disposalToken; /// <summary> /// Single task used to both hydrate the remote host with the initial workspace solution, /// and track if that work completed. Prior to it completing, we will try to get all /// navigate-to requests from our caches. Once it is populated though, we can attempt to /// use the latest data instead. /// </summary> private static readonly object s_gate = new(); private static Task? s_remoteHostHydrateTask = null; public INavigateToSearchService? GetNavigateToSearchService(Project project) => project.GetLanguageService<INavigateToSearchService>(); public async ValueTask<bool> IsFullyLoadedAsync(CancellationToken cancellationToken) { var workspaceService = _solution.Services.GetService<IWorkspaceNavigateToSearcherHostService>(); if (workspaceService != null) return await workspaceService.IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false); var service = _solution.Services.GetRequiredService<IWorkspaceStatusService>(); // We consider ourselves fully loaded when both the project system has completed loaded // us, and we've totally hydrated the oop side. Until that happens, we'll attempt to // return cached data from languages that support that. var isProjectSystemFullyLoaded = await service.IsFullyLoadedAsync(cancellationToken).ConfigureAwait(false); if (!isProjectSystemFullyLoaded) return false; var isRemoteHostFullyLoaded = GetRemoteHostHydrateTask().IsCompleted; return isRemoteHostFullyLoaded; } /// <summary> /// If we're in a solution that is using OOP, this kicks off a task to get the oop side in /// sync with us. Until that happens, we'll continue to use the cached results from prior /// sessions so that we can get results very quickly right after launch without forcing the /// user to wait for OOP to hydrate the entire solution over. This strikes a good balance /// of speed and accuracy as most of the time cached results will be fast and good enough, /// and eventually (usually within dozens of seconds, even for large projects) we will /// switch over to full and accurate results which can also come back quickly. /// </summary> /// <remarks> /// If we do report cached data, we inform the user of this so they know the results may be /// incomplete or inaccurate and that they can try again later if necessary. /// </remarks> #pragma warning disable VSTHRD200 // Use "Async" suffix for async methods private Task GetRemoteHostHydrateTask() #pragma warning restore VSTHRD200 // Use "Async" suffix for async methods { lock (s_gate) { // Only need to do this once. if (s_remoteHostHydrateTask == null) { // If there are no projects in this solution that use OOP, then there's nothing we need to do. if (_solution.Projects.All(p => !RemoteSupportedLanguages.IsSupported(p.Language))) { s_remoteHostHydrateTask = Task.CompletedTask; } else { var asyncToken = _asyncListener.BeginAsyncOperation(nameof(GetRemoteHostHydrateTask)); s_remoteHostHydrateTask = Task.Run(async () => { var client = await RemoteHostClient.TryGetClientAsync(_solution.Services, _disposalToken).ConfigureAwait(false); if (client != null) { await client.TryInvokeAsync<IRemoteNavigateToSearchService>( _solution, (service, solutionInfo, cancellationToken) => service.HydrateAsync(solutionInfo, cancellationToken), _disposalToken).ConfigureAwait(false); } }, _disposalToken); s_remoteHostHydrateTask.CompletesAsyncOperation(asyncToken); } } return s_remoteHostHydrateTask; } } }