Skip to content

Commit

Permalink
Merge pull request dotnet#64576 from CyrusNajmabadi/asyncStreamOOP
Browse files Browse the repository at this point in the history
Use IAsyncEnumerable in NavigateTo OOP calls.
  • Loading branch information
CyrusNajmabadi authored Oct 17, 2022
2 parents 5bf1a43 + 229228f commit 83550ee
Show file tree
Hide file tree
Showing 27 changed files with 611 additions and 389 deletions.
1 change: 1 addition & 0 deletions eng/Versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@
<SystemTextEncodingExtensionsVersion>4.3.0</SystemTextEncodingExtensionsVersion>
<!-- Note: When updating SystemTextJsonVersion ensure that the version is no higher than what is used by MSBuild. -->
<SystemTextJsonVersion>6.0.0</SystemTextJsonVersion>
<SystemThreadingChannelsVersion>6.0.0</SystemThreadingChannelsVersion>
<SystemThreadingTasksDataflowVersion>6.0.0</SystemThreadingTasksDataflowVersion>
<!-- We need System.ValueTuple assembly version at least 4.0.3.0 on net47 to make F5 work against Dev15 - see https://github.com/dotnet/roslyn/issues/29705 -->
<SystemValueTupleVersion>4.5.0</SystemValueTupleVersion>
Expand Down
50 changes: 12 additions & 38 deletions src/EditorFeatures/CSharpTest/NavigateTo/NavigateToSearcherTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Threading;
Expand Down Expand Up @@ -38,37 +39,14 @@ private static void SetupSearchProject(
pattern,
ImmutableHashSet<string>.Empty,
It.IsAny<Document?>(),
It.IsAny<Func<INavigateToSearchResult, Task>>(),
It.IsAny<CancellationToken>())).Callback(
(Project project,
ImmutableArray<Document> priorityDocuments,
string pattern,
IImmutableSet<string> kinds,
Document? activeDocument,
Func<INavigateToSearchResult, Task> onResultFound,
CancellationToken cancellationToken) =>
{
if (result != null)
onResultFound(result);
}).Returns(Task.CompletedTask);
It.IsAny<CancellationToken>())).Returns(GetEnumerable(result));

searchService.Setup(ss => ss.SearchGeneratedDocumentsAsync(
It.IsAny<Project>(),
pattern,
ImmutableHashSet<string>.Empty,
It.IsAny<Document?>(),
It.IsAny<Func<INavigateToSearchResult, Task>>(),
It.IsAny<CancellationToken>())).Callback(
(Project project,
string pattern,
IImmutableSet<string> kinds,
Document? activeDocument,
Func<INavigateToSearchResult, Task> onResultFound,
CancellationToken cancellationToken) =>
{
if (result != null)
onResultFound(result);
}).Returns(Task.CompletedTask);
It.IsAny<CancellationToken>())).Returns(GetEnumerable(result));

// Followed by a generated doc search.
}
Expand All @@ -80,22 +58,18 @@ private static void SetupSearchProject(
pattern,
ImmutableHashSet<string>.Empty,
It.IsAny<Document?>(),
It.IsAny<Func<INavigateToSearchResult, Task>>(),
It.IsAny<CancellationToken>())).Callback(
(Project project,
ImmutableArray<Document> priorityDocuments,
string pattern2,
IImmutableSet<string> kinds,
Document? activeDocument,
Func<INavigateToSearchResult, Task> onResultFound2,
CancellationToken cancellationToken) =>
{
if (result != null)
onResultFound2(result);
}).Returns(Task.CompletedTask);
It.IsAny<CancellationToken>())).Returns(GetEnumerable(result));
}
}

#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously
private static async IAsyncEnumerable<INavigateToSearchResult> GetEnumerable(INavigateToSearchResult? result)
#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously
{
if (result != null)
yield return result;
}

private static ValueTask<bool> IsFullyLoadedAsync(bool projectSystem, bool remoteHost)
=> new(projectSystem && remoteHost);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.NavigateTo;
using Microsoft.CodeAnalysis.Navigation;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Text;
using Roslyn.Utilities;

