Skip to content

feat: add SuppressDisposal to opt out of container disposal#111

Merged
vbreuss merged 3 commits into
mainfrom
feat/suppress-disposal
Jul 16, 2026
Merged

feat: add SuppressDisposal to opt out of container disposal#111
vbreuss merged 3 commits into
mainfrom
feat/suppress-disposal

Conversation

@vbreuss

@vbreuss vbreuss commented Jul 16, 2026

Copy link
Copy Markdown
Member

SuppressDisposal = true on the generic lifetime attributes tells the container to construct a service but not tear it down: it never calls the instance's Dispose/DisposeAsync. On its own this is the built counterpart of a pre-built Instance (ExternallyOwned — teardown belongs to you or to something outside the container). Paired with an OnRelease hook it enables pooling, where the hook returns the object to a pool instead of disposing it, and with Owned<T> the rent/return cycle is bounded to a using block.

Behavior

  • DisposalOf short-circuits to None and NeedsDisposal reads false for a suppressed instance, so it is never tracked for teardown and lifetime-safety analysis (AWT118) treats it like any other non-owned service.
  • AWT156 (synchronous dispose of an async-only disposable) excludes suppressed instances — the container never disposes them, so no drain can throw on one.
  • Coalescing reports AWT166 when a later registration opts into SuppressDisposal the winning registration did not, consistent with how Eager and the lifecycle hooks already coalesce.
  • New diagnostic AWT192: SuppressDisposal on a pre-built Instance is a silent no-op (the container never owns or disposes an Instance), so it is rejected — mirroring AWT165 for lifecycle hooks. It is allowed on constructor- and factory-produced registrations.

Docs

Adds a "Suppressing disposal" section to the disposal page (ExternallyOwned plus return-to-pool via OnRelease and Owned<T>), a note on the lifecycle-hooks page that OnRelease can replace disposal, a cross-reference from the factories page, and the AWT192 diagnostics entry.

Tests

Runtime coverage for suppression (instance not disposed, OnRelease still runs) and a full rent/return/reuse pooling round-trip through Owned<T>; generator coverage for the AWT166 coalescing conflict and the AWT192 guard; public API approvals updated for the new attribute property.

`SuppressDisposal = true` on the generic lifetime attributes tells the
container to construct a service but not tear it down: it never calls the
instance's `Dispose`/`DisposeAsync`. On its own this is the built counterpart
of a pre-built `Instance` (ExternallyOwned — teardown belongs to you or to
something outside the container). Paired with an `OnRelease` hook it enables
pooling, where the hook returns the object to a pool instead of disposing it,
and with `Owned<T>` the rent/return cycle is bounded to a `using` block.

## Behavior

- `DisposalOf` short-circuits to `None` and `NeedsDisposal` reads `false` for a
  suppressed instance, so it is never tracked for teardown and lifetime-safety
  analysis (AWT118) treats it like any other non-owned service.
- AWT156 (synchronous dispose of an async-only disposable) excludes suppressed
  instances — the container never disposes them, so no drain can throw on one.
- Coalescing reports AWT166 when a later registration opts into
  `SuppressDisposal` the winning registration did not, consistent with how
  `Eager` and the lifecycle hooks already coalesce.
- New diagnostic **AWT192**: `SuppressDisposal` on a pre-built `Instance` is a
  silent no-op (the container never owns or disposes an `Instance`), so it is
  rejected — mirroring AWT165 for lifecycle hooks. It is allowed on
  constructor- and factory-produced registrations.

## Docs

Adds a "Suppressing disposal" section to the disposal page (ExternallyOwned
plus return-to-pool via `OnRelease` and `Owned<T>`), a note on the
lifecycle-hooks page that `OnRelease` can replace disposal, a cross-reference
from the factories page, and the AWT192 diagnostics entry.

## Tests

Runtime coverage for suppression (instance not disposed, `OnRelease` still
runs) and a full rent/return/reuse pooling round-trip through `Owned<T>`;
generator coverage for the AWT166 coalescing conflict and the AWT192 guard;
public API approvals updated for the new attribute property.
@vbreuss vbreuss self-assigned this Jul 16, 2026
@vbreuss vbreuss added the enhancement New feature or request label Jul 16, 2026
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown

Test Results

   18 files  ± 0     18 suites  ±0   8m 37s ⏱️ +39s
1 128 tests +14  1 127 ✅ +14  1 💤 ±0  0 ❌ ±0 
5 501 runs  +69  5 500 ✅ +69  1 💤 ±0  0 ❌ ±0 

Results for commit b8956fe. ± Comparison against base commit f70bc2a.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown

