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

Undo the change to make the IServiceProvider in Configure scoped #4148

Closed
davidfowl opened this issue Jun 6, 2018 · 44 comments
Closed

Undo the change to make the IServiceProvider in Configure scoped #4148

davidfowl opened this issue Jun 6, 2018 · 44 comments
Assignees
Labels
area-hosting Includes Hosting area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions bug This issue describes a behavior which is not expected - a bug.

Comments

@davidfowl
Copy link
Member

Turns out people were capturing the IServiceProvider here to store in statics. Initially this change was made to make running EF migrations easier but then that pattern was moved into Program.Main. We've had several reports about this breaking things so we should consider reverting it.

Here's the initial change:
aspnet/Hosting#1106

We can consider this for a later patch as well.

@louislewis2
Copy link

Yes please, this has broken a key part of our system.

@davidfowl
Copy link
Member Author

The workaround is trivial. Just FYI, assign app.ApplicationServices instead of injection IServicerProvider into Configure.

@louislewis2
Copy link

Sorry David, I do not quite follow the workaround. Is there a change required in my startup file, or should I me changing away from injecting IServiceProvider into those background jobs, and use something else?

@davidfowl
Copy link
Member Author

davidfowl commented Jun 6, 2018

Sorry David, I do not quite follow the workaround. Is there a change required in my startup file, or should I me changing away from injecting IServiceProvider into those background jobs, and use something else?

Today I assume you are doing this:

public void Configure(IServiceProvider provider)
{
    new BackgroundJob(provider).Run();  // Something like this?
}

There's like various ways to solve it:

Using IHostedService https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1

This is the preferred way.

Using DI and Configure:

public void Configure(BackgroundJob job)
{
    job.Run(); 
}

In this model you add BackgroundJob to the container and take services into the ctor.

Using ApplicationServices directly in Configure:

public void Configure(IApplicationBuilder app)
{
    new BackgroundJob(app.ApplicationServices).Run();  // Something like this?
}

@louislewis2
Copy link

louislewis2 commented Jun 6, 2018

Not quite,

The system runs on a super simple CQRS implementation.
At startup, various handles are registered dynamically into the dependency injection container.

The specific part that breaks is that CommandResultReactors get run the background, and as I mentioned it just worked.

So instead of manually calling the constructor as shown above, I rely in the DI container to do its magic.
On this line, you can see where the handlers are being resolved.

https://github.com/louislewis2/innovation/blob/master/src/Innovation.ServiceBus.InProcess/Dispatching/Dispatcher.cs#L477

On this line, you can see where the dispatcher starts running through the reactors.
https://github.com/louislewis2/innovation/blob/master/src/Innovation.ServiceBus.InProcess/Dispatching/Dispatcher.cs#L250

On this line, you can see where the handle methods are actually called.
https://github.com/louislewis2/innovation/blob/master/src/Innovation.ServiceBus.InProcess/Dispatching/Dispatcher.cs#L434

When running in the context of a web application, and requests are present. The exception will be thrown on this line.
https://github.com/louislewis2/innovation/blob/master/test/Innovation.SampleApi.Consumer/Handlers/Customers/Reactors/InsertCustomerCommandResultReactor.cs#L38

Because the reactors run behind the scenes, I always in them would request that a IServiceProvider is injected, so that I can create a scope and request things like database contexts from it safely.

It is there, that it now breaks with the object disposed exception.

I hope this helps explain the problem better, and hopefully why the sample code does not help me out to the point where I can implement a work around.

@davidfowl
Copy link
Member Author

I hope this helps explain the problem better, and hopefully why the sample code does not help me out to the point where I can implement a work around.

Nope, sorry, I don't see how this breaks. What I need to see is what code activates your types. Where does that happen? If my sample code doesn't help you then I don't see how fixing this issue helps you.

@louislewis2
Copy link

The best way I can explain is that the dependency injection container, activate the types.
The types are discovered at application startup, by the simple class called runtime..

https://github.com/louislewis2/innovation/blob/master/src/Innovation.ServiceBus.InProcess/InnovationRuntime.cs#L231-L241

When a type is required, I simply request it from the di container.

https://github.com/louislewis2/innovation/blob/master/src/Innovation.ServiceBus.InProcess/Dispatching/Dispatcher.cs#L479

Nowhere is the constructor manually called, its all di dependent.

@davidfowl
Copy link
Member Author

Nowhere is the constructor manually called, its all di dependent.

Sure.

This is getting much closer, but it's not all there yet. Where do you resolve the Dispatcher in the web case. I just need to see the composition root not all of the implementation details.

@louislewis2
Copy link

In a base class, inheriting from controller I resolve it like this.

private IDispatcher GetDispatcher()
{
    return this.HttpContext.RequestServices.GetRequiredService<IDispatcher>();
}