Expand All @@ -35,62 +36,58 @@ public VSTypeScriptNavigateToSearchService(

public bool CanFilter => _searchService?.CanFilter ?? false;

public async Task SearchDocumentAsync(
public async IAsyncEnumerable<INavigateToSearchResult> SearchDocumentAsync(
Document document,
string searchPattern,
IImmutableSet<string> kinds,
Document? activeDocument,
Func<INavigateToSearchResult, Task> onResultFound,
CancellationToken cancellationToken)
[EnumeratorCancellation] CancellationToken cancellationToken)
{
if (_searchService != null)
{
var results = await _searchService.SearchDocumentAsync(document, searchPattern, kinds, cancellationToken).ConfigureAwait(false);
foreach (var result in results)
await onResultFound(Convert(result)).ConfigureAwait(false);
yield return Convert(result);
}
}

public async Task SearchProjectAsync(
public async IAsyncEnumerable<INavigateToSearchResult> SearchProjectAsync(
Project project,
ImmutableArray<Document> priorityDocuments,
string searchPattern,
IImmutableSet<string> kinds,
Document? activeDocument,
Func<INavigateToSearchResult, Task> onResultFound,
CancellationToken cancellationToken)
[EnumeratorCancellation] CancellationToken cancellationToken)
{
if (_searchService != null)
{
var results = await _searchService.SearchProjectAsync(project, priorityDocuments, searchPattern, kinds, cancellationToken).ConfigureAwait(false);
foreach (var result in results)
await onResultFound(Convert(result)).ConfigureAwait(false);
yield return Convert(result);
}
}

public Task SearchCachedDocumentsAsync(
public IAsyncEnumerable<INavigateToSearchResult> SearchCachedDocumentsAsync(
Project project,
ImmutableArray<Document> priorityDocuments,
string searchPattern,
IImmutableSet<string> kinds,
Document? activeDocument,
Func<INavigateToSearchResult, Task> onResultFound,
CancellationToken cancellationToken)
{
// we don't support searching cached documents.
return Task.CompletedTask;
return AsyncEnumerable<INavigateToSearchResult>.Empty;
}

public Task SearchGeneratedDocumentsAsync(
public IAsyncEnumerable<INavigateToSearchResult> SearchGeneratedDocumentsAsync(
Project project,
string searchPattern,
IImmutableSet<string> kinds,
Document? activeDocument,
Func<INavigateToSearchResult, Task> onResultFound,
CancellationToken cancellationToken)
{
// we don't support searching generated documents.
return Task.CompletedTask;
return AsyncEnumerable<INavigateToSearchResult>.Empty;
}

private static INavigateToSearchResult Convert(IVSTypeScriptNavigateToSearchResult result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,19 @@
// 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.Concurrent;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.PatternMatching;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Remote;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Storage;
using Roslyn.Utilities;

Expand Down Expand Up @@ -52,50 +54,52 @@ private static bool ShouldSearchCachedDocuments(
return cachedIndexMap != null && stringTable != null;
}

public async Task SearchCachedDocumentsAsync(
public async IAsyncEnumerable<INavigateToSearchResult> SearchCachedDocumentsAsync(
Project project,
ImmutableArray<Document> priorityDocuments,
string searchPattern,
IImmutableSet<string> kinds,
Document? activeDocument,
Func<INavigateToSearchResult, Task> onResultFound,
CancellationToken cancellationToken)
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var solution = project.Solution;
var onItemFound = GetOnItemFoundCallback(solution, activeDocument, onResultFound, cancellationToken);

var documentKeys = project.Documents.SelectAsArray(DocumentKey.ToDocumentKey);
var priorityDocumentKeys = priorityDocuments.SelectAsArray(DocumentKey.ToDocumentKey);

var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var callback = new NavigateToSearchServiceCallback(onItemFound);
await client.TryInvokeAsync<IRemoteNavigateToSearchService>(
(service, callbackId, cancellationToken) =>
service.SearchCachedDocumentsAsync(documentKeys, priorityDocumentKeys, searchPattern, kinds.ToImmutableArray(), callbackId, cancellationToken),
callback, cancellationToken).ConfigureAwait(false);
var result = client.TryInvokeStreamAsync<IRemoteNavigateToSearchService, RoslynNavigateToItem>(
(service, cancellationToken) =>
service.SearchCachedDocumentsAsync(documentKeys, priorityDocumentKeys, searchPattern, kinds.ToImmutableArray(), cancellationToken),
cancellationToken);

return;
await foreach (var item in ConvertItemsAsync(solution, activeDocument, result, cancellationToken).ConfigureAwait(false))
yield return item;
}
else
{
var storageService = solution.Services.GetPersistentStorageService();
var result = SearchCachedDocumentsInCurrentProcessAsync(
storageService, documentKeys, priorityDocumentKeys, searchPattern, kinds, cancellationToken);

var storageService = solution.Services.GetPersistentStorageService();
await SearchCachedDocumentsInCurrentProcessAsync(
storageService, documentKeys, priorityDocumentKeys, searchPattern, kinds, onItemFound, cancellationToken).ConfigureAwait(false);
await foreach (var item in ConvertItemsAsync(solution, activeDocument, result, cancellationToken).ConfigureAwait(false))
yield return item;
}
}

public static async Task SearchCachedDocumentsInCurrentProcessAsync(
public static async IAsyncEnumerable<RoslynNavigateToItem> SearchCachedDocumentsInCurrentProcessAsync(
IChecksummedPersistentStorageService storageService,
ImmutableArray<DocumentKey> documentKeys,
ImmutableArray<DocumentKey> priorityDocumentKeys,
string searchPattern,
IImmutableSet<string> kinds,
Func<RoslynNavigateToItem, Task> onItemFound,
CancellationToken cancellationToken)
[EnumeratorCancellation] CancellationToken cancellationToken)
{
// Quick abort if OOP is now fully loaded.
if (!ShouldSearchCachedDocuments(out _, out _))
return;
yield break;

var highPriDocsSet = priorityDocumentKeys.ToSet();
var lowPriDocs = documentKeys.WhereAsArray(d => !highPriDocsSet.Contains(d));
Expand All @@ -104,40 +108,35 @@ public static async Task SearchCachedDocumentsInCurrentProcessAsync(
var (patternName, patternContainer) = PatternMatcher.GetNameAndContainer(searchPattern);
var declaredSymbolInfoKindsSet = new DeclaredSymbolInfoKindSet(kinds);

await SearchCachedDocumentsInCurrentProcessAsync(
await foreach (var item in SearchCachedDocumentsInCurrentProcessAsync(
storageService, patternName, patternContainer, declaredSymbolInfoKindsSet,
onItemFound, priorityDocumentKeys, cancellationToken).ConfigureAwait(false);
priorityDocumentKeys, cancellationToken).ConfigureAwait(false))
{
yield return item;
}

await SearchCachedDocumentsInCurrentProcessAsync(
await foreach (var item in SearchCachedDocumentsInCurrentProcessAsync(
storageService, patternName, patternContainer, declaredSymbolInfoKindsSet,
onItemFound, lowPriDocs, cancellationToken).ConfigureAwait(false);
lowPriDocs, cancellationToken).ConfigureAwait(false))
{
yield return item;
}
}

private static async Task SearchCachedDocumentsInCurrentProcessAsync(
private static IAsyncEnumerable<RoslynNavigateToItem> SearchCachedDocumentsInCurrentProcessAsync(
IChecksummedPersistentStorageService storageService,
string patternName,
string? patternContainer,
DeclaredSymbolInfoKindSet kinds,
Func<RoslynNavigateToItem, Task> onItemFound,
ImmutableArray<DocumentKey> documentKeys,
CancellationToken cancellationToken)
{
using var _ = ArrayBuilder<Task>.GetInstance(out var tasks);
using var _ = ArrayBuilder<IAsyncEnumerable<RoslynNavigateToItem>>.GetInstance(out var builder);

foreach (var documentKey in documentKeys)
{
tasks.Add(Task.Run(async () =>
{
var index = await GetIndexAsync(storageService, documentKey, cancellationToken).ConfigureAwait(false);
if (index == null)
return;

await ProcessIndexAsync(
documentKey.Id, document: null, patternName, patternContainer, kinds, onItemFound, index, cancellationToken).ConfigureAwait(false);
}, cancellationToken));
}
builder.Add(ProcessStaleIndexAsync(storageService, patternName, patternContainer, kinds, documentKey, cancellationToken));

await Task.WhenAll(tasks).ConfigureAwait(false);
return builder.ToImmutable().MergeAsync(cancellationToken);
}

private static Task<TopLevelSyntaxTreeIndex?> GetIndexAsync(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// See the LICENSE file in the project root for more information.

using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.PatternMatching;
Expand All @@ -14,41 +16,42 @@ namespace Microsoft.CodeAnalysis.NavigateTo
{
internal abstract partial class AbstractNavigateToSearchService
{
public async Task SearchGeneratedDocumentsAsync(
public async IAsyncEnumerable<INavigateToSearchResult> SearchGeneratedDocumentsAsync(
Project project,
string searchPattern,
IImmutableSet<string> kinds,
Document? activeDocument,
Func<INavigateToSearchResult, Task> onResultFound,
CancellationToken cancellationToken)
[EnumeratorCancellation] CancellationToken cancellationToken)
{
var solution = project.Solution;
var onItemFound = GetOnItemFoundCallback(solution, activeDocument, onResultFound, cancellationToken);

var client = await RemoteHostClient.TryGetClientAsync(project, cancellationToken).ConfigureAwait(false);
if (client != null)
{
var callback = new NavigateToSearchServiceCallback(onItemFound);

await client.TryInvokeAsync<IRemoteNavigateToSearchService>(
var result = client.TryInvokeStreamAsync<IRemoteNavigateToSearchService, RoslynNavigateToItem>(
solution,
(service, solutionInfo, callbackId, cancellationToken) =>
service.SearchGeneratedDocumentsAsync(solutionInfo, project.Id, searchPattern, kinds.ToImmutableArray(), callbackId, cancellationToken),
callback, cancellationToken).ConfigureAwait(false);
(service, solutionInfo, cancellationToken) =>
service.SearchGeneratedDocumentsAsync(solutionInfo, project.Id, searchPattern, kinds.ToImmutableArray(), cancellationToken),
cancellationToken);

return;
await foreach (var item in ConvertItemsAsync(solution, activeDocument, result, cancellationToken).ConfigureAwait(false))
yield return item;
}
else
{
var result = SearchGeneratedDocumentsInCurrentProcessAsync(
project, searchPattern, kinds, cancellationToken);

await SearchGeneratedDocumentsInCurrentProcessAsync(
project, searchPattern, kinds, onItemFound, cancellationToken).ConfigureAwait(false);
await foreach (var item in ConvertItemsAsync(solution, activeDocument, result, cancellationToken).ConfigureAwait(false))
yield return item;
}
}

public static async Task SearchGeneratedDocumentsInCurrentProcessAsync(
public static async IAsyncEnumerable<RoslynNavigateToItem> SearchGeneratedDocumentsInCurrentProcessAsync(
Project project,
string pattern,
IImmutableSet<string> kinds,
Func<RoslynNavigateToItem, Task> onResultFound,
CancellationToken cancellationToken)
[EnumeratorCancellation] CancellationToken cancellationToken)
{
// If the user created a dotted pattern then we'll grab the last part of the name
var (patternName, patternContainerOpt) = PatternMatcher.GetNameAndContainer(pattern);
Expand All @@ -57,7 +60,8 @@ public static async Task SearchGeneratedDocumentsInCurrentProcessAsync(

// First generate all the source-gen docs. Then handoff to the standard search routine to find matches in them.
var generatedDocs = await project.GetSourceGeneratedDocumentsAsync(cancellationToken).ConfigureAwait(false);
await ProcessDocumentsAsync(searchDocument: null, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, onResultFound, generatedDocs.ToSet<Document>(), cancellationToken).ConfigureAwait(false);
await foreach (var item in ProcessDocumentsAsync(searchDocument: null, patternName, patternContainerOpt, declaredSymbolInfoKindsSet, generatedDocs.ToSet<Document>(), cancellationToken).ConfigureAwait(false))
yield return item;
}
}
}
Loading

0 comments on commit 83550ee

Please sign in to comment.