Skip to content

Commit 00d5e51

Browse files
Add cache to TagHelperDocumentContext
TagHelperBinders are expensive to create, and Razor often creates them for the same set of tag helpers. This change introduces a cache to avoid creating a new TagHelperDocumentContext (and therefore, a new TagHelperBinder) for the same TagHelperCollection. Note: This requires adding some deduping logic to Rename, which started creating duplicate ranges with this change.
1 parent a845656 commit 00d5e51

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)