@davidfowl
Copy link
Member Author

Great (I also found that code)! I have no idea how this specific change affects any of your code. If something is getting disposed it's because of something else. HttpContext.RequestServices is the scoped service provider and it goes away after the request is done.

@louislewis2
Copy link

Well, the application has been in production since January and has been working without hassles.
The only change, is the update to 2.1 and then we started finding this issue. I also ended up being puzzled, and then started looking online when I found the previous issue, where this issue started from. 🤷‍♂️

@davidfowl
Copy link
Member Author

I'm not saying its not another issue or change, but it certainly isn't this one. Are you running things outside of the request scope that were started during a request scope?

@louislewis2
Copy link

The reactors could possibly still be running after the request has finished, possibly.
In the code base, they are used to add the result of a command and other key bits of information to a user interactions table.

@davidfowl
Copy link
Member Author

My guess is that you were getting lucky before. The timing of when the per request service provider gets disposed changed in 2.1 but it's still "after the request is over". That has nothing to do with this issue. If you resolve a scoped service using RequestServices, it will get disposed when the request is over. It seems as though you are doing that here.

@louislewis2
Copy link

David, that explains it better than anything else. I am currently working making changes in code to fix the issue that we currently have in the code base, simply removing the reactors for now. Later on I will need to come up with a better fix.

Thanks for the time you put into this, it is greatly appreciated. 👍

@davidfowl
Copy link
Member Author

/cc @muratg @DamianEdwards I'd like to get this patched in 2.1.x

@Eilon
Copy link
Member

Eilon commented Jul 13, 2018

Moving to 2.1.4 because it's kind of too late for 2.1.3.

@muratg
Copy link
Contributor

muratg commented Aug 8, 2018

@davidfowl please send the shiproom template for a patch. If it meets the bar, it'll likely be 2.1.5.

@crtjr64
Copy link

crtjr64 commented Aug 10, 2018

@davidfowl Does My issue relate?
1.Its a very simple logger that saves errors and soon audit data to our database
2. It uses a dBContext and EF created in startup.

            services.AddDbContext<PcoLoggerContext>(options =>
                options.UseSqlServer(Configuration.GetConnectionString("PCOConnection")));

services.AddScoped(provider => provider.GetService());

  1. Its declared in controller (tried in startup but it breaks)

Logger.Setcontext(db);

and initializes a context where it is appears similar to IServiceProvider. Its static so I can use it without instantiation throughout the app to quickly and securely log away issues and this will also be used for the audit process as well.

   private static DbContext db; 

    public static void Setcontext(DbContext cnn)
    {
        db = cnn;
    }

Finally the db,Add(log); is where I get same error as in this issue.

        lock (db)
        {
            log.LogMsg = GetMsg(ex, msg);

            **db.Add(log);**

        }

Is this the same issue or would moving logging into the dependency injection ins startup (which appears similar to the above workaround) resolve my issue.

@davidfowl
Copy link
Member Author

davidfowl commented Aug 12, 2018

Where are you calling SetContext? and where are you using the context? Is it outside of a request thread?

@ccccccmd
Copy link

i meet the same exception , but i am not sure it's the same problem.

sutartup config

  public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime lifetime,
        IBusControl bus)
    {
        Extensions.ServiceLocator.Instance = app.ApplicationServices;
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
        }
        app.UseMvc();
        lifetime.ApplicationStarted.Register(() => { bus.StartAsync(); });
    }

My consumer

public class LotteryCompletedHandler : IConsumer<LotteryCompleted>
{

    public async Task Consume(ConsumeContext<LotteryCompleted> context)
    {
        using (var serviceScope = ServiceLocator.Instance.CreateScope())
        {
            var repository = serviceScope.ServiceProvider.GetService<ILotteryRepository>();
			
			..insert to db
		}
	}
}

Exception:

at Microsoft.Extensions.DependencyInjection.ServiceLookup.ThrowHelper.ThrowObjectDisposedException() at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope) at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType) at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType) at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType) at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)

@davidfowl
Copy link
Member Author

Looks unrelated. This line should be fine:

Extensions.ServiceLocator.Instance = app.ApplicationServices;

@ccccccmd
Copy link

ok, i only got one this exception log in last 3 month, i'll check it for more information.
thanks !!

@muratg
Copy link
Contributor

muratg commented Aug 30, 2018

@davidfowl if you'd like to bring this to shiproom for a patch release, please fill out the template. Thanks!

@davidfowl davidfowl self-assigned this Sep 8, 2018
@muratg
Copy link
Contributor

muratg commented Oct 22, 2018

@davidfowl Are you doing this this week? If not, could you please update the milestone?

