diff --git a/SpellingExclusions.dic b/SpellingExclusions.dic index 74cf1b2bf18..bed2046dde9 100644 --- a/SpellingExclusions.dic +++ b/SpellingExclusions.dic @@ -7,3 +7,4 @@ microsoft vsls Blazor Metacode +Poolable diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/ComponentTagHelperDescriptorProvider.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/ComponentTagHelperDescriptorProvider.cs index 3f1b93c1369..ec3b5ec2fc3 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/ComponentTagHelperDescriptorProvider.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/ComponentTagHelperDescriptorProvider.cs @@ -125,7 +125,7 @@ private static TagHelperDescriptor CreateNameMatchingDescriptor( { metadata.IsGeneric = true; - using var cascadeGenericTypeAttributes = new PooledHashSet(StringHashSetPool.Ordinal); + using var cascadeGenericTypeAttributes = new PooledHashSet(StringComparer.Ordinal); foreach (var attribute in type.GetAttributes()) { @@ -610,7 +610,7 @@ private static void CreateContextParameter(TagHelperDescriptorBuilder builder, s // - are not indexers private static ImmutableArray<(IPropertySymbol property, PropertyKind kind)> GetProperties(INamedTypeSymbol type) { - using var names = new PooledHashSet(StringHashSetPool.Ordinal); + using var names = new PooledHashSet(StringComparer.Ordinal); using var results = new PooledArrayBuilder<(IPropertySymbol, PropertyKind)>(); var currentType = type; diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/DefaultTagHelperDescriptorFactory.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/DefaultTagHelperDescriptorFactory.cs index 80d321f90d2..066182915ea 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/DefaultTagHelperDescriptorFactory.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/CSharp/DefaultTagHelperDescriptorFactory.cs @@ -375,7 +375,7 @@ private static bool IsPotentialDictionaryProperty(IPropertySymbol property) private static void CollectAccessibleProperties( INamedTypeSymbol typeSymbol, ref PooledArrayBuilder properties) { - using var names = new PooledHashSet(StringHashSetPool.Ordinal); + using var names = new PooledHashSet(StringComparer.Ordinal); // Traverse the type hierarchy to find all accessible properties. var currentType = typeSymbol; diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/AllowedChildTagDescriptorBuilder_Pooling.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/AllowedChildTagDescriptorBuilder_Pooling.cs index 9300f359434..481a7093d29 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/AllowedChildTagDescriptorBuilder_Pooling.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/AllowedChildTagDescriptorBuilder_Pooling.cs @@ -8,7 +8,8 @@ namespace Microsoft.AspNetCore.Razor.Language; public partial class AllowedChildTagDescriptorBuilder { - internal static readonly ObjectPool Pool = DefaultPool.Create(Policy.Instance); + internal static readonly ObjectPool Pool = + DefaultPool.Create(static () => new AllowedChildTagDescriptorBuilder()); internal static AllowedChildTagDescriptorBuilder GetInstance(TagHelperDescriptorBuilder parent) { @@ -26,15 +27,4 @@ private protected override void Reset() Name = null; DisplayName = null; } - - private sealed class Policy : PooledBuilderPolicy - { - public static readonly Policy Instance = new(); - - private Policy() - { - } - - public override AllowedChildTagDescriptorBuilder Create() => new(); - } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/BoundAttributeDescriptorBuilder_Pooling.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/BoundAttributeDescriptorBuilder_Pooling.cs index db6e31a7846..31ce2aa85fb 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/BoundAttributeDescriptorBuilder_Pooling.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/BoundAttributeDescriptorBuilder_Pooling.cs @@ -8,7 +8,8 @@ namespace Microsoft.AspNetCore.Razor.Language; public partial class BoundAttributeDescriptorBuilder { - internal static readonly ObjectPool Pool = DefaultPool.Create(Policy.Instance); + internal static readonly ObjectPool Pool = + DefaultPool.Create(static () => new BoundAttributeDescriptorBuilder()); internal static BoundAttributeDescriptorBuilder GetInstance(TagHelperDescriptorBuilder parent) { @@ -36,15 +37,4 @@ private protected override void Reset() ContainingType = null; Parameters.Clear(); } - - private sealed class Policy : PooledBuilderPolicy - { - public static readonly Policy Instance = new(); - - private Policy() - { - } - - public override BoundAttributeDescriptorBuilder Create() => new(); - } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/BoundAttributeParameterDescriptorBuilder_Pooling.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/BoundAttributeParameterDescriptorBuilder_Pooling.cs index 0613dbf22bf..c15dbef2b72 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/BoundAttributeParameterDescriptorBuilder_Pooling.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/BoundAttributeParameterDescriptorBuilder_Pooling.cs @@ -8,7 +8,8 @@ namespace Microsoft.AspNetCore.Razor.Language; public partial class BoundAttributeParameterDescriptorBuilder { - internal static readonly ObjectPool Pool = DefaultPool.Create(Policy.Instance); + internal static readonly ObjectPool Pool = + DefaultPool.Create(static () => new BoundAttributeParameterDescriptorBuilder()); internal static BoundAttributeParameterDescriptorBuilder GetInstance(BoundAttributeDescriptorBuilder parent) { @@ -29,15 +30,4 @@ private protected override void Reset() Name = null; PropertyName = null; } - - private sealed class Policy : PooledBuilderPolicy - { - public static readonly Policy Instance = new(); - - private Policy() - { - } - - public override BoundAttributeParameterDescriptorBuilder Create() => new(); - } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentBindLoweringPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentBindLoweringPass.cs index 3de4b21afca..7f84cc9f7ab 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentBindLoweringPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentBindLoweringPass.cs @@ -217,7 +217,7 @@ private static void ProcessDuplicates( ref PooledArrayBuilder> references, ref PooledArrayBuilder> parameterReferences) { - using var _ = ReferenceEqualityHashSetPool.GetPooledObject(out var parents); + using var _ = SpecializedPools.GetPooledReferenceEqualityHashSet(out var parents); foreach (var reference in references) { @@ -307,7 +307,7 @@ private static void ProcessDuplicateAttributes(IntermediateNode node) // If we still have duplicates at this point then they are genuine conflicts. // Use a hash set to quickly determine whether there are any duplicates. // If so, we need to do a more expensive pass to identify and remove them. - using var _ = StringHashSetPool.Ordinal.GetPooledObject(out var duplicates); + using var _ = SpecializedPools.GetPooledStringHashSet(out var duplicates); foreach (var child in children) { @@ -329,7 +329,7 @@ static void ReportDiagnosticAndRemoveDuplicates(IntermediateNode node) { var children = node.Children; - using var _ = StringDictionaryPool.Builder>.Ordinal.GetPooledObject(out var duplicates); + using var _ = SpecializedPools.GetPooledStringDictionary.Builder>(out var duplicates); for (var i = 0; i < children.Count; i++) { diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentEventHandlerLoweringPass.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentEventHandlerLoweringPass.cs index a0428735fc0..68ae25f5aae 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentEventHandlerLoweringPass.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Components/ComponentEventHandlerLoweringPass.cs @@ -36,7 +36,7 @@ protected override void ExecuteCore( // For each event handler *usage* we need to rewrite the tag helper node to map to basic constructs. // Each usage will be represented by a tag helper property that is a descendant of either // a component or element. - using var _ = ReferenceEqualityHashSetPool.GetPooledObject(out var parents); + using var _ = SpecializedPools.GetPooledReferenceEqualityHashSet(out var parents); var references = documentNode.FindDescendantReferences(); foreach (var reference in references) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorIntermediateNodeLoweringPhase.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorIntermediateNodeLoweringPhase.cs index d9a9aaaf626..8f100de820f 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorIntermediateNodeLoweringPhase.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorIntermediateNodeLoweringPhase.cs @@ -187,7 +187,7 @@ private static IReadOnlyList ImportDirectives( private static void PostProcessImportedDirectives(DocumentIntermediateNode document) { - using var _ = ReferenceEqualityHashSetPool.GetPooledObject(out var seenDirectives); + using var _ = SpecializedPools.GetPooledReferenceEqualityHashSet(out var seenDirectives); var references = document.FindDescendantReferences(); for (var i = references.Length - 1; i >= 0; i--) diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorTagHelperContextDiscoveryPhase.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorTagHelperContextDiscoveryPhase.cs index 0aceb9158e0..2e4ad2a7365 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorTagHelperContextDiscoveryPhase.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorTagHelperContextDiscoveryPhase.cs @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Razor.Language.Legacy; using Microsoft.AspNetCore.Razor.Language.Syntax; using Microsoft.AspNetCore.Razor.PooledObjects; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.Language; @@ -73,7 +72,7 @@ internal static ReadOnlyMemory GetMemoryWithoutGlobalPrefix(string s) return mem; } - internal abstract class DirectiveVisitor : SyntaxWalker + internal abstract class DirectiveVisitor : SyntaxWalker, IPoolableObject { private bool _isInitialized; private string? _filePath; @@ -173,7 +172,7 @@ internal sealed class TagHelperDirectiveVisitor : DirectiveVisitor /// A larger pool of lists to handle scenarios where tag helpers /// originate from a large number of assemblies. /// - private static readonly ObjectPool> s_pool = ListPool.Create(100); + private static readonly ListPool s_pool = ListPool.Create(poolSize: 100); /// /// A map from assembly name to list of . Lists are allocated from and returned to diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorTagHelperContextDiscoveryPhase_Pooling.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorTagHelperContextDiscoveryPhase_Pooling.cs index 1f4dfbb2673..1107139723e 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorTagHelperContextDiscoveryPhase_Pooling.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/DefaultRazorTagHelperContextDiscoveryPhase_Pooling.cs @@ -10,27 +10,8 @@ namespace Microsoft.AspNetCore.Razor.Language; internal partial class DefaultRazorTagHelperContextDiscoveryPhase { - private static readonly ObjectPool s_tagHelperDirectiveVisitorPool = DefaultPool.Create(DirectiveVisitorPolicy.Instance); - private static readonly ObjectPool s_componentDirectiveVisitorPool = DefaultPool.Create(DirectiveVisitorPolicy.Instance); - - private sealed class DirectiveVisitorPolicy : IPooledObjectPolicy - where T : DirectiveVisitor, new() - { - public static readonly DirectiveVisitorPolicy Instance = new(); - - private DirectiveVisitorPolicy() - { - } - - public T Create() => new(); - - public bool Return(T visitor) - { - visitor.Reset(); - - return true; - } - } + private static readonly ObjectPool s_tagHelperDirectiveVisitorPool = DefaultPool.Create(); + private static readonly ObjectPool s_componentDirectiveVisitorPool = DefaultPool.Create(); internal readonly ref struct PooledDirectiveVisitor(DirectiveVisitor visitor, bool isComponentDirectiveVisitor) { diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Legacy/ClassifiedSpanVisitor.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Legacy/ClassifiedSpanVisitor.cs index c3cc20f56b6..3a5fa5509df 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Legacy/ClassifiedSpanVisitor.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Legacy/ClassifiedSpanVisitor.cs @@ -9,9 +9,13 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy; -internal sealed class ClassifiedSpanVisitor : SyntaxWalker +internal sealed class ClassifiedSpanVisitor : SyntaxWalker, IPoolableObject { - private static readonly ObjectPool Pool = DefaultPool.Create(Policy.Instance, size: 5); + // Significantly larger than DefaultPool.MaximumObjectSize as there shouldn't be much concurrency + // of these arrays (we limit the number of pooled items to 5) and they are commonly large + public const int MaximumObjectSize = DefaultPool.DefaultMaximumObjectSize * 32; + + private static readonly ObjectPool Pool = DefaultPool.Create(static () => new ClassifiedSpanVisitor(), poolSize: 5); private readonly ImmutableArray.Builder _spans; @@ -433,11 +437,11 @@ private void AddSpan(SyntaxToken token, SpanKindInternal kind, AcceptedCharacter private void AddSpan(SourceSpan span, SpanKindInternal kind, AcceptedCharactersInternal acceptedCharacters) => _spans.Add(new(span, CurrentBlockSpan, kind, _currentBlockKind, acceptedCharacters)); - private void Reset() + void IPoolableObject.Reset() { _spans.Clear(); - if (_spans.Capacity > Policy.MaximumObjectSize) + if (_spans.Capacity > MaximumObjectSize) { // Differs from ArrayBuilderPool.Policy's behavior as we allow our array to grow significantly larger _spans.Capacity = 0; @@ -448,26 +452,4 @@ private void Reset() _currentBlockSpan = null; _currentBlockKind = BlockKindInternal.Markup; } - - private sealed class Policy : IPooledObjectPolicy - { - public static readonly Policy Instance = new(); - - // Significantly larger than DefaultPool.MaximumObjectSize as there shouldn't be much concurrency - // of these arrays (we limit the number of pooled items to 5) and they are commonly large - public const int MaximumObjectSize = DefaultPool.MaximumObjectSize * 32; - - private Policy() - { - } - - public ClassifiedSpanVisitor Create() => new(); - - public bool Return(ClassifiedSpanVisitor visitor) - { - visitor.Reset(); - - return true; - } - } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Legacy/ParserContext.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Legacy/ParserContext.cs index 001b310bf0c..5ddd3cc9606 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Legacy/ParserContext.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/Legacy/ParserContext.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Threading; using Microsoft.AspNetCore.Razor.PooledObjects; @@ -38,7 +37,7 @@ public ParserContext(RazorSourceDocument source, RazorParserOptions options, Can _errorSinkStack = StackPool.Default.Get(); _errorSinkStack.Push(new ErrorSink()); - _seenDirectivesSet = StringHashSetPool.Ordinal.Get(); + _seenDirectivesSet = SpecializedPools.StringHashSet.Ordinal.Get(); Source = new SeekableTextReader(SourceDocument); } @@ -52,7 +51,7 @@ public void Dispose() } StackPool.Default.Return(_errorSinkStack); - StringHashSetPool.Ordinal.Return(_seenDirectivesSet); + SpecializedPools.StringHashSet.Ordinal.Return(_seenDirectivesSet); } public ErrorSink ErrorSink => _errorSinkStack.Peek(); @@ -74,7 +73,7 @@ public ErrorScope PushNewErrorScope(ErrorSink errorSink) // Debug Helpers #if DEBUG -[DebuggerDisplay("{" + nameof(DebuggerToString) + "(),nq}")] +[System.Diagnostics.DebuggerDisplay("{" + nameof(DebuggerToString) + "(),nq}")] internal partial class ParserContext { private string Unparsed diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.cs index 9a1ea1eb3d6..3774b8783c1 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorCodeGenerationOptions.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using Microsoft.CodeAnalysis; namespace Microsoft.AspNetCore.Razor.Language; diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorParserOptions.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorParserOptions.cs index ae213a67853..739bb4de3c2 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorParserOptions.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RazorParserOptions.cs @@ -5,7 +5,6 @@ using System.Collections.Immutable; using System.Linq; using Microsoft.AspNetCore.Razor.Utilities; -using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.Extensions.Internal; diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RequiredAttributeDescriptorBuilder_Pooling.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RequiredAttributeDescriptorBuilder_Pooling.cs index ab9428c9d15..b47177a2f2d 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RequiredAttributeDescriptorBuilder_Pooling.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/RequiredAttributeDescriptorBuilder_Pooling.cs @@ -8,7 +8,8 @@ namespace Microsoft.AspNetCore.Razor.Language; public partial class RequiredAttributeDescriptorBuilder { - internal static readonly ObjectPool Pool = DefaultPool.Create(Policy.Instance); + internal static readonly ObjectPool Pool = + DefaultPool.Create(static () => new RequiredAttributeDescriptorBuilder()); internal static RequiredAttributeDescriptorBuilder GetInstance(TagMatchingRuleDescriptorBuilder parent) { @@ -29,15 +30,4 @@ private protected override void Reset() Value = null; ValueComparison = default; } - - private sealed class Policy : PooledBuilderPolicy - { - public static readonly Policy Instance = new(); - - private Policy() - { - } - - public override RequiredAttributeDescriptorBuilder Create() => new(); - } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperBinder.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperBinder.cs index c85bcd90d97..c48b61748f1 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperBinder.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperBinder.cs @@ -56,7 +56,7 @@ private static void ProcessDescriptors( using var catchAllToAdd = new MemoryBuilder(initialCapacity: descriptors.Length, clearArray: true); // The builders are indexed using a map of "tag name" to the index of the builder in the array. - using var _1 = StringDictionaryPool.OrdinalIgnoreCase.GetPooledObject(out var tagNameToBuilderIndexMap); + using var _1 = SpecializedPools.GetPooledStringDictionary(ignoreCase: true, out var tagNameToBuilderIndexMap); using var _2 = HashSetPool.GetPooledObject(out var tagHelperSet); #if NET diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperDescriptorBuilder_Pooling.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperDescriptorBuilder_Pooling.cs index 2b23da4ff9a..43a7ee992ea 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperDescriptorBuilder_Pooling.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperDescriptorBuilder_Pooling.cs @@ -9,7 +9,8 @@ namespace Microsoft.AspNetCore.Razor.Language; public partial class TagHelperDescriptorBuilder { - private static readonly ObjectPool s_pool = DefaultPool.Create(Policy.Instance); + private static readonly ObjectPool s_pool = + DefaultPool.Create(static () => new TagHelperDescriptorBuilder()); internal static TagHelperDescriptorBuilder GetInstance(string name, string assemblyName) => GetInstance(TagHelperKind.ITagHelper, name, assemblyName); @@ -46,17 +47,6 @@ private protected override void Reset() TagMatchingRules.Clear(); } - private sealed class Policy : PooledBuilderPolicy - { - public static readonly Policy Instance = new(); - - private Policy() - { - } - - public override TagHelperDescriptorBuilder Create() => new(); - } - /// /// Retrieves a pooled instance. /// diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperObjectBuilder`1.PooledBuilderPolicy`1.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperObjectBuilder`1.PooledBuilderPolicy`1.cs deleted file mode 100644 index 5e4296ea48e..00000000000 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperObjectBuilder`1.PooledBuilderPolicy`1.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.ObjectPool; - -namespace Microsoft.AspNetCore.Razor.Language; - -public abstract partial class TagHelperObjectBuilder - where T : TagHelperObject -{ - private protected abstract class PooledBuilderPolicy : IPooledObjectPolicy - where TBuilder : TagHelperObjectBuilder - { - private const int MaxSize = 32; - - public abstract TBuilder Create(); - - public bool Return(TBuilder builder) - { - builder._isBuilt = false; - - if (builder._diagnostics is { } diagnostics) - { - diagnostics.Clear(); - - if (diagnostics.Capacity > MaxSize) - { - diagnostics.Capacity = MaxSize; - } - } - - builder.Reset(); - - return true; - } - } -} diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperObjectBuilder`1.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperObjectBuilder`1.cs index 4251dcf13d5..8d7de7b83b9 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperObjectBuilder`1.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperObjectBuilder`1.cs @@ -7,7 +7,7 @@ namespace Microsoft.AspNetCore.Razor.Language; -public abstract partial class TagHelperObjectBuilder +public abstract partial class TagHelperObjectBuilder : IPoolableObject where T : TagHelperObject { private ImmutableArray.Builder? _diagnostics; @@ -50,4 +50,23 @@ private protected virtual void CollectDiagnostics(ref PooledHashSet MaxSize) + { + diagnostics.Capacity = MaxSize; + } + } + + Reset(); + } } diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagMatchingRuleDescriptorBuilder_Pooling.cs b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagMatchingRuleDescriptorBuilder_Pooling.cs index 902e156c5da..58e3f099dfb 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagMatchingRuleDescriptorBuilder_Pooling.cs +++ b/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagMatchingRuleDescriptorBuilder_Pooling.cs @@ -8,7 +8,8 @@ namespace Microsoft.AspNetCore.Razor.Language; public partial class TagMatchingRuleDescriptorBuilder { - internal static readonly ObjectPool Pool = DefaultPool.Create(Policy.Instance); + internal static readonly ObjectPool Pool = + DefaultPool.Create(static () => new TagMatchingRuleDescriptorBuilder()); internal static TagMatchingRuleDescriptorBuilder GetInstance(TagHelperDescriptorBuilder parent) { @@ -28,15 +29,4 @@ private protected override void Reset() TagStructure = default; Attributes.Clear(); } - - private sealed class Policy : PooledBuilderPolicy - { - public static readonly Policy Instance = new(); - - private Policy() - { - } - - public override TagMatchingRuleDescriptorBuilder Create() => new(); - } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs index 3eee29c2391..143d89e6a96 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/AutoInsert/AutoInsertService.cs @@ -25,7 +25,7 @@ internal class AutoInsertService(IEnumerable onAutoInsert private static ImmutableArray CalculateTriggerCharacters(IEnumerable onAutoInsertProviders) { using var builder = new PooledArrayBuilder(); - using var _ = StringHashSetPool.Ordinal.GetPooledObject(out var set); + using var _ = SpecializedPools.GetPooledStringHashSet(out var set); foreach (var provider in onAutoInsertProviders) { var triggerCharacter = provider.TriggerCharacter; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/CodeActions/CodeActionResolveService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/CodeActions/CodeActionResolveService.cs index d960d27e2ac..111ba8c5e72 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/CodeActions/CodeActionResolveService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/CodeActions/CodeActionResolveService.cs @@ -152,7 +152,7 @@ private bool TryGetResolver(RazorCodeActionResolutionParams resolutio private static FrozenDictionary CreateResolverMap(IEnumerable codeActionResolvers) where T : ICodeActionResolver { - using var _ = StringDictionaryPool.GetPooledObject(out var resolverMap); + using var _ = SpecializedPools.GetPooledStringDictionary(out var resolverMap); foreach (var resolver in codeActionResolvers) { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Completion/DirectiveAttributeCompletionItemProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Completion/DirectiveAttributeCompletionItemProvider.cs index ed40d77c300..f4ea021b3b1 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Completion/DirectiveAttributeCompletionItemProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Completion/DirectiveAttributeCompletionItemProvider.cs @@ -88,7 +88,7 @@ internal static ImmutableArray GetAttributeCompletions( } // Use ordinal dictionary because attributes are case sensitive when matching - using var _ = StringDictionaryPool<(ImmutableArray, ImmutableArray)>.Ordinal.GetPooledObject(out var attributeCompletions); + using var _ = SpecializedPools.GetPooledStringDictionary<(ImmutableArray, ImmutableArray)>(out var attributeCompletions); var inSnippetContext = InSnippetContext(containingAttribute, razorCompletionOptions); foreach (var descriptor in descriptorsForTag) diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Completion/DirectiveAttributeParameterCompletionItemProvider.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Completion/DirectiveAttributeParameterCompletionItemProvider.cs index 3baea7f5008..7f75b2ff84e 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Completion/DirectiveAttributeParameterCompletionItemProvider.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Completion/DirectiveAttributeParameterCompletionItemProvider.cs @@ -64,7 +64,7 @@ internal static ImmutableArray GetAttributeParameterComplet } // Use ordinal dictionary because attributes are case sensitive when matching - using var _ = StringDictionaryPool>.Ordinal.GetPooledObject(out var attributeCompletions); + using var _ = SpecializedPools.GetPooledStringDictionary>(out var attributeCompletions); foreach (var descriptor in descriptorsForTag) { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentMapping/RazorEditHelper.TextChangeBuilder.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentMapping/RazorEditHelper.TextChangeBuilder.cs index 62860a425cb..f50a1595195 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentMapping/RazorEditHelper.TextChangeBuilder.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/DocumentMapping/RazorEditHelper.TextChangeBuilder.cs @@ -16,7 +16,6 @@ using Microsoft.AspNetCore.Razor.PooledObjects; using Microsoft.CodeAnalysis.Razor.Protocol; using Microsoft.CodeAnalysis.Text; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.CodeAnalysis.Razor.DocumentMapping; @@ -24,7 +23,7 @@ internal static partial class RazorEditHelper { private sealed class TextChangeBuilder : IDisposable { - private ObjectPool.Builder> Pool => ArrayBuilderPool.Default; + private static ArrayBuilderPool Pool => ArrayBuilderPool.Default; private readonly ImmutableArray.Builder _builder; private readonly IDocumentMappingService _documentMappingService; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RazorCodeDocumentExtensions_ClassifiedSpans.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RazorCodeDocumentExtensions_ClassifiedSpans.cs index 37df5f94554..41b5c359a3f 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RazorCodeDocumentExtensions_ClassifiedSpans.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Extensions/RazorCodeDocumentExtensions_ClassifiedSpans.cs @@ -28,7 +28,7 @@ private enum SpanKind private record struct ClassifiedSpan(SourceSpan Span, SpanKind Kind); - private sealed class ClassifiedSpanVisitor : SyntaxWalker + private sealed class ClassifiedSpanVisitor : SyntaxWalker, IPoolableObject { private enum BlockKind { @@ -47,7 +47,11 @@ private enum BlockKind HtmlComment } - private static readonly ObjectPool s_pool = DefaultPool.Create(Policy.Instance, size: 5); + // Significantly larger than DefaultPool.MaximumObjectSize as there shouldn't be much concurrency + // of these arrays (we limit the number of pooled items to 5) and they are commonly large + public const int MaximumObjectSize = DefaultPool.DefaultMaximumObjectSize * 32; + + private static readonly ObjectPool s_pool = DefaultPool.Create(static () => new ClassifiedSpanVisitor(), poolSize: 5); private readonly ImmutableArray.Builder _spans; @@ -447,11 +451,11 @@ private void AddSpan(SyntaxToken token, SpanKind kind) private void AddSpan(SourceSpan span, SpanKind kind) => _spans.Add(new(span, kind)); - private void Reset() + void IPoolableObject.Reset() { _spans.Clear(); - if (_spans.Capacity > Policy.MaximumObjectSize) + if (_spans.Capacity > MaximumObjectSize) { // Differs from ArrayBuilderPool.Policy's behavior as we allow our array to grow significantly larger _spans.Capacity = 0; @@ -460,27 +464,5 @@ private void Reset() _source = null!; _currentBlockKind = BlockKind.Markup; } - - private sealed class Policy : IPooledObjectPolicy - { - public static readonly Policy Instance = new(); - - // Significantly larger than DefaultPool.MaximumObjectSize as there shouldn't be much concurrency - // of these arrays (we limit the number of pooled items to 5) and they are commonly large - public const int MaximumObjectSize = DefaultPool.MaximumObjectSize * 32; - - private Policy() - { - } - - public ClassifiedSpanVisitor Create() => new(); - - public bool Return(ClassifiedSpanVisitor visitor) - { - visitor.Reset(); - - return true; - } - } } } diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs index ddfc0a60b67..22313f45b1d 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/ProjectSystem/ProjectState.cs @@ -15,13 +15,12 @@ using Microsoft.CodeAnalysis.Razor.ProjectEngineHost; using Microsoft.CodeAnalysis.Razor.Utilities; using Microsoft.CodeAnalysis.Text; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.CodeAnalysis.Razor.ProjectSystem; internal sealed class ProjectState { - private static readonly ObjectPool.Builder>> s_importMapBuilderPool = + private static readonly DictionaryPool.Builder> s_importMapBuilderPool = DictionaryPool.Builder>.Create(FilePathNormalizingComparer.Instance); private static readonly ImmutableDictionary s_emptyDocuments diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Rename/RenameService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Rename/RenameService.cs index 7bf2b77017d..210af93d9f0 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Rename/RenameService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Rename/RenameService.cs @@ -95,7 +95,7 @@ internal class RenameService( private static ImmutableArray GetAllDocumentSnapshots(string filePath, ISolutionQueryOperations solutionQueryOperations) { using var documentSnapshots = new PooledArrayBuilder(); - using var _ = StringHashSetPool.GetPooledObject(out var documentPaths); + using var _ = SpecializedPools.GetPooledStringHashSet(out var documentPaths); foreach (var project in solutionQueryOperations.GetProjects()) { diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/SemanticTokens/AbstractRazorSemanticTokensInfoService.Policy.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/SemanticTokens/AbstractRazorSemanticTokensInfoService.Policy.cs deleted file mode 100644 index e89cc528397..00000000000 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/SemanticTokens/AbstractRazorSemanticTokensInfoService.Policy.cs +++ /dev/null @@ -1,40 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using Microsoft.Extensions.ObjectPool; - -namespace Microsoft.CodeAnalysis.Razor.SemanticTokens; - -internal abstract partial class AbstractRazorSemanticTokensInfoService -{ - private sealed class Policy : IPooledObjectPolicy> - { - public static readonly Policy Instance = new(); - - // Significantly larger than DefaultPool.MaximumObjectSize as these arrays are commonly large. - // The 2048 limit should be large enough for nearly all semantic token requests, while still - // keeping the backing arrays off the LOH. - public const int MaximumObjectSize = 2048; - - private Policy() - { - } - - public List Create() => []; - - public bool Return(List list) - { - var count = list.Count; - - list.Clear(); - - if (count > MaximumObjectSize) - { - list.TrimExcess(); - } - - return true; - } - } -} diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/SemanticTokens/AbstractRazorSemanticTokensInfoService.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/SemanticTokens/AbstractRazorSemanticTokensInfoService.cs index d2753817fc1..7719d377ab1 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/SemanticTokens/AbstractRazorSemanticTokensInfoService.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/SemanticTokens/AbstractRazorSemanticTokensInfoService.cs @@ -15,7 +15,6 @@ using Microsoft.CodeAnalysis.Razor.Logging; using Microsoft.CodeAnalysis.Razor.ProjectSystem; using Microsoft.CodeAnalysis.Text; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.CodeAnalysis.Razor.SemanticTokens; @@ -29,7 +28,11 @@ internal abstract partial class AbstractRazorSemanticTokensInfoService( private const int TokenSize = 5; // Use a custom pool as these lists commonly exceed the size threshold for returning into the default ListPool. - private static readonly ObjectPool> s_pool = DefaultPool.Create(Policy.Instance, size: 8); + // These lists are significantly larger than DefaultPool.MaximumObjectSize as these arrays are commonly large. + // The 2048 limit should be large enough for nearly all semantic token requests, while still + // keeping the backing arrays off the LOH. + + private static readonly ListPool s_pool = ListPool.Create(maximumObjectSize: 2048, poolSize: 8); private readonly IDocumentMappingService _documentMappingService = documentMappingService; private readonly ISemanticTokensLegendService _semanticTokensLegendService = semanticTokensLegendService; diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Serialization/MessagePack/Formatters/SerializerCachingOptions.ReferenceMap`1.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Serialization/MessagePack/Formatters/SerializerCachingOptions.ReferenceMap`1.cs index 34118f7d22a..3993ed91be8 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Serialization/MessagePack/Formatters/SerializerCachingOptions.ReferenceMap`1.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Serialization/MessagePack/Formatters/SerializerCachingOptions.ReferenceMap`1.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using Microsoft.AspNetCore.Razor.PooledObjects; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.CodeAnalysis.Razor.Serialization.MessagePack.Formatters; @@ -13,11 +12,11 @@ internal partial class SerializerCachingOptions public struct ReferenceMap : IDisposable where T : notnull { - private readonly ObjectPool> _dictionaryPool; + private readonly DictionaryPool _dictionaryPool; private List _values; private Dictionary _valueToIdMap; - public ReferenceMap(ObjectPool> dictionaryPool) + public ReferenceMap(DictionaryPool dictionaryPool) { _dictionaryPool = dictionaryPool; _values = ListPool.Default.Get(); diff --git a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Serialization/MessagePack/Formatters/SerializerCachingOptions.cs b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Serialization/MessagePack/Formatters/SerializerCachingOptions.cs index 993040f0814..e257b46bfc6 100644 --- a/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Serialization/MessagePack/Formatters/SerializerCachingOptions.cs +++ b/src/Razor/src/Microsoft.CodeAnalysis.Razor.Workspaces/Serialization/MessagePack/Formatters/SerializerCachingOptions.cs @@ -2,17 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using MessagePack; using Microsoft.AspNetCore.Razor.PooledObjects; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.CodeAnalysis.Razor.Serialization.MessagePack.Formatters; internal partial class SerializerCachingOptions(MessagePackSerializerOptions copyFrom) : MessagePackSerializerOptions(copyFrom), IDisposable { - private static readonly ObjectPool> s_stringPool - = StringDictionaryPool.Ordinal; + private static readonly DictionaryPool s_stringPool = SpecializedPools.StringDictionary.Ordinal; private ReferenceMap? _stringMap; diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/JsonDataReader.Policy.cs b/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/JsonDataReader.Policy.cs deleted file mode 100644 index 3083859e6b5..00000000000 --- a/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/JsonDataReader.Policy.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.ObjectPool; - -namespace Microsoft.AspNetCore.Razor.Serialization.Json; - -internal partial class JsonDataReader -{ - private sealed class Policy : IPooledObjectPolicy - { - public static readonly Policy Instance = new(); - - private Policy() - { - } - - public JsonDataReader Create() => new(); - - public bool Return(JsonDataReader dataWriter) - { - dataWriter._reader = null; - return true; - } - } -} diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/JsonDataReader.cs b/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/JsonDataReader.cs index 2a8df6d673b..20756cbddb7 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/JsonDataReader.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/JsonDataReader.cs @@ -18,9 +18,9 @@ namespace Microsoft.AspNetCore.Razor.Serialization.Json; /// This is an abstraction used to read JSON data. Currently, this /// wraps a from JSON.NET. /// -internal partial class JsonDataReader +internal partial class JsonDataReader : IPoolableObject { - private static readonly ObjectPool s_pool = DefaultPool.Create(Policy.Instance); + private static readonly ObjectPool s_pool = DefaultPool.Create(() => new JsonDataReader()); public static JsonDataReader Get(JsonReader reader) { @@ -33,6 +33,11 @@ public static JsonDataReader Get(JsonReader reader) public static void Return(JsonDataReader dataReader) => s_pool.Return(dataReader); + void IPoolableObject.Reset() + { + _reader = null; + } + [AllowNull] private JsonReader _reader; diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/JsonDataWriter.Policy.cs b/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/JsonDataWriter.Policy.cs deleted file mode 100644 index aae1541065e..00000000000 --- a/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/JsonDataWriter.Policy.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.Extensions.ObjectPool; - -namespace Microsoft.AspNetCore.Razor.Serialization.Json; - -internal partial class JsonDataWriter -{ - private sealed class Policy : IPooledObjectPolicy - { - public static readonly Policy Instance = new(); - - private Policy() - { - } - - public JsonDataWriter Create() => new(); - - public bool Return(JsonDataWriter dataWriter) - { - dataWriter._writer = null; - return true; - } - } -} diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/JsonDataWriter.cs b/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/JsonDataWriter.cs index 06053d85468..5ef23a22273 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/JsonDataWriter.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/JsonDataWriter.cs @@ -19,9 +19,9 @@ namespace Microsoft.AspNetCore.Razor.Serialization.Json; /// This is an abstraction used to write JSON data. Currently, this /// wraps a from JSON.NET. /// -internal partial class JsonDataWriter +internal partial class JsonDataWriter : IPoolableObject { - private static readonly ObjectPool s_pool = DefaultPool.Create(Policy.Instance); + private static readonly ObjectPool s_pool = DefaultPool.Create(() => new JsonDataWriter()); public static JsonDataWriter Get(JsonWriter writer) { @@ -34,6 +34,11 @@ public static JsonDataWriter Get(JsonWriter writer) public static void Return(JsonDataWriter dataWriter) => s_pool.Return(dataWriter); + void IPoolableObject.Reset() + { + _writer = null; + } + [AllowNull] private JsonWriter _writer; diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/Microsoft.AspNetCore.Razor.Serialization.Json.projitems b/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/Microsoft.AspNetCore.Razor.Serialization.Json.projitems index ae65dff8446..4326d060f4b 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/Microsoft.AspNetCore.Razor.Serialization.Json.projitems +++ b/src/Shared/Microsoft.AspNetCore.Razor.Serialization.Json/Microsoft.AspNetCore.Razor.Serialization.Json.projitems @@ -12,9 +12,7 @@ - - diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/TestTagMatchingRuleDescriptorBuilderExtensions.cs b/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/TestTagMatchingRuleDescriptorBuilderExtensions.cs index 705e3768fe9..c7e1aa65e80 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/TestTagMatchingRuleDescriptorBuilderExtensions.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Test.Common/Language/TestTagMatchingRuleDescriptorBuilderExtensions.cs @@ -4,7 +4,6 @@ #nullable disable using System; -using Microsoft.CodeAnalysis; namespace Microsoft.AspNetCore.Razor.Language; diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/PooledObjects/PooledHashSetTests.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/PooledObjects/PooledHashSetTests.cs new file mode 100644 index 00000000000..863f72d1eb4 --- /dev/null +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/PooledObjects/PooledHashSetTests.cs @@ -0,0 +1,661 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using Microsoft.AspNetCore.Razor.PooledObjects; +using Xunit; + +namespace Microsoft.AspNetCore.Razor.Utilities.Shared.Test.PooledObjects; + +public class PooledHashSetTests +{ + [Fact] + public void Constructor_Default_CreatesEmptySet() + { + using var set = new PooledHashSet(); + + Assert.Equal(0, set.Count); + } + + [Fact] + public void Constructor_WithCapacity_CreatesEmptySet() + { + using var set = new PooledHashSet(10); + + Assert.Equal(0, set.Count); + } + + [Fact] + public void Constructor_WithComparer_CreatesEmptySet() + { + using var set = new PooledHashSet(StringComparer.OrdinalIgnoreCase); + + Assert.Equal(0, set.Count); + } + + [Fact] + public void Constructor_WithComparerAndCapacity_CreatesEmptySet() + { + using var set = new PooledHashSet(StringComparer.OrdinalIgnoreCase, 10); + + Assert.Equal(0, set.Count); + } + + [Fact] + public void Constructor_WithPool_CreatesEmptySet() + { + var pool = HashSetPool.Default; + using var set = new PooledHashSet(pool); + + Assert.Equal(0, set.Count); + } + + [Fact] + public void Constructor_WithPoolAndCapacity_CreatesEmptySet() + { + var pool = HashSetPool.Default; + using var set = new PooledHashSet(pool, 10); + + Assert.Equal(0, set.Count); + } + + [Fact] + public void Add_SingleItem_ReturnsTrue() + { + using var set = new PooledHashSet(); + + var result = set.Add(42); + + Assert.True(result); + Assert.Equal(1, set.Count); + } + + [Fact] + public void Add_DuplicateSingleItem_ReturnsFalse() + { + using var set = new PooledHashSet(); + + set.Add(42); + var result = set.Add(42); + + Assert.False(result); + Assert.Equal(1, set.Count); + } + + [Fact] + public void Add_TwoItems_CreatesHashSet() + { + using var set = new PooledHashSet(); + + var result1 = set.Add(42); + var result2 = set.Add(24); + + Assert.True(result1); + Assert.True(result2); + Assert.Equal(2, set.Count); + } + + [Fact] + public void Add_DuplicateInHashSet_ReturnsFalse() + { + using var set = new PooledHashSet(); + + set.Add(42); + set.Add(24); + var result = set.Add(42); + + Assert.False(result); + Assert.Equal(2, set.Count); + } + + [Fact] + public void Add_WithCustomComparer_UsesSameComparerForSingleItem() + { + using var set = new PooledHashSet(StringComparer.OrdinalIgnoreCase); + + set.Add("Hello"); + var result = set.Add("HELLO"); + + Assert.False(result); + Assert.Equal(1, set.Count); + } + + [Fact] + public void Add_WithCustomComparer_UsesSameComparerForHashSet() + { + using var set = new PooledHashSet(StringComparer.OrdinalIgnoreCase); + + set.Add("Hello"); + set.Add("World"); + var result = set.Add("HELLO"); + + Assert.False(result); + Assert.Equal(2, set.Count); + } + + [Fact] + public void Contains_EmptySet_ReturnsFalse() + { + using var set = new PooledHashSet(); + + var result = set.Contains(42); + + Assert.False(result); + } + + [Fact] + public void Contains_SingleItem_Exists_ReturnsTrue() + { + using var set = new PooledHashSet(); + set.Add(42); + + var result = set.Contains(42); + + Assert.True(result); + } + + [Fact] + public void Contains_SingleItem_DoesNotExist_ReturnsFalse() + { + using var set = new PooledHashSet(); + set.Add(42); + + var result = set.Contains(24); + + Assert.False(result); + } + + [Fact] + public void Contains_HashSet_Exists_ReturnsTrue() + { + using var set = new PooledHashSet(); + set.Add(42); + set.Add(24); + + var result = set.Contains(42); + + Assert.True(result); + } + + [Fact] + public void Contains_HashSet_DoesNotExist_ReturnsFalse() + { + using var set = new PooledHashSet(); + set.Add(42); + set.Add(24); + + var result = set.Contains(99); + + Assert.False(result); + } + + [Fact] + public void Contains_WithCustomComparer_UsesSameComparerForSingleItem() + { + using var set = new PooledHashSet(StringComparer.OrdinalIgnoreCase); + set.Add("Hello"); + + var result = set.Contains("HELLO"); + + Assert.True(result); + } + + [Fact] + public void Contains_WithCustomComparer_UsesSameComparerForHashSet() + { + using var set = new PooledHashSet(StringComparer.OrdinalIgnoreCase); + set.Add("Hello"); + set.Add("World"); + + var result = set.Contains("HELLO"); + + Assert.True(result); + } + + [Fact] + public void Count_EmptySet_ReturnsZero() + { + using var set = new PooledHashSet(); + + Assert.Equal(0, set.Count); + } + + [Fact] + public void Count_SingleItem_ReturnsOne() + { + using var set = new PooledHashSet(); + set.Add(42); + + Assert.Equal(1, set.Count); + } + + [Fact] + public void Count_MultipleItems_ReturnsCorrectCount() + { + using var set = new PooledHashSet(); + set.Add(42); + set.Add(24); + set.Add(99); + + Assert.Equal(3, set.Count); + } + + [Fact] + public void ToArray_EmptySet_ReturnsEmptyArray() + { + using var set = new PooledHashSet(); + + var result = set.ToArray(); + + Assert.Empty(result); + } + + [Fact] + public void ToArray_SingleItem_ReturnsArrayWithSingleItem() + { + using var set = new PooledHashSet(); + set.Add(42); + + var result = set.ToArray(); + + Assert.Single(result); + Assert.Equal(42, result[0]); + } + + [Fact] + public void ToArray_MultipleItems_ReturnsArrayWithAllItems() + { + using var set = new PooledHashSet(); + set.Add(42); + set.Add(24); + set.Add(99); + + var result = set.ToArray(); + + Assert.Equal(3, result.Length); + Assert.Contains(42, result); + Assert.Contains(24, result); + Assert.Contains(99, result); + } + + [Fact] + public void ToImmutableArray_EmptySet_ReturnsEmptyImmutableArray() + { + using var set = new PooledHashSet(); + + var result = set.ToImmutableArray(); + + Assert.True(result.IsEmpty); + } + + [Fact] + public void ToImmutableArray_SingleItem_ReturnsImmutableArrayWithSingleItem() + { + using var set = new PooledHashSet(); + set.Add(42); + + var result = set.ToImmutableArray(); + + Assert.Single(result); + Assert.Equal(42, result[0]); + } + + [Fact] + public void ToImmutableArray_MultipleItems_ReturnsImmutableArrayWithAllItems() + { + using var set = new PooledHashSet(); + set.Add(42); + set.Add(24); + set.Add(99); + + var result = set.ToImmutableArray(); + + Assert.Equal(3, result.Length); + Assert.Contains(42, result); + Assert.Contains(24, result); + Assert.Contains(99, result); + } + + [Fact] + public void OrderByAsArray_EmptySet_ReturnsEmptyImmutableArray() + { + using var set = new PooledHashSet(); + + var result = set.OrderByAsArray(x => x); + + Assert.True(result.IsEmpty); + } + + [Fact] + public void OrderByAsArray_SingleItem_ReturnsImmutableArrayWithSingleItem() + { + using var set = new PooledHashSet(); + set.Add(42); + + var result = set.OrderByAsArray(x => x); + + Assert.Single(result); + Assert.Equal(42, result[0]); + } + + [Fact] + public void OrderByAsArray_MultipleItems_ReturnsOrderedImmutableArray() + { + using var set = new PooledHashSet(); + set.Add(99); + set.Add(24); + set.Add(42); + + var result = set.OrderByAsArray(x => x); + + Assert.Equal(3, result.Length); + Assert.Equal(24, result[0]); + Assert.Equal(42, result[1]); + Assert.Equal(99, result[2]); + } + + [Fact] + public void UnionWith_ImmutableArray_Empty_NoChange() + { + using var set = new PooledHashSet(); + set.Add(42); + + ImmutableArray other = []; + set.UnionWith(other); + + Assert.Equal(1, set.Count); + Assert.True(set.Contains(42)); + } + + [Fact] + public void UnionWith_ImmutableArray_Default_NoChange() + { + using var set = new PooledHashSet(); + set.Add(42); + + ImmutableArray other = default; + set.UnionWith(other); + + Assert.Equal(1, set.Count); + Assert.True(set.Contains(42)); + } + + [Fact] + public void UnionWith_ImmutableArray_SingleItem_AddsItem() + { + using var set = new PooledHashSet(); + + ImmutableArray other = [42]; + set.UnionWith(other); + + Assert.Equal(1, set.Count); + Assert.True(set.Contains(42)); + } + + [Fact] + public void UnionWith_ImmutableArray_MultipleItems_AddsAllItems() + { + using var set = new PooledHashSet(); + + ImmutableArray other = [42, 24, 99]; + set.UnionWith(other); + + Assert.Equal(3, set.Count); + Assert.True(set.Contains(42)); + Assert.True(set.Contains(24)); + Assert.True(set.Contains(99)); + } + + [Fact] + public void UnionWith_ImmutableArray_WithExistingItems_UnionCorrectly() + { + using var set = new PooledHashSet(); + set.Add(42); + + ImmutableArray other = [24, 99, 42]; + set.UnionWith(other); + + Assert.Equal(3, set.Count); + Assert.True(set.Contains(42)); + Assert.True(set.Contains(24)); + Assert.True(set.Contains(99)); + } + + [Fact] + public void UnionWith_ReadOnlyList_Null_NoChange() + { + using var set = new PooledHashSet(); + set.Add(42); + + IReadOnlyList? other = null; + set.UnionWith(other); + + Assert.Equal(1, set.Count); + Assert.True(set.Contains(42)); + } + + [Fact] + public void UnionWith_ReadOnlyList_Empty_NoChange() + { + using var set = new PooledHashSet(); + set.Add(42); + + set.UnionWith(Array.Empty()); + + Assert.Equal(1, set.Count); + Assert.True(set.Contains(42)); + } + + [Fact] + public void UnionWith_ReadOnlyList_SingleItem_AddsItem() + { + using var set = new PooledHashSet(); + + int[] other = [42]; + set.UnionWith(other); + + Assert.Equal(1, set.Count); + Assert.True(set.Contains(42)); + } + + [Fact] + public void UnionWith_ReadOnlyList_MultipleItems_AddsAllItems() + { + using var set = new PooledHashSet(); + + int[] other = [42, 24, 99]; + set.UnionWith(other); + + Assert.Equal(3, set.Count); + Assert.True(set.Contains(42)); + Assert.True(set.Contains(24)); + Assert.True(set.Contains(99)); + } + + [Fact] + public void UnionWith_ReadOnlyList_WithExistingItems_UnionCorrectly() + { + using var set = new PooledHashSet(); + set.Add(42); + + int[] other = [24, 99, 42]; + set.UnionWith(other); + + Assert.Equal(3, set.Count); + Assert.True(set.Contains(42)); + Assert.True(set.Contains(24)); + Assert.True(set.Contains(99)); + } + + [Fact] + public void ClearAndFree_EmptySet_DoesNotThrow() + { + var set = new PooledHashSet(); + + set.ClearAndFree(); + + Assert.Equal(0, set.Count); + } + + [Fact] + public void ClearAndFree_SingleItem_ClearsSet() + { + var set = new PooledHashSet(); + set.Add(42); + + set.ClearAndFree(); + + Assert.Equal(0, set.Count); + Assert.False(set.Contains(42)); + } + + [Fact] + public void ClearAndFree_MultipleItems_ClearsSet() + { + var set = new PooledHashSet(); + set.Add(42); + set.Add(24); + + set.ClearAndFree(); + + Assert.Equal(0, set.Count); + Assert.False(set.Contains(42)); + Assert.False(set.Contains(24)); + } + + [Fact] + public void Dispose_CallsClearAndFree() + { + var set = new PooledHashSet(); + set.Add(42); + set.Add(24); + + set.Dispose(); + + Assert.Equal(0, set.Count); + Assert.False(set.Contains(42)); + Assert.False(set.Contains(24)); + } + + [Fact] + public void UsingStatement_AutomaticallyDisposesSet() + { + PooledHashSet capturedSet; + + using (var set = new PooledHashSet()) + { + set.Add(42); + set.Add(24); + capturedSet = set; + Assert.Equal(2, capturedSet.Count); + } + + // After using statement, set should be disposed + Assert.Equal(0, capturedSet.Count); + } + + [Fact] + public void MultipleOperations_WorkCorrectly() + { + using var set = new PooledHashSet(); + + // Start with single item optimization + Assert.True(set.Add("first")); + Assert.Equal(1, set.Count); + Assert.True(set.Contains("first")); + + // Transition to HashSet + Assert.True(set.Add("second")); + Assert.Equal(2, set.Count); + Assert.True(set.Contains("first")); + Assert.True(set.Contains("second")); + + // Add more items + Assert.True(set.Add("third")); + Assert.False(set.Add("first")); // Duplicate + Assert.Equal(3, set.Count); + + // Union with array + string[] other = ["fourth", "first", "fifth"]; + set.UnionWith(other); + Assert.Equal(5, set.Count); + + // Check final state + var array = set.ToArray(); + Assert.Equal(5, array.Length); + Assert.Contains("first", array); + Assert.Contains("second", array); + Assert.Contains("third", array); + Assert.Contains("fourth", array); + Assert.Contains("fifth", array); + } + + [Fact] + public void StringComparer_Ordinal_WorksCorrectly() + { + using var set = new PooledHashSet(StringComparer.Ordinal); + + set.Add("Hello"); + set.Add("hello"); + + Assert.Equal(2, set.Count); + Assert.True(set.Contains("Hello")); + Assert.True(set.Contains("hello")); + Assert.False(set.Contains("HELLO")); + } + + [Fact] + public void StringComparer_OrdinalIgnoreCase_WorksCorrectly() + { + using var set = new PooledHashSet(StringComparer.OrdinalIgnoreCase); + + set.Add("Hello"); + Assert.False(set.Add("hello")); + Assert.False(set.Add("HELLO")); + + Assert.Equal(1, set.Count); + Assert.True(set.Contains("Hello")); + Assert.True(set.Contains("hello")); + Assert.True(set.Contains("HELLO")); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(10)] + [InlineData(100)] + public void Capacity_Constructor_DoesNotAffectFunctionality(int capacity) + { + using var set = new PooledHashSet(capacity); + + for (var i = 0; i < 50; i++) + { + set.Add(i); + } + + Assert.Equal(50, set.Count); + + for (var i = 0; i < 50; i++) + { + Assert.True(set.Contains(i)); + } + } + + [Fact] + public void OrderByAsArray_WithComplexKeySelector_WorksCorrectly() + { + using var set = new PooledHashSet(); + set.Add("apple"); + set.Add("banana"); + set.Add("cherry"); + + var result = set.OrderByAsArray(s => s.Length); + + Assert.Equal(3, result.Length); + Assert.Equal("apple", result[0]); // Length 5 + Assert.Equal("banana", result[1]); // Length 6 + Assert.Equal("cherry", result[2]); // Length 6 (stable sort) + } +} diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/PooledObjects/TestArrayBuilderPool`1.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/PooledObjects/TestArrayBuilderPool`1.cs index d7b8feb1109..1554e3b28ec 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/PooledObjects/TestArrayBuilderPool`1.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared.Test/PooledObjects/TestArrayBuilderPool`1.cs @@ -3,17 +3,16 @@ using System.Collections.Immutable; using Microsoft.AspNetCore.Razor.PooledObjects; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.Utilities.Shared.Test.PooledObjects; internal static class TestArrayBuilderPool { - public static ObjectPool.Builder> Create( - IPooledObjectPolicy.Builder>? policy = null, int size = 1) - => DefaultPool.Create(policy ?? NoReturnPolicy.Instance, size); + public static ArrayBuilderPool Create( + ArrayBuilderPool.PooledObjectPolicy? policy = null, int size = 1) + => ArrayBuilderPool.Create(policy ?? NoReturnPolicy.Instance, size); - public sealed class NoReturnPolicy : IPooledObjectPolicy.Builder> + public sealed class NoReturnPolicy : ArrayBuilderPool.PooledObjectPolicy { public static readonly NoReturnPolicy Instance = new(); @@ -21,10 +20,10 @@ private NoReturnPolicy() { } - public ImmutableArray.Builder Create() + public override ImmutableArray.Builder Create() => ImmutableArray.CreateBuilder(); - public bool Return(ImmutableArray.Builder obj) + public override bool Return(ImmutableArray.Builder obj) => false; } } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/Optional`1.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/Optional`1.cs new file mode 100644 index 00000000000..14a7e7f1ed3 --- /dev/null +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/Optional`1.cs @@ -0,0 +1,20 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.AspNetCore.Razor; + +public readonly struct Optional(T value) +{ + public bool HasValue { get; } = true; + + public T Value { get; } = value; + + public T GetValueOrDefault(T defaultValue) + => HasValue ? Value : defaultValue; + + public static implicit operator Optional(T value) + => new(value); + + public override string ToString() + => HasValue ? Value?.ToString() ?? "null" : "unspecified"; +} diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ArrayBuilderPool`1.Policy.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ArrayBuilderPool`1.Policy.cs index 351e44fb014..51fb43f8988 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ArrayBuilderPool`1.Policy.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ArrayBuilderPool`1.Policy.cs @@ -2,31 +2,57 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Immutable; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; -internal static partial class ArrayBuilderPool +internal partial class ArrayBuilderPool { - private class Policy : IPooledObjectPolicy.Builder> + private sealed class Policy : PooledObjectPolicy { - public static readonly Policy Instance = new(); + // This is the default initial capacity for ImmutableArray.Builder. + private const int DefaultInitialCapacity = 8; - private Policy() + public static readonly Policy Default = new(DefaultInitialCapacity, DefaultMaximumObjectSize); + + private readonly int _initialCapacity; + private readonly int _maximumObjectSize; + + private Policy(int initialCapacity, int maximumObjectSize) { + ArgHelper.ThrowIfNegative(initialCapacity); + ArgHelper.ThrowIfNegative(maximumObjectSize); + + _initialCapacity = initialCapacity; + _maximumObjectSize = maximumObjectSize; + } + + public static Policy Create( + Optional initialCapacity = default, + Optional maximumObjectSize = default) + { + if ((!initialCapacity.HasValue || initialCapacity.Value == Default._initialCapacity) && + (!maximumObjectSize.HasValue || maximumObjectSize.Value == Default._maximumObjectSize)) + { + return Default; + } + + return new( + initialCapacity.GetValueOrDefault(DefaultInitialCapacity), + maximumObjectSize.GetValueOrDefault(DefaultMaximumObjectSize)); } - public ImmutableArray.Builder Create() => ImmutableArray.CreateBuilder(); + public override ImmutableArray.Builder Create() + => ImmutableArray.CreateBuilder(); - public bool Return(ImmutableArray.Builder builder) + public override bool Return(ImmutableArray.Builder builder) { var count = builder.Count; builder.Clear(); - if (count > DefaultPool.MaximumObjectSize) + if (count > _maximumObjectSize) { - builder.Capacity = DefaultPool.MaximumObjectSize; + builder.Capacity = _maximumObjectSize; } return true; diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ArrayBuilderPool`1.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ArrayBuilderPool`1.cs index 5c00d70d294..97105fdca2a 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ArrayBuilderPool`1.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ArrayBuilderPool`1.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Immutable; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; @@ -14,9 +13,23 @@ namespace Microsoft.AspNetCore.Razor.PooledObjects; /// Instances originating from this pool are intended to be short-lived and are suitable /// for temporary work. Do not return them as the results of methods or store them in fields. /// -internal static partial class ArrayBuilderPool +internal sealed partial class ArrayBuilderPool : CustomObjectPool.Builder> { - public static readonly ObjectPool.Builder> Default = DefaultPool.Create(Policy.Instance); + public static readonly ArrayBuilderPool Default = Create(); + + private ArrayBuilderPool(PooledObjectPolicy policy, Optional poolSize) + : base(policy, poolSize) + { + } + + public static ArrayBuilderPool Create( + Optional initialCapacity = default, + Optional maximumObjectSize = default, + Optional poolSize = default) + => new(Policy.Create(initialCapacity, maximumObjectSize), poolSize); + + public static ArrayBuilderPool Create(PooledObjectPolicy policy, Optional poolSize = default) + => new(policy, poolSize); public static PooledObject.Builder> GetPooledObject() => Default.GetPooledObject(); diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/CustomObjectPool`1.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/CustomObjectPool`1.cs new file mode 100644 index 00000000000..74f576d201e --- /dev/null +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/CustomObjectPool`1.cs @@ -0,0 +1,25 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.Extensions.ObjectPool; + +namespace Microsoft.AspNetCore.Razor.PooledObjects; + +internal abstract class CustomObjectPool : DefaultObjectPool + where T : class +{ + protected const int DefaultPoolSize = DefaultPool.DefaultPoolSize; + protected const int DefaultMaximumObjectSize = DefaultPool.DefaultMaximumObjectSize; + + protected CustomObjectPool(PooledObjectPolicy policy, Optional poolSize) + : base(policy, poolSize.HasValue ? poolSize.Value : DefaultPoolSize) + { + } + + public abstract class PooledObjectPolicy : IPooledObjectPolicy + { + public abstract T Create(); + + public abstract bool Return(T obj); + } +} diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DefaultPool.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DefaultPool.cs index a420c84303d..c052b07928b 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DefaultPool.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DefaultPool.cs @@ -1,15 +1,53 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; internal static class DefaultPool { - public const int MaximumObjectSize = 512; + public const int DefaultPoolSize = 20; + public const int DefaultMaximumObjectSize = 512; - public static ObjectPool Create(IPooledObjectPolicy policy, int size = 20) + public static ObjectPool Create(IPooledObjectPolicy policy, Optional poolSize = default) where T : class - => new DefaultObjectPool(policy, size); + => new DefaultObjectPool(policy, poolSize.HasValue ? poolSize.Value : DefaultPoolSize); + + public static ObjectPool Create(Optional poolSize = default) + where T : class, IPoolableObject, new() + => Create(new PoolableObjectPolicy(static () => new()), poolSize); + + public static ObjectPool Create(Func factory, Optional poolSize = default) + where T : class, IPoolableObject + => Create(new PoolableObjectPolicy(factory), poolSize); + + public static ObjectPool Create(TArg arg, Func factory, Optional poolSize = default) + where T : class, IPoolableObject + => Create(new PoolableObjectPolicy(arg, factory), poolSize); + + private sealed class PoolableObjectPolicy(Func factory) : IPooledObjectPolicy + where T : class, IPoolableObject + { + public T Create() => factory(); + + public bool Return(T obj) + { + obj.Reset(); + return true; + } + } + + private sealed class PoolableObjectPolicy(TArg arg, Func factory) : IPooledObjectPolicy + where T : class, IPoolableObject + { + public T Create() => factory(arg); + + public bool Return(T obj) + { + obj.Reset(); + return true; + } + } } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DictionaryBuilderPool`2.Policy.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DictionaryBuilderPool`2.Policy.cs index 66d0e53d595..63f8e1fb73a 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DictionaryBuilderPool`2.Policy.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DictionaryBuilderPool`2.Policy.cs @@ -3,21 +3,36 @@ using System.Collections.Generic; using System.Collections.Immutable; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; -internal static partial class DictionaryBuilderPool +internal partial class DictionaryBuilderPool { - private class Policy(IEqualityComparer? keyComparer = null) : IPooledObjectPolicy.Builder> + private sealed class Policy : PooledObjectPolicy { - public static readonly Policy Instance = new(); + public static readonly Policy Default = new(keyComparer: null); - private readonly IEqualityComparer? _keyComparer = keyComparer; + private readonly IEqualityComparer? _keyComparer; - public ImmutableDictionary.Builder Create() => ImmutableDictionary.CreateBuilder(_keyComparer); + private Policy(IEqualityComparer? keyComparer) + { + _keyComparer = keyComparer; + } + + public static Policy Create(Optional?> keyComparer = default) + { + if (!keyComparer.HasValue || keyComparer.Value == Default._keyComparer) + { + return Default; + } + + return new(keyComparer.GetValueOrDefault(null)); + } + + public override ImmutableDictionary.Builder Create() + => ImmutableDictionary.CreateBuilder(_keyComparer); - public bool Return(ImmutableDictionary.Builder builder) + public override bool Return(ImmutableDictionary.Builder builder) { builder.Clear(); diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DictionaryBuilderPool`2.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DictionaryBuilderPool`2.cs index a200c7ac99f..e947fca6e52 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DictionaryBuilderPool`2.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DictionaryBuilderPool`2.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.Immutable; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; @@ -15,17 +14,28 @@ namespace Microsoft.AspNetCore.Razor.PooledObjects; /// Instances originating from this pool are intended to be short-lived and are suitable /// for temporary work. Do not return them as the results of methods or store them in fields. /// -internal static partial class DictionaryBuilderPool +internal sealed partial class DictionaryBuilderPool : CustomObjectPool.Builder> where TKey : notnull { - public static readonly ObjectPool.Builder> Default = DefaultPool.Create(Policy.Instance); + public static readonly DictionaryBuilderPool Default = Create(); - public static ObjectPool.Builder> Create(IEqualityComparer comparer) - => DefaultPool.Create(new Policy(comparer)); + private DictionaryBuilderPool(PooledObjectPolicy policy, Optional poolSize) + : base(policy, poolSize) + { + } + + public static DictionaryBuilderPool Create( + Optional?> keyComparer = default, + Optional poolSize = default) + => new(Policy.Create(keyComparer), poolSize); + + public static DictionaryBuilderPool Create(PooledObjectPolicy policy, Optional poolSize = default) + => new(policy, poolSize); public static PooledObject.Builder> GetPooledObject() => Default.GetPooledObject(); - public static PooledObject.Builder> GetPooledObject(out ImmutableDictionary.Builder builder) + public static PooledObject.Builder> GetPooledObject( + out ImmutableDictionary.Builder builder) => Default.GetPooledObject(out builder); } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DictionaryPool`2.Policy.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DictionaryPool`2.Policy.cs index 76d2fa5a610..1eee780818c 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DictionaryPool`2.Policy.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DictionaryPool`2.Policy.cs @@ -2,34 +2,52 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; -internal static partial class DictionaryPool +internal partial class DictionaryPool where TKey : notnull { - private class Policy : IPooledObjectPolicy> + private sealed class Policy : PooledObjectPolicy { - public static readonly Policy Instance = new(); + public static readonly Policy Default = new(comparer: null, DefaultMaximumObjectSize); private readonly IEqualityComparer? _comparer; + private readonly int _maximumObjectSize; - public Policy(IEqualityComparer? comparer = null) + private Policy(IEqualityComparer? comparer, int maximumObjectSize) { + ArgHelper.ThrowIfNegative(maximumObjectSize); + _comparer = comparer; + _maximumObjectSize = maximumObjectSize; + } + + public static Policy Create( + Optional?> comparer = default, + Optional maximumObjectSize = default) + { + if ((!comparer.HasValue || comparer.Value == Default._comparer) && + (!maximumObjectSize.HasValue || maximumObjectSize.Value == Default._maximumObjectSize)) + { + return Default; + } + + return new( + comparer.GetValueOrDefault(null), + maximumObjectSize.GetValueOrDefault(DefaultMaximumObjectSize)); } - public Dictionary Create() => new(_comparer); + public override Dictionary Create() => new(_comparer); - public bool Return(Dictionary map) + public override bool Return(Dictionary map) { var count = map.Count; map.Clear(); // If the map grew too large, don't return it to the pool. - return count <= DefaultPool.MaximumObjectSize; + return count <= _maximumObjectSize; } } } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DictionaryPool`2.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DictionaryPool`2.cs index e388a4932e9..d4f5bea63a8 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DictionaryPool`2.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/DictionaryPool`2.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; @@ -14,13 +13,24 @@ namespace Microsoft.AspNetCore.Razor.PooledObjects; /// Instances originating from this pool are intended to be short-lived and are suitable /// for temporary work. Do not return them as the results of methods or store them in fields. /// -internal static partial class DictionaryPool +internal sealed partial class DictionaryPool : CustomObjectPool> where TKey : notnull { - public static readonly ObjectPool> Default = DefaultPool.Create(Policy.Instance); + public static readonly DictionaryPool Default = Create(); - public static ObjectPool> Create(IEqualityComparer comparer) - => DefaultPool.Create(new Policy(comparer)); + private DictionaryPool(PooledObjectPolicy policy, Optional poolSize) + : base(policy, poolSize) + { + } + + public static DictionaryPool Create( + Optional?> comparer = default, + Optional maximumObjectSize = default, + Optional poolSize = default) + => new(Policy.Create(comparer, maximumObjectSize), poolSize); + + public static DictionaryPool Create(PooledObjectPolicy policy, Optional poolSize = default) + => new(policy, poolSize); public static PooledObject> GetPooledObject() => Default.GetPooledObject(); diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/HashSetPool`1.Policy.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/HashSetPool`1.Policy.cs index 7163d622f10..68813a44f91 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/HashSetPool`1.Policy.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/HashSetPool`1.Policy.cs @@ -2,32 +2,51 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; -internal static partial class HashSetPool +internal partial class HashSetPool { - private class Policy : IPooledObjectPolicy> + private sealed class Policy : PooledObjectPolicy { - public static readonly Policy Instance = new(); + public static readonly Policy Default = new(comparer: EqualityComparer.Default, DefaultMaximumObjectSize); - private readonly IEqualityComparer? _comparer; + public IEqualityComparer Comparer { get; } - public Policy(IEqualityComparer? comparer = null) + private readonly int _maximumObjectSize; + + private Policy(IEqualityComparer? comparer, int maximumObjectSize) + { + ArgHelper.ThrowIfNegative(maximumObjectSize); + + Comparer = comparer ?? EqualityComparer.Default; + _maximumObjectSize = maximumObjectSize; + } + + public static Policy Create( + Optional?> comparer = default, + Optional maximumObjectSize = default) { - _comparer = comparer; + if ((!comparer.HasValue || comparer.Value is null || comparer.Value == Default.Comparer) && + (!maximumObjectSize.HasValue || maximumObjectSize.Value == Default._maximumObjectSize)) + { + return Default; + } + + return new( + comparer.GetValueOrDefault(EqualityComparer.Default) ?? EqualityComparer.Default, + maximumObjectSize.GetValueOrDefault(DefaultMaximumObjectSize)); } - public HashSet Create() => new(_comparer); + public override HashSet Create() => new(Comparer); - public bool Return(HashSet set) + public override bool Return(HashSet set) { var count = set.Count; set.Clear(); - if (count > DefaultPool.MaximumObjectSize) + if (count > _maximumObjectSize) { set.TrimExcess(); } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/HashSetPool`1.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/HashSetPool`1.cs index 58b1de1216a..01b713979d0 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/HashSetPool`1.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/HashSetPool`1.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; @@ -14,12 +13,25 @@ namespace Microsoft.AspNetCore.Razor.PooledObjects; /// Instances originating from this pool are intended to be short-lived and are suitable /// for temporary work. Do not return them as the results of methods or store them in fields. /// -internal static partial class HashSetPool +internal sealed partial class HashSetPool : CustomObjectPool> { - public static readonly ObjectPool> Default = DefaultPool.Create(Policy.Instance); + public static readonly HashSetPool Default = Create(); - public static ObjectPool> Create(IEqualityComparer comparer) - => DefaultPool.Create(new Policy(comparer)); + private readonly Policy _policy; + + private HashSetPool(Policy policy, Optional poolSize) + : base(policy, poolSize) + { + _policy = policy; + } + + public IEqualityComparer Comparer => _policy.Comparer; + + public static HashSetPool Create( + Optional?> comparer = default, + Optional maximumObjectSize = default, + Optional poolSize = default) + => new(Policy.Create(comparer, maximumObjectSize), poolSize); public static PooledObject> GetPooledObject() => Default.GetPooledObject(); diff --git a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperDescriptorBuilder.Policy.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/IPoolableObject.cs similarity index 58% rename from src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperDescriptorBuilder.Policy.cs rename to src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/IPoolableObject.cs index 1dba4ef22c8..048d73cdebd 100644 --- a/src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/TagHelperDescriptorBuilder.Policy.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/IPoolableObject.cs @@ -1,8 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.AspNetCore.Razor.Language; +namespace Microsoft.AspNetCore.Razor.PooledObjects; -public partial class TagHelperDescriptorBuilder +internal interface IPoolableObject { + void Reset(); } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ListPool`1.Policy.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ListPool`1.Policy.cs index b9190fab670..a0ae4646a91 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ListPool`1.Policy.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ListPool`1.Policy.cs @@ -2,29 +2,41 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; -internal static partial class ListPool +internal partial class ListPool { - private class Policy : IPooledObjectPolicy> + private sealed class Policy : PooledObjectPolicy { - public static readonly Policy Instance = new(); + public static readonly Policy Default = new(DefaultMaximumObjectSize); - private Policy() + private readonly int _maximumObjectSize; + + private Policy(int maximumObjectSize) + { + _maximumObjectSize = maximumObjectSize; + } + + public static Policy Create(Optional maximumObjectSize = default) { + if (!maximumObjectSize.HasValue || maximumObjectSize.Value == Default._maximumObjectSize) + { + return Default; + } + + return new(maximumObjectSize.GetValueOrDefault(DefaultMaximumObjectSize)); } - public List Create() => new(); + public override List Create() => []; - public bool Return(List list) + public override bool Return(List list) { var count = list.Count; list.Clear(); - if (count > DefaultPool.MaximumObjectSize) + if (count > _maximumObjectSize) { list.TrimExcess(); } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ListPool`1.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ListPool`1.cs index 5383253c542..d07b8be3d18 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ListPool`1.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ListPool`1.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; @@ -14,12 +13,22 @@ namespace Microsoft.AspNetCore.Razor.PooledObjects; /// Instances originating from this pool are intended to be short-lived and are suitable /// for temporary work. Do not return them as the results of methods or store them in fields. /// -internal static partial class ListPool +internal sealed partial class ListPool : CustomObjectPool> { - public static readonly ObjectPool> Default = DefaultPool.Create(Policy.Instance); + public static readonly ListPool Default = Create(); - public static ObjectPool> Create(int size = 20) - => DefaultPool.Create(Policy.Instance, size); + private ListPool(PooledObjectPolicy policy, Optional poolSize) + : base(policy, poolSize) + { + } + + public static ListPool Create( + Optional maximumObjectSize = default, + Optional poolSize = default) + => new(Policy.Create(maximumObjectSize), poolSize); + + public static ListPool Create(PooledObjectPolicy policy, Optional poolSize = default) + => new(policy, poolSize); public static PooledObject> GetPooledObject() => Default.GetPooledObject(); diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/PooledArrayBuilder`1.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/PooledArrayBuilder`1.cs index e1871439809..28fa62e2701 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/PooledArrayBuilder`1.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/PooledArrayBuilder`1.cs @@ -9,7 +9,6 @@ using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using Microsoft.AspNetCore.Razor.Utilities; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; @@ -34,7 +33,7 @@ internal partial struct PooledArrayBuilder : IDisposable /// private const int InlineCapacity = 4; - private ObjectPool.Builder>? _builderPool; + private ArrayBuilderPool? _builderPool; /// /// A builder to be used as storage after the first time that the number @@ -59,7 +58,7 @@ internal partial struct PooledArrayBuilder : IDisposable /// private int _inlineCount; - public PooledArrayBuilder(int? capacity = null, ObjectPool.Builder>? builderPool = null) + public PooledArrayBuilder(int? capacity = null, ArrayBuilderPool? builderPool = null) { _capacity = capacity is > InlineCapacity ? capacity : null; _builderPool = builderPool; diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/PooledDictionaryBuilder`2.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/PooledDictionaryBuilder`2.cs index bb633e25da2..78abd353350 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/PooledDictionaryBuilder`2.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/PooledDictionaryBuilder`2.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; @@ -13,10 +12,10 @@ namespace Microsoft.AspNetCore.Razor.PooledObjects; /// it's needed. Note: Dispose this to ensure that the pooled array builder is returned /// to the pool. /// -internal ref struct PooledDictionaryBuilder(ObjectPool.Builder>? pool) +internal ref struct PooledDictionaryBuilder(DictionaryBuilderPool? pool) where TKey : notnull { - private readonly ObjectPool.Builder> _pool = pool ?? DictionaryBuilderPool.Default; + private readonly DictionaryBuilderPool _pool = pool ?? DictionaryBuilderPool.Default; private ImmutableDictionary.Builder? _builder; diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/PooledHashSet`1.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/PooledHashSet`1.cs index edbe6b26003..08b2d1430f6 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/PooledHashSet`1.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/PooledHashSet`1.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; -using Microsoft.Extensions.ObjectPool; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; namespace Microsoft.AspNetCore.Razor.PooledObjects; @@ -15,80 +17,228 @@ namespace Microsoft.AspNetCore.Razor.PooledObjects; /// internal ref struct PooledHashSet { - private readonly ObjectPool> _pool; -#pragma warning disable IDE0052 // Used in NET only code below. Called API doesn't exist on framework. + private readonly IEqualityComparer _comparer; + + // Used in NET only code below. Called API doesn't exist on framework. +#pragma warning disable IDE0052 private readonly int? _capacity; #pragma warning restore IDE0052 + + private HashSetPool? _pool; + private (bool hasValue, T value) _item; private HashSet? _set; public PooledHashSet() - : this(pool: null, capacity: null) { + _comparer = EqualityComparer.Default; } - public PooledHashSet(ObjectPool> pool) - : this(pool, capacity: null) + public PooledHashSet(int capacity) { + _comparer = EqualityComparer.Default; + _capacity = capacity; } - public PooledHashSet(int capacity) - : this(pool: null, capacity) + public PooledHashSet(IEqualityComparer comparer) { + ValidateComparer(comparer); + _comparer = comparer; } - public PooledHashSet(ObjectPool>? pool, int? capacity) + public PooledHashSet(IEqualityComparer comparer, int capacity) { - _pool = pool ?? HashSetPool.Default; + ValidateComparer(comparer); + _comparer = comparer; _capacity = capacity; } + public PooledHashSet(HashSetPool pool, int capacity) + { + _comparer = pool.Comparer; + _pool = pool; + _capacity = capacity; + } + + public PooledHashSet(HashSetPool pool) + { + _comparer = pool.Comparer; + _pool = pool; + } + + [Conditional("DEBUG")] + private static void ValidateComparer(IEqualityComparer comparer) + { + if (!ReferenceEquals(comparer, EqualityComparer.Default) && + !ReferenceEquals(comparer, StringComparer.Ordinal) && + !ReferenceEquals(comparer, StringComparer.OrdinalIgnoreCase)) + { + ThrowHelper.ThrowArgumentException(nameof(comparer), + "Only EqualityComparer.Default, StringComparer.Ordinal, and StringComparer.OrdinalIgnoreCase are supported. Please provide a pool."); + } + } + + private readonly IEqualityComparer Comparer => _comparer ?? EqualityComparer.Default; + + private readonly bool HasSingleItem => _item.hasValue && _set is null; + + [MemberNotNullWhen(false, nameof(_set))] + private readonly bool SetIsNullOrEmpty => _set is null || _set.Count == 0; + public void Dispose() { ClearAndFree(); } public readonly int Count - => _set?.Count ?? 0; + { + get + { + if (HasSingleItem) + { + return 1; + } + + if (SetIsNullOrEmpty) + { + return 0; + } + + return _set.Count; + } + } public bool Add(T item) { - _set ??= GetHashSet(); + if (_set is null) + { + // Optimized for the single item case. + if (!HasSingleItem) + { + _item.value = item; + _item.hasValue = true; + return true; + } + + if (Comparer.Equals(_item.value, item)) + { + // Duplicate of single item. + return false; + } + + _set = AcquireHashSet(); + } + return _set.Add(item); } public void ClearAndFree() { - if (_set is { } set) + if (_pool is not null && _set is { } set) { _pool.Return(set); - _set = null; } + + _set = null; + _pool = null; + _item = default; } public readonly bool Contains(T item) - => _set?.Contains(item) ?? false; + { + if (_set is null) + { + return _item.hasValue && Comparer.Equals(_item.value, item); + } + + return _set.Contains(item); + } public readonly T[] ToArray() - => _set?.ToArray() ?? []; + { + if (HasSingleItem) + { + return [_item.value]; + } + + if (SetIsNullOrEmpty) + { + return []; + } + + var result = new T[_set.Count]; + _set.CopyTo(result); + + return result; + } public readonly ImmutableArray ToImmutableArray() - => _set?.ToImmutableArray() ?? []; + { + return ImmutableCollectionsMarshal.AsImmutableArray(ToArray()); + } public readonly ImmutableArray OrderByAsArray(Func keySelector) - => _set?.OrderByAsArray(keySelector) ?? []; + { + if (HasSingleItem) + { + return [_item.value]; + } + + if (SetIsNullOrEmpty) + { + return []; + } + + return _set.OrderByAsArray(keySelector); + } + + public void UnionWith(ImmutableArray other) + { + if (other.IsDefaultOrEmpty) + { + return; + } + + if (other.Length == 1) + { + Add(other[0]); + return; + } + + _set ??= AcquireHashSet(); + + // Avoid boxing the ImmutableArray + var array = ImmutableCollectionsMarshal.AsArray(other)!; - public void UnionWith(IList? other) + _set.UnionWith(array); + } + + public void UnionWith(IReadOnlyList? other) { - if (other?.Count > 0) + if (other is null || other.Count == 0) { - _set ??= GetHashSet(); - _set.UnionWith(other); + return; } + + if (other.Count == 1) + { + Add(other[0]); + return; + } + + _set ??= AcquireHashSet(); + + _set.UnionWith(other); } - private readonly HashSet GetHashSet() + private HashSet AcquireHashSet() { - var result = _pool.Get(); + Debug.Assert(_set is null); + + _pool ??= TrySelectPool(Comparer); + + var result = _pool is not null + ? _pool.Get() + : new HashSet(Comparer); #if NET if (_capacity is int capacity) @@ -97,6 +247,35 @@ private readonly HashSet GetHashSet() } #endif + if (HasSingleItem) + { + result.Add(_item.value); + _item = default; + } + return result; + + static HashSetPool? TrySelectPool(IEqualityComparer comparer) + { + if (ReferenceEquals(comparer, EqualityComparer.Default)) + { + return HashSetPool.Default; + } + + if (typeof(T) == typeof(string)) + { + if (ReferenceEquals(comparer, StringComparer.Ordinal)) + { + return (HashSetPool)(object)SpecializedPools.StringHashSet.Ordinal; + } + + if (ReferenceEquals(comparer, StringComparer.OrdinalIgnoreCase)) + { + return (HashSetPool)(object)SpecializedPools.StringHashSet.OrdinalIgnoreCase; + } + } + + return null; + } } } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/PooledList`1.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/PooledList`1.cs index a3a3a4464c3..0ecab2f0283 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/PooledList`1.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/PooledList`1.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; @@ -15,7 +14,7 @@ namespace Microsoft.AspNetCore.Razor.PooledObjects; /// internal ref partial struct PooledList { - private readonly ObjectPool> _pool; + private readonly ListPool _pool; private List? _list; public PooledList() @@ -23,7 +22,7 @@ public PooledList() { } - public PooledList(ObjectPool> pool) + public PooledList(ListPool pool) { _pool = pool; } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/QueuePool.Policy.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/QueuePool.Policy.cs deleted file mode 100644 index 25de56e1322..00000000000 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/QueuePool.Policy.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using Microsoft.Extensions.ObjectPool; - -namespace Microsoft.AspNetCore.Razor.PooledObjects; - -internal static partial class QueuePool -{ - private class Policy : IPooledObjectPolicy> - { - public static readonly Policy Instance = new(); - - private Policy() - { - } - - public Queue Create() => new Queue(); - - public bool Return(Queue queue) - { - var count = queue.Count; - - queue.Clear(); - - if (count > DefaultPool.MaximumObjectSize) - { - queue.TrimExcess(); - } - - return true; - } - } -} diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/QueuePool.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/QueuePool.cs deleted file mode 100644 index e4ca195fdc8..00000000000 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/QueuePool.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using Microsoft.Extensions.ObjectPool; - -namespace Microsoft.AspNetCore.Razor.PooledObjects; - -internal static partial class QueuePool -{ - public static readonly ObjectPool> Default = DefaultPool.Create(Policy.Instance); - - public static PooledObject> GetPooledObject() - => Default.GetPooledObject(); - - public static PooledObject> GetPooledObject(out Queue queue) - => Default.GetPooledObject(out queue); -} diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/QueuePool`1.Policy.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/QueuePool`1.Policy.cs new file mode 100644 index 00000000000..b927913c457 --- /dev/null +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/QueuePool`1.Policy.cs @@ -0,0 +1,47 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Razor.PooledObjects; + +internal partial class QueuePool +{ + private sealed class Policy : PooledObjectPolicy + { + public static readonly Policy Default = new(DefaultMaximumObjectSize); + + private readonly int _maximumObjectSize; + + private Policy(int maximumObjectSize) + { + _maximumObjectSize = maximumObjectSize; + } + + public static Policy Create(Optional maximumObjectSize = default) + { + if (!maximumObjectSize.HasValue || maximumObjectSize.Value == Default._maximumObjectSize) + { + return Default; + } + + return new(maximumObjectSize.GetValueOrDefault(DefaultMaximumObjectSize)); + } + + public override Queue Create() => new(); + + public override bool Return(Queue queue) + { + var count = queue.Count; + + queue.Clear(); + + if (count > _maximumObjectSize) + { + queue.TrimExcess(); + } + + return true; + } + } +} diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/QueuePool`1.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/QueuePool`1.cs new file mode 100644 index 00000000000..54e11f311f1 --- /dev/null +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/QueuePool`1.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Razor.PooledObjects; + +internal sealed partial class QueuePool : CustomObjectPool> +{ + public static readonly QueuePool Default = Create(); + + private QueuePool(PooledObjectPolicy policy, Optional poolSize) + : base(policy, poolSize) + { + } + + public static QueuePool Create( + Optional maximumObjectSize = default, + Optional poolSize = default) + => new(Policy.Create(maximumObjectSize), poolSize); + + public static QueuePool Create(PooledObjectPolicy policy, Optional poolSize = default) + => new(policy, poolSize); + + public static PooledObject> GetPooledObject() + => Default.GetPooledObject(); + + public static PooledObject> GetPooledObject(out Queue queue) + => Default.GetPooledObject(out queue); +} diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ReferenceEqualityHashSetPool`1.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ReferenceEqualityHashSetPool`1.cs deleted file mode 100644 index 59a892eeeec..00000000000 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/ReferenceEqualityHashSetPool`1.cs +++ /dev/null @@ -1,28 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Collections.Generic; -using Microsoft.AspNetCore.Razor.Utilities; -using Microsoft.Extensions.ObjectPool; - -namespace Microsoft.AspNetCore.Razor.PooledObjects; - -/// -/// A pool of instances that compares items using reference equality. -/// -/// -/// -/// Instances originating from this pool are intended to be short-lived and are suitable -/// for temporary work. Do not return them as the results of methods or store them in fields. -/// -internal static partial class ReferenceEqualityHashSetPool - where T : class -{ - public static readonly ObjectPool> Default = HashSetPool.Create(ReferenceEqualityComparer.Instance); - - public static PooledObject> GetPooledObject() - => Default.GetPooledObject(); - - public static PooledObject> GetPooledObject(out HashSet set) - => Default.GetPooledObject(out set); -} diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/SpecializedPools.ReferenceEqualityHashSet`1.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/SpecializedPools.ReferenceEqualityHashSet`1.cs new file mode 100644 index 00000000000..13cd869b766 --- /dev/null +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/SpecializedPools.ReferenceEqualityHashSet`1.cs @@ -0,0 +1,30 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using Microsoft.AspNetCore.Razor.Utilities; + +namespace Microsoft.AspNetCore.Razor.PooledObjects; + +internal static partial class SpecializedPools +{ + /// + /// A pool of instances that compares items using reference equality. + /// + /// + /// + /// Instances originating from this pool are intended to be short-lived and are suitable + /// for temporary work. Do not return them as the results of methods or store them in fields. + /// + internal static class ReferenceEqualityHashSet + where T : class + { + public static readonly HashSetPool Default = HashSetPool.Create(ReferenceEqualityComparer.Instance); + + public static PooledObject> GetPooledObject() + => Default.GetPooledObject(); + + public static PooledObject> GetPooledObject(out HashSet set) + => Default.GetPooledObject(out set); + } +} diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/SpecializedPools.StringDictionary`1.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/SpecializedPools.StringDictionary`1.cs new file mode 100644 index 00000000000..1f3fd6557ca --- /dev/null +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/SpecializedPools.StringDictionary`1.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Razor.PooledObjects; + +internal static partial class SpecializedPools +{ + /// + /// Pooled instances when the key is of type . + /// + /// + /// + /// Instances originating from this pool are intended to be short-lived and are suitable + /// for temporary work. Do not return them as the results of methods or store them in fields. + /// + internal static class StringDictionary + { + public static readonly DictionaryPool Ordinal = DictionaryPool.Create(StringComparer.Ordinal); + public static readonly DictionaryPool OrdinalIgnoreCase = DictionaryPool.Create(StringComparer.OrdinalIgnoreCase); + + public static PooledObject> GetPooledObject() + => Ordinal.GetPooledObject(); + + public static PooledObject> GetPooledObject(out Dictionary map) + => Ordinal.GetPooledObject(out map); + + public static PooledObject> GetPooledObject(bool ignoreCase) + => ignoreCase + ? OrdinalIgnoreCase.GetPooledObject() + : Ordinal.GetPooledObject(); + + public static PooledObject> GetPooledObject(bool ignoreCase, out Dictionary map) + => ignoreCase + ? OrdinalIgnoreCase.GetPooledObject(out map) + : Ordinal.GetPooledObject(out map); + } +} diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/SpecializedPools.StringHashSet.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/SpecializedPools.StringHashSet.cs new file mode 100644 index 00000000000..e72991eb33b --- /dev/null +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/SpecializedPools.StringHashSet.cs @@ -0,0 +1,40 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Razor.PooledObjects; + +internal static partial class SpecializedPools +{ + /// + /// A pool of instances that compares strings. + /// + /// + /// + /// Instances originating from this pool are intended to be short-lived and are suitable + /// for temporary work. Do not return them as the results of methods or store them in fields. + /// + internal static class StringHashSet + { + public static readonly HashSetPool Ordinal = HashSetPool.Create(StringComparer.Ordinal); + public static readonly HashSetPool OrdinalIgnoreCase = HashSetPool.Create(StringComparer.OrdinalIgnoreCase); + + public static PooledObject> GetPooledObject() + => Ordinal.GetPooledObject(); + + public static PooledObject> GetPooledObject(out HashSet set) + => Ordinal.GetPooledObject(out set); + + public static PooledObject> GetPooledObject(bool ignoreCase) + => ignoreCase + ? OrdinalIgnoreCase.GetPooledObject() + : Ordinal.GetPooledObject(); + + public static PooledObject> GetPooledObject(bool ignoreCase, out HashSet set) + => ignoreCase + ? OrdinalIgnoreCase.GetPooledObject(out set) + : Ordinal.GetPooledObject(out set); + } +} diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/SpecializedPools.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/SpecializedPools.cs new file mode 100644 index 00000000000..4a783efae00 --- /dev/null +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/SpecializedPools.cs @@ -0,0 +1,43 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Razor.PooledObjects; + +internal static partial class SpecializedPools +{ + public static PooledObject> GetPooledReferenceEqualityHashSet() + where T : class + => ReferenceEqualityHashSet.GetPooledObject(); + + public static PooledObject> GetPooledReferenceEqualityHashSet(out HashSet set) + where T : class + => ReferenceEqualityHashSet.GetPooledObject(out set); + + public static PooledObject> GetPooledStringHashSet() + => StringHashSet.GetPooledObject(); + + public static PooledObject> GetPooledStringHashSet(out HashSet set) + => StringHashSet.GetPooledObject(out set); + + public static PooledObject> GetPooledStringHashSet(bool ignoreCase) + => StringHashSet.GetPooledObject(ignoreCase); + + public static PooledObject> GetPooledStringHashSet(bool ignoreCase, out HashSet set) + => StringHashSet.GetPooledObject(ignoreCase, out set); + + public static PooledObject> GetPooledStringDictionary() + => StringDictionary.GetPooledObject(); + + public static PooledObject> GetPooledStringDictionary( + out Dictionary map) + => StringDictionary.GetPooledObject(out map); + + public static PooledObject> GetPooledStringDictionary(bool ignoreCase) + => StringDictionary.GetPooledObject(ignoreCase); + + public static PooledObject> GetPooledStringDictionary( + bool ignoreCase, out Dictionary map) + => StringDictionary.GetPooledObject(ignoreCase, out map); +} diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StackPool`1.Policy.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StackPool`1.Policy.cs index 966596e7f83..0efc814835e 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StackPool`1.Policy.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StackPool`1.Policy.cs @@ -2,29 +2,41 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; -internal static partial class StackPool +internal partial class StackPool { - private class Policy : IPooledObjectPolicy> + private sealed class Policy : PooledObjectPolicy { - public static readonly Policy Instance = new(); + public static readonly Policy Default = new(DefaultMaximumObjectSize); - private Policy() + private readonly int _maximumObjectSize; + + private Policy(int maximumObjectSize) + { + _maximumObjectSize = maximumObjectSize; + } + + public static Policy Create(Optional maximumObjectSize = default) { + if (!maximumObjectSize.HasValue || maximumObjectSize.Value == Default._maximumObjectSize) + { + return Default; + } + + return new(maximumObjectSize.GetValueOrDefault(DefaultMaximumObjectSize)); } - public Stack Create() => new(); + public override Stack Create() => new(); - public bool Return(Stack stack) + public override bool Return(Stack stack) { var count = stack.Count; stack.Clear(); - if (count > DefaultPool.MaximumObjectSize) + if (count > _maximumObjectSize) { stack.TrimExcess(); } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StackPool`1.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StackPool`1.cs index ac0e573422b..0626cebb348 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StackPool`1.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StackPool`1.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; @@ -14,11 +13,25 @@ namespace Microsoft.AspNetCore.Razor.PooledObjects; /// Instances originating from this pool are intended to be short-lived and are suitable /// for temporary work. Do not return them as the results of methods or store them in fields. /// -internal static partial class StackPool +internal sealed partial class StackPool : CustomObjectPool> { - public static readonly ObjectPool> Default = DefaultPool.Create(Policy.Instance); + public static readonly StackPool Default = Create(); - public static PooledObject> GetPooledObject() => Default.GetPooledObject(); + private StackPool(PooledObjectPolicy policy, Optional poolSize) + : base(policy, poolSize) + { + } + + public static StackPool Create( + Optional maximumObjectSize = default, + Optional poolSize = default) + => new(Policy.Create(maximumObjectSize), poolSize); + + public static StackPool Create(PooledObjectPolicy policy, Optional poolSize = default) + => new(policy, poolSize); + + public static PooledObject> GetPooledObject() + => Default.GetPooledObject(); public static PooledObject> GetPooledObject(out Stack stack) => Default.GetPooledObject(out stack); diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StopwatchPool.Policy.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StopwatchPool.Policy.cs index ead6761a22d..d6b1688b80e 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StopwatchPool.Policy.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StopwatchPool.Policy.cs @@ -2,23 +2,22 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; -internal static partial class StopwatchPool +internal partial class StopwatchPool { - private class Policy : IPooledObjectPolicy + private sealed class Policy : PooledObjectPolicy { - public static readonly Policy Instance = new(); + public static readonly Policy Default = new(); private Policy() { } - public Stopwatch Create() => new(); + public override Stopwatch Create() => new(); - public bool Return(Stopwatch watch) + public override bool Return(Stopwatch watch) { watch.Reset(); return true; diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StopwatchPool.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StopwatchPool.cs index b1352ef06aa..18b8d430315 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StopwatchPool.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StopwatchPool.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; @@ -14,9 +13,20 @@ namespace Microsoft.AspNetCore.Razor.PooledObjects; /// Instances originating from this pool are intended to be short-lived and are suitable /// for temporary work. Do not return them as the results of methods or store them in fields. /// -internal static partial class StopwatchPool +internal sealed partial class StopwatchPool : CustomObjectPool { - public static readonly ObjectPool Default = DefaultPool.Create(Policy.Instance); + public static readonly StopwatchPool Default = Create(); + + private StopwatchPool(PooledObjectPolicy policy, Optional poolSize) + : base(policy, poolSize) + { + } + + public static StopwatchPool Create(Optional poolSize = default) + => new(Policy.Default, poolSize); + + public static StopwatchPool Create(PooledObjectPolicy policy, Optional poolSize = default) + => new(policy, poolSize); public static PooledObject GetPooledObject() => Default.GetPooledObject(); diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StringBuilderPool.Policy.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StringBuilderPool.Policy.cs index 294ef60cef7..a6573644588 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StringBuilderPool.Policy.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StringBuilderPool.Policy.cs @@ -2,29 +2,41 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Text; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; -internal static partial class StringBuilderPool +internal partial class StringBuilderPool { - private class Policy : IPooledObjectPolicy + private sealed class Policy : PooledObjectPolicy { - public static readonly Policy Instance = new(); + public static readonly Policy Default = new(DefaultMaximumObjectSize); - private Policy() + private readonly int _maximumObjectSize; + + private Policy(int maximumObjectSize) + { + _maximumObjectSize = maximumObjectSize; + } + + public static Policy Create(Optional maximumObjectSize = default) { + if (!maximumObjectSize.HasValue || maximumObjectSize.Value == Default._maximumObjectSize) + { + return Default; + } + + return new(maximumObjectSize.GetValueOrDefault(DefaultMaximumObjectSize)); } - public StringBuilder Create() => new(); + public override StringBuilder Create() => new(); - public bool Return(StringBuilder builder) + public override bool Return(StringBuilder builder) { builder.Clear(); - if (builder.Capacity > DefaultPool.MaximumObjectSize) + if (builder.Capacity > _maximumObjectSize) { - builder.Capacity = DefaultPool.MaximumObjectSize; + builder.Capacity = _maximumObjectSize; } return true; diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StringBuilderPool.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StringBuilderPool.cs index 36b269cdd48..14f4ad6ebe6 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StringBuilderPool.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StringBuilderPool.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Text; -using Microsoft.Extensions.ObjectPool; namespace Microsoft.AspNetCore.Razor.PooledObjects; @@ -14,9 +13,22 @@ namespace Microsoft.AspNetCore.Razor.PooledObjects; /// Instances originating from this pool are intended to be short-lived and are suitable /// for temporary work. Do not return them as the results of methods or store them in fields. /// -internal static partial class StringBuilderPool +internal sealed partial class StringBuilderPool : CustomObjectPool { - public static readonly ObjectPool Default = DefaultPool.Create(Policy.Instance); + public static readonly StringBuilderPool Default = Create(); + + private StringBuilderPool(PooledObjectPolicy policy, Optional poolSize) + : base(policy, poolSize) + { + } + + public static StringBuilderPool Create( + Optional maximumObjectSize = default, + Optional poolSize = default) + => new(Policy.Create(maximumObjectSize), poolSize); + + public static StringBuilderPool Create(PooledObjectPolicy policy, Optional poolSize = default) + => new(policy, poolSize); public static PooledObject GetPooledObject() => Default.GetPooledObject(); diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StringDictionaryPool`1.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StringDictionaryPool`1.cs deleted file mode 100644 index 42fafb4537e..00000000000 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StringDictionaryPool`1.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using Microsoft.Extensions.ObjectPool; - -namespace Microsoft.AspNetCore.Razor.PooledObjects; - -/// -/// Pooled instances when the key is of type . -/// -/// -/// -/// Instances originating from this pool are intended to be short-lived and are suitable -/// for temporary work. Do not return them as the results of methods or store them in fields. -/// -internal static partial class StringDictionaryPool -{ - public static readonly ObjectPool> Ordinal = DictionaryPool.Create(StringComparer.Ordinal); - public static readonly ObjectPool> OrdinalIgnoreCase = DictionaryPool.Create(StringComparer.OrdinalIgnoreCase); - - public static PooledObject> GetPooledObject() - => Ordinal.GetPooledObject(); - - public static PooledObject> GetPooledObject(out Dictionary map) - => Ordinal.GetPooledObject(out map); - - public static PooledObject> GetPooledObject(bool ignoreCase) - => ignoreCase - ? OrdinalIgnoreCase.GetPooledObject() - : Ordinal.GetPooledObject(); - - public static PooledObject> GetPooledObject(bool ignoreCase, out Dictionary map) - => ignoreCase - ? OrdinalIgnoreCase.GetPooledObject(out map) - : Ordinal.GetPooledObject(out map); -} diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StringHashSetPool.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StringHashSetPool.cs deleted file mode 100644 index 797d89c3488..00000000000 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PooledObjects/StringHashSetPool.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System; -using System.Collections.Generic; -using Microsoft.Extensions.ObjectPool; - -namespace Microsoft.AspNetCore.Razor.PooledObjects; - -/// -/// A pool of instances that compares strings. -/// -/// -/// -/// Instances originating from this pool are intended to be short-lived and are suitable -/// for temporary work. Do not return them as the results of methods or store them in fields. -/// -internal static partial class StringHashSetPool -{ - public static readonly ObjectPool> Ordinal = HashSetPool.Create(StringComparer.Ordinal); - public static readonly ObjectPool> OrdinalIgnoreCase = HashSetPool.Create(StringComparer.OrdinalIgnoreCase); - - public static ObjectPool> Create(IEqualityComparer comparer) - => HashSetPool.Create(comparer); - - public static PooledObject> GetPooledObject() - => Ordinal.GetPooledObject(); - - public static PooledObject> GetPooledObject(out HashSet set) - => Ordinal.GetPooledObject(out set); - - public static PooledObject> GetPooledObject(bool ignoreCase) - => ignoreCase - ? OrdinalIgnoreCase.GetPooledObject() - : Ordinal.GetPooledObject(); - - public static PooledObject> GetPooledObject(bool ignoreCase, out HashSet set) - => ignoreCase - ? OrdinalIgnoreCase.GetPooledObject(out set) - : Ordinal.GetPooledObject(out set); -} diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PublicAPI/PublicAPI.Unshipped.txt b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PublicAPI/PublicAPI.Unshipped.txt index 91b0e1a43b9..db480634fd1 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PublicAPI/PublicAPI.Unshipped.txt +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/PublicAPI/PublicAPI.Unshipped.txt @@ -1 +1,9 @@ -#nullable enable \ No newline at end of file +#nullable enable +Microsoft.AspNetCore.Razor.Optional +Microsoft.AspNetCore.Razor.Optional.GetValueOrDefault(T defaultValue) -> T +Microsoft.AspNetCore.Razor.Optional.HasValue.get -> bool +Microsoft.AspNetCore.Razor.Optional.Optional() -> void +Microsoft.AspNetCore.Razor.Optional.Optional(T value) -> void +Microsoft.AspNetCore.Razor.Optional.Value.get -> T +override Microsoft.AspNetCore.Razor.Optional.ToString() -> string! +static Microsoft.AspNetCore.Razor.Optional.implicit operator Microsoft.AspNetCore.Razor.Optional(T value) -> Microsoft.AspNetCore.Razor.Optional \ No newline at end of file diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/Utilities/Checksum.Builder.Policy.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/Utilities/Checksum.Builder.Policy.cs index 3c00d09e57c..35d1fa6f0bc 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/Utilities/Checksum.Builder.Policy.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/Utilities/Checksum.Builder.Policy.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.Extensions.ObjectPool; +using Microsoft.AspNetCore.Razor.PooledObjects; #if NET5_0_OR_GREATER using System.Diagnostics; #endif @@ -19,28 +19,38 @@ internal sealed partial record Checksum { internal readonly ref partial struct Builder { - private sealed class Policy : IPooledObjectPolicy + private sealed class HashingTypePool : CustomObjectPool { - public static readonly Policy Instance = new(); + public static readonly HashingTypePool Default = new(Policy.Instance, DefaultPoolSize); - private Policy() + private HashingTypePool(PooledObjectPolicy policy, Optional poolSize) + : base(policy, poolSize) { } - public HashingType Create() + private sealed class Policy : PooledObjectPolicy + { + public static readonly Policy Instance = new(); + + private Policy() + { + } + + public override HashingType Create() #if NET5_0_OR_GREATER - => HashingType.CreateHash(HashAlgorithmName.SHA256); + => HashingType.CreateHash(HashAlgorithmName.SHA256); #else => HashingType.Create(); #endif - public bool Return(HashingType hash) - { + public override bool Return(HashingType hash) + { #if NET5_0_OR_GREATER - Debug.Assert(hash.AlgorithmName == HashAlgorithmName.SHA256); + Debug.Assert(hash.AlgorithmName == HashAlgorithmName.SHA256); #endif - return true; + return true; + } } } } diff --git a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/Utilities/Checksum.Builder.cs b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/Utilities/Checksum.Builder.cs index 22d3227e31c..d9ec2e58eef 100644 --- a/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/Utilities/Checksum.Builder.cs +++ b/src/Shared/Microsoft.AspNetCore.Razor.Utilities.Shared/Utilities/Checksum.Builder.cs @@ -8,8 +8,6 @@ using System.Buffers.Binary; using System.Diagnostics; using System.Runtime.InteropServices; -using Microsoft.AspNetCore.Razor.PooledObjects; -using Microsoft.Extensions.ObjectPool; // PERFORMANCE: Care has been taken to avoid using IncrementalHash on .NET Framework, which can cause // threadpool starvation. Essentially, on .NET Framework, IncrementalHash ends up using the OS implementation @@ -31,7 +29,7 @@ internal sealed partial record Checksum { internal readonly ref partial struct Builder { - private static readonly ObjectPool s_hashPool = DefaultPool.Create(Policy.Instance); + private static readonly HashingTypePool s_hashPool = HashingTypePool.Default; private enum TypeKind : byte {