Rebus with Simple Injector breaking change

669 Views Asked by At

Scenario:

  • Web API application using multiple satellite library
  • .NET Framework 4.6
  • Simple Injector
  • Rebus
  • Rebus.AzureServiceBus
  • Rebus.SimpleInjector

In my application there are multiple satellite library most of which have a class implementing SimpleInjector IPackage interface, that is, to group container registrations in different libraries. Those packages get registered at Startup

container.RegisterPackages(AppDomain.CurrentDomain.GetAssemblies());

One of the packages contains Rebus configuration

IContainerAdapter adapter = new SimpleInjectorContainerAdapter( container );
Configure.With( adapter )
            .Transport( t => t.UseAzureServiceBusAsOneWayClient( connectionString, AzureServiceBusMode.Standard ) )
            .Routing( r =>
            r.TypeBased()
                .MapAssemblyOf<TransactionCreated>( "MyQueue" )
            )
            .Options( oc => {
                oc.SetNumberOfWorkers( 1 );
            } )
            .Start();

This morning we have upgraded Rebus packages to the following versions:

  • Rebus 4.0.1
  • Rebus.AzureServiceBus 4.0.0
  • Rebus.SimpleInjector 4.0.0

After the upgrade the system stopped working and now we get the following error

The container can't be changed after the first call to GetInstance, GetAllInstances and Verify. Please see https://simpleinjector.org/locked to understand why the container is locked. The following stack trace describes the location where the container was locked:

By debugging on the code we can see that another package get registered after the one that register rebus and so the reason of the error. We can confirm that no modification have been done at the code and it was correctly working as expected with the previous releases.

I can also confirm that by downgrading to these versions the problem disappear

  • Rebus 3.1.5
  • Rebus.AzureServiceBus 3.0.0
  • Rebus.SimpleInjector 3.0.0

Any suggestion?

EDIT: As per @Steven request I am adding the full stack trace

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.InvalidOperationException: The container can't be changed after the first call to GetInstance, GetAllInstances and Verify. Please see https://simpleinjector.org/locked to understand why the container is locked. The following stack trace describes the location where the container was locked:

at Rebus.SimpleInjector.SimpleInjectorContainerAdapter.SetBus(IBus bus)   
at Rebus.Config.RebusConfigurer.Start()
at XXX.YYY.EndpointEvents.Producer.IOC.EndpointEventsProducerModule.RegisterServices(Container container) 
at SimpleInjector.PackageExtensions.RegisterPackages(Container container, IEnumerable assemblies)
at XXX.YYY.WebAPI.SimpleInjectorWebApiInitializer.InitializeContainer(Container container) 
at XXX.YYY.WebAPI.SimpleInjectorWebApiInitializer.Initialize() 
at XXX.YYY.WebAPI.Startup.Configuration(IAppBuilder app)
at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor)
at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(Object obj, Object[] parameters, Object[] arguments)
at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
at Owin.Loader.DefaultLoader.<>c__DisplayClass12.<MakeDelegate>b__b(IAppBuilder builder)
at Owin.Loader.DefaultLoader.<>c__DisplayClass1<LoadImplementation>b__0(IAppBuilder builder) 
at Microsoft.Owin.Host.SystemWeb.OwinHttpModule.<>c__DisplayClass2.<InitializeBlueprint>b__0(IAppBuilder builder) 
at Microsoft.Owin.Host.SystemWeb.OwinAppContext.Initialize(Action startup)
at Microsoft.Owin.Host.SystemWeb.OwinBuilder.Build(Action startup)
at Microsoft.Owin.Host.SystemWeb.OwinHttpModule.InitializeBlueprint()  
at System.Threading.LazyInitializer.EnsureInitializedCore[T](T& target, Boolean& initialized, Object& syncLock, Func valueFactory)  
at Microsoft.Owin.Host.SystemWeb.OwinHttpModule.Init(HttpApplication context)  
at System.Web.HttpApplication.RegisterEventSubscriptionsWithIIS(IntPtr appContext, HttpContext context, MethodInfo[] handlers)
at System.Web.HttpApplication.InitSpecial(HttpApplicationState state, MethodInfo[] handlers, IntPtr appContext, HttpContext context)
at System.Web.HttpApplicationFactory.GetSpecialApplicationInstance(IntPtr appContext, HttpContext context)
at System.Web.Hosting.PipelineRuntime.InitializeApplication(IntPtr appContext)
3

There are 3 best solutions below

1
On BEST ANSWER

Sorry for being to late to react to this question :)

I finally had the time to understand how SimpleInjector wants container registrations to be made, and understand how the Rebus configuration API could somehow be bent into working that way.

It turned out that the usual

Configure.With(new MyFavoriteContainerAdapter(container))
    .(...)
    .Start();

spell had to be moved somehow into a Func<IBus>, making it possible to finish ALL Rebus-relevant registrations (as well as your own) before actually starting the bus.

The result (which is out in Rebus.SimpleInjector 5.0.0-b01 on NuGet.org just now) is this API:

public class RebusPackage : IPackage
{
    public void RegisterServices(Container container)
    {
        Console.WriteLine("Calling RebusPackage");

        container.ConfigureRebus(
            configurer => configurer
                .Transport(t => t.UseInMemoryTransport(new InMemNetwork(), "test"))
                .Start()
        );

    }
}

which is how it can look if you are using SimpleInjector.Packaging, or simply

container.ConfigureRebus(
    configurer => configurer
        .Transport(t => t.UseInMemoryTransport(new InMemNetwork(), "test"))
        .Start()
);

if all you have is a SimpleInjector container.

When you think it is time to start the bus, you either

container.StartBus();

or you wait until IBus is resolved.

2
On

Your EndpointEventsProducerModule is calling RebusConfigurer.Start. Start resolves IBus from the container.

Since modules can be be called in any order, you should only make registrations in modules. Remove the Start call and call it after your call to container.Verify().

0
On

As you can see here there is new coded added to ensure the bus is disposed, that introduced a problem, your code seems to be fine, just need to wait the fix. Below the new lines

 +            // cheat and activate the IBus singleton behind the scenes, thus ensuring that the container will dispose it when it is time
 +            var registration = _container.GetRegistration(typeof(IBus));
 +  +            registration.GetInstance();
 +