Skip to content

Commit

Permalink
Add unloadability support to ReliabilityFramework (dotnet#20418)
Browse files Browse the repository at this point in the history
* Add unloadability support to ReliabilityFramework

This change adds support for unloadable AssemblyLoadContext to the GC
ReliabilityFramework. It basically mimics what was there ifdef-ed out
for AppDomains.
It allows stress testing GC when running inside of an unloadable
AssemblyLoadContext. GC has some special handling for collectible
classes and this allows testing the respective code paths.

* Make tests run on separate threads

The tests running in the assembly load context were bot using separate
threads per test and so they were not really stressing GC as much as
possible. This change fixes that.
I've also merged code for AppDomains / AssemblyLoadContext at most
places so that the differences are clearly visible.
  • Loading branch information
janvorli authored and A-And committed Nov 20, 2018
1 parent a296cb7 commit cc4daad
Show file tree
Hide file tree
Showing 6 changed files with 566 additions and 81 deletions.
89 changes: 54 additions & 35 deletions tests/src/GC/Stress/Framework/LoaderClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
using System.Reflection;
using System.Threading;
using System.IO;
using System.Runtime.Loader;
#if !PROJECTK_BUILD
using System.Runtime.Remoting;
#endif
#endif

public enum eReasonForUnload
{
Expand All @@ -32,10 +33,11 @@ public class LoaderClass
: MarshalByRefObject
#endif
{
#if !PROJECTK_BUILD
private Assembly assem;
string assembly;
#endif
#if !PROJECTK_BUILD
ReliabilityFramework myRf;
#endif

public LoaderClass()
{
Expand All @@ -45,15 +47,20 @@ public void SuppressConsole()
{
Console.SetOut(System.IO.TextWriter.Null);
}
#if !PROJECTK_BUILD

/// <summary>
/// Executes a LoadFrom in the app domain LoaderClass has been loaded into. Attempts to load a given assembly, looking in the
/// given paths & the current directory.
/// </summary>
/// <param name="path">The assembly to load</param>
/// <param name="paths">Paths to search for the given assembly</param>
public void LoadFrom(string path, string[] paths, ReliabilityFramework rf)
public void LoadFrom(string path, string[] paths
#if !PROJECTK_BUILD
, ReliabilityFramework rf
#endif
)
{
#if !PROJECTK_BUILD
myRf = rf;

AssemblyName an = new AssemblyName();
Expand All @@ -62,21 +69,32 @@ public void LoadFrom(string path, string[] paths, ReliabilityFramework rf)
//register AssemblyLoad and DomainUnload events
AppDomain.CurrentDomain.AssemblyLoad += new AssemblyLoadEventHandler(this.UnloadOnAssemblyLoad);
AppDomain.CurrentDomain.DomainUnload += new EventHandler(this.UnloadOnDomainUnload);

#else
AssemblyLoadContext alc = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly());
#endif
try
{
#if !PROJECTK_BUILD
assem = Assembly.Load(an);
#else
assembly = path;
assem = alc.LoadFromAssemblyPath(assembly);
#endif
}
catch
{
try
{
FileInfo fi = new FileInfo(path);

assembly = fi.FullName;
#if !PROJECTK_BUILD
an = new AssemblyName();
an.CodeBase = assembly = fi.FullName;
an.CodeBase = assembly;

assem = Assembly.Load(an);
#else
assem = alc.LoadFromAssemblyPath(assembly);
#endif
}
catch
{
Expand All @@ -86,16 +104,20 @@ public void LoadFrom(string path, string[] paths, ReliabilityFramework rf)
{
try
{
assembly = ReliabilityConfig.ConvertPotentiallyRelativeFilenameToFullPath(basePath, path);
#if !PROJECTK_BUILD
an = new AssemblyName();
an.CodeBase = assembly = ReliabilityConfig.ConvertPotentiallyRelativeFilenameToFullPath(basePath, path);
an.CodeBase = assembly;

assem = Assembly.Load(an);
#else
assem = alc.LoadFromAssemblyPath(assembly);
#endif
break;
}
catch
{
continue;
}
break;
}
}
}
Expand All @@ -107,22 +129,33 @@ public void LoadFrom(string path, string[] paths, ReliabilityFramework rf)
/// </summary>
/// <param name="assemblyName">The assembly name to load</param>
/// <param name="paths">paths to look in if the initial load fails</param>
public void Load(string assemblyName, string[] paths, ReliabilityFramework rf)
public void Load(string assemblyName, string[] paths
#if !PROJECTK_BUILD
, ReliabilityFramework rf
#endif
)
{
#if !PROJECTK_BUILD
myRf = rf;

AssemblyName an = new AssemblyName(assemblyName);
assembly = assemblyName;

#endif
try
{
#if !PROJECTK_BUILD
assem = Assembly.Load(an);
#else
LoadFrom(assemblyName + ".exe", paths);
#endif
}
catch
{
Console.WriteLine("Load failed for: {0}", assemblyName);
LoadFrom(assemblyName, paths, rf); // couldn't load the assembly, try doing a LoadFrom with paths.

#if !PROJECTK_BUILD
LoadFrom(assemblyName, paths, rf); // couldn't load the assembly, try doing a LoadFrom with paths.
#endif
}
}

Expand Down Expand Up @@ -150,27 +183,6 @@ public ApartmentState CheckMainForThreadType()
return (ApartmentState.Unknown);
}

#endif
/// <summary>
/// Helper function to call into the app domain and make sure that we're still functioning in a sane way.
/// </summary>
public bool StillAlive()
{
return (true);
}

#if !PROJECTK_BUILD
/// <summary>
/// Gets the full path to the loaded assembly.
/// </summary>
public string FullPath
{
get
{
return (assembly);
}
}

/// <summary>
/// Gets back the test object. If the test is an assembly (to be executed) we return a string which is the full path.
/// If we're an ISingleReliabilityTest or IMultipleReliabilityTest we return the object it's self.
Expand Down Expand Up @@ -201,6 +213,7 @@ public Object GetTest()
{
if (t.GetInterface("ISingleReliabilityTest") != null || t.GetInterface("IMultipleReliabilityTest") != null)
{
#if !PROJECTK_BUILD
ObjectHandle handle;
if (assembly.IndexOf("\\") != -1)
handle = AppDomain.CurrentDomain.CreateInstanceFrom(assembly, t.FullName);
Expand All @@ -213,18 +226,24 @@ public Object GetTest()
ourObj = handle.Unwrap();
break;
}
#else
ourObj = Activator.CreateInstance(t);
#endif
}

}
}

if (ourObj == null)
return (assembly);
#if !PROJECTK_BUILD
if (!(ourObj is MarshalByRefObject))
throw new ArgumentException("All tests implementing ISingleReliabilityTest or IMultipleReliabilityTest must inherit from MarshalByRefObject");
#endif

return (ourObj);
}
#if !PROJECTK_BUILD
/// <summary>
/// This stops our MBR from being deallocated by the distributed GC mechanism in remoting.
/// </summary>
Expand Down
73 changes: 67 additions & 6 deletions tests/src/GC/Stress/Framework/ReliabilityConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
public enum TestStartModeEnum
{
AppDomainLoader,
ProcessLoader
ProcessLoader,
AssemblyLoadContextLoader
}

