Honor ExternallyOwned ownership for decorators (#1402)#1486
Merged
Conversation
…1402) When a decorated service is registered as ExternallyOwned, the decorator wrapping it was still tracked for disposal by the lifetime scope because the dynamically-created decorator registration defaults to OwnedByLifetimeScope. In DisposalTrackingMiddleware, when the context has a DecoratorTarget, that target IS the underlying component registration. If that underlying registration is ExternallyOwned, the decorator should inherit that intent and also not be tracked for disposal — the caller has opted out of lifetime management for the entire decorated chain.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## develop #1486 +/- ##
===========================================
+ Coverage 77.69% 77.76% +0.07%
===========================================
Files 217 217
Lines 5829 5834 +5
Branches 1253 1254 +1
===========================================
+ Hits 4529 4537 +8
+ Misses 764 760 -4
- Partials 536 537 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
This was referenced Jun 17, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #1402. When a decorated service was registered as
ExternallyOwned, the decorator wrapping it was still created with the defaultOwnedByLifetimeScopeownership. The lifetime scope therefore tracked the decorator for disposal and disposed it (and, through it, the decorated instance) — violating the user'sExternallyOwnedopt-out and causing double-disposal.Fix
DisposalTrackingMiddleware.Executenow uses the effective ownership when deciding whether to track an instance for disposal:context.DecoratorTarget?.Ownership ?? context.Registration.Ownership. For non-decorator resolutionsDecoratorTargetis null and behavior is unchanged; for decorator resolutions the underlying decorated component's ownership governs, so anExternallyOwneddecorated service no longer has its decorator disposed by the scope.This was chosen over propagating ownership onto the decorator registration at build time, because the decorator registration is created before the decorated registration is known. The middleware approach is non-invasive and covers all decorator styles (generic, non-generic, lambda, open-generic) as well as async disposal, since they all route through
DisposalTrackingMiddleware.Tests
Adds
DecoratorNotDisposedWhenDecoratedServiceIsExternallyOwned: anExternallyOwneddisposable service decorated by a disposable decorator, asserting neither is disposed when the scope is disposed. The existing decorator disposal tests (InstancePerDependency,InstancePerLifetimeScope,InstancePerMatchingLifetimeScope,SingleInstance) serve as the regression guard for normal-ownership behavior.No public API changes. Zero warnings/errors; full suites pass on net8.0 and net10.0.