Skip to content

Commit

Permalink
Merge branch 'navToRelatedDocuments' into navToRelatedDocuments2
Browse files Browse the repository at this point in the history
  • Loading branch information
CyrusNajmabadi committed Feb 6, 2025
2 parents 578800e + 42afc4c commit 94738ec
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System.Collections.Concurrent;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.Diagnostics.EngineV2
Expand All @@ -13,30 +14,25 @@ internal partial class DiagnosticIncrementalAnalyzer
private static class InMemoryStorage
{
// the reason using nested map rather than having tuple as key is so that I dont have a gigantic map
private static readonly ConcurrentDictionary<DiagnosticAnalyzer, ConcurrentDictionary<(object key, string stateKey), CacheEntry>> s_map =
private static readonly ConcurrentDictionary<DiagnosticAnalyzer, ConcurrentDictionary<(ProjectOrDocumentId key, string stateKey), CacheEntry>> s_map =
new(concurrencyLevel: 2, capacity: 10);

public static bool TryGetValue(DiagnosticAnalyzer analyzer, (object key, string stateKey) key, out CacheEntry entry)
public static bool TryGetValue(DiagnosticAnalyzer analyzer, (ProjectOrDocumentId key, string stateKey) key, out CacheEntry entry)
{
AssertKey(key);

entry = default;
return s_map.TryGetValue(analyzer, out var analyzerMap) &&
analyzerMap.TryGetValue(key, out entry);
}

public static void Cache(DiagnosticAnalyzer analyzer, (object key, string stateKey) key, CacheEntry entry)
public static void Cache(DiagnosticAnalyzer analyzer, (ProjectOrDocumentId key, string stateKey) key, CacheEntry entry)
{
AssertKey(key);

// add new cache entry
var analyzerMap = s_map.GetOrAdd(analyzer, _ => new ConcurrentDictionary<(object key, string stateKey), CacheEntry>(concurrencyLevel: 2, capacity: 10));
var analyzerMap = s_map.GetOrAdd(analyzer, _ => new ConcurrentDictionary<(ProjectOrDocumentId key, string stateKey), CacheEntry>(concurrencyLevel: 2, capacity: 10));
analyzerMap[key] = entry;
}

public static void Remove(DiagnosticAnalyzer analyzer, (object key, string stateKey) key)
public static void Remove(DiagnosticAnalyzer analyzer, (ProjectOrDocumentId key, string stateKey) key)
{
AssertKey(key);
// remove the entry
if (!s_map.TryGetValue(analyzer, out var analyzerMap))
{
Expand All @@ -56,23 +52,9 @@ public static void DropCache(DiagnosticAnalyzer analyzer)
// drop any cache related to given analyzer
s_map.TryRemove(analyzer, out _);
}

// make sure key is either documentId or projectId
private static void AssertKey((object key, string stateKey) key)
=> Contract.ThrowIfFalse(key.key is DocumentId or ProjectId);
}

// in memory cache entry
private readonly struct CacheEntry
{
public readonly VersionStamp Version;
public readonly ImmutableArray<DiagnosticData> Diagnostics;

public CacheEntry(VersionStamp version, ImmutableArray<DiagnosticData> diagnostics)
{
Version = version;
Diagnostics = diagnostics;
}
}
private readonly record struct CacheEntry(VersionStamp Version, ImmutableArray<DiagnosticData> Diagnostics);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Shared.Extensions;
Expand Down Expand Up @@ -217,12 +218,12 @@ public async ValueTask SaveToInMemoryStorageAsync(Project project, DiagnosticAna
continue;
}

AddToInMemoryStorage(serializerVersion, project, document, document.Id, SyntaxStateName, result.GetDocumentDiagnostics(document.Id, AnalysisKind.Syntax));
AddToInMemoryStorage(serializerVersion, project, document, document.Id, SemanticStateName, result.GetDocumentDiagnostics(document.Id, AnalysisKind.Semantic));
AddToInMemoryStorage(serializerVersion, project, document, document.Id, NonLocalStateName, result.GetDocumentDiagnostics(document.Id, AnalysisKind.NonLocal));
AddToInMemoryStorage(serializerVersion, new(document.Id), SyntaxStateName, result.GetDocumentDiagnostics(document.Id, AnalysisKind.Syntax));
AddToInMemoryStorage(serializerVersion, new(document.Id), SemanticStateName, result.GetDocumentDiagnostics(document.Id, AnalysisKind.Semantic));
AddToInMemoryStorage(serializerVersion, new(document.Id), NonLocalStateName, result.GetDocumentDiagnostics(document.Id, AnalysisKind.NonLocal));
}