public enum AppDomainLoaderMode
Expand All @@ -30,6 +31,14 @@ public enum AppDomainLoaderMode
Lazy
}

public enum AssemblyLoadContextLoaderMode
{
FullIsolation,
Normal,
RoundRobin,
Lazy
}

[Flags]
public enum LoggingLevels
{
Expand All @@ -41,8 +50,9 @@ public enum LoggingLevels
Logging = 0x20,
UrtFrameworks = 0x40,
TestStarter = 0x80,
AssemblyLoadContext = 0x100,

All = (0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x80)
All = (0x01 | 0x02 | 0x04 | 0x08 | 0x10 | 0x20 | 0x80 | 0x100)
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -97,7 +107,9 @@ internal class RFConfigOptions
private const string configULWaitTime = "ulWaitTime";
private const string configCcFailMail = "ccFailMail";
private const string configAppDomainLoaderMode = "appDomainLoaderMode";
private const string configAssemblyLoadContextLoaderMode = "assemblyLoadContextLoaderMode";
private const string configRoundRobinAppDomainCount = "numAppDomains";
private const string configRoundRobinAssemblyLoadContextCount = "numAssemblyLoadContexts";
// Attributes for the <Assembly ...> tag
private const string configAssemblyName = "id";
private const string configAssemblyFilename = "filename";
Expand Down Expand Up @@ -137,6 +149,7 @@ internal class RFConfigOptions
// Test start modes
private const string configTestStartModeAppDomainLoader = "appDomainLoader";
private const string configTestStartModeProcessLoader = "processLoader";
private const string configTestStartModeAssemblyLoadContextLoader = "assemblyLoadContextLoader";
private const string configTestStartModeTaskLoader = "taskLoader";

// APp domain loader modes
Expand All @@ -145,6 +158,12 @@ internal class RFConfigOptions
private const string configAppDomainLoaderModeRoundRobin = "roundRobin";
private const string configAppDomainLoaderModeLazy = "lazy";

// AssemblyLoadContext loader modes
private const string configAssemblyLoadContextLoaderModeFullIsolation = "fullIsolation";
private const string configAssemblyLoadContextLoaderModeNormal = "normal";
private const string configAssemblyLoadContextLoaderModeRoundRobin = "roundRobin";
private const string configAssemblyLoadContextLoaderModeLazy = "lazy";


/// <summary>
/// The ReliabilityConfig constructor. Takes 2 config files: The primary config & the test config file. We then load these up
Expand Down Expand Up @@ -496,6 +515,9 @@ private void GetTestsToRun(string testConfig)
}
_curTestSet.DefaultTestStartMode = TestStartModeEnum.AppDomainLoader;
break;
case configTestStartModeAssemblyLoadContextLoader:
_curTestSet.DefaultTestStartMode = TestStartModeEnum.AssemblyLoadContextLoader;
break;
case configTestStartModeProcessLoader:
_curTestSet.DefaultTestStartMode = TestStartModeEnum.ProcessLoader;
break;
Expand All @@ -517,6 +539,20 @@ private void GetTestsToRun(string testConfig)
throw new Exception(String.Format("The value {0} is not an integer", currentXML.Value));
}
break;
case configRoundRobinAssemblyLoadContextCount:
try
{
_curTestSet.NumAssemblyLoadContexts = Convert.ToInt32(currentXML.Value);
if (_curTestSet.NumAssemblyLoadContexts <= 0)
{
throw new Exception("Number of AssemblyLoadContexts must be greater than zero!");
}
}
catch
{
throw new Exception(String.Format("The value {0} is not an integer", currentXML.Value));
}
break;
case configAppDomainLoaderMode:
switch (currentXML.Value)
{
Expand All @@ -537,6 +573,26 @@ private void GetTestsToRun(string testConfig)
throw new Exception(String.Format("Unknown AD Loader mode {0} specified!", currentXML.Value));
}
break;
case configAssemblyLoadContextLoaderMode:
switch (currentXML.Value)
{
case configAssemblyLoadContextLoaderModeFullIsolation:
_curTestSet.AssemblyLoadContextLoaderMode = AssemblyLoadContextLoaderMode.FullIsolation;
break;
case configAssemblyLoadContextLoaderModeNormal:
_curTestSet.AssemblyLoadContextLoaderMode = AssemblyLoadContextLoaderMode.Normal;
break;
case configAssemblyLoadContextLoaderModeRoundRobin:
_curTestSet.AssemblyLoadContextLoaderMode = AssemblyLoadContextLoaderMode.RoundRobin;
break;
case configAssemblyLoadContextLoaderModeLazy:
_curTestSet.AssemblyLoadContextLoaderMode = AssemblyLoadContextLoaderMode.Lazy;
break;

default:
throw new Exception(String.Format("Unknown ALC Loader mode {0} specified!", currentXML.Value));
}
break;
case configPercentPassIsPass:
_curTestSet.PercentPassIsPass = Convert.ToInt32(currentXML.Value);
break;
Expand Down Expand Up @@ -813,6 +869,9 @@ private void GetTestsToRun(string testConfig)
case configTestStartModeProcessLoader:
rt.TestStartMode = TestStartModeEnum.ProcessLoader;
break;
case configTestStartModeAssemblyLoadContextLoader:
rt.TestStartMode = TestStartModeEnum.AssemblyLoadContextLoader;
break;
default:
throw new Exception(String.Format("Unknown test starter {0} specified!", currentXML.Value));
}
Expand Down Expand Up @@ -891,23 +950,25 @@ private void GetTestsToRun(string testConfig)
}

