Skip to content

Commit 3b924a5

Browse files
authored
Merge pull request #15 from autofac/feature/issue-6-custom-pool
Add custom ObjectPoolProvider support for pooled registrations
2 parents 5a2b871 + bae97d6 commit 3b924a5

51 files changed

Lines changed: 2485 additions & 755 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,6 @@ publish/
109109
*.pubxml
110110

111111
# NuGet Packages Directory
112-
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
113112
packages/
114113

115114
# Windows Azure Build Output
@@ -123,6 +122,8 @@ AppPackages/
123122
sql/
124123
*.Cache
125124
ClientBin/
125+
[Ss]tyle[Cc]op.*
126+
!stylecop.json
126127
~$*
127128
*~
128129
*.dbmdl
@@ -132,6 +133,7 @@ node_modules/
132133
bower_components/
133134
wwwroot/
134135
project.lock.json
136+
*.Designer.cs
135137

136138
# RIA/Silverlight projects
137139
Generated_Code/

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ repos:
88
- id: end-of-file-fixer
99
- id: trailing-whitespace
1010
- repo: https://github.com/igorshubovych/markdownlint-cli
11-
rev: "e72a3ca1632f0b11a07d171449fe447a7ff6795e" # frozen: v0.48.0
11+
rev: "c7c1c7640e610068e8e4754e9f1bf109bd987dc7" # post-v0.48.0 with patches
1212
hooks:
1313
- id: markdownlint
1414
args:

.vscode/extensions.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
"recommendations": [
33
"davidanson.vscode-markdownlint",
44
"editorconfig.editorconfig",
5-
"ms-dotnettools.csharp"
5+
"ms-dotnettools.csdevkit",
6+
"travisillig.vscode-json-stable-stringify"
67
]
78
}

.vscode/settings.json

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,30 @@
33
"autofac",
44
"xunit"
55
],
6-
"omnisharp.enableEditorConfigSupport": true
6+
"coverage-gutters.coverageBaseDir": "artifacts/logs",
7+
"coverage-gutters.coverageFileNames": [
8+
"**/coverage.cobertura.xml"
9+
],
10+
"dotnet.defaultSolution": "Autofac.Pooling.sln",
11+
"dotnet.unitTestDebuggingOptions": {
12+
"enableStepFiltering": false,
13+
"justMyCode": false,
14+
"requireExactSource": false,
15+
"sourceLinkOptions": {
16+
"*": {
17+
"enabled": true
18+
}
19+
},
20+
"suppressJITOptimizations": true,
21+
"symbolOptions": {
22+
"searchNuGetOrgSymbolServer": true
23+
}
24+
},
25+
"explorer.fileNesting.enabled": true,
26+
"explorer.fileNesting.patterns": {
27+
"*.resx": "$(capture).*.resx, $(capture).designer.cs, $(capture).designer.vb"
28+
},
29+
"files.watcherExclude": {
30+
"**/target": true
31+
}
732
}

.vscode/tasks.json

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,37 @@
11
{
2+
"linux": {
3+
"options": {
4+
"shell": {
5+
"args": [
6+
"-NoProfile",
7+
"-Command"
8+
],
9+
"executable": "pwsh"
10+
}
11+
}
12+
},
13+
"osx": {
14+
"options": {
15+
"shell": {
16+
"args": [
17+
"-NoProfile",
18+
"-Command"
19+
],
20+
"executable": "/usr/local/bin/pwsh"
21+
}
22+
}
23+
},
224
"tasks": [
25+
{
26+
"command": "If (Test-Path ${workspaceFolder}/artifacts/logs) { Remove-Item ${workspaceFolder}/artifacts/logs -Recurse -Force }; New-Item -Path ${workspaceFolder}/artifacts/logs -ItemType Directory -Force | Out-Null",
27+
"label": "create log directory",
28+
"type": "shell"
29+
},
330
{
431
"args": [
532
"build",
6-
"Autofac.Pooling.sln",
33+
"${workspaceFolder}/Autofac.Pooling.sln",
34+
"--tl:off",
735
"/property:GenerateFullPaths=true",
836
"/consoleloggerparameters:NoSummary"
937
],
@@ -13,12 +41,48 @@
1341
"kind": "build"
1442
},
1543
"label": "build",
16-
"presentation": {
17-
"reveal": "silent"
18-
},
1944
"problemMatcher": "$msCompile",
2045
"type": "shell"
46+
},
47+
{
48+
"args": [
49+
"test",
50+
"${workspaceFolder}/Autofac.Pooling.sln",
51+
"/property:GenerateFullPaths=true",
52+
"/consoleloggerparameters:NoSummary",
53+
"--results-directory",
54+
"artifacts/logs",
55+
"--logger:trx",
56+
"--collect:XPlat Code Coverage",
57+
"--settings:build/Coverage.runsettings",
58+
"--filter",
59+
"FullyQualifiedName!~Bench"
60+
],
61+
"command": "dotnet",
62+
"dependsOn": [
63+
"create log directory"
64+
],
65+
"group": {
66+
"isDefault": true,
67+
"kind": "test"
68+
},
69+
"label": "test",
70+
"problemMatcher": "$msCompile",
71+
"type": "process"
2172
}
2273
],
23-
"version": "2.0.0"
74+
"version": "2.0.0",
75+
"windows": {
76+
"options": {
77+
"shell": {
78+
"args": [
79+
"-NoProfile",
80+
"-ExecutionPolicy",
81+
"Bypass",
82+
"-Command"
83+
],
84+
"executable": "pwsh.exe"
85+
}
86+
}
87+
}
2488
}

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
MIT License
1+
The MIT License (MIT)
22

33
Copyright (c) 2020 Autofac Project
44

README.md

Lines changed: 104 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,19 @@
11
# Autofac.Pooling
22

3-
[![Build status](https://github.com/autofac/Autofac.Pooling/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/autofac/Autofac.Pooling/actions/workflows/ci.yml) [![Open in Visual Studio Code](https://open.vscode.dev/badges/open-in-vscode.svg)](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.
44

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+
[![Build status](https://github.com/autofac/Autofac.Pooling/actions/workflows/main.yml/badge.svg)](https://github.com/autofac/Autofac.Pooling/actions/workflows/main.yml) [![codecov](https://codecov.io/gh/Autofac/Autofac.Pooling/branch/develop/graph/badge.svg)](https://codecov.io/gh/Autofac/Autofac.Pooling) [![NuGet](https://img.shields.io/nuget/v/Autofac.Pooling.svg)](https://nuget.org/packages/Autofac.Pooling)
96

107
Please file issues and pull requests for this package in this repository rather than in the Autofac core repo.
118

129
- [Documentation](https://autofac.readthedocs.io/advanced/pooled-instances.html)
1310
- [NuGet](https://www.nuget.org/packages/Autofac.Pooling)
1411
- [Contributing](https://autofac.readthedocs.io/en/latest/contributors.html)
12+
- [Open in Visual Studio Code](https://open.vscode.dev/autofac/Autofac.Pooling)
1513

16-
## Getting Started
14+
## Quick Start
1715

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:
2117

2218
```csharp
2319
var builder = new ContainerBuilder();
@@ -52,7 +48,104 @@ using (var scope2 = container.BeginLifetimeScope())
5248
// end of the lifetime scope.
5349
```
5450

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+
55149
## Get Help
56150

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).

build/CodeAnalysisDictionary.xml

Lines changed: 0 additions & 47 deletions
This file was deleted.

codecov.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
11
codecov:
22
branch: develop
3-
require_ci_to_pass: yes
3+
require_ci_to_pass: true
4+
coverage:
5+
status:
6+
project:
7+
default:
8+
threshold: 1%

0 commit comments

Comments
 (0)