Windsor: NET 6.0 ForcedScope Failed with "No Available Scope" on GET request

First of all, great work all of you. I’m trying to port some project of mine to NET 6.0 and AspNetCore, which are new to me. I’ve downloaded master, and using the Castle.Windsor.Extensions.Dependency.Injection. When I run a simple Hello World I’m facing a “No Scope Available exception”.

My initialization code is pretty simple, just trying to put all together:

private static void Main(string[] args)
{
    IApplicationInitializer initializer;
    var builder = WebApplication.CreateBuilder(args);
    builder.Host.UseTaxologicCoreInitialization("WebPortal", out initializer);
    initializer.Initialize();

    var app = builder.Build();

    app.MapGet("/", () => "Hello World!");

    app.Run();
}

IAplicationInitializer implementation does all the windsor registration.

UseTaxologicCoreInitialization is just

    public static IHostBuilder UseTaxologicCoreInitialization(this IHostBuilder hostBuilder, string _webSiteName, out IApplicationInitializer initializer)
    {
        var ioc = new WindsorContainer();
        var f = new WindsorServiceProviderFactory(ioc);
        ioc.Register(Component.For<IHostBuilder>().Instance(hostBuilder));

        initializer = new WindsorApplicationInitializer(ioc, new AppPathFinder(), _webSiteName);

        return hostBuilder.UseWindsorContainerServiceProvider(f);
    }

If I run this code, it fails at app.Run() when it gets it first request to / with an exception “No Scope Available” at line 27 of ExtensionContainerScopeCache , that comes fron ForcedScope constructor.

For what I can infere from the code, as I’m no expert in Castle internals, the ForcedScope stashes the current Scope, set as current the passed one and restores it on ForcedSope disposal. The problem is that when there is no current scope in the cache it fails when tries to get it to set previousScope in

internal ForcedScope(ExtensionContainerScopeBase scope)
		{
			previousScope = ExtensionContainerScopeCache.Current;
			this.scope = scope;
			ExtensionContainerScopeCache.Current = scope;
		}

I did a couple of small changes to prevent that, and the exception has gone. These are the changes so you can evaluate them and include if you think that has value:

in ExtensionContainerScopeCache I added a getter to know if Current has value

internal static bool hasContext
		{
			get => current.Value is not null;
		}

in ForcedScope I changed the Constructor with this

internal ForcedScope(ExtensionContainerScopeBase scope)
		{
			if (ExtensionContainerScopeCache.hasContext)
				previousScope = ExtensionContainerScopeCache.Current;
			else
				previousScope = null;
			this.scope = scope;
			ExtensionContainerScopeCache.Current = scope;
		}

It just prevent the exception if there is no current scope

Looking forward to hear your comments about the changes, or any comments on how to prevent the exception by other means

Thanks in advance

Fernando

About this issue

  • Original URL
  • State: open
  • Created a year ago
  • Reactions: 1
  • Comments: 17 (7 by maintainers)

Commits related to this issue

Most upvoted comments

I was involved with the original change for #577 (initial commit was a minimal change in Extensions.DI and was related to the root scope, not clear what changes were done afterwards) and will look at.

If someone can add a unit test that exposes the bug, that would be super useful!