int testCopies = 1;
if (_curTestSet.AppDomainLoaderMode == AppDomainLoaderMode.FullIsolation)
if (_curTestSet.AppDomainLoaderMode == AppDomainLoaderMode.FullIsolation ||
_curTestSet.AssemblyLoadContextLoaderMode == AssemblyLoadContextLoaderMode.FullIsolation)
{
// in this mode each copy of the test is ran in it's own app domain,
// in this mode each copy of the test is ran in it's own app domain or AssemblyLoadContext,
// fully isolated from all other copies of the test. If the user
// specified a cloning level we need to duplicate the test.
testCopies = rt.ConcurrentCopies;
rt.ConcurrentCopies = 1;
}
else if (_curTestSet.AppDomainLoaderMode == AppDomainLoaderMode.RoundRobin)
else if (_curTestSet.AppDomainLoaderMode == AppDomainLoaderMode.RoundRobin ||
_curTestSet.AssemblyLoadContextLoaderMode == AssemblyLoadContextLoaderMode.RoundRobin)
{
// In this mode each test is ran in an app domain w/ other tests.
testCopies = rt.ConcurrentCopies;
rt.ConcurrentCopies = 1;
}
else
{
// Normal mode - tests are ran in app domains w/ copies of themselves
// Normal mode - tests are ran in app domains / AssemblyLoadContexts w/ copies of themselves
}

string refOrId = rt.RefOrID;
Expand Down
Loading

0 comments on commit cc4daad

Please sign in to comment.