Skip to content

Commit caf68e1

Browse files
authored
feat: add object pool pattern (#490)
Adds a bounded fluent ObjectPool<T> with lease-based rent/return semantics, a source-generated factory path, DI-backed spreadsheet example, docs, catalog entries, benchmark coverage, and TinyBDD tests.\n\nFixes #486
1 parent 85fa569 commit caf68e1

27 files changed

Lines changed: 996 additions & 14 deletions

File tree

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -473,14 +473,14 @@ var cachedRemoteProxy = Proxy<int, string>.Create(id => remoteProxy.Execute(id))
473473
---
474474

475475
## Patterns Table
476-
PatternKit currently tracks 114 production-readiness patterns. Each catalog pattern is represented in tests, documentation, real-world examples, IoC integration, and the BenchmarkDotNet coverage matrix.
476+
PatternKit currently tracks 115 production-readiness patterns. Each catalog pattern is represented in tests, documentation, real-world examples, IoC integration, and the BenchmarkDotNet coverage matrix.
477477

478478
| Category | Count | Patterns |
479479
| --- | ---: | --- |
480480
| Application Architecture | 26 | Activity Tracker, Aggregate Root, Anti-Corruption Layer, Audit Log, Bounded Context, Context Map, CQRS, Data Mapper, Domain Event, Domain Service, Event Sourcing, Eventual Consistency Monitor, Feature Toggle, Identity Map, Manual Task Gate, Materialized View, Repository, Service Layer, Snapshot / Checkpoint Management, Specification, Table Data Gateway, Timeout Manager, Transaction Script, Unit of Work, Value Object, Workflow Orchestration |
481481
| Behavioral | 11 | Chain of Responsibility, Command, Interpreter, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor |
482482
| Cloud Architecture | 20 | Ambassador, Backends for Frontends, Bulkhead, Cache-Aside, Cache Stampede Protection, Circuit Breaker, External Configuration Store, Gateway Aggregation, Gateway Routing, Health Endpoint Monitoring, Leader Election, Priority Queue, Queue-Based Load Leveling, Rate Limiting, Read-Through Cache, Retry, Scheduler Agent Supervisor, Sidecar, Strangler Fig, Write-Through Cache |
483-
| Creational | 5 | Abstract Factory, Builder, Factory Method, Prototype, Singleton |
483+
| Creational | 6 | Abstract Factory, Builder, Factory Method, Object Pool, Prototype, Singleton |
484484
| Enterprise Integration | 41 | Aggregator, Canonical Data Model, Channel Adapter, Channel Purger, Claim Check, Competing Consumers, Content Enricher, Content-Based Router, Control Bus, Correlation Identifier, Dead Letter Channel, Durable Subscriber, Dynamic Router, Event Notification, Event-Carried State Transfer, Event-Driven Consumer, Guaranteed Delivery, Invalid Message Channel, Mailbox, Message Bus, Message Channel, Message Envelope, Message Expiration, Message Filter, Message History, Message Store, Message Translator, Messaging Bridge, Messaging Gateway, Pipes and Filters, Polling Consumer, Publish-Subscribe, Recipient List, Request-Reply, Resequencer, Routing Slip, Saga / Process Manager, Scatter-Gather, Service Activator, Splitter, Wire Tap |
485485
| Messaging Reliability | 3 | Idempotent Receiver, Inbox, Outbox |
486486
| Structural | 7 | Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy |
@@ -521,6 +521,8 @@ BenchmarkDotNet guidance is documented in [docs/guides/benchmarks.md](docs/guide
521521
| Backends For Frontends | Execution | 25.40 ns | 216 B | 29.77 ns | 216 B | Same allocation; fluent was faster for the web summary shaping workflow. |
522522
| Builder | Construction | 48.24 ns | 320 B | 160.455 us | 129,236 B | Fluent builder construction is much lighter; generated measures full HostApplicationBuilder composition. |
523523
| Builder | Execution | 65.14 ns | 352 B | 500.676 us | 279,923 B | Fluent option building is much lighter; generated exercises the full host-backed application build. |
524+
| Object Pool | Construction | Pending | Pending | Pending | Pending | Covered by the BenchmarkDotNet matrix; publish measured values after the next benchmark refresh. |
525+
| Object Pool | Execution | Pending | Pending | Pending | Pending | Covered by the BenchmarkDotNet matrix; publish measured values after the next benchmark refresh. |
524526
| Bridge | Construction | 53.115 ns | 504 B | 3.821 ns | 24 B | Generated bridge construction was materially faster and allocated less. |
525527
| Bridge | Execution | 91.848 ns | 664 B | 30.004 ns | 160 B | Generated bridge forwarding was faster and allocated less for notice rendering. |
526528
| Bulkhead | Construction | 20.56 ns | 216 B | 20.48 ns | 216 B | Effectively equivalent for this microbenchmark. |
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using BenchmarkDotNet.Attributes;
2+
using PatternKit.Creational.ObjectPool;
3+
using PatternKit.Examples.ObjectPoolDemo;
4+
5+
namespace PatternKit.Benchmarks.Creational;
6+
7+
[BenchmarkCategory("Creational", "ObjectPool")]
8+
public class ObjectPoolBenchmarks
9+
{
10+
private static readonly FormulaEvaluationRequest Request = new(
11+
"D4",
12+
"subtotal + tax + 5",
13+
new Dictionary<string, decimal>
14+
{
15+
["subtotal"] = 100m,
16+
["tax"] = 8.25m
17+
});
18+
19+
[Benchmark(Baseline = true, Description = "Fluent: create object pool")]
20+
[BenchmarkCategory("Fluent", "Construction")]
21+
public ObjectPool<FormulaEvaluationBuffer> Fluent_CreateObjectPool()
22+
=> SpreadsheetFormulaBufferPools.CreateFluent();
23+
24+
[Benchmark(Description = "Generated: create object pool")]
25+
[BenchmarkCategory("Generated", "Construction")]
26+
public ObjectPool<FormulaEvaluationBuffer> Generated_CreateObjectPool()
27+
=> SpreadsheetFormulaBufferPools.CreateGenerated();
28+
29+
[Benchmark(Description = "Fluent: evaluate spreadsheet formula")]
30+
[BenchmarkCategory("Fluent", "Execution")]
31+
public FormulaEvaluationResult Fluent_EvaluateSpreadsheetFormula()
32+
=> SpreadsheetFormulaObjectPoolDemoRunner.RunFluent(Request);
33+
34+
[Benchmark(Description = "Generated: evaluate spreadsheet formula")]
35+
[BenchmarkCategory("Generated", "Execution")]
36+
public FormulaEvaluationResult Generated_EvaluateSpreadsheetFormula()
37+
=> SpreadsheetFormulaObjectPoolDemoRunner.RunGenerated(Request);
38+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Spreadsheet Formula Object Pool
2+
3+
This example pools resettable formula evaluation buffers for a spreadsheet service. It demonstrates both the fluent and source-generated Object Pool routes, then registers the generated pool through `IServiceCollection`.
4+
5+
```csharp
6+
services.AddSpreadsheetFormulaObjectPoolDemo();
7+
8+
var runner = provider.GetRequiredService<SpreadsheetFormulaObjectPoolDemoRunner>();
9+
var result = runner.Run(new FormulaEvaluationRequest(
10+
"D4",
11+
"subtotal + tax + 5",
12+
new Dictionary<string, decimal>
13+
{
14+
["subtotal"] = 100m,
15+
["tax"] = 8.25m
16+
}));
17+
```
18+
19+
The pool is singleton-scoped, while every formula evaluation uses a short-lived lease. Returning the lease resets the buffer and keeps the retained pool bounded.

docs/examples/toc.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
- name: Order Projection Eventual Consistency Monitor
2323
href: order-projection-eventual-consistency-monitor.md
2424

25+
- name: Spreadsheet Formula Object Pool
26+
href: spreadsheet-formula-object-pool.md
27+
2528
- name: Auth & Logging with `ActionChain<HttpRequest>`
2629
href: auth-logging-chain.md
2730

docs/generators/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ PatternKit includes a Roslyn incremental generator package (`PatternKit.Generato
3030
| [**Builder**](builder.md) | GoF-aligned builders with mutable or state-projection models, sync/async pipelines | `[GenerateBuilder]` |
3131
| [**Factory Method**](factory-method.md) | Keyed dispatcher from a static partial class | `[FactoryMethod]` |
3232
| [**Factory Class**](factory-class.md) | GoF-style factory mapping keys to products | `[FactoryClass]` |
33+
| [**Object Pool**](object-pool.md) | Bounded pooled-resource factories with optional reset hooks | `[GenerateObjectPool]` |
3334
| [**Prototype**](prototype.md) | Clone/deep-copy generation for types | `[Prototype]` |
3435
| [**Singleton**](singleton.md) | Thread-safe singleton accessors with optional factory hooks | `[Singleton]` |
3536

docs/generators/object-pool.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# Object Pool Generator
2+
3+
`GenerateObjectPoolAttribute` creates a static factory for `PatternKit.Creational.ObjectPool.ObjectPool<T>`.
4+
5+
| Setting | Type | Default | Description |
6+
| --- | --- | --- | --- |
7+
| `itemType` | `Type` | Required | Pooled item type. It must be a value type or expose a public parameterless constructor. |
8+
| `FactoryMethodName` | `string` | `Create` | Generated static factory method name. |
9+
| `MaxRetained` | `int` | `-1` | When set to `0` or greater, emits `WithMaxRetained(value)`. |
10+
| `ResetMethodName` | `string?` | `null` | Optional instance method invoked from `OnReturn`. |
11+
12+
```csharp
13+
[GenerateObjectPool(typeof(FormulaEvaluationBuffer), FactoryMethodName = "CreateGenerated", MaxRetained = 16, ResetMethodName = nameof(FormulaEvaluationBuffer.Reset))]
14+
public static partial class SpreadsheetFormulaBufferPools;
15+
```
16+
17+
Diagnostics:
18+
19+
| ID | Severity | Description |
20+
| --- | --- | --- |
21+
| `PKOP001` | Error | The host type is not partial. |
22+
| `PKOP002` | Error | The factory method name is blank or `MaxRetained` is below `-1`. |
23+
| `PKOP003` | Error | The pooled item type cannot be created with `new T()`. |

docs/generators/toc.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,9 @@
106106
- name: Manual Task Gate
107107
href: manual-task-gate.md
108108

109+
- name: Object Pool
110+
href: object-pool.md
111+
109112
- name: Workflow Orchestration
110113
href: workflow-orchestration.md
111114

docs/guides/benchmark-results.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ The latest measured timings below were captured on Windows 11, Intel Core i9-149
4141
| Backends For Frontends | Execution | 25.40 ns | 216 B | 29.77 ns | 216 B | Same allocation; fluent was faster for the web summary shaping workflow. |
4242
| Builder | Construction | 48.24 ns | 320 B | 160.455 us | 129,236 B | Fluent builder construction is much lighter; generated measures full HostApplicationBuilder composition. |
4343
| Builder | Execution | 65.14 ns | 352 B | 500.676 us | 279,923 B | Fluent option building is much lighter; generated exercises the full host-backed application build. |
44+
| Object Pool | Construction | Pending | Pending | Pending | Pending | Covered by the BenchmarkDotNet matrix; publish measured values after the next benchmark refresh. |
45+
| Object Pool | Execution | Pending | Pending | Pending | Pending | Covered by the BenchmarkDotNet matrix; publish measured values after the next benchmark refresh. |
4446
| Bridge | Construction | 53.115 ns | 504 B | 3.821 ns | 24 B | Generated bridge construction was materially faster and allocated less. |
4547
| Bridge | Execution | 91.848 ns | 664 B | 30.004 ns | 160 B | Generated bridge forwarding was faster and allocated less for notice rendering. |
4648
| Bulkhead | Construction | 20.56 ns | 216 B | 20.48 ns | 216 B | Effectively equivalent for this microbenchmark. |
@@ -248,19 +250,19 @@ The latest measured timings below were captured on Windows 11, Intel Core i9-149
248250

249251
## Coverage Matrix Summary
250252

251-
The coverage matrix currently publishes 114 catalog patterns and 456 pattern route results. Each pattern has four BenchmarkDotNet routes: fluent construction, fluent execution, source-generated construction, and source-generated execution. The reusable hosting integration matrix publishes 9 reusable hosting integration route results for package-level `IServiceCollection` registrations.
253+
The coverage matrix currently publishes 115 catalog patterns and 460 pattern route results. Each pattern has four BenchmarkDotNet routes: fluent construction, fluent execution, source-generated construction, and source-generated execution. The reusable hosting integration matrix publishes 9 reusable hosting integration route results for package-level `IServiceCollection` registrations.
252254

253255
| Category | Patterns | Published route results |
254256
| --- | ---: | ---: |
255257
| Application Architecture | 26 | 104 |
256258
| Behavioral | 11 | 44 |
257259
| Cloud Architecture | 20 | 80 |
258-
| Creational | 5 | 20 |
260+
| Creational | 6 | 24 |
259261
| Enterprise Integration | 41 | 164 |
260262
| Messaging Reliability | 3 | 12 |
261263
| Structural | 7 | 28 |
262264

263-
The generator matrix currently publishes 108 generator source route results.
265+
The generator matrix currently publishes 109 generator source route results.
264266

265267
## Hosting Integration Matrix Results
266268

@@ -338,9 +340,10 @@ The generator matrix currently publishes 108 generator source route results.
338340
| Cloud Architecture | Strangler Fig | Covered | Covered | Covered | Covered |
339341
| Cloud Architecture | Write-Through Cache | Covered | Covered | Covered | Covered |
340342
| Creational | Abstract Factory | Covered | Covered | Covered | Covered |
341-
| Creational | Builder | Covered | Covered | Covered | Covered |
342-
| Creational | Factory Method | Covered | Covered | Covered | Covered |
343-
| Creational | Prototype | Covered | Covered | Covered | Covered |
343+
| Creational | Builder | Covered | Covered | Covered | Covered |
344+
| Creational | Factory Method | Covered | Covered | Covered | Covered |
345+
| Creational | Object Pool | Covered | Covered | Covered | Covered |
346+
| Creational | Prototype | Covered | Covered | Covered | Covered |
344347
| Creational | Singleton | Covered | Covered | Covered | Covered |
345348
| Enterprise Integration | Aggregator | Covered | Covered | Covered | Covered |
346349
| Enterprise Integration | Canonical Data Model | Covered | Covered | Covered | Covered |
@@ -484,8 +487,9 @@ The generator matrix currently publishes 108 generator source route results.
484487
| ServiceActivatorGenerator | `src/PatternKit.Generators/Messaging/ServiceActivatorGenerator.cs` | Covered |
485488
| SplitterAggregatorGenerator | `src/PatternKit.Generators/Messaging/SplitterAggregatorGenerator.cs` | Covered |
486489
| WireTapGenerator | `src/PatternKit.Generators/Messaging/WireTapGenerator.cs` | Covered |
487-
| ObserverGenerator | `src/PatternKit.Generators/Observer/ObserverGenerator.cs` | Covered |
488-
| PriorityQueueGenerator | `src/PatternKit.Generators/PriorityQueue/PriorityQueueGenerator.cs` | Covered |
490+
| ObserverGenerator | `src/PatternKit.Generators/Observer/ObserverGenerator.cs` | Covered |
491+
| ObjectPoolGenerator | `src/PatternKit.Generators/ObjectPool/ObjectPoolGenerator.cs` | Covered |
492+
| PriorityQueueGenerator | `src/PatternKit.Generators/PriorityQueue/PriorityQueueGenerator.cs` | Covered |
489493
| PrototypeGenerator | `src/PatternKit.Generators/PrototypeGenerator.cs` | Covered |
490494
| ProxyGenerator | `src/PatternKit.Generators/ProxyGenerator.cs` | Covered |
491495
| QueueLoadLevelingPolicyGenerator | `src/PatternKit.Generators/QueueLoadLeveling/QueueLoadLevelingPolicyGenerator.cs` | Covered |

docs/guides/benchmarks.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ The following numbers were captured on Windows 11, Intel Core i9-14900K, .NET SD
5858
| Backends For Frontends | Execution | 25.40 ns | 216 B | 29.77 ns | 216 B | Same allocation; fluent was faster for the web summary shaping workflow. |
5959
| Builder | Construction | 48.24 ns | 320 B | 160.455 us | 129,236 B | Fluent builder construction is much lighter; generated measures full HostApplicationBuilder composition. |
6060
| Builder | Execution | 65.14 ns | 352 B | 500.676 us | 279,923 B | Fluent option building is much lighter; generated exercises the full host-backed application build. |
61+
| Object Pool | Construction | Pending | Pending | Pending | Pending | Covered by the BenchmarkDotNet matrix; publish measured values after the next benchmark refresh. |
62+
| Object Pool | Execution | Pending | Pending | Pending | Pending | Covered by the BenchmarkDotNet matrix; publish measured values after the next benchmark refresh. |
6163
| Bridge | Construction | 53.115 ns | 504 B | 3.821 ns | 24 B | Generated bridge construction was materially faster and allocated less. |
6264
| Bridge | Execution | 91.848 ns | 664 B | 30.004 ns | 160 B | Generated bridge forwarding was faster and allocated less for notice rendering. |
6365
| Bulkhead | Construction | 20.56 ns | 216 B | 20.48 ns | 216 B | Effectively equivalent for this microbenchmark. |

docs/guides/pattern-coverage.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ The source of truth is `PatternKitPatternCatalog` in `src/PatternKit.Examples/Pr
4444

4545
| Family | Pattern | Fluent/runtime path | Source-generated path |
4646
| --- | --- | --- | --- |
47+
| Creational | Object Pool | `ObjectPool<T>` | Object Pool generator |
4748
| Enterprise Integration | Message Channel | `MessageChannel<TPayload>` | Message Channel generator |
4849
| Enterprise Integration | Channel Purger | `ChannelPurger<TPayload>` | Channel Purger generator |
4950
| Enterprise Integration | Invalid Message Channel | `InvalidMessageChannel<TPayload>` | Invalid Message Channel generator |

0 commit comments

Comments
 (0)