🚀 Benchmark Results

Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.302
[Host] : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v4

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Build Size Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 8 12.968 ns 0.3556 ns 0.3326 ns 0.84 144 B 1.00
Awaiten 8 15.475 ns 0.5273 ns 0.4932 ns 1.00 144 B 1.00
MsDI 8 1,157.652 ns 8.8873 ns 7.8784 ns 74.88 5688 B 39.50
Autofac 8 26,904.562 ns 126.5244 ns 112.1606 ns 1,740.29 33094 B 229.82
Jab 8 8.157 ns 0.1254 ns 0.1173 ns 0.53 96 B 0.67
PureDI 8 13.154 ns 0.0942 ns 0.0787 ns 0.85 128 B 0.89
DryIoc 8 634.902 ns 7.9782 ns 7.4628 ns 41.07 1528 B 10.61
SimpleInjector 8 9,357.538 ns 115.8522 ns 96.7418 ns 605.28 24761 B 171.95
baseline* 256 62.378 ns 0.2342 ns 0.2076 ns 0.97 2128 B 1.00
Awaiten 256 64.002 ns 0.5426 ns 0.4531 ns 1.00 2128 B 1.00
MsDI 256 11,941.837 ns 321.9667 ns 301.1679 ns 186.59 61016 B 28.67
Autofac 256 645,468.107 ns 4,958.6424 ns 4,638.3172 ns 10,085.63 720292 B 338.48
Jab 256 54.409 ns 0.2956 ns 0.2765 ns 0.85 2080 B 0.98
PureDI 256 58.991 ns 0.3156 ns 0.2952 ns 0.92 2112 B 0.99
DryIoc 256 37,660.677 ns 444.2877 ns 371.0003 ns 588.46 80540 B 37.85
SimpleInjector 256 286,369.719 ns 4,222.1685 ns 3,949.4190 ns 4,474.61 573058 B 269.29
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 7763 2.61GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.302
[Host] : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Realistic Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 260.1 ns 2.99 ns 2.79 ns 1.02 568 B 1.00
Awaiten 256.1 ns 2.65 ns 2.47 ns 1.00 568 B 1.00
MsDI 636.1 ns 5.26 ns 4.66 ns 2.48 1104 B 1.94
Autofac 6,989.3 ns 50.93 ns 45.15 ns 27.30 10784 B 18.99
Jab 196.5 ns 1.03 ns 0.96 ns 0.77 432 B 0.76
DryIoc 424.3 ns 3.95 ns 3.69 ns 1.66 944 B 1.66
SimpleInjector 757.6 ns 2.11 ns 1.87 ns 2.96 1096 B 1.93
PureDI 198.4 ns 4.17 ns 3.90 ns 0.77 632 B 1.11
Details

BenchmarkDotNet v0.15.8, Linux Ubuntu 24.04.4 LTS (Noble Numbat)
AMD EPYC 9V74 2.60GHz, 1 CPU, 4 logical and 2 physical cores
.NET SDK 10.0.302
[Host] : .NET 10.0.10 (10.0.10, 10.0.1026.32716), X64 RyuJIT x86-64-v3

Job=InProcess Toolchain=InProcessEmitToolchain IterationCount=15
LaunchCount=1 WarmupCount=10

Resolve Size Mean Error StdDev Ratio Allocated Alloc Ratio
baseline* 8 5.962 ns 0.1032 ns 0.0915 ns 0.78 - NA
Awaiten 8 7.687 ns 0.1463 ns 0.1297 ns 1.00 - NA
MsDI 8 9.959 ns 0.0053 ns 0.0044 ns 1.30 - NA
Autofac 8 104.537 ns 1.0235 ns 0.9073 ns 13.60 656 B NA
Jab 8 2.468 ns 0.0048 ns 0.0040 ns 0.32 - NA
PureDI 8 4.756 ns 0.0065 ns 0.0058 ns 0.62 - NA
DryIoc 8 8.917 ns 0.1816 ns 0.1699 ns 1.16 - NA
SimpleInjector 8 11.022 ns 0.0368 ns 0.0307 ns 1.43 - NA
baseline* 256 6.481 ns 0.1283 ns 0.1200 ns 0.80 - NA
Awaiten 256 8.098 ns 0.0057 ns 0.0051 ns 1.00 - NA
MsDI 256 8.279 ns 0.0086 ns 0.0072 ns 1.02 - NA
Autofac 256 104.729 ns 0.6553 ns 0.6130 ns 12.93 656 B NA
Jab 256 50.295 ns 4.4401 ns 4.1533 ns 6.21 - NA
PureDI 256 9.163 ns 0.0068 ns 0.0060 ns 1.13 - NA
DryIoc 256 10.278 ns 0.0212 ns 0.0188 ns 1.27 - NA
SimpleInjector 256 17.584 ns 0.1187 ns 0.1111 ns 2.17 - NA

baseline* rows show the corresponding Awaiten benchmark from the most recent successful main branch build with results, for regression comparison.

vbreuss added 2 commits July 16, 2026 19:10
Fix a comment referencing AWT189 instead of AWT192, and add tests for
the two uncovered SuppressDisposal paths: the AWT156 exemption for an
async-only disposable that suppresses disposal, and a runtime check that
suppression skips DisposeAsync, not only synchronous Dispose.
An OnRelease hook queues a closure on the owner per construction,
retaining the instance until teardown exactly like disposal tracking, so
a SuppressDisposal pooled transient accumulates on the root the same way
a tracked disposable would (and starves its pool until the container
dies). Both withholding predicates (IsWithheld, BuildsFreshDisposable -
the latter also backing AWT118) now test NeedsDisposal || HasReleaseHook.

Deliberate consequence: a non-disposable transient with OnRelease is now
root-withheld under strict safety too; the lifecycle-hook tests that
exercise root-owned release semantics opt into LifetimeSafety.Loose. A
SuppressDisposal service without a release hook tracks nothing and stays
resolvable everywhere.
@vbreuss
vbreuss force-pushed the feat/suppress-disposal branch from 12be1d0 to b8956fe Compare July 16, 2026 17:51
@vbreuss
vbreuss enabled auto-merge (squash) July 16, 2026 17:54
@sonarqubecloud

Copy link
Copy Markdown

@vbreuss
vbreuss merged commit a9ecf93 into main Jul 16, 2026
14 checks passed
@vbreuss
vbreuss deleted the feat/suppress-disposal branch July 16, 2026 17:57
github-actions Bot added a commit that referenced this pull request Jul 16, 2026
github-actions Bot added a commit that referenced this pull request Jul 16, 2026
@github-actions

Copy link
Copy Markdown

This is addressed in release v0.6.0.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request state: released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant