diff --git a/src/coreclr/vm/threadstatics.cpp b/src/coreclr/vm/threadstatics.cpp
index 885453073508c5..889e9ea3cf4c63 100644
--- a/src/coreclr/vm/threadstatics.cpp
+++ b/src/coreclr/vm/threadstatics.cpp
@@ -377,7 +377,7 @@ void FreeLoaderAllocatorHandlesForTLSData(Thread *pThread)
#endif
for (const auto& entry : g_pThreadStaticCollectibleTypeIndices->CollectibleEntries())
{
- _ASSERTE((entry.TlsIndex.GetIndexOffset() <= pThread->cLoaderHandles) || allRemainingIndicesAreNotValid);
+ _ASSERTE((entry.TlsIndex.GetIndexOffset() >= pThread->cLoaderHandles) || !allRemainingIndicesAreNotValid);
if (entry.TlsIndex.GetIndexOffset() >= pThread->cLoaderHandles)
{
#ifndef _DEBUG
@@ -390,7 +390,9 @@ void FreeLoaderAllocatorHandlesForTLSData(Thread *pThread)
{
if (pThread->pLoaderHandles[entry.TlsIndex.GetIndexOffset()] != (LOADERHANDLE)NULL)
{
- entry.pMT->GetLoaderAllocator()->FreeHandle(pThread->pLoaderHandles[entry.TlsIndex.GetIndexOffset()]);
+ LoaderAllocator *pLoaderAllocator = entry.pMT->GetLoaderAllocator();
+ if (pLoaderAllocator->IsExposedObjectLive())
+ pLoaderAllocator->FreeHandle(pThread->pLoaderHandles[entry.TlsIndex.GetIndexOffset()]);
pThread->pLoaderHandles[entry.TlsIndex.GetIndexOffset()] = (LOADERHANDLE)NULL;
}
}
diff --git a/src/tests/Loader/CollectibleAssemblies/Statics/CollectibleTLSStaticCollection.cs b/src/tests/Loader/CollectibleAssemblies/Statics/CollectibleTLSStaticCollection.cs
new file mode 100644
index 00000000000000..d84d3d05769c80
--- /dev/null
+++ b/src/tests/Loader/CollectibleAssemblies/Statics/CollectibleTLSStaticCollection.cs
@@ -0,0 +1,103 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System;
+using System.Reflection;
+using System.Reflection.Emit;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+using Xunit;
+
+namespace CollectibleThreadStaticShutdownRace
+{
+ public class CollectibleThreadStaticShutdownRace
+ {
+ Action? UseTLSStaticFromLoaderAllocator = null;
+ GCHandle IsLoaderAllocatorLive;
+ static ulong s_collectibleIndex;
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ void CallUseTLSStaticFromLoaderAllocator()
+ {
+ UseTLSStaticFromLoaderAllocator!();
+ UseTLSStaticFromLoaderAllocator = null;
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ bool CheckLALive()
+ {
+ return IsLoaderAllocatorLive.Target != null;
+ }
+
+
+ void ThreadThatWaitsForLoaderAllocatorToDisappear()
+ {
+ CallUseTLSStaticFromLoaderAllocator();
+ while (CheckLALive())
+ {
+ GC.Collect(2);
+ }
+ }
+
+ void CreateLoaderAllocatorWithTLS()
+ {
+ ulong collectibleIndex = s_collectibleIndex++;
+
+ var ab =
+ AssemblyBuilder.DefineDynamicAssembly(
+ new AssemblyName($"CollectibleDerivedAssembly{collectibleIndex}"),
+ AssemblyBuilderAccess.RunAndCollect);
+ var mob = ab.DefineDynamicModule($"CollectibleDerivedModule{collectibleIndex}");
+ var tb =
+ mob.DefineType(
+ $"CollectibleDerived{collectibleIndex}",
+ TypeAttributes.Class | TypeAttributes.Public,
+ typeof(object));
+
+ {
+ var fb =
+ tb.DefineField(
+ "Field", typeof(int), FieldAttributes.Static);
+ fb.SetCustomAttribute(typeof(ThreadStaticAttribute).GetConstructors()[0], new byte[0]);
+
+ var mb =
+ tb.DefineMethod(
+ "Method",
+ MethodAttributes.Public | MethodAttributes.Static);
+ var ilg = mb.GetILGenerator();
+ ilg.Emit(OpCodes.Ldc_I4_1);
+ ilg.Emit(OpCodes.Stsfld, fb);
+ ilg.Emit(OpCodes.Ret);
+ }
+
+ Type createdType = tb.CreateType();
+ UseTLSStaticFromLoaderAllocator = (Action)createdType.GetMethods()[0].CreateDelegate(typeof(Action));
+ IsLoaderAllocatorLive = GCHandle.Alloc(createdType, GCHandleType.WeakTrackResurrection);
+ }
+
+ void ForceCollectibleTLSStaticToGoThroughThreadTermination()
+ {
+ int iteration = 0;
+ for (int i = 0; i < 10; i++)
+ {
+ Console.WriteLine($"Iteration {iteration++}");
+ var createLAThread = new Thread(CreateLoaderAllocatorWithTLS);
+ createLAThread.Start();
+ createLAThread.Join();
+
+ var crashThread = new Thread(ThreadThatWaitsForLoaderAllocatorToDisappear);
+ crashThread.Start();
+ crashThread.Join();
+ }
+
+ }
+
+ [Fact]
+ public static void TestEntryPoint()
+ {
+ new CollectibleThreadStaticShutdownRace().ForceCollectibleTLSStaticToGoThroughThreadTermination();
+ }
+ }
+}
+
diff --git a/src/tests/Loader/CollectibleAssemblies/Statics/CollectibleTLSStaticCollection.csproj b/src/tests/Loader/CollectibleAssemblies/Statics/CollectibleTLSStaticCollection.csproj
new file mode 100644
index 00000000000000..b2bf6ae30660e7
--- /dev/null
+++ b/src/tests/Loader/CollectibleAssemblies/Statics/CollectibleTLSStaticCollection.csproj
@@ -0,0 +1,10 @@
+
+
+
+ true
+ true
+
+
+
+
+
diff --git a/src/tests/issues.targets b/src/tests/issues.targets
index 7a2cff6628af3a..918777676ea5cb 100644
--- a/src/tests/issues.targets
+++ b/src/tests/issues.targets
@@ -1010,6 +1010,9 @@
https://github.com/dotnet/runtimelab/issues/155: Collectible assemblies
+
+ https://github.com/dotnet/runtimelab/issues/155: Collectible assemblies
+
https://github.com/dotnet/runtimelab/issues/155: Collectible assemblies