seesharper / LightInject

An ultra lightweight IoC container
http://www.lightinject.net
MIT License
621 stars 120 forks source link

Implementing PerJobLifetime() for use with Hangfire #416

Open vegar opened 6 years ago

vegar commented 6 years ago

I'm trying to understand scope management in LightInject by looking at the scope manger from the LightInject.Web project. The goal is to ensure that each job inside of Hangfire gets their own instance of services.

As far as I have found, there is no global state available in hangfire - meaning that there is no Hangfire.CurrentJob or anything similar to the HttpContext.Current for web applications. What do exist, though, is a IServerFilter with OnPerforming and OnPerformed methods, both being passed a PerformContext-object with a dictionary where key-value pairs can be added.

Should I just use the PerScopeLifetime-class and begin new scopes in OnPerforming and end them in OnPerformed? Or would it make more sense to implement a PerJobLifetime similar to the PerRequestLifeTime with its own scope manager and everything?

seesharper commented 6 years ago

I am not familiar with Hangfire, but I would most certainly start with managing the scopes inside OnPerforming and OnPerformed and see where that takes you

vegar commented 6 years ago

It took me just a short step.

I'm having huge trouble with threads currently. Seems like async/await breaks the scope. Both for PerRequestLifetime()-services on the webside, and PerScopeLifetime() on the background worker.

I'm using MediatR alot, and have opened an issue there to see if Bogard is able to explain what's going on.

Anyway, 'mid-request', Lightinject throws exception telling me there is no request scope available, and on the background worker, I get new instances for services where I expected to receive same instance as long as I haven't ended the scope.

😢

vegar commented 6 years ago

From my IoC config:

var container =  new ServiceContainer {
  ScopeManagerProvider = new PerLogicalCallContextScopeManagerProvider()
}; 
container.Register<IPrincipalProvider, HangfireJobPrincipalProvider>(new PerScopeLifetime()); 

The IPrincipalProvider has methods for setting and getting current user.

In my IServerFilter, I have code like this: (passing the container to the constructor of the filter...)

public void OnPerforming(PerformingContext filterContext) 
{ 
    var scope = _container.BeginScope(); 
    filterContext.Items[key] = scope;

    var user = filterContext.GetJobParameter<Identity>("User"); 
    if (user != null) {
       var p = _container.GetInstance<IPrincipalProvider>()
       p.SetUser(user);
   }
}

public void OnPerformed(PerformedContext filterContext)
{
    var scope = filterContext.Items[key] as Scope;
    scope.Dispose();
}

And it doesn't work....

When instances of my IParticipantProvider-interfaces are injected into code, it receives new copies instead of the previously populated instance.

vegar commented 6 years ago

One thought that just came to me: Could my problem be related to services with different lifetime depending on each other? Except - all other services are basically transient, so I can't see the problem there either....

seesharper commented 6 years ago

A small repro of this would be very useful. How is this hosted? Is it a web app and if so, is it an IIS web app? I'd love to get to the bottom of this, but I really need something to look at where I can set a breakpoint and see it fail. There are so many ifs and buts when it comes to ambient context depending on the hosting environment. Also take a look at #386 for more information about HttpContext.Current and async/await.

vegar commented 6 years ago

I would love to create a small repo, but I'm not sure I'll manage...

Anyway. It's a Azure Cloud Service hosted project with one WebRole and one WorkerRole. Both are targeting .NET Framework 4.5.2 and hosted in IIS Express when running locally.

vegar commented 6 years ago

I had a look at #386 earlier, and have to admit that it goes a little over my head... Same for the 4.7.1 feature that was announced. As I understand it, it gives an option to manually restore context between execution steps?

None of this should relate to the problems I have with the background worker, though, should it?

When it says No .ConfigAwaiter(false) - I can of cause prevent such a call in my code, but if I await a library method, and that method awaits another call, with .ConfigAwaiter(false) - does that count? Will that break the chain?

seesharper commented 6 years ago

Will that break the chain?

It could, but that scenario should be rare. If you inject some Func<T> somewhere that is used to resolve a scoped instance, it could break if you're somehow lost the synchronization context. I would not worry to much about that, but it is something to be aware of.

If I understand correctly, your main issue here is to ensure a single IPrincipalProvider across the boundaries of OnPerforming and OnPerformed ?

seesharper commented 6 years ago

https://www.hangfire.io/extensions.html#ioc-containers

You are aware of this I assume?

seesharper commented 6 years ago

https://github.com/sbosell/Hangfire.LightInject

vegar commented 6 years ago

Yes, my goal is to ensure a single instance of IPrincipalProvider across the bounderies of OnPerforming and OnPerformed.

We are using @sbosells extension to inject dependencies into our job implementations. When looking at the source, I see there is some scope handling there as well. I'm not sure how that applies, though.

These projects simplify the integration between Hangfire and your favorite IoC Container. They provide custom implementation of JobActivator class as well as registration extensions that allow you to use unit of work pattern or deterministic disposal in your background jobs. https://www.hangfire.io/extensions.html

Maybe I already have 'per job'-lifetime handling out of the box?

seesharper commented 6 years ago

Yeah, I would at least try to use the extension first. It has a few thousands downloads on NuGet too 👍

seesharper commented 6 years ago

Did you get any further with this?

vegar commented 6 years ago

Not yet. Been occupied with other things :-)


From: Bernhard Richter notifications@github.com Sent: Wednesday, May 16, 2018 5:32:11 PM To: seesharper/LightInject Cc: Vegar Vikan; Author Subject: Re: [seesharper/LightInject] Implementing PerJobLifetime() for use with Hangfire (#416)

Did you get any further with this?

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHubhttps://github.com/seesharper/LightInject/issues/416#issuecomment-389562655, or mute the threadhttps://github.com/notifications/unsubscribe-auth/AAuqZYVIspVFTmJtpHbmXuHPqoW4VvAFks5tzEZ6gaJpZM4T7oSo.