Skip to content

Regression in autofac 9.3.3 resolution #1503

Closed

Description

@hjalle

Describe the Bug

Upgrading to Autofac 9.3.3 causes System.InvalidOperationException: Component pipeline has already been built, and cannot be modified. when resolving an open generic component registered with multiple services (e.g. .As(typeof(IFirstService<>)).As(typeof(ISecondService<>))) if Autofac. Extensions.DependencyInjection (or any listener that attaches to PipelineBuilding via ComponentRegistryBuilder.Registered) is used.

I believe the regressions source is in commit 6d74fde (PR #1499 / issue #1465). The pipeline is pre-built

   if (additionalInfo.IsSourceQueued(source))
   {
       // Recording the implementation publishes it to whichever thread drains this source next,
       // and that thread resolves through it, so its pipeline has to be built first. Building
       // is idempotent, so the call AddRegistration makes later does nothing.
       if (_ephemeralServiceInfo is null)
       {
           provided.BuildResolvePipeline(this);
       }

       DeferSourceImplementation(additionalInfo, source, provided);
   }

and the assumption was that it is idempotent, however that doesn't seem to be the case since somewhere along the line ComponentRegistration.PipelineBuilding throws when IsPipelineBuilt == true.

Steps to Reproduce

The following unit test shows the regression

    [Fact]
    public void MultiServiceOpenGenericCanAttachToPipelineBuildingViaRegistryEvent()
    {
        var builder = new ContainerBuilder();
        builder
            .RegisterGeneric(typeof(BothServices<>))
            .As(typeof(IFirstService<>))
            .As(typeof(ISecondService<>));

        builder.ComponentRegistryBuilder.Registered += (sender, e) =>
        {
            e.ComponentRegistration.PipelineBuilding += (sender, pipeline) => { };
        };

        using var container = builder.Build();

        Assert.NotNull(container.Resolve<IFirstService<int>>());
        Assert.NotNull(container.Resolve<ISecondService<int>>());
    }

Expected Behavior

Should resolve the services without throwing.

Exception with Stack Trace

System.InvalidOperationException: Component pipeline has already been built, and cannot be modified.
       at Autofac.Core.Registration.ComponentRegistration.add_PipelineBuilding(EventHandler`1 value)
       at Autofac.Extensions.DependencyInjection.AutofacRegistration.AddFromKeyedServiceParameterMiddleware(Object sender, ComponentRegisteredEventArgs
  e)
       at Autofac.Core.Registration.ComponentRegistryBuilder.OnRegistered(Object sender, IComponentRegistration e)
       at Autofac.Core.Registration.DefaultRegisteredServicesTracker.AddRegistration(IComponentRegistration registration, Boolean preserveDefaults,
  IRegistrationSource originatingSource)
       at Autofac.Core.Registration.DefaultRegisteredServicesTracker.PopulateServiceInfo(Service service, ServiceRegistrationInfo info, Boolean
  isScopeIsolatedService)

Dependency Versions

Autofac: 9.3.3 (works on 9.3.2)

Additional Info

Activity

  1. nietras commented on Sep 18, 2026

    @nietras

    We have observed this too.

  2. changed the title [-]Regression in autofac 9.3.3 reso[/-] [+]Regression in autofac 9.3.3 resolution[/+] on Sep 18, 2026
  3. self-assigned this
    on Sep 18, 2026
  4. tillig commented on Sep 18, 2026

    @tillig
    Member

    Thanks for the report, and for pinning it to the exact line — your diagnosis is correct.

    AddRegistration raises Registered before it builds the pipeline, precisely so listeners can attach to PipelineBuilding. Holding the implementation for the second service built it before that point, so by the time Registered fired the pipeline already existed and the add accessor threw. BuildResolvePipeline is idempotent, as the comment said, but that was the wrong property to rely on — what matters is the ordering around it, which I broke.

    Fix is up in #1504, with your repro as a regression test. It removes the early build rather than reordering it, because the window that build was guarding predates the change: 9.3.2 throws the same Component pipeline has not yet been built under concurrent first resolution, so removing it restores 9.3.2's ordering rather than trading one bug for another. That residual concurrent race is tracked separately in #1500.

    Sorry for the breakage.

  5. hjalle commented on Sep 18, 2026

    @hjalle
    Author

    Thanks for the quick fix @tillig

  6. tillig commented on Sep 18, 2026

    @tillig
    Member

    Released just now in 9.3.4.

  7. hjalle commented on Sep 18, 2026

    @hjalle
    Author

    Works for me now, thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions