Skip to content

Commit 508285a

Browse files
committed
Fix decorators not respecting ExternallyOwned on decorated service (#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.
1 parent f8d4095 commit 508285a

2 files changed

Lines changed: 33 additions & 1 deletion

File tree

src/Autofac/Core/Resolving/Middleware/DisposalTrackingMiddleware.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,13 @@ public void Execute(ResolveRequestContext context, Action<ResolveRequestContext>
2828
{
2929
next(context);
3030

31-
if (context.Registration.Ownership == InstanceOwnership.OwnedByLifetimeScope)
31+
// When a decorator is being resolved, context.DecoratorTarget is the underlying component
32+
// registration. If the underlying component is ExternallyOwned, the decorator should also
33+
// not be tracked for disposal - the caller opted out of lifetime management for the entire
34+
// decorated chain. See https://github.com/autofac/Autofac/issues/1402.
35+
var effectiveOwnership = context.DecoratorTarget?.Ownership ?? context.Registration.Ownership;
36+
37+
if (effectiveOwnership == InstanceOwnership.OwnedByLifetimeScope)
3238
{
3339
// The fact this adds instances for disposal agnostic of the activator is
3440
// important. The ProvidedInstanceActivator will NOT dispose of the provided

test/Autofac.Specification.Test/Features/DecoratorTests.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,32 @@ public void DecoratorAndDecoratedBothDisposedWhenSingleInstance()
675675
Assert.Equal(1, decorated.DisposeCallCount);
676676
}
677677

678+
[Fact]
679+
public void DecoratorNotDisposedWhenDecoratedServiceIsExternallyOwned()
680+
{
681+
// #1402: A decorator wrapping an ExternallyOwned service should itself
682+
// not be disposed by the lifetime scope.
683+
var builder = new ContainerBuilder();
684+
builder.RegisterType<DisposableImplementor>()
685+
.As<IDecoratedService>()
686+
.ExternallyOwned();
687+
builder.RegisterDecorator<DisposableDecorator, IDecoratedService>();
688+
var container = builder.Build();
689+
690+
DisposableDecorator decorator;
691+
DisposableImplementor decorated;
692+
693+
using (var scope = container.BeginLifetimeScope())
694+
{
695+
var instance = scope.Resolve<IDecoratedService>();
696+
decorator = (DisposableDecorator)instance;
697+
decorated = (DisposableImplementor)instance.Decorated;
698+
}
699+
700+
Assert.Equal(0, decorator.DisposeCallCount);
701+
Assert.Equal(0, decorated.DisposeCallCount);
702+
}
703+
678704
[Fact]
679705
public void DecoratorAppliedOnlyOnceToComponentWithExternalRegistrySource()
680706
{

0 commit comments

Comments
 (0)