Here are a couple more cases that can fail:

		[Fact]
		public async Task Can_Resolve_From_CastleWindsor() {

			var serviceProvider = new ServiceCollection();
			var container = new WindsorContainer();
			var f = new WindsorServiceProviderFactory(container);
			f.CreateBuilder(serviceProvider);

			container.Register(
				// Component.For<IUserService>().ImplementedBy<UserService>().LifestyleNetTransient(),
				Classes.FromThisAssembly().BasedOn<IUserService>().WithServiceAllInterfaces().LifestyleNetStatic()
				);

			IServiceProvider sp = f.CreateServiceProvider(container);

			IUserService actualUserService;
			actualUserService = container.Resolve<IUserService>();
			Assert.NotNull(actualUserService);

			TaskCompletionSource<IUserService> tcs = new TaskCompletionSource<IUserService>();

			ThreadPool.UnsafeQueueUserWorkItem(state => {
				IUserService actualUserService = null;
				try {
					// resolving (with the underlying Castle Windsor, not using Service Provider) with a lifecycle that has an
                                        // accessor that uses something that is AsyncLocal might be troublesome.
                                        // the custom lifecycle accessor will kicks in, but noone assigns the Current scope (which is uninitialized)
					actualUserService = container.Resolve<IUserService>();
					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);
		}

		[Fact]
		public async Task Can_Resolve_From_ServiceProvider_cretaed_in_UnsafeQueueUserWorkItem() {

			var serviceProvider = new ServiceCollection();
			var container = new WindsorContainer();
			var f = new WindsorServiceProviderFactory(container);
			f.CreateBuilder(serviceProvider);

			container.Register(
				// Component.For<IUserService>().ImplementedBy<UserService>().LifestyleNetTransient(),
				Classes.FromThisAssembly().BasedOn<IUserService>().WithServiceAllInterfaces().LifestyleNetStatic()
				);

			TaskCompletionSource<IUserService> tcs = new TaskCompletionSource<IUserService>();

			ThreadPool.UnsafeQueueUserWorkItem(state => {
				IUserService actualUserService = null;
				try {
					// creating a service provider here will be troublesome too
					IServiceProvider sp = f.CreateServiceProvider(container);

					actualUserService = sp.GetService<IUserService>();
					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);
		}

Trying to resolve (with the underlying Castle Windsor) something that has a lifestyle that kicks in any scope accessor will result in the usual AsyncLocal problem.

It’s a pretty ugly case but in my application it happens because of different “adapters” for dependency injection that use the same Castle Windsor container (AspNetCore and Akka, to be more specific, but may happens in many other scenario).

Trying to create (or resolve) a Service Provider in an Unsafe Thread has the same problem.

EDIT: the typical problematic scenario is like this:

  • configure an AspNet application
  • use the Castle Windsor adapter to “replace” the standard Ms DI.
  • configure AspNet to use logging and any other standard feature.
  • register “something” that depends on the Ms extension logger.
  • start a background unsafe thread (like the ones of AspNet Core)
  • try to resolve “something” from the Castle Winsdor container (not from the adapted IServiceProvider) and you’ll get the error
  • the problem happens because the standard aspnet core services will be registered in castle with the scoped lifesyles that use ExtensionContainerRootScopeAccessor or ExtensionContainerScopeAccessor

Hi, I already had a look at it this weekend using the original commits on the branch (commits were squashed before merge) and from my tests it seems the initial commits were fine and the bug was introduced in later commits (as part of the same change). #577 was a fix for #563 and I’d really like to implement a fix instead of reverting everything.

My next steps are to first add an extra unit test. I could reproduce the error when actually setting up and running a minimal Asp.Net Core app (as reported by @fsalas ), but not without Asp.Net Core. I will add a test that uses the Asp.Net Core testing facility and see if that works for the test case. Then I will work on the fix itself and use the new test as a reference.

Is there a specific reason you need to upgrade to 6.0, or can you hold off for now and stick with 5.1.1/5.1.2? I plan to have a fix for this by the end of the coming weekend. I use this extension myself on several applications that are in active development, so it’s in my own interest to have it fixed sooner rather than later.

Do you mean that it’s not considered as a bug? Weird…

No, I mean this is an open source project that needs contributors.

Ok, understood. We have reverted our code to 5.1.2 but as soon as I have a time slot, I will do my best to help on this one.

Do you mean that it’s not considered as a bug? Weird…

No, I mean this is an open source project that needs contributors.

Hello all

Having similar problem trying to migrate working app from Windsor 5.1.2 -> 6.0. App also targeted to .net6 (same behavior with .net7) Exception happens on 1st request. image Previously in code we have UseWindsorContainerServiceProvider call image According to Windsor src this call must initialize ExtensionContainerScopeCache.Current, from where exception is thrown. But it remains uninitialized. Current is stored in AsyncLocal internal static readonly AsyncLocal<ExtensionContainerScopeBase> current = new AsyncLocal<ExtensionContainerScopeBase>();

So it can be different Async context on initialization and 1st request

But why all works on previous version and do not work on Windsor 6?