Skip to content
This repository has been archived by the owner on Jan 23, 2023. It is now read-only.

Disable noncollectible alc finalization #21189

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ public abstract class AssemblyLoadContext
private static bool _isProcessExiting;

// Id used by contextsToUnload
private readonly long id;
private readonly long _id;

// synchronization primitive to protect against usage of this instance while unloading
private readonly object unloadLock = new object();
private readonly object _unloadLock;

// Indicates the state of this ALC (Alive or in Unloading state)
private InternalState state;
private InternalState _state;

[DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)]
private static extern bool CanUseAppPathAssemblyLoadContextInCurrentDomain();
Expand Down Expand Up @@ -68,6 +68,16 @@ internal AssemblyLoadContext(bool fRepresentsTPALoadContext, bool isCollectible)
{
// Initialize the VM side of AssemblyLoadContext if not already done.
IsCollectible = isCollectible;
// The _unloadLock needs to be assigned after the IsCollectible to ensure proper behavior of the finalizer
// even in case the following allocation fails or the thread is aborted between these two lines.
_unloadLock = new object();

if (!isCollectible)
{
// For non collectible AssemblyLoadContext, the finalizer should never be called and thus the AssemblyLoadContext should not
// be on the finalizer queue.
GC.SuppressFinalize(this);
}

// Add this instance to the list of alive ALC
lock (ContextsToUnload)
Expand All @@ -86,17 +96,23 @@ internal AssemblyLoadContext(bool fRepresentsTPALoadContext, bool isCollectible)
Resolving = null;
Unloading = null;

id = _nextId++;
ContextsToUnload.Add(id, new WeakReference<AssemblyLoadContext>(this, true));
_id = _nextId++;
ContextsToUnload.Add(_id, new WeakReference<AssemblyLoadContext>(this, true));
}
}

~AssemblyLoadContext()
{
// Only valid for a Collectible ALC. Non-collectible ALCs have the finalizer suppressed.
// We get here only in case the explicit Unload was not initiated.
Debug.Assert(state != InternalState.Unloading);
InitiateUnload();
// Use the _unloadLock as a guard to detect the corner case when the constructor of the AssemblyLoadContext was not executed
// e.g. due to the JIT failing to JIT it.
if (_unloadLock != null)
{
// Only valid for a Collectible ALC. Non-collectible ALCs have the finalizer suppressed.
Debug.Assert(IsCollectible);
// We get here only in case the explicit Unload was not initiated.
Debug.Assert(_state != InternalState.Unloading);
InitiateUnload();
}
}

private void InitiateUnload()
Expand All @@ -107,11 +123,11 @@ private void InitiateUnload()

// When in Unloading state, we are not supposed to be called on the finalizer
// as the native side is holding a strong reference after calling Unload
lock (unloadLock)
lock (_unloadLock)
{
if (!_isProcessExiting)
{
Debug.Assert(state == InternalState.Alive);
Debug.Assert(_state == InternalState.Alive);

var thisStrongHandle = GCHandle.Alloc(this, GCHandleType.Normal);
var thisStrongHandlePtr = GCHandle.ToIntPtr(thisStrongHandle);
Expand All @@ -120,14 +136,14 @@ private void InitiateUnload()
PrepareForAssemblyLoadContextRelease(m_pNativeAssemblyLoadContext, thisStrongHandlePtr);
}

state = InternalState.Unloading;
_state = InternalState.Unloading;
}

if (!_isProcessExiting)
{
lock (ContextsToUnload)
{
ContextsToUnload.Remove(id);
ContextsToUnload.Remove(_id);
}
}
}
Expand All @@ -151,7 +167,7 @@ public Assembly LoadFromAssemblyPath(string assemblyPath)
throw new ArgumentNullException(nameof(assemblyPath));
}

lock (unloadLock)
lock (_unloadLock)
{
VerifyIsAlive();
if (PathInternal.IsPartiallyQualified(assemblyPath))
Expand All @@ -173,7 +189,7 @@ public Assembly LoadFromNativeImagePath(string nativeImagePath, string assemblyP
throw new ArgumentNullException(nameof(nativeImagePath));
}

lock (unloadLock)
lock (_unloadLock)
{
VerifyIsAlive();

Expand Down Expand Up @@ -212,7 +228,7 @@ public Assembly LoadFromStream(Stream assembly, Stream assemblySymbols)
{
throw new BadImageFormatException(SR.BadImageFormat_BadILFormat);
}
lock (unloadLock)
lock (_unloadLock)
{
VerifyIsAlive();

Expand Down Expand Up @@ -261,7 +277,7 @@ public void Unload()

private void VerifyIsAlive()
{
if (state != InternalState.Alive)
if (_state != InternalState.Alive)
{
throw new InvalidOperationException(SR.GetResourceString("AssemblyLoadContext_Verify_NotUnloading"));
}
Expand Down