|
11 | 11 | using Autofac.Core.Registration; |
12 | 12 | using Autofac.Core.Resolving.Middleware; |
13 | 13 | using Autofac.Core.Resolving.Pipeline; |
| 14 | +using Microsoft.Extensions.ObjectPool; |
14 | 15 |
|
15 | 16 | namespace Autofac.Pooling; |
16 | 17 |
|
@@ -138,6 +139,56 @@ public static IRegistrationBuilder<TLimit, TActivatorData, TSingleRegistrationSt |
138 | 139 | return registration; |
139 | 140 | } |
140 | 141 |
|
| 142 | + /// <summary> |
| 143 | + /// Configure the component so that every dependent component or manual resolve within a single <see cref="ILifetimeScope"/> |
| 144 | + /// will return the same, shared instance, retrieved from a single pool of instances shared by all lifetime scopes. |
| 145 | + /// When the scope ends, the instance will be returned to the pool. |
| 146 | + /// </summary> |
| 147 | + /// <remarks> |
| 148 | + /// <para> |
| 149 | + /// This method accepts a factory function that returns the <see cref="ObjectPool{TLimit}"/> to use. The factory is invoked |
| 150 | + /// during resolve, so you may resolve dependencies from the <see cref="IComponentContext"/> (e.g. <c>sp => sp.Resolve<ObjectPool<T>>()</c>). |
| 151 | + /// This allows the pool itself to be registered as a component and have its dependencies managed by the container. |
| 152 | + /// </para> |
| 153 | + /// |
| 154 | + /// <para> |
| 155 | + /// If the <see cref="ObjectPool{TLimit}"/> created by the factory implements <see cref="IDisposable"/>, it will be disposed |
| 156 | + /// when the container is disposed. Components implementing <see cref="IPooledComponent"/> will still receive |
| 157 | + /// <see cref="IPooledComponent.OnGetFromPool"/> and <see cref="IPooledComponent.OnReturnToPool"/> lifecycle notifications. |
| 158 | + /// </para> |
| 159 | + /// </remarks> |
| 160 | + /// <typeparam name="TLimit">Registration limit type.</typeparam> |
| 161 | + /// <typeparam name="TActivatorData">Activator data type.</typeparam> |
| 162 | + /// <typeparam name="TSingleRegistrationStyle">Registration style.</typeparam> |
| 163 | + /// <param name="registration">The registration.</param> |
| 164 | + /// <param name="poolFactory"> |
| 165 | + /// A factory that returns the <see cref="ObjectPool{TLimit}"/> to use for this registration. |
| 166 | + /// Invoked during resolve with access to the current <see cref="IComponentContext"/>. |
| 167 | + /// </param> |
| 168 | + /// <returns>The registration builder.</returns> |
| 169 | + public static IRegistrationBuilder<TLimit, TActivatorData, TSingleRegistrationStyle> |
| 170 | + PooledInstancePerLifetimeScope<TLimit, TActivatorData, TSingleRegistrationStyle>( |
| 171 | + this IRegistrationBuilder<TLimit, TActivatorData, TSingleRegistrationStyle> registration, |
| 172 | + Func<IComponentContext, ObjectPool<TLimit>> poolFactory) |
| 173 | + where TSingleRegistrationStyle : SingleRegistrationStyle |
| 174 | + where TActivatorData : IConcreteActivatorData |
| 175 | + where TLimit : class |
| 176 | + { |
| 177 | + if (registration == null) |
| 178 | + { |
| 179 | + throw new ArgumentNullException(nameof(registration)); |
| 180 | + } |
| 181 | + |
| 182 | + if (poolFactory == null) |
| 183 | + { |
| 184 | + throw new ArgumentNullException(nameof(poolFactory)); |
| 185 | + } |
| 186 | + |
| 187 | + RegisterPooled(registration, poolFactory, null); |
| 188 | + |
| 189 | + return registration; |
| 190 | + } |
| 191 | + |
141 | 192 | /// <summary> |
142 | 193 | /// Configure the component so that every dependent component or manual resolve within |
143 | 194 | /// a <see cref="ILifetimeScope"/> tagged with any of the provided tags value gets the same, shared instance, |
@@ -378,4 +429,113 @@ private static void RegisterPooled<TLimit, TActivatorData, TSingleRegistrationSt |
378 | 429 |
|
379 | 430 | callback.Callback = newCallback; |
380 | 431 | } |
| 432 | + |
| 433 | + private static void RegisterPooled<TLimit, TActivatorData, TSingleRegistrationStyle>( |
| 434 | + IRegistrationBuilder<TLimit, TActivatorData, TSingleRegistrationStyle> registration, |
| 435 | + Func<IComponentContext, ObjectPool<TLimit>> poolFactory, |
| 436 | + object[]? tags) |
| 437 | + where TSingleRegistrationStyle : SingleRegistrationStyle |
| 438 | + where TActivatorData : IConcreteActivatorData |
| 439 | + where TLimit : class |
| 440 | + { |
| 441 | + if (registration == null) |
| 442 | + { |
| 443 | + throw new ArgumentNullException(nameof(registration)); |
| 444 | + } |
| 445 | + |
| 446 | + if (poolFactory == null) |
| 447 | + { |
| 448 | + throw new ArgumentNullException(nameof(poolFactory)); |
| 449 | + } |
| 450 | + |
| 451 | + // Mark the lifetime appropriately. |
| 452 | + var regData = registration.RegistrationData; |
| 453 | + |
| 454 | + regData.Lifetime = new PooledLifetime(); |
| 455 | + regData.Sharing = InstanceSharing.None; |
| 456 | + |
| 457 | + var callback = regData.DeferredCallback ?? throw new NotSupportedException(RegistrationExtensionsResources.RequiresCallbackContainer); |
| 458 | + |
| 459 | + if (registration.ActivatorData.Activator is ProvidedInstanceActivator) |
| 460 | + { |
| 461 | + // Can't use provided instance activators with pooling (because it would try to repeatedly activate). |
| 462 | + throw new NotSupportedException(RegistrationExtensionsResources.CannotUseProvidedInstances); |
| 463 | + } |
| 464 | + |
| 465 | + var original = callback.Callback; |
| 466 | + |
| 467 | + Action<IComponentRegistryBuilder> newCallback = registry => |
| 468 | + { |
| 469 | + // Only do the additional registrations if we are still using a PooledLifetime. |
| 470 | + if (!(regData.Lifetime is PooledLifetime)) |
| 471 | + { |
| 472 | + original(registry); |
| 473 | + return; |
| 474 | + } |
| 475 | + |
| 476 | + var pooledInstanceService = new UniqueService(); |
| 477 | + |
| 478 | + var instanceActivator = registration.ActivatorData.Activator; |
| 479 | + |
| 480 | + if (registration.ResolvePipeline.Middleware.Any(c => c is CoreEventMiddleware ev && ev.EventType == ResolveEventType.OnRelease)) |
| 481 | + { |
| 482 | + // OnRelease shouldn't be used with pooled instances, because if a policy chooses not to return them to the pool, |
| 483 | + // the Disposal will be fired, not the OnRelease call; this means that OnRelease wouldn't fire until the container is disposed, |
| 484 | + // which is not what we want. |
| 485 | + throw new NotSupportedException(RegistrationExtensionsResources.OnReleaseNotSupported); |
| 486 | + } |
| 487 | + |
| 488 | + // First, we going to create a pooled instance activator, that will be resolved when we want to |
| 489 | + // **actually** resolve a new instance (during 'Create'). |
| 490 | + // The instances themselves are owned by the pool, and will be disposed when the pool disposes |
| 491 | + // (or when the instance is not returned to the pool). |
| 492 | + var pooledInstanceRegistration = new ComponentRegistration( |
| 493 | + Guid.NewGuid(), |
| 494 | + instanceActivator, |
| 495 | + RootScopeLifetime.Instance, |
| 496 | + InstanceSharing.None, |
| 497 | + InstanceOwnership.ExternallyOwned, |
| 498 | + registration.ResolvePipeline, |
| 499 | + new[] { pooledInstanceService }, |
| 500 | + new Dictionary<string, object?>()); |
| 501 | + |
| 502 | + registry.Register(pooledInstanceRegistration); |
| 503 | + |
| 504 | + var poolService = new PoolService(pooledInstanceRegistration); |
| 505 | + |
| 506 | + var poolRegistration = new ComponentRegistration( |
| 507 | + Guid.NewGuid(), |
| 508 | + new PoolActivator<TLimit>(poolFactory), |
| 509 | + RootScopeLifetime.Instance, |
| 510 | + InstanceSharing.Shared, |
| 511 | + InstanceOwnership.OwnedByLifetimeScope, |
| 512 | + new[] { poolService }, |
| 513 | + new Dictionary<string, object?>()); |
| 514 | + |
| 515 | + registry.Register(poolRegistration); |
| 516 | + |
| 517 | + var pooledGetLifetime = tags is null ? CurrentScopeLifetime.Instance : new MatchingScopeLifetime(tags); |
| 518 | + |
| 519 | + // Next, create a new registration with a custom activator, that copies metadata and services from |
| 520 | + // the original registration. This registration will access the pool and return an instance from it. |
| 521 | + var poolGetRegistration = new ComponentRegistration( |
| 522 | + Guid.NewGuid(), |
| 523 | + new PoolGetActivator<TLimit>(poolService, new DelegatingPooledRegistrationPolicy<TLimit>()), |
| 524 | + pooledGetLifetime, |
| 525 | + InstanceSharing.Shared, |
| 526 | + InstanceOwnership.OwnedByLifetimeScope, |
| 527 | + regData.Services, |
| 528 | + regData.Metadata); |
| 529 | + |
| 530 | + registry.Register(poolGetRegistration); |
| 531 | + |
| 532 | + // Finally, add a service pipeline stage to just before the sharing middleware, for each supported service, to extract the pooled instance from the pool instance container. |
| 533 | + foreach (var srv in regData.Services) |
| 534 | + { |
| 535 | + registry.RegisterServiceMiddleware(srv, new PooledInstanceUnpackMiddleware<TLimit>(), MiddlewareInsertionMode.StartOfPhase); |
| 536 | + } |
| 537 | + }; |
| 538 | + |
| 539 | + callback.Callback = newCallback; |
| 540 | + } |
381 | 541 | } |
0 commit comments