Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WindsorScopedServiceProvider remains in scope captured at resolve-time (Scope cache was already disposed) #639

Open
ikkentim opened this issue Nov 10, 2022 · 0 comments

Comments

@ikkentim
Copy link
Contributor

ikkentim commented Nov 10, 2022

When a WindsorScopedServiceProvider is created it captures the scope. This has some odd effects, for example when a IServiceProvider is injected into a singleton instance. In this case the captured scope would outlive the lifetime of the scope.

The following sample crashes with the error System.ObjectDisposedException: Scope cache was already disposed. This is most likely a bug in the calling code..

using Castle.Core;
using Castle.MicroKernel;
using Castle.MicroKernel.Context;
using Castle.Windsor.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;

// construct a ServiceProvider with a transient TeapotService and a singleton SingletonTeapotService.
var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<TeapotService>()
    .AddSingleton<SingletonTeapotService>();

var factory = new WindsorServiceProviderFactory();
var builder = factory.CreateBuilder(serviceCollection);
var serviceProvider  = factory.CreateServiceProvider(builder);

// call to singleton in scope
using (var scope = serviceProvider.CreateScope())
{
    // resolving the singleton from the scope causes the WindsorScopedServiceProvider (dependency of
    // SingletonTeapotService) to capture the current scope instead of the root scope! this means that
    // the WindsorScopedServiceProvider instance will be in an invalid state after the scope has ended.
    scope.ServiceProvider.GetRequiredService<SingletonTeapotService>().Say();
}

// call to singleton in rootscope while its dependency WindsorScopedServiceProvider is in an invalid state
serviceProvider.GetRequiredService<SingletonTeapotService>().Say(); // error! ObjectDisposedException

class SingletonTeapotService
{
    private readonly IServiceProvider _serviceProvider;
    public SingletonTeapotService(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider;
    public void Say() => _serviceProvider.GetRequiredService<TeapotService>().Say();
}

class TeapotService : IDisposable
{
    public TeapotService() => Console.WriteLine("create teapot");
    public void Say() => Console.WriteLine("I'm a teapot!");
    public void Dispose() => Console.WriteLine("dispose teapot");
}
Castle.Windsor.dll!Castle.MicroKernel.Lifestyle.Scoped.ScopeCache.this[object].set(object id, Castle.MicroKernel.Burden value)
Castle.Windsor.Extensions.DependencyInjection.dll!Castle.Windsor.Extensions.DependencyInjection.Scope.ExtensionContainerScopeBase.GetCachedInstance(Castle.Core.ComponentModel model, Castle.MicroKernel.Lifestyle.Scoped.ScopedInstanceActivationCallback createInstance)
Castle.Windsor.dll!Castle.MicroKernel.Lifestyle.ScopedLifestyleManager.Resolve(Castle.MicroKernel.Context.CreationContext context, Castle.MicroKernel.IReleasePolicy releasePolicy)
Castle.Windsor.dll!Castle.MicroKernel.Handlers.DefaultHandler.ResolveCore(Castle.MicroKernel.Context.CreationContext context, bool requiresDecommission, bool instanceRequired, out Castle.MicroKernel.Burden burden)
Castle.Windsor.dll!Castle.MicroKernel.Handlers.DefaultHandler.Resolve(Castle.MicroKernel.Context.CreationContext context, bool instanceRequired)
Castle.Windsor.dll!Castle.MicroKernel.DefaultKernel.ResolveComponent(Castle.MicroKernel.IHandler handler, System.Type service, Castle.MicroKernel.Arguments additionalArguments, Castle.MicroKernel.IReleasePolicy policy, bool ignoreParentContext)
Castle.Windsor.dll!Castle.MicroKernel.DefaultKernel.Castle.MicroKernel.IKernelInternal.Resolve(System.Type service, Castle.MicroKernel.Arguments arguments, Castle.MicroKernel.IReleasePolicy policy, bool ignoreParentContext)
Castle.Windsor.dll!Castle.MicroKernel.DefaultKernel.Resolve(System.Type service, Castle.MicroKernel.Arguments arguments)
Castle.Windsor.Extensions.DependencyInjection.dll!Castle.Windsor.Extensions.DependencyInjection.WindsorScopedServiceProvider.ResolveInstanceOrNull(System.Type serviceType, bool isOptional)
Castle.Windsor.Extensions.DependencyInjection.dll!Castle.Windsor.Extensions.DependencyInjection.WindsorScopedServiceProvider.GetRequiredService(System.Type serviceType)
Microsoft.Extensions.DependencyInjection.Abstractions.dll!Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService<TeapotService>(System.IServiceProvider provider)
ConsoleApp37.dll!SingletonTeapotService.Say()
ConsoleApp37.dll!Program.<Main>$(string[] args)

I'd have expected this code to behave in the same way Microsoft.Extensions.DependencyInection would. With MS DI the SingletonTeapotService's IServiceProvider would be scoped to the rootscope.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant