Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Keep the solution pinned (on host and OOP) when making multiple calls between systems. #70765

Merged
merged 10 commits into from
Nov 11, 2023
12 changes: 10 additions & 2 deletions src/Features/Core/Portable/NavigateTo/NavigateToSearcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
Expand All @@ -35,6 +36,7 @@ internal class NavigateToSearcher
private readonly INavigateToSearchCallback _callback;
private readonly string _searchPattern;
private readonly IImmutableSet<string> _kinds;
private readonly IAsynchronousOperationListener _listener;
private readonly IStreamingProgressTracker _progress_doNotAccessDirectly;

private readonly Document? _activeDocument;
Expand All @@ -47,13 +49,15 @@ private NavigateToSearcher(
Solution solution,
INavigateToSearchCallback callback,
string searchPattern,
IImmutableSet<string> kinds)
IImmutableSet<string> kinds,
IAsynchronousOperationListener listener)
{
_host = host;
_solution = solution;
_callback = callback;
_searchPattern = searchPattern;
_kinds = kinds;
_listener = listener;
_progress_doNotAccessDirectly = new StreamingProgressTracker((current, maximum, ct) =>
{
callback.ReportProgress(current, maximum);
Expand All @@ -80,7 +84,7 @@ public static NavigateToSearcher Create(
INavigateToSearcherHost? host = null)
{
host ??= new DefaultNavigateToSearchHost(solution, asyncListener, disposalToken);
return new NavigateToSearcher(host, solution, callback, searchPattern, kinds);
return new NavigateToSearcher(host, solution, callback, searchPattern, kinds, asyncListener);
}

private async Task AddProgressItemsAsync(int count, CancellationToken cancellationToken)
Expand Down Expand Up @@ -112,6 +116,10 @@ public async Task SearchAsync(
{
using var navigateToSearch = Logger.LogBlock(FunctionId.NavigateTo_Search, KeyValueLogMessage.Create(LogType.UserAction), cancellationToken);

// We're potentially about to make many calls over to our OOP service to perform searches. Ensure the
// solution we're searching stays pinned between us and it while this is happening.
using var _ = RemoteKeepAliveSession.Create(_solution, _listener);

if (searchCurrentDocument)
{
await SearchCurrentDocumentAsync(cancellationToken).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ internal sealed class RemoteKeepAliveSession : IDisposable
{
private readonly CancellationTokenSource _cancellationTokenSource = new();

private RemoteKeepAliveSession(Solution solution, IAsynchronousOperationListener listener)
private RemoteKeepAliveSession(SolutionState solution, IAsynchronousOperationListener listener)
{
var cancellationToken = _cancellationTokenSource.Token;
var token = listener.BeginAsyncOperation(nameof(RemoteKeepAliveSession));
Expand Down Expand Up @@ -72,9 +72,12 @@ public void Dispose()
/// the same solution during the life of this session do not need to resync the solution. Nor do they then need
/// to rebuild any compilations they've already built due to the solution going away and then coming back.
/// </summary>
public static RemoteKeepAliveSession Create(Solution solution, IAsynchronousOperationListener listener)
=> Create(solution.State, listener);

/// <inheritdoc cref="Create(Solution, IAsynchronousOperationListener)"/>
public static RemoteKeepAliveSession Create(
Solution solution,
IAsynchronousOperationListener listener)
SolutionState solution, IAsynchronousOperationListener listener)
{
return new RemoteKeepAliveSession(solution, listener);
}
Expand Down
11 changes: 10 additions & 1 deletion src/Workspaces/Core/Portable/Remote/RemoteHostClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,11 +104,20 @@ public async ValueTask<Optional<TResult>> TryInvokeAsync<TService, TResult>(

// solution, no callback:

public async ValueTask<bool> TryInvokeAsync<TService>(
public ValueTask<bool> TryInvokeAsync<TService>(
Solution solution,
Func<TService, Checksum, CancellationToken, ValueTask> invocation,
CancellationToken cancellationToken)
where TService : class
{
return TryInvokeAsync(solution.State, invocation, cancellationToken);
}

public async ValueTask<bool> TryInvokeAsync<TService>(
SolutionState solution,
Func<TService, Checksum, CancellationToken, ValueTask> invocation,
CancellationToken cancellationToken)
where TService : class
{
using var connection = CreateConnection<TService>(callbackTarget: null);
return await connection.TryInvokeAsync(solution, invocation, cancellationToken).ConfigureAwait(false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Shared.Collections;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.SourceGeneration;
using Microsoft.CodeAnalysis.SourceGeneratorTelemetry;
using Microsoft.CodeAnalysis.Text;
Expand Down Expand Up @@ -98,7 +99,12 @@ await generatorInfo.Documents.States.Values.SelectAsArrayAsync(
if (client is null)
return null;

// We're going to be making multiple calls over to OOP. No point in resyncing data multiple times. Keep a
// single connection, and keep this solution instance alive (and synced) on both sides of the connection
// throughout the calls.
var listenerProvider = solution.Services.ExportProvider.GetExports<IAsynchronousOperationListenerProvider>().First().Value;
using var connection = client.CreateConnection<IRemoteSourceGenerationService>(callbackTarget: null);
using var _ = RemoteKeepAliveSession.Create(solution, listenerProvider.GetListener(FeatureAttribute.Workspace));

// First, grab the info from our external host about the generated documents it has for this project.
var projectId = this.ProjectState.Id;
Expand Down