@davidfowl
Copy link
Member Author

Moved

@nwoolls
Copy link

nwoolls commented Nov 12, 2018

@davidfowl what is the recommended work-around for this if IServiceProvider is being injected into controllers, not Configure()? This works fine in 2.0, but in 2.1 we are getting the error "Cannot access a disposed object".

@tidusjar
Copy link

@nwoolls I'd question why you are doing that and not just inject the dependencies that you require.

But if you want a work around the HttpContext holds an instance of IServiceProvider that you would be able to use

@davidfowl
Copy link
Member Author

@nwoolls This doesn't affect controller injection. You probably always had that bug if it worked in 2.0 and not 2.1. You're likely extending the lifetime of the scoped service provider by mistake.

@nwoolls
Copy link

nwoolls commented Nov 12, 2018

@tidusjar I have the same question out to our devs - it is being used as you say, to resolve dependencies instead of using straight up DI. My feel is that the right solution is to use DI directly instead of injecting IServiceProvider.

@nwoolls
Copy link

nwoolls commented Nov 12, 2018

@davidfowl not disagreeing, but this is pretty easy for us to reproduce with 2.1 and we cannot with 2.0.

@davidfowl
Copy link
Member Author

What exactly are you doing?

@nwoolls
Copy link

nwoolls commented Nov 12, 2018

@davidfowl the current code injects IServiceProvider into the controller. Then later passes that reference on to a (static) method that calls e.g. var myDependency = provider.GetService(typeof(IDependency)) as IDependency; ISTM we should / could inject IDependency instead, re-enforced by @tidusjar's comment.

@davidfowl
Copy link
Member Author

Then later passes that reference on to a (static) method that calls e.g. var myDependency = provider.GetService(typeof(IDependency)) as IDependency; ISTM we should / could inject IDependency instead, re-enforced by @tidusjar's comment.

What's the lifetime of IDependency? Transient? Scoped? Is IDependency only used in the static method or is stored somewhere and used later (hence the after disposal).

@nwoolls
Copy link

nwoolls commented Nov 12, 2018

What's the lifetime of IDependency? Transient? Scoped?

@davidfowl Transient

Is IDependency only used in the static method or is stored somewhere and used later (hence the after disposal).

It is only used in the static method. It's a static class with helper methods - no state.

@davidfowl
Copy link
Member Author

Where are you using it? In the controller method ?

@nwoolls
Copy link

nwoolls commented Nov 13, 2018

@davidfowl something like this - I'll work on a sample that reproduces it.

Startup.cs

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IDependency, Dependency>();
}

Controller.cs

private readonly IServiceProvider serviceProvider;
public Controller(IServiceProvider serviceProvider)
{
    serviceProvider = serviceProvider;
}

[HttpPost]
public virtual IActionResult SomePost()
{
    Task.Run(() => StaticClass.StaticMethod(serviceProvider));
    ...
}

StaticClass.cs

public static async Task StaticMethod(IServiceProvider provider)
{
    var dependency = provider.GetService(typeof(IDependency)) as Dependency;
    ...
}

@nwoolls
Copy link

nwoolls commented Nov 13, 2018

@davidfowl thanks - we'll try those guidelines and see what shakes loose.

@davidfowl
Copy link
Member Author

I assume you're intentionally doing a fire and forget here?

@nwoolls
Copy link

nwoolls commented Nov 13, 2018

@davidfowl yes that is intentional.

@nwoolls
Copy link

nwoolls commented Nov 20, 2018

@davidfowl confirmed - the guidance you linked did the trick for eliminating the "Cannot access a disposed object" error on .NET Core 2.1. Thank you!

@natemcmaster natemcmaster transferred this issue from aspnet/Hosting Nov 20, 2018
@natemcmaster natemcmaster added the bug This issue describes a behavior which is not expected - a bug. label Nov 20, 2018
@natemcmaster natemcmaster added this to the 3.0.0-preview2 milestone Nov 20, 2018
@muratg
Copy link
Contributor

muratg commented Jan 9, 2019

@davidfowl Do you expect to get this in in a week or so?

@davidfowl
Copy link
Member Author

This issue has become irrelevant in 3.0 with the move to generic host. I'm closing this issue because we won't be back porting it. Most of the other issues here don't have much to do with this particular change.

@ghost ghost locked as resolved and limited conversation to collaborators Dec 4, 2019
@amcasey amcasey added the area-hosting Includes Hosting label Jun 1, 2023
@amcasey amcasey added area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions and removed area-runtime labels Aug 24, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-hosting Includes Hosting area-networking Includes servers, yarp, json patch, bedrock, websockets, http client factory, and http abstractions bug This issue describes a behavior which is not expected - a bug.
Projects
None yet
Development

No branches or pull requests