|
1 | 1 | # Autofac.Pooling |
2 | 2 |
|
3 | | -[](https://github.com/autofac/Autofac.Pooling/actions/workflows/ci.yml) [](https://open.vscode.dev/autofac/Autofac.Pooling) |
| 3 | +Support for pooled instance lifetime scopes in [Autofac](https://autofac.org) dependency injection. Autofac can help you implement a pool of components in your application without you having to write your own pooling implementation, and making these pooled components feel more natural in the world of DI. |
4 | 4 |
|
5 | | -Support for pooled instance lifetime scopes in Autofac dependency injection. |
6 | | - |
7 | | -Autofac can help you implement a pool of components in your application without you having to write your |
8 | | -own pooling implementation, and making these pooled components feel more natural in the world of DI. |
| 5 | +[](https://github.com/autofac/Autofac.Pooling/actions/workflows/main.yml) [](https://codecov.io/gh/Autofac/Autofac.Pooling) [](https://nuget.org/packages/Autofac.Pooling) |
9 | 6 |
|
10 | 7 | Please file issues and pull requests for this package in this repository rather than in the Autofac core repo. |
11 | 8 |
|
12 | 9 | - [Documentation](https://autofac.readthedocs.io/advanced/pooled-instances.html) |
13 | 10 | - [NuGet](https://www.nuget.org/packages/Autofac.Pooling) |
14 | 11 | - [Contributing](https://autofac.readthedocs.io/en/latest/contributors.html) |
| 12 | +- [Open in Visual Studio Code](https://open.vscode.dev/autofac/Autofac.Pooling) |
15 | 13 |
|
16 | | -## Getting Started |
| 14 | +## Quick Start |
17 | 15 |
|
18 | | -Once you've added a reference to the `Autofac.Pooling` package, you can start using |
19 | | -the new `PooledInstancePerLifetimeScope` and `PooledInstancePerMatchingLifetimeScope` |
20 | | -methods: |
| 16 | +Once you've added a reference to the `Autofac.Pooling` package, you can start using the new `PooledInstancePerLifetimeScope` and `PooledInstancePerMatchingLifetimeScope` methods: |
21 | 17 |
|
22 | 18 | ```csharp |
23 | 19 | var builder = new ContainerBuilder(); |
@@ -52,7 +48,104 @@ using (var scope2 = container.BeginLifetimeScope()) |
52 | 48 | // end of the lifetime scope. |
53 | 49 | ``` |
54 | 50 |
|
| 51 | +## Custom Pool Providers |
| 52 | + |
| 53 | +By default, pooled instances are stored in a `DefaultObjectPool` sized from the policy's `MaximumRetained` value. If you need full control over *where* instances are stored and *when* they are evicted (for example, a cache-backed pool with an idle timeout), you can supply your own `Microsoft.Extensions.ObjectPool.ObjectPoolProvider`: |
| 54 | + |
| 55 | +```csharp |
| 56 | +var builder = new ContainerBuilder(); |
| 57 | + |
| 58 | +// Register the backing cache in Autofac. Because it is registered here, the |
| 59 | +// container owns it and disposes it at shutdown, and it can be shared with the |
| 60 | +// rest of the application. |
| 61 | +builder.RegisterInstance(new MemoryCache(new MemoryCacheOptions())) |
| 62 | + .As<IMemoryCache>(); |
| 63 | + |
| 64 | +// Register the provider itself so Autofac constructs it and injects the cache. |
| 65 | +builder.RegisterType<CacheObjectPoolProvider>() |
| 66 | + .SingleInstance(); |
| 67 | + |
| 68 | +builder.RegisterType<MyCustomConnection>() |
| 69 | + .As<ICustomConnection>() |
| 70 | + // The provider factory receives the IComponentContext, so it can resolve |
| 71 | + // the provider - and its dependencies - straight from the container. |
| 72 | + .PooledInstancePerLifetimeScope( |
| 73 | + ctx => ctx.Resolve<CacheObjectPoolProvider>()); |
| 74 | + |
| 75 | +var container = builder.Build(); |
| 76 | +``` |
| 77 | + |
| 78 | +Autofac still owns *construction* of the pooled instances (they are resolved through the container, so dependency injection and the `IPooledComponent` / `IPooledRegistrationPolicy` callbacks all work as normal). The provider only controls storage and eviction. The provider's `Create<T>(IPooledObjectPolicy<T>)` method is the seam: Autofac hands in its own policy whose `Create()` resolves a fully-injected instance, so your pool delegates construction to it rather than new-ing up objects itself. |
| 79 | + |
| 80 | +A cache-backed provider that takes its cache from Autofac looks like this: |
| 81 | + |
| 82 | +```csharp |
| 83 | +public sealed class CacheObjectPoolProvider : ObjectPoolProvider |
| 84 | +{ |
| 85 | + private readonly IMemoryCache _cache; |
| 86 | + |
| 87 | + // The cache is injected from Autofac rather than created here, so its |
| 88 | + // lifetime is managed by the container and shared across every pool this |
| 89 | + // provider creates. |
| 90 | + public CacheObjectPoolProvider(IMemoryCache cache) |
| 91 | + { |
| 92 | + _cache = cache; |
| 93 | + } |
| 94 | + |
| 95 | + public override ObjectPool<T> Create<T>(IPooledObjectPolicy<T> policy) |
| 96 | + => new CacheObjectPool<T>(_cache, policy); |
| 97 | +} |
| 98 | + |
| 99 | +public sealed class CacheObjectPool<T> : ObjectPool<T> |
| 100 | + where T : class |
| 101 | +{ |
| 102 | + private readonly IMemoryCache _cache; |
| 103 | + private readonly IPooledObjectPolicy<T> _policy; |
| 104 | + |
| 105 | + public CacheObjectPool(IMemoryCache cache, IPooledObjectPolicy<T> policy) |
| 106 | + { |
| 107 | + _cache = cache; |
| 108 | + _policy = policy; |
| 109 | + } |
| 110 | + |
| 111 | + public override T Get() |
| 112 | + // Ask the cache for a stored instance; build a new one through Autofac on a miss. |
| 113 | + => _cache.TryGetValue(typeof(T), out T? item) && item is not null |
| 114 | + ? item |
| 115 | + : _policy.Create(); |
| 116 | + |
| 117 | + public override void Return(T obj) |
| 118 | + { |
| 119 | + // The policy decides whether the instance is fit to be retained. |
| 120 | + if (_policy.Return(obj)) |
| 121 | + { |
| 122 | + // Dispose the instance when the cache eventually evicts it; the pool |
| 123 | + // owns disposal of instances the cache drops. |
| 124 | + var options = new MemoryCacheEntryOptions { SlidingExpiration = TimeSpan.FromMinutes(5) }; |
| 125 | + options.RegisterPostEvictionCallback((_, value, _, _) => (value as IDisposable)?.Dispose()); |
| 126 | + _cache.Set(typeof(T), obj, options); |
| 127 | + } |
| 128 | + else if (obj is IDisposable disposable) |
| 129 | + { |
| 130 | + // The pool owns disposal of instances it declines. |
| 131 | + disposable.Dispose(); |
| 132 | + } |
| 133 | + } |
| 134 | +} |
| 135 | +``` |
| 136 | + |
| 137 | +The pool does not implement `IDisposable` because it does not own the cache — Autofac creates the `IMemoryCache`, so Autofac disposes it. The pool only takes responsibility for the pooled instances themselves, disposing them when the policy declines a return and when the cache evicts them. |
| 138 | + |
| 139 | +Things to know about custom providers: |
| 140 | + |
| 141 | +- The provider factory is invoked **once** per registration, when the pool is built, resolved from the pool-owning (root) scope. Because the factory gets an `IComponentContext`, the provider can be registered like any other component and resolved from the container, letting Autofac inject its dependencies (the `IMemoryCache` above). Registering the provider and cache as singletons shares one provider and cache across every type that uses them. |
| 142 | +- With a custom provider, `IPooledRegistrationPolicy.MaximumRetained` does **not** size the pool — the provider owns sizing and eviction. You can still supply a custom policy alongside the provider with the `PooledInstancePerLifetimeScope(policyFactory, providerFactory)` overload to control the `Get` / `Return` behavior. |
| 143 | +- The pool is **shared across all lifetime scopes and threads**, so your pool must be thread-safe. |
| 144 | +- **Disposal contract:** |
| 145 | + - If the pool implements `IDisposable`, the container disposes it at container shutdown. |
| 146 | + - Because `ObjectPool<T>.Return(T)` returns `void` (no kept/dropped signal) and there is no "permanently evicted" callback, **the custom pool is responsible for disposing instances it declines on `Return` or evicts asynchronously.** Autofac cannot see those instances. |
| 147 | + - Instances that never entered the pool (because the policy chose not to call the pool) are disposed by normal lifetime scope disposal. |
| 148 | + |
55 | 149 | ## Get Help |
56 | 150 |
|
57 | | -**Need help with Autofac?** We have [a documentation site](https://autofac.readthedocs.io/) as well as [API documentation](https://autofac.org/apidoc/). We're ready to answer your questions on [Stack Overflow](https://stackoverflow.com/questions/tagged/autofac) |
58 | | -or check out the [discussion forum](https://groups.google.com/forum/#forum/autofac). |
| 151 | +**Need help with Autofac?** We have [a documentation site](https://autofac.readthedocs.io/) as well as [API documentation](https://autofac.org/apidoc/). We're ready to answer your questions on [Stack Overflow](https://stackoverflow.com/questions/tagged/autofac) or check out the [discussion forum](https://groups.google.com/forum/#forum/autofac). |
0 commit comments