-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
Fix leaks after unsuccessful assembly load #68203
Conversation
I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label. |
Tagging subscribers to this area: @vitek-karas, @agocke, @VSadov Issue DetailsIn progress. This is about regular (not collectible) assembly loads. Unsuccessful load laves stuff behind that can accumulate rather quickly if the client, such as serializer, opportunistically loads various non-existant things. Fixes:#58093
|
} | ||
|
||
internal void RemoveFromAllContexts() | ||
{ | ||
Dictionary<long, WeakReference<AssemblyLoadContext>> allContexts = AllContexts; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to be the only functional change here - that we now remove the ALC from this dictionary. But that dictionary only has a weak ref to the ALC - so how come it caused a memory leak?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is In Progress. There will be changes on the native side as well. The managed side by itself only leaks dictionary entries and weak references (and weak handles with them).
I don't mind taking a targeted fix for the serialization scenario, but I would like to see a discussion of fixing this more broadly? |
I did not think about that as it seems more intentional than I will think about this. It may be possible to fix this more generally. Perhaps we could do the cleanup of unused contexts in the finalizer. |
The non-collectible ALCs are not designed to be destroyable. I do not think we should be adding overhead to non-collectible ALCs by trying to make them destroyable or collectible. Would it be better to fix the LoadFile problem by caching the failed LoadFile ALCs for given path? Ie change |
I believe that .NET Framework also leaks on Assembly.LoadFile failures in the general case. It may work fine for the trivial case of non-existent file - we can add early file existence check to match that behavior. |
If Empty not collectible contexts are relatively simple things since they do not have allocators. I think disposing them when they did not load anything could be doable. |
I was thinking that we could fix it such that implementing the same pattern as I think we should at least understand (and document) what the current behavior is in terms of leaking memory/resources. |
I think the idea was to just reuse the same ALC for the exact same path. We would still try the load every time. |
Pre-probing for file existence in LoadFile seems like a good idea regardless. |
Do we even need to remember the path? Can we use the last ALC that failed? |
I do not think you want to be reusing failed ALCs for different paths. The failed assembly can be stuck in the ALC already that interact in odd ways with anything new that you would load into it. |
Hmm. If ALC remembers the failure, then can we safely reuse it at all ? - for the same path or different. |
I think (this has always been a mystery to me) that the failure cache is only for "lookup by name" failures. Not for loading from a file path. I agree that we should not reuse ALC from a different path. For example, what if we find and load the assembly, but then it fails because it has something weird in its header. Does the runtime actually free this fully? Ideally it should, but I would not trust it right now. |
If the failure happens early during the binding, the runtime will free the assembly fully. Once we bind to a file and start actually loading it (ie going through runtime/src/coreclr/vm/domainassembly.h Lines 36 to 49 in d603356
|
@jkotas @vitek-karas I will think more about this and get back to you
|
Agree.
|
While it may be possible in some cases to unload/destroy the unused load context, it appears to be complicated. Based on the cost/risk vs. benefit, I think we should go with just probing for the file presence for now. This would not preclude more involved fixes in the future - if we find that desirable. |
Thanks!! |
This was merge with red CI. The CI failure (#68477) was introduced by this change. The failure is hitting all CI jobs. I am going to revert the change to stabilize the CI. |
The Wasm browser tests did not look like ones that could be affected by the change (and being only ones affected). Turns out they could. :-\ |
If you are merging on red, it is a best practice to always link to issues that track the failures and open a new issue if the failure is not tracked. It will make you think twice about whether the failure is really unrelated to your changes. |
I agree. I should have checked for existing issues. The change is in the shared code though. I am still not sure how it could fail and specifically on Wasm. |
Somehow it is possible to load files that do not exist? |
wasm browser is a weird hybrid between single file and regular layout. Assemblies are accessible via fake file paths, but the files do not actually exist. |
I think it would fail for single file too. That is unfortunate. Since we virtualize assembly files, we cant rely on physical file check. |
I do not think we are virtualizing files for regular single file. |
The easiest way to deal with this problem may be to ifdef out the file existence check for browser. |
Right. We virtualize when loading by assembly name, but load by file name just opens a PEImage using its path. We could search in the bundle, but we do not. I also noticed that mono implementation of |
The new attempt at this change is at #68502 |
This is about regular (not collectible) assembly loads. Unsuccessful load leaves stuff behind that can accumulate rather quickly if the client, such as serializer, opportunistically loads various non-existant things.
The leak is a regression compared to .net FX.
As the load fails we leak both the managed and the native parts of the context into which we loaded nothing.
Fixes:#58093