Repository navigation
Fix Assembly reference in unloadability - #67922
Conversation
When an assembly A1 from an unloadable AssemblyLoadContext 1 is referenced by another assembly A2 from an unloadable AssemblyLoadContext 2, e.g. when a class from A2 implements an interface from A1 and there are no instances of types from A1 and the managed assembly type and the related AssemblyLoadContext is gone too, the AssemblyLoadContext 1 can incorrectly get collected, resulting in later crashes, null references etc. The problem is caused by the fact that we were missing adding references between native LoaderAllocators corresponding to the AssemblyLoadContexts in these cases. The fix is to add such reference whenever an AssemblyLoadContext.Load or the Resolving event of an AssemblyLoadContext returns an assembly from a collectible AssemblyLoadContext. That way, that collectible AssemblyLoadContext is held alive for the lifetime of the other context.
|
|
||
| EEFileLoadException::Throw(pSpec, FUSION_E_CACHEFILE_FAILED, NULL); | ||
| DomainAssembly *pParentAssembly = pSpec->GetParentAssembly(); | ||
| BINDER_SPACE::Assembly *pBinderSpaceAssembly = pPEAssembly->GetHostAssembly(); |
There was a problem hiding this comment.
This should probably check HasHostAssembly / that pBinderSpaceAssembly is not null. Some cases (dynamically emitted assemblies is one, I think) might not have one - should we be adding the reference in those cases too?.
There was a problem hiding this comment.
If there is no host assembly, how can we get the LoaderAllocator of such an assembly? It seems it is possible to return a dynamically emitted collectible assembly from the ALC.Load / Resolving event handler.
There was a problem hiding this comment.
You should be able to GetAssemblyBinder and then get the LoaderAllocator off off that.
There was a problem hiding this comment.
For the default ALC/binder (AssemblyBinder::IsDefault=true), I think the loader allocator on the binder will be null, but we shouldn't need the reference in that case.
There was a problem hiding this comment.
There is a problem with GetAssemblyBinder usage in general - if the assembly was marked for unload before it was returned from the ALC.Load / event, the binder had the LoaderAllocator already set to NULL. I have to check if it is a problem for the dynamic assemblies too.
There was a problem hiding this comment.
There is @vitek-karas' request (which I'd second) for documenting expectations:
I think this is worth a comment in the code. Not the "create reference" that one is relatively easy to see, but when we expect NULLs
There was a problem hiding this comment.
Does the following look good?
There are situations when `pParentAssembly` is NULL. For example when assembly is loaded via reflection. It is ok
to ignore those.
The purpose of the following code is to catch scenarios when resolution happens in another ALC solely based on
ALC.Load or ALC.Resolving event handler. In such cases we need to add a reference here as it is not guaranteed
to be added otherwise. These are also cases when `pParentAssembly` will always be set.
There was a problem hiding this comment.
I think it would be useful to call out both 'parent assembly' for the request and the 'result assembly' for the result. Thoughts on something like the below?
If the ALC for the result (pPEAssembly) is collectible and not the same as the ALC for the request (pSpec),
which can happen when extension points (AssemblyLoadContext.Load, AssemblyLoadContext.Resolving) resolve the
assembly, we need to ensure the result ALC is not collected before the request ALC. We do this by adding an
explicit reference between the LoaderAllocators corresponding to the ALCs.
To get the LoaderAllocators, we rely on the DomainAssembly corresponding to:
- Parent assembly for the request
- For loads via explicit load or reflection, this will be NULL. In these cases, the request ALC should not
implicitly (as in not detectable via managed references) depend on the result ALC, so it is fine to not
add the reference.
- For loads via assembly references, this will never be NULL.
- Result assembly for the result
- For dynamic assemblies, there will be no host assembly, so we have the result assembly / LoaderAllocator.
We currently block resolving to dynamic assemblies, so we simply assert that we have a host assembly.
- For non-dynamic assemblies, we should be able to get the host assembly.
There was a problem hiding this comment.
This looks better indeed. These details may not be obvious.
| // TODO: Disabling the below assertion as currently we experience | ||
| // inconsistency on resolving the Microsoft.Office.Interop.MSProject.dll | ||
| // This causes below assertion to fire and crashes the VS. This issue | ||
| // is being tracked with Dev10 Bug 658555. Brought back it when this bug | ||
| // is fixed. | ||
| // _ASSERTE(FALSE); |
There was a problem hiding this comment.
Side note - can you please create an issue to get this removed/resolved?
The comment is about .NET Framework, so it should be stale... hopefully.
There was a problem hiding this comment.
I've logged #68005 to follow up.
There are some really old TODOs in loader area and in runtime in general. Could be a good idea to clean that up, just as a matter of code hygiene.
Co-authored-by: Jeremy Koritzinsky <jkoritzinsky@gmail.com>
Co-authored-by: Elinor Fung <elfung@microsoft.com>
|
Thanks everybody!! |
When an assembly A1 from an unloadable
AssemblyLoadContext1 is referencedby another assembly A2 from an unloadable
AssemblyLoadContext2, e.g.when a class from A2 implements an interface from A1 and there are no
instances of types from A1 and the managed assembly type and the related
AssemblyLoadContextis gone too, theAssemblyLoadContext1 canincorrectly get collected, resulting in later crashes, null references
etc.
The problem is caused by the fact that we were missing adding references
between native
LoaderAllocatorscorresponding to theAssemblyLoadContextsin these cases.The fix is to add such reference whenever an
AssemblyLoadContext.Loadorthe Resolving event of an
AssemblyLoadContextreturns an assembly from acollectible
AssemblyLoadContext. That way, that collectibleAssemblyLoadContextis held alive for the lifetime of the other context.I have also added a regression test that crashes without this fix.
Close #67144