AddToInMemoryStorage(serializerVersion, project, document: null, result.ProjectId, NonLocalStateName, result.GetOtherDiagnostics());
AddToInMemoryStorage(serializerVersion, new(result.ProjectId), NonLocalStateName, result.GetOtherDiagnostics());
}

private async Task<DiagnosticAnalysisResult> LoadInitialAnalysisDataAsync(Project project, CancellationToken cancellationToken)
Expand Down Expand Up @@ -277,11 +278,8 @@ private async Task<DiagnosticAnalysisResult> LoadInitialProjectAnalysisDataAsync
}

private void AddToInMemoryStorage(
VersionStamp serializerVersion, Project project, TextDocument? document, object key, string stateKey, ImmutableArray<DiagnosticData> diagnostics)
VersionStamp serializerVersion, ProjectOrDocumentId key, string stateKey, ImmutableArray<DiagnosticData> diagnostics)
{
Contract.ThrowIfFalse(document == null || document.Project == project);

// if serialization fail, hold it in the memory
InMemoryStorage.Cache(_owner.Analyzer, (key, stateKey), new CacheEntry(serializerVersion, diagnostics));
}

Expand All @@ -291,7 +289,7 @@ private async ValueTask<bool> TryGetDiagnosticsFromInMemoryStorageAsync(VersionS
var project = document.Project;
var documentId = document.Id;

var diagnostics = await GetDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, document, documentId, SyntaxStateName, cancellationToken).ConfigureAwait(false);
var diagnostics = await GetDiagnosticsFromInMemoryStorageAsync(serializerVersion, new(documentId), SyntaxStateName, cancellationToken).ConfigureAwait(false);
if (!diagnostics.IsDefault)
{
builder.AddSyntaxLocals(documentId, diagnostics);
Expand All @@ -301,7 +299,7 @@ private async ValueTask<bool> TryGetDiagnosticsFromInMemoryStorageAsync(VersionS
success = false;
}

diagnostics = await GetDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, document, documentId, SemanticStateName, cancellationToken).ConfigureAwait(false);
diagnostics = await GetDiagnosticsFromInMemoryStorageAsync(serializerVersion, new(documentId), SemanticStateName, cancellationToken).ConfigureAwait(false);
if (!diagnostics.IsDefault)
{
builder.AddSemanticLocals(documentId, diagnostics);
Expand All @@ -311,7 +309,7 @@ private async ValueTask<bool> TryGetDiagnosticsFromInMemoryStorageAsync(VersionS
success = false;
}

diagnostics = await GetDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, document, documentId, NonLocalStateName, cancellationToken).ConfigureAwait(false);
diagnostics = await GetDiagnosticsFromInMemoryStorageAsync(serializerVersion, new(documentId), NonLocalStateName, cancellationToken).ConfigureAwait(false);
if (!diagnostics.IsDefault)
{
builder.AddNonLocals(documentId, diagnostics);
Expand All @@ -326,7 +324,7 @@ private async ValueTask<bool> TryGetDiagnosticsFromInMemoryStorageAsync(VersionS

private async ValueTask<bool> TryGetProjectDiagnosticsFromInMemoryStorageAsync(VersionStamp serializerVersion, Project project, Builder builder, CancellationToken cancellationToken)
{
var diagnostics = await GetDiagnosticsFromInMemoryStorageAsync(serializerVersion, project, document: null, project.Id, NonLocalStateName, cancellationToken).ConfigureAwait(false);
var diagnostics = await GetDiagnosticsFromInMemoryStorageAsync(serializerVersion, new(project.Id), NonLocalStateName, cancellationToken).ConfigureAwait(false);
if (!diagnostics.IsDefault)
{
builder.AddOthers(diagnostics);
Expand All @@ -337,10 +335,8 @@ private async ValueTask<bool> TryGetProjectDiagnosticsFromInMemoryStorageAsync(V
}

private ValueTask<ImmutableArray<DiagnosticData>> GetDiagnosticsFromInMemoryStorageAsync(
VersionStamp serializerVersion, Project project, TextDocument? document, object key, string stateKey, CancellationToken _)
VersionStamp serializerVersion, ProjectOrDocumentId key, string stateKey, CancellationToken _)
{
Contract.ThrowIfFalse(document == null || document.Project == project);

return InMemoryStorage.TryGetValue(_owner.Analyzer, (key, stateKey), out var entry) && serializerVersion == entry.Version
? new(entry.Diagnostics)
: default;
Expand All @@ -357,12 +353,12 @@ private void RemoveInMemoryCache(DiagnosticAnalysisResult lastResult)

private void RemoveInMemoryCacheEntries(DocumentId id)
{
RemoveInMemoryCacheEntry(id, SyntaxStateName);
RemoveInMemoryCacheEntry(id, SemanticStateName);
RemoveInMemoryCacheEntry(id, NonLocalStateName);
RemoveInMemoryCacheEntry(new(id), SyntaxStateName);
RemoveInMemoryCacheEntry(new(id), SemanticStateName);
RemoveInMemoryCacheEntry(new(id), NonLocalStateName);
}

private void RemoveInMemoryCacheEntry(object key, string stateKey)
private void RemoveInMemoryCacheEntry(ProjectOrDocumentId key, string stateKey)
{
// remove in memory cache if entry exist
InMemoryStorage.Remove(_owner.Analyzer, (key, stateKey));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,16 @@ namespace Microsoft.CodeAnalysis.LanguageServer.Handler.Diagnostics;
/// Wrapper around project and document ids for convenience in caching diagnostic results and
/// use in the <see cref="IDiagnosticSource"/>
/// </summary>
internal readonly struct ProjectOrDocumentId
internal readonly record struct ProjectOrDocumentId
{
/// <summary>
/// Non-null if this represents a documentId. Used for equality comparisons.
/// </summary>
[SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used for equality comparison.")]
private readonly DocumentId? _documentId;

/// <summary>
/// Non-null if this represents a projectId. Used for equality comparisons.
/// </summary>
[SuppressMessage("CodeQuality", "IDE0052:Remove unread private members", Justification = "Used for equality comparison.")]
private readonly ProjectId? _projectId;

public ProjectOrDocumentId(ProjectId projectId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,7 @@ private async Task<CompilationWithAnalyzersCacheEntry> CreateCompilationWithAnal
{
hostAnalyzerBuilder.AddRange(analyzers);
}

analyzerMapBuilder.AppendAnalyzerMap(analyzers);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,33 @@ namespace Microsoft.CodeAnalysis.PooledObjects;

internal sealed partial class PooledStringBuilder : IPooled
{
private static readonly ObjectPool<PooledStringBuilder> s_keepLargeInstancesPool = CreatePool();

public static PooledDisposer<PooledStringBuilder> GetInstance(out StringBuilder instance)
=> GetInstance(discardLargeInstances: true, out instance);

public static PooledDisposer<PooledStringBuilder> GetInstance(bool discardLargeInstances, out StringBuilder instance)
{
var pooledInstance = GetInstance();
// If we're discarding large instances (the default behavior), then just use the normal pool. If we're not, use
// a specific pool so that *other* normal callers don't accidentally get it and discard it.
var pooledInstance = discardLargeInstances ? GetInstance() : s_keepLargeInstancesPool.Allocate();
instance = pooledInstance;
return new PooledDisposer<PooledStringBuilder>(pooledInstance);
return new PooledDisposer<PooledStringBuilder>(pooledInstance, discardLargeInstances);
}

void IPooled.Free(bool discardLargeInstances)
{
// If we're discarding large instances, use the default behavior (which already does that). Otherwise, always
// clear and free the instance back to its originating pool.
if (discardLargeInstances)
{
Free();
}
else
{
this.Builder.Clear();
_pool.Free(this);
}
}

void IPooled.Free(bool discardLargeInstances)
Expand Down

0 comments on commit 94738ec

Please sign in to comment.