Closed srogovtsev closed 2 years ago
Actually, even simpler solution would be just to suppress finalization for created AutofacWebApiDependencyScope
, because it doesn't have its own resources, and we expect outer OWIN pipeline to properly dispose of the ILifetimeScope
.
I remember there were some challenges around the timing of creation/disposal of the various lifetime scope incarnations (the actual request lifetime scope, the AutofacWebApiDependencyScope
, etc.) and getting them to align well when folks tried to use MVC, Web API, OWIN, and even sometimes Web Forms, all in the same application. "We let X dispose the scope, but Y is still alive and needs it!" Lots of pain around that, since OWIN was sort of a bolt-on after the fact.
I don't remember if that's why we left disposal like this or if there was something else.
I do see that Web API already appears to inherently register the dependency scope for disposal if it's the thing that initially creates it (which is what would happen in the non-OWIN scenario). In the OWIN scenario, we need to make sure we at least let the Autofac ILifetimeScope
live until it gets back to the OWIN middleware - if we actively dispose AutofacWebApiDependencyScope
with the request message, I don't know how that impacts middleware that would need the nested ILifetimeScope
after the WebAPI portion of things runs.
However, I think that latter solution - GC.SuppressFinalize(dependencyScope)
- is probably a reasonable solution to the issue you're seeing. It would mean the OWIN pipeline still gets to dispose the ILifetimeScope
(rather than having the AutofacWebApiDependencyScope
do it) but you wouldn't end up seeing it in the finalizer queue.
Unclear why this never came up before, but given this stuff is many years old, it could be anything from "the finalization mechanism has changed and it wasn't a problem before" to "no one really cared that many years ago and it's only now that it's been interesting to solve."
Would you accept a patch with GC.SuppressFinalize?
On Fri, 15 Jul 2022 at 10:08, Travis Illig @.***> wrote:
I remember there were some challenges around the timing of creation/disposal of the various lifetime scope incarnations (the actual request lifetime scope, the AutofacWebApiDependencyScope, etc.) and getting them to align well when folks tried to use MVC, Web API, OWIN, and even sometimes Web Forms, all in the same application. "We let X dispose the scope, but Y is still alive and needs it!" Lots of pain around that, since OWIN was sort of a bolt-on after the fact.
I don't remember if that's why we left disposal like this or if there was something else.
I do see that Web API already appears to inherently register the dependency scope for disposal https://github.com/jbogard/aspnetwebstack/blob/730c683da2458430d36e3e360aba68932ba69fa4/src/System.Web.Http/HttpRequestMessageExtensions.cs#L57 if it's the thing that initially creates it (which is what would happen in the non-OWIN scenario). In the OWIN scenario, we need to make sure we at least let the Autofac ILifetimeScope live until it gets back to the OWIN middleware https://github.com/autofac/Autofac.Owin/blob/04476d90a2d7a73782146f5d840218861dfe37ff/src/Autofac.Integration.Owin/AutofacAppBuilderExtensions.cs#L393
- if we actively dispose AutofacWebApiDependencyScope with the request message, I don't know how that impacts middleware that would need the nested ILifetimeScope after the WebAPI portion of things runs.
However, I think that latter solution - GC.SuppressFinalize(dependencyScope) - is probably a reasonable solution to the issue you're seeing. It would mean the OWIN pipeline still gets to dispose the ILifetimeScope (rather than having the AutofacWebApiDependencyScope do it) but you wouldn't end up seeing it in the finalizer queue.
Unclear why this never came up before, but given this stuff is many years old, it could be anything from "the finalization mechanism has changed and it wasn't a problem before" to "no one really cared that many years ago and it's only now that it's been interesting to solve."
— Reply to this email directly, view it on GitHub https://github.com/autofac/Autofac.WebApi.Owin/issues/15#issuecomment-1185583598, or unsubscribe https://github.com/notifications/unsubscribe-auth/AARZOZKLFADHHAUHBYZ3BBLVUFWG7ANCNFSM53VN5PKQ . You are receiving this because you authored the thread.Message ID: @.***>
Sure.
I will say, though, that it might take a bit to get an updated release cut. As we're touching these older packages, we're taking the time to get all the build tooling and such up to date, start publishing .snupkg
symbols, etc. I can totally handle all that, I just wanted to set expectations that once the PR is accepted, you won't see a release in the next 10 minutes or something. It'd be next week.
you won't see a release in the next 10 minutes or something. It'd be next week.
Oh, this is perfectly fine (thank for the notice anyway), frankly, I wasn't expecting any answer till next week. If anything, I appreciate that you keep even older packages up to recent standards.
And to elaborate on your earlier comment, as I've finally back to regular keyboard:
I remember there were some challenges around the timing of creation/disposal of the various lifetime scope incarnations (the actual request lifetime scope, the AutofacWebApiDependencyScope, etc.) and getting them to align well when folks tried to use MVC, Web API, OWIN, and even sometimes Web Forms, all in the same application.
Yep, that's me exactly, I do remember those challenges vividly (and I think maybe even an occasional PR or two related to the reuse of the lifetime scopes), so I do appreciate the complexity.
In the OWIN scenario, we need to make sure we at least let the Autofac ILifetimeScope live until it gets back to the OWIN middleware
Yeah, this is why my first proposal was to tie created AutofacWebApiDependencyScope
to the ILifetimeScope.Disposer
, which would dispose the (dependency) scope with the (lifetime) scope, which is definitely when nobody else would be using it anyways (if they tried, they'd just get "scope is disposed" from ILifetimeScope
anyways).
But after a consideration, just suppressing the GC altogether seems to be a cleaner way of achieving everyone's happiness without introducing too much stuff.
Unclear why this never came up before, but given this stuff is many years old, it could be anything from "the finalization mechanism has changed and it wasn't a problem before" to "no one really cared that many years ago and it's only now that it's been interesting to solve."
My take is that nobody really cared, because usually there isn't that much stuff in the lifetime scope. All the really dangerous stuff is usually under IDisposable
, which ILifetimeScope
perfectly cares for, so a slight accumulation of references till the next finalizer run is not a big deal. It's just our particular case, when we have some state (that we, frankly, shouldn't have had in the first place, but that's an altogether different architectural challenge of ours) that is referenced by the scope indirectly, which is why it cannot be properly disposed, and in a particular case something big hit that state, so we were investigating memory dump and came onto this. I'm still not even convinced this is a recurring issue, but it'd be nice to plug it.
Anyways, I'll be drafting the PR this weekend. The only thing that bothers me is that I don't really understand if I can write a test for this scenario.
It has just occurred to me that the finalizer for AutofacWebApiDependencyScope
actually does nothing (it calls Dispose
with false
, which does nothing). As can be seen in MS documentation (https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/implementing-dispose#implement-the-dispose-pattern, see the first example, BaseClassWithSafeHandle
), we can just remove the finalizer altogether. Shouldn't we do that instead of suppressing the finalization?
In fact, according to https://stackoverflow.com/a/29150251/1105881, GC.SuppressFinalize
would not be enough, as it will remove the object from the "freachable queue", but not the finalization queue, so this probably raises the importance of completely removing the finalizer.
I'm going to leave this open just for tracking purposes - the whole solution requires that we also take the updated Autofac.WebApi2 package, which I'm working on releasing.
Thank you for keeping track of this.
Just got Autofac.WebApi2 6.1.1 published.
Autofac.WebApi2.Owin 6.1.0 is published and should have the sum total of all the fixes that went into getting this resolved. Base dependencies are updated to include the Autofac.WebApi2 and Autofac.Owin changes made recently; and core Autofac is also updated to minimum 6.4.0. (It appears NuGet is still indexing it, but it's there. Give it a few minutes.)
I think that should do it.
Thanks for the help with this @srogovtsev!
And thanks again to you for the willingness to maintain legacy integrations.
Describe the Bug
We seem to have problem with
AutofacWebApiDependencyScope
sticking in the finalizer queue and consuming resources.Steps to Reproduce
HttpContext
, which was stored inIOwinContext
, which was stored inILifetimeScope
AutofacWebApiDependencyScope
in the finalizer queue, undisposed, holding a reference to disposedILifetimeScope
, which holds a reference to no longer relevant resourcesExpected Behavior
AutofacWebApiDependencyScope
doesn't stay in memory after the request is completeAdditional Info
It is my understanding of
DependencyScopeHandler
code that it, in fact, creates aAutofacWebApiDependencyScope
, which is never disposed of, and as such will leave theAutofacWebApiDependencyScope
for the finalizer to consume:https://github.com/autofac/Autofac.WebApi.Owin/blob/aa10f6f13e121235550d683975a63e8b75f78cab/src/Autofac.Integration.WebApi.Owin/DependencyScopeHandler.cs#L47-L50
This can be pretty easily rectified by adding the scope to either
ILifetimeScope.Disposer
orHttpRequestMessage.AddResourceForDisposal
, but I assume I am missing some rationale for why this was originally done like this.