Skip to content

Commit 4c588fe

Browse files
[release/10.0] Test for unloadable ALC frees file lock (#120122)
* Test for unloadable ALC frees file lock Remove extra AddRef() to remove file lock of DLL. * Fix test bug * Feedback. * Add ending new line to test file. * Prevent inlining of methods where the member is used. * Disable test on native AOT and non-coreclr targets * Spelling --------- Co-authored-by: Aaron R Robinson <arobins@microsoft.com>
1 parent ec27556 commit 4c588fe

File tree

5 files changed

+121
-15
lines changed

5 files changed

+121
-15
lines changed

src/coreclr/vm/assembly.cpp

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,10 @@ Assembly * Assembly::Create(
329329
PRECONDITION(pLoaderAllocator != NULL);
330330
PRECONDITION(pLoaderAllocator->IsCollectible() || pLoaderAllocator == SystemDomain::GetGlobalLoaderAllocator());
331331
}
332-
CONTRACTL_END
332+
CONTRACTL_END;
333+
334+
// Validate the assembly about to be created is suitable for execution.
335+
pPEAssembly->ValidateForExecution();
333336

334337
NewHolder<Assembly> pAssembly (new Assembly(pPEAssembly, pLoaderAllocator));
335338

@@ -540,21 +543,16 @@ Assembly *Assembly::CreateDynamic(AssemblyBinder* pBinder, NativeAssemblyNamePar
540543
RETURN pRetVal;
541544
} // Assembly::CreateDynamic
542545

543-
544-
545546
void Assembly::SetDomainAssembly(DomainAssembly *pDomainAssembly)
546547
{
547548
CONTRACTL
548549
{
550+
STANDARD_VM_CHECK;
549551
PRECONDITION(CheckPointer(pDomainAssembly));
550-
THROWS;
551-
GC_TRIGGERS;
552-
INJECT_FAULT(COMPlusThrowOM(););
553552
}
554553
CONTRACTL_END;
555554

556555
GetModule()->SetDomainAssembly(pDomainAssembly);
557-
558556
} // Assembly::SetDomainAssembly
559557

560558
#endif // #ifndef DACCESS_COMPILE

src/coreclr/vm/assembly.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,10 @@ class Assembly
352352

353353
//****************************************************************************************
354354

355+
private:
355356
Assembly();
357+
358+
public:
356359
~Assembly();
357360

358361
BOOL GetResource(LPCSTR szName, DWORD *cbResource,

src/coreclr/vm/domainassembly.cpp

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,17 @@ DomainAssembly::DomainAssembly(PEAssembly* pPEAssembly, LoaderAllocator* pLoader
2121
{
2222
CONTRACTL
2323
{
24+
STANDARD_VM_CHECK;
2425
CONSTRUCTOR_CHECK;
25-
THROWS; // ValidateForExecution
26-
GC_TRIGGERS; // ValidateForExecution
27-
MODE_ANY;
2826
}
2927
CONTRACTL_END;
3028

31-
pPEAssembly->AddRef();
32-
pPEAssembly->ValidateForExecution();
33-
3429
// Create the Assembly
3530
NewHolder<Assembly> assembly = Assembly::Create(pPEAssembly, memTracker, pLoaderAllocator);
31+
assembly->SetDomainAssembly(this);
3632

3733
m_pAssembly = assembly.Extract();
3834

39-
m_pAssembly->SetDomainAssembly(this);
40-
4135
#ifndef PEIMAGE_FLAT_LAYOUT_ONLY
4236
// Creating the Assembly should have ensured the PEAssembly is loaded
4337
_ASSERT(GetPEAssembly()->IsLoaded());
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
<PropertyGroup>
3+
<!-- Needed for GC.WaitForPendingFinalizers -->
4+
<RequiresProcessIsolation>true</RequiresProcessIsolation>
5+
<GCStressIncompatible>true</GCStressIncompatible>
6+
<UnloadabilityIncompatible>true</UnloadabilityIncompatible>
7+
<!-- Collectible ALCs are not supported -->
8+
<NativeAotIncompatible>true</NativeAotIncompatible>
9+
<CLRTestTargetUnsupported Condition="'$(RuntimeFlavor)' != 'coreclr'">true</CLRTestTargetUnsupported>
10+
</PropertyGroup>
11+
<ItemGroup>
12+
<Compile Include="Program.cs" />
13+
</ItemGroup>
14+
</Project>
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.IO;
6+
using System.Runtime.CompilerServices;
7+
using System.Runtime.Loader;
8+
using Xunit;
9+
10+
public class Program
11+
{
12+
[Fact]
13+
public static void EntryPoint()
14+
{
15+
string directoryPath = Path.Combine(AppContext.BaseDirectory, "ToDelete");
16+
string originalAssemblyPath = typeof(Program).Assembly.Location;
17+
string newAssemblyPath = Path.Combine(directoryPath, Path.GetFileName(originalAssemblyPath));
18+
19+
// If the directory already exists, delete it
20+
if (Directory.Exists(directoryPath))
21+
{
22+
Console.WriteLine("Temp directory already exists, deleting...");
23+
Directory.Delete(directoryPath, true);
24+
}
25+
26+
// Create a directory to copy the assembly to
27+
Directory.CreateDirectory(directoryPath);
28+
try
29+
{
30+
File.Copy(originalAssemblyPath, newAssemblyPath);
31+
32+
UnloadableAssemblyContext assemblyContext = UnloadableAssemblyContext.Create();
33+
assemblyContext.RunWithAssemblyLoadContext(
34+
context =>
35+
{
36+
context.LoadFromAssemblyPath(newAssemblyPath);
37+
});
38+
39+
assemblyContext.Unload();
40+
}
41+
finally
42+
{
43+
Directory.Delete(directoryPath, recursive: true);
44+
}
45+
46+
Assert.False(Directory.Exists(directoryPath));
47+
}
48+
49+
class UnloadableAssemblyContext
50+
{
51+
private readonly WeakReference _weakAssemblyLoadContextReference;
52+
53+
private AssemblyLoadContext? _assemblyLoadContext;
54+
55+
private UnloadableAssemblyContext()
56+
{
57+
_assemblyLoadContext = new AssemblyLoadContext("AssemblyOnDiskTest", isCollectible: true);
58+
_weakAssemblyLoadContextReference = new WeakReference(_assemblyLoadContext, trackResurrection: true);
59+
}
60+
61+
[MethodImpl(MethodImplOptions.NoInlining)]
62+
public static UnloadableAssemblyContext Create()
63+
{
64+
return new UnloadableAssemblyContext();
65+
}
66+
67+
[MethodImpl(MethodImplOptions.NoInlining)]
68+
public void RunWithAssemblyLoadContext(Action<AssemblyLoadContext> action)
69+
{
70+
action(_assemblyLoadContext!);
71+
}
72+
73+
public void Unload()
74+
{
75+
TriggerUnload();
76+
77+
const int maxRetries = 32;
78+
for (int i = 0; _weakAssemblyLoadContextReference.IsAlive && i <= maxRetries; i++)
79+
{
80+
GC.Collect();
81+
GC.WaitForPendingFinalizers();
82+
83+
if (i == maxRetries)
84+
{
85+
Assert.Fail("Could not unload AssemblyLoadContext.");
86+
}
87+
}
88+
}
89+
90+
[MethodImpl(MethodImplOptions.NoInlining)]
91+
private void TriggerUnload()
92+
{
93+
_assemblyLoadContext!.Unload();
94+
_assemblyLoadContext = null;
95+
}
96+
}
97+
}

0 commit comments

Comments
 (0)