From 8eceee7b4a56c6405421e088ccec661be8e97f1c Mon Sep 17 00:00:00 2001 From: lwx Date: Wed, 17 Jan 2024 15:29:53 +0800 Subject: [PATCH] Bugfix net6 ms di no available scope #662 --- .../ResolveFromThreadpoolUnsafe.cs | 752 ++++++++++++++++++ .../Scope/ExtensionContainerRootScope.cs | 29 - .../ExtensionContainerRootScopeAccessor.cs | 2 +- .../Scope/ExtensionContainerScope.cs | 9 +- .../Scope/ForcedScope.cs | 4 +- .../Scope/WindsorScopeFactory.cs | 8 +- .../WindsorScopedServiceProvider.cs | 2 +- .../WindsorServiceProviderFactoryBase.cs | 7 +- 8 files changed, 768 insertions(+), 45 deletions(-) create mode 100644 src/Castle.Windsor.Extensions.DependencyInjection.Tests/ResolveFromThreadpoolUnsafe.cs delete mode 100644 src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScope.cs diff --git a/src/Castle.Windsor.Extensions.DependencyInjection.Tests/ResolveFromThreadpoolUnsafe.cs b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/ResolveFromThreadpoolUnsafe.cs new file mode 100644 index 0000000000..ca0a2133a9 --- /dev/null +++ b/src/Castle.Windsor.Extensions.DependencyInjection.Tests/ResolveFromThreadpoolUnsafe.cs @@ -0,0 +1,752 @@ +using Castle.MicroKernel.Lifestyle; +using Castle.MicroKernel.Registration; +using Castle.Windsor.Extensions.DependencyInjection.Extensions; +using Castle.Windsor.Extensions.DependencyInjection.Tests.Components; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace Castle.Windsor.Extensions.DependencyInjection.Tests +{ + [CollectionDefinition(nameof(DoNotParallelize), DisableParallelization = true)] + public class DoNotParallelize { } + + /// + /// These is the original Castle Windsor Dependency Injection behavior. + /// + public class ResolveFromThreadpoolUnsafe_NetStatic : AbstractResolveFromThreadpoolUnsafe + { + public ResolveFromThreadpoolUnsafe_NetStatic() : base() + { + } + + #region "Singleton" + + /// + /// This test will Succeed is we use standard Castle Windsor Singleton lifestyle instead of the custom + /// NetStatic lifestyle. + /// + [Fact] + public async Task Cannot_Resolve_LifestyleNetStatic_From_WindsorContainer_NoRootScopeAvailable() + { + var serviceProvider = new ServiceCollection(); + var container = new WindsorContainer(); + var f = new WindsorServiceProviderFactory(container); + f.CreateBuilder(serviceProvider); + + container.Register( + Component.For().ImplementedBy().LifeStyle.NetStatic() + ); + + IServiceProvider sp = f.CreateServiceProvider(container); + + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + + TaskCompletionSource tcs = new TaskCompletionSource(); + + ThreadPool.UnsafeQueueUserWorkItem(state => + { + try + { + var actualUserService = container.Resolve(); + Assert.NotNull(actualUserService); + } + catch (Exception ex) + { + tcs.SetException(ex); + return; + } + tcs.SetResult(actualUserService); + }, null); + + // Wait for the work item to complete. + var ex = await Catches.ExceptionAsync(async () => + { + var task = tcs.Task; + IUserService result = await task; + // The test succeeds if we use standard Castle Windsor Singleton lifestyle instead of the custom NetStatic lifestyle. + Assert.NotNull(result); + }); + + // This test will fail if we use NetStatic lifestyle + Assert.NotNull(ex); + Assert.IsType(ex); + Assert.Equal("No root scope available", ex.Message); + + (sp as IDisposable)?.Dispose(); + container.Dispose(); + } + + #endregion + } + + /// + /// Mapping NetStatic to usual Singleton lifestyle. + /// + public class ResolveFromThreadpoolUnsafe_Singleton : AbstractResolveFromThreadpoolUnsafe + { + public ResolveFromThreadpoolUnsafe_Singleton() : base() + { + } + + #region "Singleton" + + /// + /// This test will Succeed is we use standard Castle Windsor Singleton lifestyle instead of the custom + /// NetStatic lifestyle. + /// + [Fact] + public async Task Can_Resolve_LifestyleNetStatic_From_WindsorContainer() + { + var serviceProvider = new ServiceCollection(); + var container = new WindsorContainer(); + var f = new WindsorServiceProviderFactory(container); + f.CreateBuilder(serviceProvider); + + container.Register( + Component.For().ImplementedBy().LifeStyle.Singleton + ); + + IServiceProvider sp = f.CreateServiceProvider(container); + + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + + TaskCompletionSource tcs = new TaskCompletionSource(); + + ThreadPool.UnsafeQueueUserWorkItem(state => + { + try + { + var actualUserService = container.Resolve(); + Assert.NotNull(actualUserService); + } + catch (Exception ex) + { + tcs.SetException(ex); + return; + } + tcs.SetResult(actualUserService); + }, null); + + // Wait for the work item to complete. + var ex = await Catches.ExceptionAsync(async () => + { + var task = tcs.Task; + IUserService result = await task; + // The test succeeds if we use standard Castle Windsor Singleton lifestyle instead of the custom NetStatic lifestyle. + Assert.NotNull(result); + }); + + (sp as IDisposable)?.Dispose(); + container.Dispose(); + } + + #endregion + } + + /// + /// relying on static state (WindsorDependencyInjectionOptions) is not good for tests + /// that might run in parallel, can lead to false positives / negatives. + /// + [Collection(nameof(DoNotParallelize))] + public abstract class AbstractResolveFromThreadpoolUnsafe + { + #region Singleton + + /* + * Singleton tests should never fail, given you have a container instance you should always + * be able to resolve a singleton from it. + */ + + [Fact] + public async Task Can_Resolve_LifestyleSingleton_From_ServiceProvider() + { + var serviceProvider = new ServiceCollection(); + var container = new WindsorContainer(); + var f = new WindsorServiceProviderFactory(container); + f.CreateBuilder(serviceProvider); + + container.Register( + Component.For().ImplementedBy() + ); + + IServiceProvider sp = f.CreateServiceProvider(container); + + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + + TaskCompletionSource tcs = new TaskCompletionSource(); + + ThreadPool.UnsafeQueueUserWorkItem(state => + { + try + { + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + } + catch (Exception ex) + { + tcs.SetException(ex); + return; + } + tcs.SetResult(actualUserService); + }, null); + + // Wait for the work item to complete. + var task = tcs.Task; + IUserService result = await task; + Assert.NotNull(result); + + (sp as IDisposable)?.Dispose(); + container.Dispose(); + } + + [Fact] + public async Task Can_Resolve_LifestyleSingleton_From_WindsorContainer() + { + var serviceProvider = new ServiceCollection(); + var container = new WindsorContainer(); + var f = new WindsorServiceProviderFactory(container); + f.CreateBuilder(serviceProvider); + + container.Register( + Component.For().ImplementedBy() + ); + + IServiceProvider sp = f.CreateServiceProvider(container); + + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + + TaskCompletionSource tcs = new TaskCompletionSource(); + + ThreadPool.UnsafeQueueUserWorkItem(state => + { + try + { + var actualUserService = container.Resolve(); + Assert.NotNull(actualUserService); + } + catch (Exception ex) + { + tcs.SetException(ex); + return; + } + tcs.SetResult(actualUserService); + }, null); + + // Wait for the work item to complete. + var task = tcs.Task; + IUserService result = await task; + Assert.NotNull(result); + + (sp as IDisposable)?.Dispose(); + container.Dispose(); + } + + [Fact] + public async Task Can_Resolve_LifestyleNetStatic_From_ServiceProvider() + { + var serviceProvider = new ServiceCollection(); + var container = new WindsorContainer(); + var f = new WindsorServiceProviderFactory(container); + f.CreateBuilder(serviceProvider); + + container.Register( + Component.For().ImplementedBy().LifeStyle.NetStatic() + ); + + IServiceProvider sp = f.CreateServiceProvider(container); + + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + + TaskCompletionSource tcs = new TaskCompletionSource(); + + ThreadPool.UnsafeQueueUserWorkItem(state => + { + try + { + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + } + catch (Exception ex) + { + tcs.SetException(ex); + return; + } + tcs.SetResult(actualUserService); + }, null); + + // Wait for the work item to complete. + var task = tcs.Task; + IUserService result = await task; + Assert.NotNull(result); + + (sp as IDisposable)?.Dispose(); + container.Dispose(); + } + + #endregion + + #region Scoped + + /* + * Scoped tests might fail if for whatever reason you do not have a current scope + * (like when you run from Threadpool.UnsafeQueueUserWorkItem). + */ + + /// + /// This test will fail because the service provider adapter + /// does not create a standard Castle Windsor scope + /// + [Fact] + public async Task Cannot_Resolve_LifestyleScoped_From_ServiceProvider() + { + var serviceProvider = new ServiceCollection(); + var container = new WindsorContainer(); + var f = new WindsorServiceProviderFactory(container); + f.CreateBuilder(serviceProvider); + + container.Register( + Component.For().ImplementedBy().LifestyleScoped() + ); + + IServiceProvider sp = f.CreateServiceProvider(container); + + // must create a standard Castle Windsor scope (not managed by the adapter) + using (var s = container.BeginScope()) + { + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + + TaskCompletionSource tcs = new TaskCompletionSource(); + + ThreadPool.UnsafeQueueUserWorkItem(state => + { + try + { + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + } + catch (Exception ex) + { + tcs.SetException(ex); + return; + } + tcs.SetResult(actualUserService); + }, null); + + // Wait for the work item to complete. + var ex = await Catches.ExceptionAsync(async () => + { + var task = tcs.Task; + IUserService result = await task; + Assert.NotNull(result); + }); + + Assert.NotNull(ex); + Assert.IsType(ex); + Assert.StartsWith("Scope was not available. Did you forget to call container.BeginScope()?", ex.Message); + } + + (sp as IDisposable)?.Dispose(); + container.Dispose(); + } + + /// + /// This test will fail because the service provider adapter + /// does not create a standard Castle Windsor scope + /// + [Fact] + public async Task Cannot_Resolve_LifestyleScoped_From_WindsorContainer() + { + var serviceProvider = new ServiceCollection(); + var container = new WindsorContainer(); + var f = new WindsorServiceProviderFactory(container); + f.CreateBuilder(serviceProvider); + + container.Register( + Component.For().ImplementedBy().LifestyleScoped() + ); + + IServiceProvider sp = f.CreateServiceProvider(container); + + // must create a standard Castle Windsor scope (not managed by the adapter) + using (var s = container.BeginScope()) + { + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + + TaskCompletionSource tcs = new TaskCompletionSource(); + + ThreadPool.UnsafeQueueUserWorkItem(state => + { + try + { + var actualUserService = container.Resolve(); + Assert.NotNull(actualUserService); + } + catch (Exception ex) + { + tcs.SetException(ex); + return; + } + tcs.SetResult(actualUserService); + }, null); + + // Wait for the work item to complete. + var ex = await Catches.ExceptionAsync(async () => + { + var task = tcs.Task; + IUserService result = await task; + Assert.NotNull(result); + }); + + Assert.NotNull(ex); + Assert.IsType(ex); + Assert.StartsWith("Scope was not available. Did you forget to call container.BeginScope()?", ex.Message); + } + + (sp as IDisposable)?.Dispose(); + container.Dispose(); + } + + /// + /// This test succeeds because WindsorScopedServiceProvider captured the root scope on creation + /// and forced it to be current before service resolution. + /// Scoped is tied to the rootscope = potential memory leak. + /// + [Fact] + public async Task Can_Resolve_LifestyleScopedToNetServiceScope_From_ServiceProvider_MemoryLeak() + { + var serviceProvider = new ServiceCollection(); + var container = new WindsorContainer(); + var f = new WindsorServiceProviderFactory(container); + f.CreateBuilder(serviceProvider); + + container.Register( + Component.For().ImplementedBy().LifeStyle.ScopedToNetServiceScope() + ); + + IServiceProvider sp = f.CreateServiceProvider(container); + + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + + TaskCompletionSource tcs = new TaskCompletionSource(); + + ThreadPool.UnsafeQueueUserWorkItem(state => + { + try + { + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + } + catch (Exception ex) + { + tcs.SetException(ex); + return; + } + tcs.SetResult(actualUserService); + }, null); + + // Wait for the work item to complete. + var task = tcs.Task; + IUserService result = await task; + Assert.NotNull(result); + + (sp as IDisposable)?.Dispose(); + container.Dispose(); + } + + [Fact] + public async Task Cannot_Resolve_LifestyleScopedToNetServiceScope_From_WindsorContainer() + { + var serviceProvider = new ServiceCollection(); + var container = new WindsorContainer(); + var f = new WindsorServiceProviderFactory(container); + f.CreateBuilder(serviceProvider); + + container.Register( + Component.For().ImplementedBy().LifeStyle.ScopedToNetServiceScope() + ); + + IServiceProvider sp = f.CreateServiceProvider(container); + + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + + TaskCompletionSource tcs = new TaskCompletionSource(); + + ThreadPool.UnsafeQueueUserWorkItem(state => + { + try + { + var actualUserService = container.Resolve(); + Assert.NotNull(actualUserService); + } + catch (Exception ex) + { + tcs.SetException(ex); + return; + } + tcs.SetResult(actualUserService); + }, null); + + // Wait for the work item to complete. + var ex = await Catches.ExceptionAsync(async () => + { + var task = tcs.Task; + IUserService result = await task; + Assert.NotNull(result); + }); + + Assert.NotNull(ex); + Assert.IsType(ex); + Assert.StartsWith("No scope available", ex.Message); + + (sp as IDisposable)?.Dispose(); + container.Dispose(); + } + + #endregion + + #region Transient + + /* + * Transient tests failure is questionable: + * - if you have a container you should be able to resolve transient without a scope, + * but they might be tracked by the container itself (or the IServiceProvider) + * - when windsor container is disposed all transient services are disposed as well + * - when a IServiceProvider is disposed all transient services (created by it) are disposed as well + * - problem is: we have una instance of a windsor container passed on to multiple instances of IServiceProvider + * one solution will be to tie the Transients to a scope, and the scope is tied to service provider + * when both of them are disposed, the transient services are disposed as well + */ + + [Fact] + public async Task Can_Resolve_LifestyleTransient_From_ServiceProvider() + { + var serviceProvider = new ServiceCollection(); + var container = new WindsorContainer(); + var f = new WindsorServiceProviderFactory(container); + f.CreateBuilder(serviceProvider); + + container.Register( + Component.For().ImplementedBy().LifestyleTransient() + ); + + IServiceProvider sp = f.CreateServiceProvider(container); + + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + + TaskCompletionSource tcs = new TaskCompletionSource(); + + ThreadPool.UnsafeQueueUserWorkItem(state => + { + try + { + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + } + catch (Exception ex) + { + tcs.SetException(ex); + return; + } + tcs.SetResult(actualUserService); + }, null); + + // Wait for the work item to complete. + var task = tcs.Task; + IUserService result = await task; + Assert.NotNull(result); + + (sp as IDisposable)?.Dispose(); + container.Dispose(); + } + + [Fact] + public async Task Can_Resolve_LifestyleTransient_From_WindsorContainer() + { + var serviceProvider = new ServiceCollection(); + var container = new WindsorContainer(); + var f = new WindsorServiceProviderFactory(container); + f.CreateBuilder(serviceProvider); + + container.Register( + Component.For().ImplementedBy().LifestyleTransient() + ); + + IServiceProvider sp = f.CreateServiceProvider(container); + + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + + TaskCompletionSource tcs = new TaskCompletionSource(); + + ThreadPool.UnsafeQueueUserWorkItem(state => + { + try + { + var actualUserService = container.Resolve(); + Assert.NotNull(actualUserService); + } + catch (Exception ex) + { + tcs.SetException(ex); + return; + } + tcs.SetResult(actualUserService); + }, null); + + // Wait for the work item to complete. + var task = tcs.Task; + IUserService result = await task; + Assert.NotNull(result); + + (sp as IDisposable)?.Dispose(); + container.Dispose(); + } + + /// + /// This test succeeds because WindsorScopedServiceProvider captured the root scope on creation + /// and forced it to be current before service resolution. + /// Transient is tied to the rootscope = potential memory leak. + /// + [Fact] + public async Task Can_Resolve_LifestyleNetTransient_From_ServiceProvider_MemoryLeak() + { + var serviceProvider = new ServiceCollection(); + var container = new WindsorContainer(); + var f = new WindsorServiceProviderFactory(container); + f.CreateBuilder(serviceProvider); + + container.Register( + Component.For().ImplementedBy().LifestyleNetTransient() + ); + + IServiceProvider sp = f.CreateServiceProvider(container); + + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + + TaskCompletionSource tcs = new TaskCompletionSource(); + + ThreadPool.UnsafeQueueUserWorkItem(state => + { + try + { + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + } + catch (Exception ex) + { + tcs.SetException(ex); + return; + } + tcs.SetResult(actualUserService); + }, null); + + // Wait for the work item to complete. + var task = tcs.Task; + IUserService result = await task; + Assert.NotNull(result); + + (sp as IDisposable)?.Dispose(); + container.Dispose(); + } + + [Fact] + public async Task Cannot_Resolve_LifestyleNetTransient_From_WindsorContainer_NoScopeAvailable() + { + var serviceProvider = new ServiceCollection(); + var container = new WindsorContainer(); + var f = new WindsorServiceProviderFactory(container); + f.CreateBuilder(serviceProvider); + + container.Register( + Component.For().ImplementedBy().LifestyleNetTransient() + ); + + IServiceProvider sp = f.CreateServiceProvider(container); + + var actualUserService = sp.GetService(); + Assert.NotNull(actualUserService); + + TaskCompletionSource tcs = new TaskCompletionSource(); + + ThreadPool.UnsafeQueueUserWorkItem(state => + { + try + { + var actualUserService = container.Resolve(); + Assert.NotNull(actualUserService); + } + catch (Exception ex) + { + tcs.SetException(ex); + return; + } + tcs.SetResult(actualUserService); + }, null); + + // Wait for the work item to complete. + var ex = await Catches.ExceptionAsync(async () => + { + var task = tcs.Task; + IUserService result = await task; + Assert.NotNull(result); + }); + + Assert.NotNull(ex); + Assert.IsType(ex); + Assert.StartsWith("No scope available", ex.Message); + + (sp as IDisposable)?.Dispose(); + container.Dispose(); + } + + #endregion + + /* + * Missing tests: we should also test what happens with injected IServiceProvider (what scope do they get?) + * Injected IServiceProvider might or might not have a scope (it depends on AsyncLocal value). + */ + } + + public static class Catches + { + public static Exception Exception(Action action) + { + try + { + action(); + } + catch (Exception e) + { + return e; + } + return null; + } + + public async static Task ExceptionAsync(Func func) + { + try + { + await func(); + } + catch (Exception e) + { + return e; + } + return null; + } + } +} \ No newline at end of file diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScope.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScope.cs deleted file mode 100644 index 95f29a07d3..0000000000 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScope.cs +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 2004-2020 Castle Project - http://www.castleproject.org/ -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -namespace Castle.Windsor.Extensions.DependencyInjection.Scope -{ - internal class ExtensionContainerRootScope : ExtensionContainerScopeBase - { - - public static ExtensionContainerRootScope BeginRootScope() - { - var scope = new ExtensionContainerRootScope(); - ExtensionContainerScopeCache.Current = scope; - return scope; - } - - internal override ExtensionContainerScopeBase RootScope => this; - } -} diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScopeAccessor.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScopeAccessor.cs index 25139efec9..8ebea69573 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScopeAccessor.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerRootScopeAccessor.cs @@ -23,7 +23,7 @@ internal class ExtensionContainerRootScopeAccessor : IScopeAccessor { public ILifetimeScope GetScope(CreationContext context) { - return ExtensionContainerScopeCache.Current.RootScope ?? throw new InvalidOperationException("No root scope available"); + return ExtensionContainerScopeCache.current.Value?.RootScope ?? throw new InvalidOperationException("No root scope available"); } public void Dispose() diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScope.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScope.cs index 2ff5a5c497..6df162c1f4 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScope.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ExtensionContainerScope.cs @@ -20,15 +20,12 @@ internal class ExtensionContainerScope : ExtensionContainerScopeBase protected ExtensionContainerScope() { - parent = ExtensionContainerScopeCache.Current; + parent = ExtensionContainerScopeCache.current.Value; } - internal override ExtensionContainerScopeBase RootScope { get; set; } - - - internal static ExtensionContainerScopeBase BeginScope() + internal static ExtensionContainerScopeBase BeginScope(ExtensionContainerScopeBase rootScope = null) { - var scope = new ExtensionContainerScope { RootScope = ExtensionContainerScopeCache.Current.RootScope }; + var scope = new ExtensionContainerScope { RootScope = rootScope }; ExtensionContainerScopeCache.Current = scope; return scope; } diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ForcedScope.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ForcedScope.cs index c9d41dfa66..b24ccfbd52 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ForcedScope.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/ForcedScope.cs @@ -25,13 +25,13 @@ internal class ForcedScope : IDisposable private readonly ExtensionContainerScopeBase previousScope; internal ForcedScope(ExtensionContainerScopeBase scope) { - previousScope = ExtensionContainerScopeCache.Current; + previousScope = ExtensionContainerScopeCache.current.Value; this.scope = scope; ExtensionContainerScopeCache.Current = scope; } public void Dispose() { - if(ExtensionContainerScopeCache.Current != scope) return; + if(ExtensionContainerScopeCache.Current != scope || previousScope == null) return; ExtensionContainerScopeCache.Current = previousScope; } } diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/WindsorScopeFactory.cs b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/WindsorScopeFactory.cs index 0686ea95a6..5545560b33 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/Scope/WindsorScopeFactory.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/Scope/WindsorScopeFactory.cs @@ -24,15 +24,17 @@ namespace Castle.Windsor.Extensions.DependencyInjection.Scope internal class WindsorScopeFactory : IServiceScopeFactory { private readonly IWindsorContainer scopeFactoryContainer; - - public WindsorScopeFactory(IWindsorContainer container) + private readonly ExtensionContainerScopeBase rootScope; + + public WindsorScopeFactory(IWindsorContainer container, ExtensionContainerScopeBase rootScope) { scopeFactoryContainer = container; + this.rootScope = rootScope; } public IServiceScope CreateScope() { - var scope = ExtensionContainerScope.BeginScope(); + var scope = ExtensionContainerScope.BeginScope(rootScope); //since WindsorServiceProvider is scoped, this gives us new instance var provider = scopeFactoryContainer.Resolve(); diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs index 4e61f2f765..0442753462 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorScopedServiceProvider.cs @@ -55,7 +55,7 @@ public object GetRequiredService(Type serviceType) public void Dispose() { - if (!(scope is ExtensionContainerRootScope)) return; + if (scope != scope.RootScope) return; if (disposing) return; disposing = true; var disposableScope = scope as IDisposable; diff --git a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorServiceProviderFactoryBase.cs b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorServiceProviderFactoryBase.cs index 21363c4fd9..0a4efca449 100644 --- a/src/Castle.Windsor.Extensions.DependencyInjection/WindsorServiceProviderFactoryBase.cs +++ b/src/Castle.Windsor.Extensions.DependencyInjection/WindsorServiceProviderFactoryBase.cs @@ -27,7 +27,7 @@ namespace Castle.Windsor.Extensions.DependencyInjection public abstract class WindsorServiceProviderFactoryBase : IServiceProviderFactory { - internal ExtensionContainerRootScope rootScope; + internal ExtensionContainerScopeBase rootScope; protected IWindsorContainer rootContainer; public virtual IWindsorContainer Container => rootContainer; @@ -44,7 +44,8 @@ public virtual IServiceProvider CreateServiceProvider(IWindsorContainer containe protected virtual void CreateRootScope() { - rootScope = ExtensionContainerRootScope.BeginRootScope(); + rootScope = ExtensionContainerScope.BeginScope(); + rootScope.RootScope = rootScope; } protected virtual void CreateRootContainer() @@ -111,7 +112,7 @@ protected virtual void RegisterFactories(IWindsorContainer container) container.Register(Component .For() .ImplementedBy() - .DependsOn(Dependency.OnValue(rootScope)) + .DependsOn(Dependency.OnValue(rootScope)) .LifestyleSingleton(), Component .For>()