Skip to content

Commit d4a50d9

Browse files
TagHelperCollection Part 5: Add weak cache for TagHelperDocumentContext (#12509)
| [Prelude](#12503) | [Part 1](#12504) | [Part 2](#12505) | [Part 3](#12506) | [Part 4](#12507) | Part 5 | This change introduces a weak cache for `TagHelperDocumentContext` keyed by the tag helper prefix string and `TagHelperCollection` checksum. This helps avoid creating new `TagHelperBinders` for the same set of tag helpers, since `TagHelperBinder` is expensive to create. ---- CI Build: https://dev.azure.com/dnceng/internal/_build/results?buildId=2842228&view=results Toolset Run: https://dev.azure.com/dnceng/internal/_build/results?buildId=2842250&view=results
2 parents a939ae2 + 00d5e51 commit d4a50d9

File tree

18 files changed

+48
-45
lines changed

18 files changed

+48
-45
lines changed

src/Compiler/Microsoft.AspNetCore.Razor.Language/test/RazorCodeDocumentExtensionsTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ public void GetAndSetTagHelperContext_ReturnsTagHelperContext()
5252
// Arrange
5353
var codeDocument = TestRazorCodeDocument.CreateEmpty();
5454

55-
var expected = TagHelperDocumentContext.Create(tagHelpers: []);
55+
var expected = TagHelperDocumentContext.GetOrCreate(tagHelpers: []);
5656
codeDocument.SetTagHelperContext(expected);
5757

5858
// Act

src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorTagHelperContextDiscoveryPhase.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument, Cancellation
5151
// This will always be null for a component document.
5252
var tagHelperPrefix = visitor.TagHelperPrefix;
5353

54-
var context = TagHelperDocumentContext.Create(tagHelperPrefix, visitor.GetResults());
54+
var context = TagHelperDocumentContext.GetOrCreate(tagHelperPrefix, visitor.GetResults());
5555
codeDocument.SetTagHelperContext(context);
5656
codeDocument.SetPreTagHelperSyntaxTree(syntaxTree);
5757
}
Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using Microsoft.AspNetCore.Razor.Utilities;
5+
46
namespace Microsoft.AspNetCore.Razor.Language;
57

68
/// <summary>
79
/// The binding information for Tag Helpers resulted to a <see cref="RazorCodeDocument"/>. Represents the
810
/// Tag Helper information after processing by directives.
911
/// </summary>
1012
internal sealed class TagHelperDocumentContext
11-
{
13+
{
14+
private static readonly CleanableWeakCache<(string? Prefix, Checksum), TagHelperDocumentContext> s_cache = new(cleanUpThreshold: 20);
15+
1216
public string? Prefix { get; }
1317
public TagHelperCollection TagHelpers { get; }
1418

@@ -20,22 +24,19 @@ private TagHelperDocumentContext(string? prefix, TagHelperCollection tagHelpers)
2024
TagHelpers = tagHelpers;
2125
}
2226

23-
public static TagHelperDocumentContext Create(TagHelperCollection tagHelpers)
24-
{
25-
ArgHelper.ThrowIfNull(tagHelpers);
26-
27-
return new(prefix: null, tagHelpers);
28-
}
27+
public static TagHelperDocumentContext GetOrCreate(TagHelperCollection tagHelpers)
28+
=> GetOrCreate(prefix: null, tagHelpers);
2929

30-
public static TagHelperDocumentContext Create(string? prefix, TagHelperCollection tagHelpers)
30+
public static TagHelperDocumentContext GetOrCreate(string? prefix, TagHelperCollection tagHelpers)
3131
{
3232
ArgHelper.ThrowIfNull(tagHelpers);
3333

34-
return new(prefix, tagHelpers);
34+
return s_cache.GetOrAdd(
35+
key: (prefix, tagHelpers.Checksum),
36+
arg: (prefix, tagHelpers),
37+
arg => new(arg.prefix, arg.tagHelpers));
3538
}
3639

3740
public TagHelperBinder GetBinder()
38-
{
39-
return _binder ?? InterlockedOperations.Initialize(ref _binder, new TagHelperBinder(Prefix, TagHelpers));
40-
}
41+
=> _binder ?? InterlockedOperations.Initialize(ref _binder, new TagHelperBinder(Prefix, TagHelpers));
4142
}

src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/LanguageServer/TagHelperCompletionBenchmark.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public object GetAttributeCompletions()
3131
{
3232
var tagHelperCompletionService = new TagHelperCompletionService();
3333
var context = new AttributeCompletionContext(
34-
TagHelperDocumentContext.Create([.. CommonResources.TelerikTagHelpers]),
34+
TagHelperDocumentContext.GetOrCreate([.. CommonResources.TelerikTagHelpers]),
3535
existingCompletions: [],
3636
currentTagName: "PageTitle",
3737
currentAttributeName: null,
@@ -48,7 +48,7 @@ public object GetElementCompletions()
4848
{
4949
var tagHelperCompletionService = new TagHelperCompletionService();
5050
var context = new ElementCompletionContext(
51-
TagHelperDocumentContext.Create([.. CommonResources.TelerikTagHelpers]),
51+
TagHelperDocumentContext.GetOrCreate([.. CommonResources.TelerikTagHelpers]),
5252
existingCompletions: s_existingElementCompletions,
5353
containingTagName: null,
5454
attributes: [],

src/Razor/benchmarks/Microsoft.AspNetCore.Razor.Microbenchmarks/Serialization/CompletionListSerializationBenchmark.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ private CompletionList GenerateCompletionList(string documentContent, int queryI
6666
var sourceDocument = RazorSourceDocument.Create(documentContent, RazorSourceDocumentProperties.Default);
6767
var codeDocument = RazorCodeDocument.Create(sourceDocument);
6868
var syntaxTree = RazorSyntaxTree.Parse(sourceDocument);
69-
var tagHelperDocumentContext = TagHelperDocumentContext.Create([.. CommonResources.LegacyTagHelpers]);
69+
var tagHelperDocumentContext = TagHelperDocumentContext.GetOrCreate([.. CommonResources.LegacyTagHelpers]);
7070

7171
var owner = syntaxTree.Root.FindInnermostNode(queryIndex, includeWhitespace: true, walkMarkersBack: true);
7272
var context = new RazorCompletionContext(codeDocument, queryIndex, owner, syntaxTree, tagHelperDocumentContext);

src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Completion/TagHelperCompletionProvider.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ private ImmutableArray<RazorCompletionItem> GetAttributeCompletions(
116116
var ancestors = containingAttribute.Parent.Ancestors();
117117
var nonDirectiveAttributeTagHelpers = tagHelperDocumentContext.TagHelpers.Where(
118118
static tagHelper => !tagHelper.BoundAttributes.Any(static attribute => attribute.IsDirectiveAttribute));
119-
var filteredContext = TagHelperDocumentContext.Create(tagHelperDocumentContext.Prefix, nonDirectiveAttributeTagHelpers);
119+
var filteredContext = TagHelperDocumentContext.GetOrCreate(tagHelperDocumentContext.Prefix, nonDirectiveAttributeTagHelpers);
120120
var (ancestorTagName, ancestorIsTagHelper) = TagHelperFacts.GetNearestAncestorTagInfo(ancestors);
121121
var attributeCompletionContext = new AttributeCompletionContext(
122122
filteredContext,

src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Completion/DefaultRazorCompletionFactsServiceTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public void GetDirectiveCompletionItems_AllProvidersCompletionItems()
1818
var sourceDocument = RazorSourceDocument.Create("", RazorSourceDocumentProperties.Default);
1919
var codeDocument = RazorCodeDocument.Create(sourceDocument);
2020
var syntaxTree = RazorSyntaxTree.Parse(TestRazorSourceDocument.Create());
21-
var tagHelperDocumentContext = TagHelperDocumentContext.Create(tagHelpers: []);
21+
var tagHelperDocumentContext = TagHelperDocumentContext.GetOrCreate(tagHelpers: []);
2222

2323
var completionItem1 = RazorCompletionItem.CreateDirective(
2424
displayText: "displayText1",

src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Completion/DirectiveAttributeCompletionItemProviderTest.AttributeNames.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@ public void GetCompletionItems_ExistingAttribute_Partial_ReturnsEmptyCollection(
180180
public void GetAttributeCompletions_NoDescriptorsForTag_ReturnsEmptyCollection()
181181
{
182182
// Arrange
183-
var documentContext = TagHelperDocumentContext.Create(tagHelpers: []);
183+
var documentContext = TagHelperDocumentContext.GetOrCreate(tagHelpers: []);
184184
var context = GetDefaultDirectivateAttributeCompletionContext("@bin");
185185

186186
// Act
@@ -197,7 +197,7 @@ public void GetAttributeCompletions_NoDirectiveAttributesForTag_ReturnsEmptyColl
197197
var descriptor = TagHelperDescriptorBuilder.CreateTagHelper("CatchAll", "TestAssembly");
198198
descriptor.BoundAttributeDescriptor(boundAttribute => boundAttribute.Name = "Test");
199199
descriptor.TagMatchingRule(rule => rule.RequireTagName("*"));
200-
var documentContext = TagHelperDocumentContext.Create([descriptor.Build()]);
200+
var documentContext = TagHelperDocumentContext.GetOrCreate([descriptor.Build()]);
201201

202202
var context = GetDefaultDirectivateAttributeCompletionContext("@bin");
203203

src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Completion/DirectiveAttributeCompletionItemProviderTest.ParameterNames.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public void GetCompletionItems_OnDirectiveAttributeParameter_ReturnsCompletions(
3535
public void GetAttributeParameterCompletions_NoDescriptorsForTag_ReturnsEmptyCollection()
3636
{
3737
// Arrange
38-
var documentContext = TagHelperDocumentContext.Create(tagHelpers: []);
38+
var documentContext = TagHelperDocumentContext.GetOrCreate(tagHelpers: []);
3939
var context = GetDefaultDirectiveAttributeCompletionContext("@bin");
4040

4141
// Act
@@ -52,7 +52,7 @@ public void GetAttributeParameterCompletions_NoDirectiveAttributesForTag_Returns
5252
var descriptor = TagHelperDescriptorBuilder.CreateTagHelper("CatchAll", "TestAssembly");
5353
descriptor.BoundAttributeDescriptor(boundAttribute => boundAttribute.Name = "Test");
5454
descriptor.TagMatchingRule(rule => rule.RequireTagName("*"));
55-
var documentContext = TagHelperDocumentContext.Create([descriptor.Build()]);
55+
var documentContext = TagHelperDocumentContext.GetOrCreate([descriptor.Build()]);
5656

5757
var context = GetDefaultDirectiveAttributeCompletionContext("@bin");
5858

src/Razor/test/Microsoft.CodeAnalysis.Razor.Workspaces.Test/Completion/DirectiveAttributeTransitionCompletionItemProviderTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace Microsoft.CodeAnalysis.Razor.Completion;
1313

1414
public class DirectiveAttributeTransitionCompletionItemProviderTest(ITestOutputHelper testOutput) : ToolingTestBase(testOutput)
1515
{
16-
private readonly TagHelperDocumentContext _tagHelperDocumentContext = TagHelperDocumentContext.Create(tagHelpers: []);
16+
private readonly TagHelperDocumentContext _tagHelperDocumentContext = TagHelperDocumentContext.GetOrCreate(tagHelpers: []);
1717
private readonly DirectiveAttributeTransitionCompletionItemProvider _provider = new(TestLanguageServerFeatureOptions.Instance);
1818

1919
[Fact]

0 commit comments

Comments
 (0)