Merge pull request #1029 from PowerShell/andschwa/initialze-default-context

Switch from AssemblyLoadContext.InitializeDefaultContext to AssemblyLoadContext.Resolving event
This commit is contained in:
Andy Schwartzmeyer 2016-05-24 12:17:58 -07:00
commit 19250e80c5
5 changed files with 52 additions and 58 deletions

View file

@ -15,14 +15,14 @@ using System.Runtime.Loader;
namespace System.Management.Automation namespace System.Management.Automation
{ {
/// <summary> /// <summary>
/// The powershell custom AssemblyLoadContext implementation /// The powershell custom assembly loader implementation
/// </summary> /// </summary>
internal partial class PowerShellAssemblyLoadContext : AssemblyLoadContext internal partial class PowerShellAssemblyLoader
{ {
#region Resource_Strings #region Resource_Strings
// We cannot use a satellite resources.dll to store resource strings for Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.dll. This is because when retrieving resource strings, ResourceManager // We cannot use a satellite resources.dll to store resource strings for Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.dll. This is because when retrieving resource strings, ResourceManager
// tries to load the satellite resources.dll using a probing approach, which will cause an infinite loop to PowerShellAssemblyLoadContext.Load(AssemblyName). // tries to load the satellite resources.dll using a probing approach, which will cause an infinite loop to PowerShellAssemblyLoader.Load(AssemblyName).
// Take the 'en-US' culture as an example. When retrieving resource string to construct an exception, ResourceManager calls Assembly.Load(..) in the following order to load the resource dll: // Take the 'en-US' culture as an example. When retrieving resource string to construct an exception, ResourceManager calls Assembly.Load(..) in the following order to load the resource dll:
// 1. Load assembly with culture 'en-US' (Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.resources, Version=3.0.0.0, Culture=en-US, PublicKeyToken=31bf3856ad364e35) // 1. Load assembly with culture 'en-US' (Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.resources, Version=3.0.0.0, Culture=en-US, PublicKeyToken=31bf3856ad364e35)
// 2. Load assembly with culture 'en' (Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.resources, Version=3.0.0.0, Culture=en, PublicKeyToken=31bf3856ad364e35) // 2. Load assembly with culture 'en' (Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.resources, Version=3.0.0.0, Culture=en, PublicKeyToken=31bf3856ad364e35)
@ -37,13 +37,6 @@ namespace System.Management.Automation
#region Constructor #region Constructor
/// <summary>
/// This constructor is for testability purpose only
/// </summary>
protected PowerShellAssemblyLoadContext()
{
}
/// <summary> /// <summary>
/// Constructor /// Constructor
/// </summary> /// </summary>
@ -51,7 +44,7 @@ namespace System.Management.Automation
/// Base directory paths that are separated by semicolon ';'. /// Base directory paths that are separated by semicolon ';'.
/// They will be the default paths to probe assemblies. /// They will be the default paths to probe assemblies.
/// </param> /// </param>
internal PowerShellAssemblyLoadContext(string basePaths) internal PowerShellAssemblyLoader(string basePaths)
{ {
#region Validation #region Validation
this.basePaths = basePaths.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries); this.basePaths = basePaths.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
@ -77,12 +70,18 @@ namespace System.Management.Automation
// - Key: namespace qualified type name (FullName) // - Key: namespace qualified type name (FullName)
// - Value: strong name of the TPA that contains the type represented by Key. // - Value: strong name of the TPA that contains the type represented by Key.
coreClrTypeCatalog = InitializeTypeCatalog(); coreClrTypeCatalog = InitializeTypeCatalog();
this.loadContext = AssemblyLoadContext.Default;
loadContext.Resolving += Resolve;
} }
#endregion Constructor #endregion Constructor
#region Fields #region Fields
// AssemblyLoadContext used by this loader
private readonly AssemblyLoadContext loadContext;
// Serialized type catalog file // Serialized type catalog file
private readonly object syncObj = new object(); private readonly object syncObj = new object();
private readonly string[] basePaths; private readonly string[] basePaths;
@ -118,10 +117,15 @@ namespace System.Management.Automation
#region Protected_Internal_Methods #region Protected_Internal_Methods
/// <summary> /// <summary>
/// Implement the AssemblyLoadContext.Load(AssemblyName). Search the requested assembly in probing paths. /// The global instance of PowerShellAssemblyLoader
/// </summary>
internal static PowerShellAssemblyLoader Instance { get; set; }
/// <summary>
/// Implement the AssemblyLoadContext.Resolving event handler. Search the requested assembly in probing paths.
/// Search the file "[assemblyName.Name][.ni].dll" in probing paths. If the file is found and it matches the requested AssemblyName, load it with LoadFromAssemblyPath. /// Search the file "[assemblyName.Name][.ni].dll" in probing paths. If the file is found and it matches the requested AssemblyName, load it with LoadFromAssemblyPath.
/// </summary> /// </summary>
protected override Assembly Load(AssemblyName assemblyName) internal Assembly Resolve(AssemblyLoadContext sender, AssemblyName assemblyName)
{ {
// Probe the assembly cache // Probe the assembly cache
Assembly asmLoaded; Assembly asmLoaded;
@ -153,7 +157,7 @@ namespace System.Management.Automation
if (File.Exists(asmFilePath)) if (File.Exists(asmFilePath))
{ {
isAssemblyFileFound = true; isAssemblyFileFound = true;
AssemblyName asmNameFound = GetAssemblyName(asmFilePath); AssemblyName asmNameFound = AssemblyLoadContext.GetAssemblyName(asmFilePath);
if (IsAssemblyMatching(assemblyName, asmNameFound)) if (IsAssemblyMatching(assemblyName, asmNameFound))
{ {
isAssemblyFileMatching = true; isAssemblyFileMatching = true;
@ -187,8 +191,8 @@ namespace System.Management.Automation
try try
{ {
asmLoaded = asmFilePath.EndsWith(".ni.dll", StringComparison.OrdinalIgnoreCase) asmLoaded = asmFilePath.EndsWith(".ni.dll", StringComparison.OrdinalIgnoreCase)
? LoadFromNativeImagePath(asmFilePath, null) ? loadContext.LoadFromNativeImagePath(asmFilePath, null)
: LoadFromAssemblyPath(asmFilePath); : loadContext.LoadFromAssemblyPath(asmFilePath);
} }
// Since .NET CLI built versions of PowerShell have all the // Since .NET CLI built versions of PowerShell have all the
// built-in assemblies in the TPA list, the above will throw, // built-in assemblies in the TPA list, the above will throw,
@ -211,6 +215,14 @@ namespace System.Management.Automation
return asmLoaded; return asmLoaded;
} }
/// <summary>
/// Load an assembly from its name.
/// </summary>
internal Assembly LoadFromAssemblyName(AssemblyName assemblyName)
{
return loadContext.LoadFromAssemblyName(assemblyName);
}
/// <summary> /// <summary>
/// Load an assembly from its file path. /// Load an assembly from its file path.
/// </summary> /// </summary>
@ -239,7 +251,7 @@ namespace System.Management.Automation
#endregion Validation #endregion Validation
Assembly asmLoaded; Assembly asmLoaded;
AssemblyName assemblyName = GetAssemblyName(assemblyPath); AssemblyName assemblyName = AssemblyLoadContext.GetAssemblyName(assemblyPath);
// Probe the assembly cache // Probe the assembly cache
if (TryGetAssemblyFromCache(assemblyName, out asmLoaded)) if (TryGetAssemblyFromCache(assemblyName, out asmLoaded))
@ -256,8 +268,8 @@ namespace System.Management.Automation
{ {
// Load the assembly through 'LoadFromNativeImagePath' or 'LoadFromAssemblyPath' // Load the assembly through 'LoadFromNativeImagePath' or 'LoadFromAssemblyPath'
asmLoaded = assemblyPath.EndsWith(".ni.dll", StringComparison.OrdinalIgnoreCase) asmLoaded = assemblyPath.EndsWith(".ni.dll", StringComparison.OrdinalIgnoreCase)
? LoadFromNativeImagePath(assemblyPath, null) ? loadContext.LoadFromNativeImagePath(assemblyPath, null)
: LoadFromAssemblyPath(assemblyPath); : loadContext.LoadFromAssemblyPath(assemblyPath);
} }
// Since .NET CLI built versions of PowerShell have all the // Since .NET CLI built versions of PowerShell have all the
// built-in assemblies in the TPA list, the above will throw, // built-in assemblies in the TPA list, the above will throw,
@ -290,7 +302,7 @@ namespace System.Management.Automation
/// </summary> /// </summary>
internal Assembly LoadFrom(Stream assembly) internal Assembly LoadFrom(Stream assembly)
{ {
var asm = LoadFromStream(assembly); var asm = loadContext.LoadFromStream(assembly);
TryAddAssemblyToCache(asm); TryAddAssemblyToCache(asm);
return asm; return asm;
} }
@ -498,13 +510,11 @@ namespace System.Management.Automation
} }
/// <summary> /// <summary>
/// Set an instance of PowerShellAssemblyLoadContext to be the default Assembly Load Context. /// Set an instance of PowerShellAssemblyLoader to be the default Assembly Load Context.
/// This is the managed entry point for Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.dll. /// This is the managed entry point for Microsoft.PowerShell.CoreCLR.AssemblyLoadContext.dll.
/// </summary> /// </summary>
public class PowerShellAssemblyLoadContextInitializer public class PowerShellAssemblyLoadContextInitializer
{ {
private static bool IsInitialized = false;
// Porting note: it's much easier to send an LPStr on Linux // Porting note: it's much easier to send an LPStr on Linux
private const UnmanagedType stringType = private const UnmanagedType stringType =
#if LINUX #if LINUX
@ -519,19 +529,9 @@ namespace System.Management.Automation
/// </summary> /// </summary>
public static void SetPowerShellAssemblyLoadContext([MarshalAs(stringType)]string basePaths) public static void SetPowerShellAssemblyLoadContext([MarshalAs(stringType)]string basePaths)
{ {
if (!IsInitialized) if (PowerShellAssemblyLoader.Instance == null)
{ {
var psAsmLoadContext = new PowerShellAssemblyLoadContext(basePaths); PowerShellAssemblyLoader.Instance = new PowerShellAssemblyLoader(basePaths);
try
{
AssemblyLoadContext.InitializeDefaultContext(psAsmLoadContext);
}
catch (System.InvalidOperationException)
{
// We may not be able to set the default context. If we're under the
// xUnit test harness, it has already been set.
}
IsInitialized = true;
} }
} }
} }

View file

@ -10,7 +10,7 @@ using System.Collections.Generic;
namespace System.Management.Automation namespace System.Management.Automation
{ {
internal partial class PowerShellAssemblyLoadContext internal partial class PowerShellAssemblyLoader
{ {
private Dictionary<string, string> InitializeTypeCatalog() private Dictionary<string, string> InitializeTypeCatalog()
{ {

View file

@ -1352,7 +1352,7 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent
<value>Cannot run a document in PowerShell Core: {0}.</value> <value>Cannot run a document in PowerShell Core: {0}.</value>
</data> </data>
<data name="InvalidAssemblyLoadContextInUse" xml:space="preserve"> <data name="InvalidAssemblyLoadContextInUse" xml:space="preserve">
<value>The default AssemblyLoadContext in use is invalid. The default AssemblyLoadContext for PowerShell Core should be of type 'PowerShellAssemblyLoadContext'.</value> <value>The default AssemblyLoadContext in use is invalid. The default AssemblyLoadContext for PowerShell Core should be of type 'PowerShellAssemblyLoader'.</value>
</data> </data>
<data name="MultipleTypeConstraintsOnMethodParam" xml:space="preserve"> <data name="MultipleTypeConstraintsOnMethodParam" xml:space="preserve">
<value>Multiple type constraints are not allowed on a method parameter.</value> <value>Multiple type constraints are not allowed on a method parameter.</value>

View file

@ -276,7 +276,7 @@ namespace System.Management.Automation
internal static IEnumerable<Assembly> GetAssemblies(string namespaceQualifiedTypeName = null) internal static IEnumerable<Assembly> GetAssemblies(string namespaceQualifiedTypeName = null)
{ {
#if CORECLR #if CORECLR
var psAssemblyLoader = GetAssemblyLoadContext(); var psAssemblyLoader = GetAssemblyLoader();
return psAssemblyLoader.GetAssemblies(namespaceQualifiedTypeName); return psAssemblyLoader.GetAssemblies(namespaceQualifiedTypeName);
#else #else
return AppDomain.CurrentDomain.GetAssemblies().Where(a => !(a.FullName.Length > 0 && a.FullName[0] == FIRST_CHAR_PSASSEMBLY_MARK)); return AppDomain.CurrentDomain.GetAssemblies().Where(a => !(a.FullName.Length > 0 && a.FullName[0] == FIRST_CHAR_PSASSEMBLY_MARK));
@ -290,7 +290,7 @@ namespace System.Management.Automation
internal static Assembly LoadFrom(string assemblyPath) internal static Assembly LoadFrom(string assemblyPath)
{ {
#if CORECLR #if CORECLR
var psAssemblyLoader = GetAssemblyLoadContext(); var psAssemblyLoader = GetAssemblyLoader();
return psAssemblyLoader.LoadFrom(assemblyPath); return psAssemblyLoader.LoadFrom(assemblyPath);
#else #else
return Assembly.LoadFrom(assemblyPath); return Assembly.LoadFrom(assemblyPath);
@ -303,7 +303,7 @@ namespace System.Management.Automation
/// </summary> /// </summary>
internal static Assembly LoadFrom(Stream assembly) internal static Assembly LoadFrom(Stream assembly)
{ {
var psAssemblyLoader = GetAssemblyLoadContext(); var psAssemblyLoader = GetAssemblyLoader();
return psAssemblyLoader.LoadFrom(assembly); return psAssemblyLoader.LoadFrom(assembly);
} }
#endif #endif
@ -315,7 +315,7 @@ namespace System.Management.Automation
internal static Assembly Load(AssemblyName assembly) internal static Assembly Load(AssemblyName assembly)
{ {
#if CORECLR #if CORECLR
var psAssemblyLoader = GetAssemblyLoadContext(); var psAssemblyLoader = GetAssemblyLoader();
return psAssemblyLoader.LoadFromAssemblyName(assembly); return psAssemblyLoader.LoadFromAssemblyName(assembly);
#else #else
return Assembly.Load(assembly); return Assembly.Load(assembly);
@ -328,7 +328,7 @@ namespace System.Management.Automation
internal static Assembly Load(string assembly) internal static Assembly Load(string assembly)
{ {
#if CORECLR #if CORECLR
var psAssemblyLoader = GetAssemblyLoadContext(); var psAssemblyLoader = GetAssemblyLoader();
return psAssemblyLoader.LoadFromAssemblyName(new AssemblyName(assembly)); return psAssemblyLoader.LoadFromAssemblyName(new AssemblyName(assembly));
#else #else
return Assembly.Load(assembly); return Assembly.Load(assembly);
@ -347,7 +347,7 @@ namespace System.Management.Automation
#if CORECLR #if CORECLR
// Create the enum type and add the dynamic assembly to assembly cache. // Create the enum type and add the dynamic assembly to assembly cache.
TypeInfo enumTypeinfo = enumBuilder.CreateTypeInfo(); TypeInfo enumTypeinfo = enumBuilder.CreateTypeInfo();
var psAssemblyLoader = GetAssemblyLoadContext(); var psAssemblyLoader = GetAssemblyLoader();
psAssemblyLoader.TryAddAssemblyToCache(enumTypeinfo.Assembly); psAssemblyLoader.TryAddAssemblyToCache(enumTypeinfo.Assembly);
#else #else
enumBuilder.CreateTypeInfo(); enumBuilder.CreateTypeInfo();
@ -370,7 +370,7 @@ namespace System.Management.Automation
throw new ArgumentNullException("assemblyShortName"); throw new ArgumentNullException("assemblyShortName");
} }
var psAssemblyLoader = GetAssemblyLoadContext(); var psAssemblyLoader = GetAssemblyLoader();
return psAssemblyLoader.ProbeAssemblyFileForMetadataAnalysis(assemblyShortName, additionalSearchPath); return psAssemblyLoader.ProbeAssemblyFileForMetadataAnalysis(assemblyShortName, additionalSearchPath);
} }
@ -380,28 +380,22 @@ namespace System.Management.Automation
/// </summary> /// </summary>
internal static IEnumerable<string> GetAvailableCoreClrDotNetTypes() internal static IEnumerable<string> GetAvailableCoreClrDotNetTypes()
{ {
var psAssemblyLoader = GetAssemblyLoadContext(); var psAssemblyLoader = GetAssemblyLoader();
return psAssemblyLoader.GetAvailableDotNetTypes(); return psAssemblyLoader.GetAvailableDotNetTypes();
} }
/// <summary> /// <summary>
/// Get the powershell custom AssemblyLoadContext. /// Get the powershell custom AssemblyLoadContext.
/// </summary> /// </summary>
internal static PowerShellAssemblyLoadContext GetAssemblyLoadContext() internal static PowerShellAssemblyLoader GetAssemblyLoader()
{ {
if (_psLoadContext == null) if (_psLoadContext == null)
{ {
_psLoadContext = AssemblyLoadContext.Default as PowerShellAssemblyLoadContext; _psLoadContext = PowerShellAssemblyLoader.Instance;
if (_psLoadContext == null)
{
// The default load context may not be ours. This can happen during,
// for instance, xUnit testing.
_psLoadContext = new PowerShellAssemblyLoadContext(String.Empty);
}
} }
return _psLoadContext; return _psLoadContext;
} }
private static volatile PowerShellAssemblyLoadContext _psLoadContext; private static volatile PowerShellAssemblyLoader _psLoadContext;
#endif #endif
/// <summary> /// <summary>

View file

@ -41,7 +41,7 @@ Usage: TypeCatalogGen.exe <{0}> <{1}>
/* /*
* Go through all reference assemblies of .NET Core and generate the type catalog -> Dictionary<NamespaceQualifiedTypeName, TPAStrongName> * Go through all reference assemblies of .NET Core and generate the type catalog -> Dictionary<NamespaceQualifiedTypeName, TPAStrongName>
* Then auto-generate the partial class 'PowerShellAssemblyLoadContext' that has the code to initialize the type catalog cache. * Then auto-generate the partial class 'PowerShellAssemblyLoader' that has the code to initialize the type catalog cache.
* *
* In CoreCLR, there is no way to get all loaded TPA assemblies (.NET Framework Assemblies). In order to get type based on type name, powershell needs to know what .NET * In CoreCLR, there is no way to get all loaded TPA assemblies (.NET Framework Assemblies). In order to get type based on type name, powershell needs to know what .NET
* types are available and in which TPA assemblies. So we have to generate the type catalog based on the reference assemblies of .NET Core. * types are available and in which TPA assemblies. So we have to generate the type catalog based on the reference assemblies of .NET Core.
@ -105,7 +105,7 @@ Usage: TypeCatalogGen.exe <{0}> <{1}>
} }
} }
WritePowerShellAssemblyLoadContextPartialClass(targetFilePath, typeNameToAssemblyMap); WritePowerShellAssemblyLoaderPartialClass(targetFilePath, typeNameToAssemblyMap);
} }
/// <summary> /// <summary>
@ -272,7 +272,7 @@ Usage: TypeCatalogGen.exe <{0}> <{1}>
/// <summary> /// <summary>
/// Generate the CSharp source code that initialize the type catalog. /// Generate the CSharp source code that initialize the type catalog.
/// </summary> /// </summary>
private static void WritePowerShellAssemblyLoadContextPartialClass(string targetFilePath, Dictionary<string, string> typeNameToAssemblyMap) private static void WritePowerShellAssemblyLoaderPartialClass(string targetFilePath, Dictionary<string, string> typeNameToAssemblyMap)
{ {
const string SourceFormat = " typeCatalog[\"{0}\"] = \"{1}\";"; const string SourceFormat = " typeCatalog[\"{0}\"] = \"{1}\";";
const string SourceHead = @"// const string SourceHead = @"//
@ -287,7 +287,7 @@ using System.Collections.Generic;
namespace System.Management.Automation namespace System.Management.Automation
{{ {{
internal partial class PowerShellAssemblyLoadContext internal partial class PowerShellAssemblyLoader
{{ {{
private Dictionary<string, string> InitializeTypeCatalog() private Dictionary<string, string> InitializeTypeCatalog()
{{ {{