Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Re-introduce Microsoft.Extensions.ObjectPool.DependencyInjection #4038

Merged
merged 10 commits into from
Jun 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Pools;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options.Validation;
using Microsoft.Extensions.Telemetry.Metering;
Expand All @@ -30,7 +29,7 @@ public static IServiceCollection AddHeaderParsing(this IServiceCollection servic
if (!Throw.IfNull(services).Any(x => x.ServiceType == typeof(HeaderParsingFeature.PoolHelper)))
{
_ = services
.AddPool<HeaderParsingFeature.PoolHelper>()
.AddPooled<HeaderParsingFeature.PoolHelper>()
.AddSingleton<IHttpContextAccessor, HttpContextAccessor>()
.AddSingleton<IHeaderRegistry, HeaderRegistry>()
.AddScoped(provider => provider.GetRequiredService<ObjectPool<HeaderParsingFeature.PoolHelper>>().Get())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

<ItemGroup>
<ProjectReference Include="..\Microsoft.Extensions.Telemetry.Abstractions\Microsoft.Extensions.Telemetry.Abstractions.csproj" />
<ProjectReference Include="..\..\ToBeMoved\DependencyInjection.Pools\DependencyInjection.Pools.csproj" />
<ProjectReference Include="..\Microsoft.Extensions.ObjectPool.DependencyInjection\Microsoft.Extensions.ObjectPool.DependencyInjection.csproj" />
<ProjectReference Include="..\Microsoft.Extensions.Options.Validation\Microsoft.Extensions.Options.Validation.csproj" />
<ProjectReference Include="..\..\ToBeRemoved\Options.ValidateOnStart\Options.ValidateOnStart.csproj" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.DependencyInjection.Pools;
using Microsoft.Extensions.Http.Resilience.Internal;
using Microsoft.Extensions.Http.Resilience.Internal.Routing;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
using Microsoft.Extensions.Options.Validation;
using Microsoft.Shared.Diagnostics;
Expand Down Expand Up @@ -36,7 +36,7 @@ public static IRoutingStrategyBuilder ConfigureOrderedGroups(this IRoutingStrate
_ = Throw.IfNull(builder);
_ = Throw.IfNull(section);

_ = builder.Services.AddPool<OrderedGroupsRoutingStrategy>();
_ = builder.Services.AddPooled<OrderedGroupsRoutingStrategy>();

return builder.ConfigureRoutingStrategy<OrderedGroupsRoutingStrategyFactory, OrderedGroupsRoutingOptions, OrderedGroupsRoutingOptionsValidator>(options => options.Bind(section));
}
Expand Down Expand Up @@ -71,7 +71,7 @@ public static IRoutingStrategyBuilder ConfigureOrderedGroups(this IRoutingStrate
_ = Throw.IfNull(builder);
_ = Throw.IfNull(configure);

_ = builder.Services.AddPool<OrderedGroupsRoutingStrategy>();
_ = builder.Services.AddPooled<OrderedGroupsRoutingStrategy>();

return builder.ConfigureRoutingStrategy<OrderedGroupsRoutingStrategyFactory, OrderedGroupsRoutingOptions, OrderedGroupsRoutingOptionsValidator>(options => options.Configure(configure));
}
Expand All @@ -90,7 +90,7 @@ public static IRoutingStrategyBuilder ConfigureWeightedGroups(this IRoutingStrat
_ = Throw.IfNull(builder);
_ = Throw.IfNull(section);

_ = builder.Services.AddPool<WeightedGroupsRoutingStrategy>();
_ = builder.Services.AddPooled<WeightedGroupsRoutingStrategy>();

return builder.ConfigureRoutingStrategy<WeightedGroupsRoutingStrategyFactory, WeightedGroupsRoutingOptions, WeightedGroupsRoutingOptionsValidator>(options => options.Bind(section));
}
Expand Down Expand Up @@ -125,7 +125,7 @@ public static IRoutingStrategyBuilder ConfigureWeightedGroups(this IRoutingStrat
_ = Throw.IfNull(builder);
_ = Throw.IfNull(configure);

_ = builder.Services.AddPool<WeightedGroupsRoutingStrategy>();
_ = builder.Services.AddPooled<WeightedGroupsRoutingStrategy>();

return builder.ConfigureRoutingStrategy<WeightedGroupsRoutingStrategyFactory, WeightedGroupsRoutingOptions, WeightedGroupsRoutingOptionsValidator>(options => options.Configure(configure));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<ProjectReference Include="..\Microsoft.Extensions.Compliance.Abstractions\Microsoft.Extensions.Compliance.Abstractions.csproj" />
<ProjectReference Include="..\Microsoft.Extensions.Options.Validation\Microsoft.Extensions.Options.Validation.csproj" />
<ProjectReference Include="..\..\ToBeMoved\DependencyInjection.NamedService\DependencyInjection.NamedService.csproj" />
<ProjectReference Include="..\..\ToBeMoved\DependencyInjection.Pools\DependencyInjection.Pools.csproj" />
<ProjectReference Include="..\Microsoft.Extensions.ObjectPool.DependencyInjection\Microsoft.Extensions.ObjectPool.DependencyInjection.csproj" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics.CodeAnalysis;

namespace Microsoft.Extensions.ObjectPool;

/// <summary>
/// Contains configuration for pools.
/// </summary>
[Experimental]
public sealed class DependencyInjectionPoolOptions
{
internal const int DefaultCapacity = 1024;

/// <summary>
/// Gets or sets the maximal capacity of the pool.
/// </summary>
/// <value>
/// The default is 1024.
/// </value>
public int Capacity { get; set; } = DefaultCapacity;
}
Original file line number Diff line number Diff line change
@@ -1,30 +1,30 @@
// Licensed to the .NET Foundation under one or more agreements.
// 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.Diagnostics.CodeAnalysis;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.ObjectPool;

namespace Microsoft.Extensions.DependencyInjection.Pools;
namespace Microsoft.Extensions.ObjectPool;

internal sealed class DependencyInjectedPolicy<TDefinition, TImplementation> : IPooledObjectPolicy<TDefinition>
where TDefinition : class
where TImplementation : class, TDefinition
internal sealed class DependencyInjectionPooledObjectPolicy<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation> : IPooledObjectPolicy<TService>
where TService : class
where TImplementation : class, TService
{
private readonly IServiceProvider _provider;
private readonly ObjectFactory _factory;
private readonly bool _isResettable;

public DependencyInjectedPolicy(IServiceProvider provider)
public DependencyInjectionPooledObjectPolicy(IServiceProvider provider)
{
_provider = provider;
_factory = ActivatorUtilities.CreateFactory(typeof(TImplementation), Type.EmptyTypes);
_isResettable = typeof(IResettable).IsAssignableFrom(typeof(TImplementation));
}

public TDefinition Create() => (TDefinition)_factory(_provider, Array.Empty<object?>());
public TService Create() => (TService)_factory(_provider, Array.Empty<object?>());

public bool Return(TDefinition obj)
public bool Return(TService obj)
{
if (_isResettable)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<AssemblyName>Microsoft.Extensions.DependencyInjection.Pools</AssemblyName>
<RootNamespace>Microsoft.Extensions.DependencyInjection.Pools</RootNamespace>
<Description>Pools integration into DI container.</Description>
<RootNamespace>Microsoft.Extensions.ObjectPool</RootNamespace>
<Description>Provides object pools from service containers.</Description>
<Workstream>Fundamentals</Workstream>
<Category>Dependency Injection</Category>
</PropertyGroup>

<PropertyGroup>
<InjectExperimentalAttributeOnLegacy>true</InjectExperimentalAttributeOnLegacy>
<InjectSharedPools>true</InjectSharedPools>
</PropertyGroup>

<PropertyGroup>
Expand All @@ -16,12 +16,13 @@
</PropertyGroup>

<ItemGroup>
<InternalsVisibleToTest Include="$(AssemblyName).Tests" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" />
<PackageReference Include="Microsoft.Extensions.ObjectPool" />
<PackageReference Include="Microsoft.Extensions.Options" />
<PackageReference Include="Microsoft.Extensions.Configuration" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
<PackageReference Include="Microsoft.Extensions.Options" />
<InternalsVisibleToTest Include="$(AssemblyName).Tests" />
</ItemGroup>
</Project>

Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// 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.Diagnostics.CodeAnalysis;
using System.Globalization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.ObjectPool;
using Microsoft.Extensions.Options;
using Microsoft.Shared.Diagnostics;

namespace Microsoft.Extensions.ObjectPool;

/// <summary>
/// Extension methods for adding <see cref="ObjectPool{T}"/> to DI container.
/// </summary>
[Experimental]
public static class ObjectPoolServiceCollectionExtensions
sebastienros marked this conversation as resolved.
Show resolved Hide resolved
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get back the overload that consumes a configuration section? We want to systematically enable things to be configured in this way and it's the convention in this repo.

Can we call all the configuration delegate just "configure" which is the convention we use in this repo?

Thanks.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Currently looking into why the section was removed after aspnetcore api-review, they had some arguments that might be worth checking.

configureOptions: there are a few occurrences in this repos in case you think it's worth going through the rest of the code for consistency.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@geeknoid This is like a custom deserialization for these settings. I see that it's checking the value is a valid integer, what else is it providing over the standard configuration methods?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So yes, we should change "configureOptions" to "configure" for general consistency.

Within this repo, we generally use a triplet of functions for configuring components. No options (you get the defaults), an Action delegate which lets you override options, and a ConfigurationSection to make it easy to consume the configuration state from an external JSON. This model has been working well for us.

@tekian Jan helped establish this pattern 3 years ago or so, maybe he has additional insights to provide.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With respect to configuration section, we make no assumptions about where in the configuration hierarchy is the section to configure the component. We let developer decide and hand us the section to bind to. IConfigurationSection, as compared to IConfiguration, knows Path which was an information we wanted to capture to be able to reconstruct configuration schema.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats fine but 90% of our code takes IConfiguration, not IConfigurationSection and it usually parses a known schema (more than just an int). The pattern should be consistent with the rest of the APIs in the stack.

/// <summary>
/// Adds an <see cref="ObjectPool{TService}"/> and lets DI return scoped instances of TService.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add to.</param>
/// <param name="configure">The action used to configure the options of the pool.</param>
/// <typeparam name="TService">The type of objects to pool.</typeparam>
/// <returns>Provided service collection.</returns>
/// <exception cref="ArgumentNullException"><paramref name="services"/> is <see langword="null"/>.</exception>
/// <remarks>
/// The default capacity is 1024.
/// The pooled type instances are obtainable by resolving <see cref="ObjectPool{TService}"/> from the DI container.
/// </remarks>
public static IServiceCollection AddPooled<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TService>(
this IServiceCollection services,
Action<DependencyInjectionPoolOptions>? configure = null)
where TService : class
{
return services.AddPooledInternal<TService, TService>(configure);
}

/// <summary>
/// Adds an <see cref="ObjectPool{TService}"/> and let DI return scoped instances of TService.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add to.</param>
/// <param name="configure">Configuration of the pool.</param>
/// <typeparam name="TService">The type of objects to pool.</typeparam>
/// <typeparam name="TImplementation">The type of the implementation to use.</typeparam>
/// <returns>Provided service collection.</returns>
/// <exception cref="ArgumentNullException"><paramref name="services"/> is <see langword="null"/>.</exception>
/// <remarks>
/// The default capacity is 1024.
/// The pooled type instances are obtainable by resolving <see cref="ObjectPool{TService}"/> from the DI container.
/// </remarks>
public static IServiceCollection AddPooled<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(
this IServiceCollection services,
Action<DependencyInjectionPoolOptions>? configure = null)
where TService : class
where TImplementation : class, TService
{
return services.AddPooledInternal<TService, TImplementation>(configure);
}

/// <summary>
/// Registers an action used to configure the <see cref="DependencyInjectionPoolOptions"/> of a typed pool.
/// </summary>
/// <typeparam name="TService">The type of objects to pool.</typeparam>
/// <param name="services">The <see cref="IServiceCollection"/> to add the services to.</param>
/// <param name="configure">The action used to configure the options.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
public static IServiceCollection ConfigurePool<TService>(this IServiceCollection services, Action<DependencyInjectionPoolOptions> configure)
where TService : class
{
return services.Configure<DependencyInjectionPoolOptions>(typeof(TService).FullName, configure);
}

/// <summary>
/// Configures DI pools.
/// </summary>
/// <param name="services">The <see cref="IServiceCollection"/> to add to.</param>
/// <param name="section">The configuration section to bind.</param>
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicParameterlessConstructor, typeof(DependencyInjectionPoolOptions))]
[UnconditionalSuppressMessage(
"Trimming",
"IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
Justification = "Addressed by [DynamicDependency]")]
public static IServiceCollection ConfigurePools(this IServiceCollection services, IConfigurationSection section)
{
foreach (var child in Throw.IfNull(section).GetChildren())
{
if (!int.TryParse(child.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var capacity))
{
Throw.ArgumentException(nameof(section), $"Can't parse '{child.Key}' value '{child.Value}' to integer.");
}

_ = services.Configure<DependencyInjectionPoolOptions>(child.Key, options => options.Capacity = capacity);
}

return services;
}

private static IServiceCollection AddPooledInternal<TService, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TImplementation>(
this IServiceCollection services,
Action<DependencyInjectionPoolOptions>? configure)
where TService : class
where TImplementation : class, TService
{
_ = Throw.IfNull(services);

if (configure != null)
{
// Register a PoolOption instance specific to the type
_ = services.ConfigurePool<TService>(configure);
}

return services
.AddSingleton<ObjectPool<TService>>(provider =>
{
var options = provider.GetService<IOptionsFactory<DependencyInjectionPoolOptions>>()?.Create(typeof(TService).FullName!) ?? new DependencyInjectionPoolOptions();

return new DefaultObjectPool<TService>(new DependencyInjectionPooledObjectPolicy<TService, TImplementation>(provider), options.Capacity);
});
}
}
Loading