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