From: Steve MacLean Date: Wed, 27 Mar 2019 23:49:17 +0000 (-0400) Subject: ALC enhancements (#22273) X-Git-Tag: accepted/tizen/unified/20190813.215958~54^2~72 X-Git-Url: http://review.tizen.org/git/?a=commitdiff_plain;h=f1440bb696e2eee957a30f37c6c735f51696edad;p=platform%2Fupstream%2Fcoreclr.git ALC enhancements (#22273) * Initial implementatation of ALC enhancements Add public constructor with name Add Name property Add Assemblies property Add All property Override ToString Name Default and Assembly.Load(...) ALCs Add AssemblyLoadContext30Extensions test --- diff --git a/src/System.Private.CoreLib/shared/System/Reflection/Assembly.cs b/src/System.Private.CoreLib/shared/System/Reflection/Assembly.cs index 35772af..c54e39f 100644 --- a/src/System.Private.CoreLib/shared/System/Reflection/Assembly.cs +++ b/src/System.Private.CoreLib/shared/System/Reflection/Assembly.cs @@ -227,7 +227,7 @@ namespace System.Reflection SerializationInfo.ThrowIfDeserializationInProgress("AllowAssembliesFromByteArrays", ref s_cachedSerializationSwitch); - AssemblyLoadContext alc = new IndividualAssemblyLoadContext(); + AssemblyLoadContext alc = new IndividualAssemblyLoadContext("Assembly.Load(byte[], ...)"); return alc.InternalLoad(rawAssembly, rawSymbolStore); } @@ -254,7 +254,7 @@ namespace System.Reflection if (s_loadfile.TryGetValue(normalizedPath, out result)) return result; - AssemblyLoadContext alc = new IndividualAssemblyLoadContext(); + AssemblyLoadContext alc = new IndividualAssemblyLoadContext(String.Format("Assembly.LoadFile({0})", path)); result = alc.LoadFromAssemblyPath(normalizedPath); s_loadfile.Add(normalizedPath, result); } diff --git a/src/System.Private.CoreLib/shared/System/Runtime/Loader/AssemblyLoadContext.cs b/src/System.Private.CoreLib/shared/System/Runtime/Loader/AssemblyLoadContext.cs index 3bf2e3e..4be5218 100644 --- a/src/System.Private.CoreLib/shared/System/Runtime/Loader/AssemblyLoadContext.cs +++ b/src/System.Private.CoreLib/shared/System/Runtime/Loader/AssemblyLoadContext.cs @@ -12,7 +12,7 @@ using System.Threading; namespace System.Runtime.Loader { - public abstract partial class AssemblyLoadContext + public partial class AssemblyLoadContext { private enum InternalState { @@ -44,18 +44,25 @@ namespace System.Runtime.Loader // Contains the reference to VM's representation of the AssemblyLoadContext private readonly IntPtr _nativeAssemblyLoadContext; - protected AssemblyLoadContext() : this(false, false) + protected AssemblyLoadContext() : this(false, false, null) { } - protected AssemblyLoadContext(bool isCollectible) : this(false, isCollectible) + protected AssemblyLoadContext(bool isCollectible) : this(false, isCollectible, null) { } - private protected AssemblyLoadContext(bool representsTPALoadContext, bool isCollectible) + public AssemblyLoadContext(string name, bool isCollectible = true) : this(false, isCollectible, name) + { + } + + private protected AssemblyLoadContext(bool representsTPALoadContext, bool isCollectible, string name) { // Initialize the VM side of AssemblyLoadContext if not already done. IsCollectible = isCollectible; + + Name = name; + // The _unloadLock needs to be assigned after the IsCollectible to ensure proper behavior of the finalizer // even in case the following allocation fails or the thread is aborted between these two lines. _unloadLock = new object(); @@ -132,6 +139,22 @@ namespace System.Runtime.Loader } } + public IEnumerable Assemblies + { + get + { + foreach (Assembly a in GetLoadedAssemblies()) + { + AssemblyLoadContext alc = GetLoadContext(a); + + if (alc == this) + { + yield return a; + } + } + } + } + // Event handler for resolving native libraries. // This event is raised if the native library could not be resolved via // the default resolution logic [including AssemblyLoadContext.LoadUnmanagedDll()] @@ -167,6 +190,37 @@ namespace System.Runtime.Loader public bool IsCollectible { get; } + public string Name { get; } + + public override string ToString() => "\"" + Name + "\" " + GetType().ToString() + " #" + _id; + + public static IEnumerable All + { + get + { + AssemblyLoadContext d = AssemblyLoadContext.Default; // Ensure default is initialized + + List> alcList = null; + lock (s_contextsToUnload) + { + // To make this thread safe we need a quick snapshot while locked + alcList = new List>(s_contextsToUnload.Values); + } + + foreach (WeakReference weakAlc in alcList) + { + AssemblyLoadContext alc = null; + + weakAlc.TryGetTarget(out alc); + + if (alc != null) + { + yield return alc; + } + } + } + } + // Helper to return AssemblyName corresponding to the path of an IL assembly public static AssemblyName GetAssemblyName(string assemblyPath) { @@ -181,7 +235,10 @@ namespace System.Runtime.Loader // Custom AssemblyLoadContext implementations can override this // method to perform custom processing and use one of the protected // helpers above to load the assembly. - protected abstract Assembly Load(AssemblyName assemblyName); + protected virtual Assembly Load(AssemblyName assemblyName) + { + return null; + } [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod public Assembly LoadFromAssemblyName(AssemblyName assemblyName) @@ -356,27 +413,15 @@ namespace System.Runtime.Loader { internal static readonly AssemblyLoadContext s_loadContext = new DefaultAssemblyLoadContext(); - internal DefaultAssemblyLoadContext() : base(true, false) + internal DefaultAssemblyLoadContext() : base(true, false, "Default") { } - - protected override Assembly Load(AssemblyName assemblyName) - { - // We were loading an assembly into TPA ALC that was not found on TPA list. As a result we are here. - // Returning null will result in the AssemblyResolve event subscribers to be invoked to help resolve the assembly. - return null; - } } internal sealed class IndividualAssemblyLoadContext : AssemblyLoadContext { - internal IndividualAssemblyLoadContext() : base(false, false) + internal IndividualAssemblyLoadContext(String name) : base(false, false, name) { } - - protected override Assembly Load(AssemblyName assemblyName) - { - return null; - } } -} \ No newline at end of file +} diff --git a/src/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.CoreCLR.cs b/src/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.CoreCLR.cs index b36c382..61bef8e 100644 --- a/src/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.CoreCLR.cs +++ b/src/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.CoreCLR.cs @@ -10,7 +10,7 @@ using System.Runtime.InteropServices; namespace System.Runtime.Loader { - public abstract partial class AssemblyLoadContext + public partial class AssemblyLoadContext { [DllImport(JitHelpers.QCall, CharSet = CharSet.Unicode)] private static extern IntPtr InitializeAssemblyLoadContext(IntPtr ptrAssemblyLoadContext, bool fRepresentsTPALoadContext, bool isCollectible); diff --git a/tests/src/Loader/AssemblyLoadContext30Extensions/AssemblyLoadContext30Extensions.cs b/tests/src/Loader/AssemblyLoadContext30Extensions/AssemblyLoadContext30Extensions.cs new file mode 100644 index 0000000..2d2a9d8 --- /dev/null +++ b/tests/src/Loader/AssemblyLoadContext30Extensions/AssemblyLoadContext30Extensions.cs @@ -0,0 +1,243 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +using System; +using System.Reflection; +using System.Runtime.Loader; +using System.IO; + +using Console = Internal.Console; + +namespace My +{ + public class CustomAssemblyLoadContext : AssemblyLoadContext + { + public CustomAssemblyLoadContext() : base() + { + } + } +} + +public class Program +{ + private static bool passed = true; + + public static void Assert(bool value, string message = "none") + { + if (!value) + { + Console.WriteLine("FAIL! " + message); + passed = false; + } + } + + public static bool AssembliesContainAssembly(AssemblyLoadContext alc, Assembly a) + { + foreach (Assembly b in alc.Assemblies) + { + if (a == b) + { + return true; + } + } + return false; + } + + public static bool PropertyAllContainsContext(AssemblyLoadContext alc) + { + foreach (AssemblyLoadContext c in AssemblyLoadContext.All) + { + if (alc == c) + { + return true; + } + } + return false; + } + + public static void DefaultName() + { + try + { + Console.WriteLine("DefaultName()"); + + AssemblyLoadContext alc = AssemblyLoadContext.Default; + Assert(alc == AssemblyLoadContext.GetLoadContext(typeof(Program).Assembly)); + Assert(PropertyAllContainsContext(alc)); + Assert(AssembliesContainAssembly(alc, typeof(Program).Assembly)); + + Console.WriteLine(alc.Name); + Assert(alc.Name == "Default"); + + Console.WriteLine(alc.GetType().ToString()); + Assert(alc.GetType().ToString() == "System.Runtime.Loader.DefaultAssemblyLoadContext"); + + Console.WriteLine(alc.ToString()); + Assert(alc.ToString().Contains("\"Default")); + Assert(alc.ToString().Contains("\" System.Runtime.Loader.DefaultAssemblyLoadContext")); + Assert(alc.ToString().Contains(" #")); + } + catch (Exception e) + { + Assert(false, e.ToString()); + } + } + + public static void AssemblyLoadFileName() + { + try + { + Console.WriteLine("AssemblyLoadFileName()"); + + String path = typeof(Program).Assembly.Location; + Assembly a = Assembly.LoadFile(path); + + Assert(a != typeof(Program).Assembly); + + AssemblyLoadContext alc = AssemblyLoadContext.GetLoadContext(a); + Assert(PropertyAllContainsContext(alc)); + Assert(AssembliesContainAssembly(alc, a)); + Assert(alc != AssemblyLoadContext.Default); + + + Console.WriteLine(alc.Name); + Assert(alc.Name == String.Format("Assembly.LoadFile({0})", path)); + + Console.WriteLine(alc.GetType().ToString()); + Assert(alc.GetType().ToString() == "System.Runtime.Loader.IndividualAssemblyLoadContext"); + + Console.WriteLine(alc.ToString()); + Assert(alc.ToString().Contains("\"" + String.Format("Assembly.LoadFile({0})", path))); + Assert(alc.ToString().Contains("\" System.Runtime.Loader.IndividualAssemblyLoadContext")); + Assert(alc.ToString().Contains(" #")); + } + catch (Exception e) + { + Assert(false, e.ToString()); + } + } + + public static void AssemblyLoadByteArrayName() + { +#if ReadAllBytes // System.IO.File.ReadAllBytes is not found when ReferenceSystemPrivateCoreLib is true + try + { + Console.WriteLine("AssemblyLoadByteArrayName()"); + + String path = typeof(Program).Assembly.Location; + Byte [] byteArray = System.IO.File.ReadAllBytes(path); + Assembly a = Assembly.Load(byteArray); + AssemblyLoadContext alc = AssemblyLoadContext.GetLoadContext(a); + Assert(PropertyAllContainsContext(alc)); + Assert(AssembliesContainAssembly(alc, a)); + + Console.WriteLine(alc.Name); + Assert(alc.Name == "Assembly.Load(byte[], ...)"); + + Console.WriteLine(alc.GetType().ToString()); + Assert(alc.GetType().ToString() == "System.Runtime.Loader.IndividualAssemblyLoadContext"); + + Console.WriteLine(alc.ToString()); + Assert(alc.ToString().Contains("\"Default")); + Assert(alc.ToString().Contains("\" System.Runtime.Loader.IndividualAssemblyLoadContext")); + Assert(alc.ToString().Contains(" #")); + } + catch (Exception e) + { + Assert(false, e.ToString()); + } +#endif + } + + public static void CustomWOName() + { + try + { + Console.WriteLine("CustomWOName()"); + + // ALC should be a concrete class + AssemblyLoadContext alc = new My.CustomAssemblyLoadContext(); + Assert(PropertyAllContainsContext(alc)); + + Console.WriteLine(alc.Name); + Assert(alc.Name == null); + + Console.WriteLine(alc.GetType().ToString()); + Assert(alc.GetType().ToString() == "My.CustomAssemblyLoadContext"); + + Console.WriteLine(alc.ToString()); + Assert(alc.ToString().Contains("\"\" ")); + Assert(alc.ToString().Contains("\" My.CustomAssemblyLoadContext")); + Assert(alc.ToString().Contains(" #")); + } + catch (Exception e) + { + Assert(false, e.ToString()); + } + } + + public static void CustomName() + { + try + { + Console.WriteLine("CustomName()"); + + // ALC should be a concrete class + AssemblyLoadContext alc = new AssemblyLoadContext("CustomName"); + Assert(PropertyAllContainsContext(alc)); + + Console.WriteLine(alc.Name); + Assert(alc.Name == "CustomName"); + + Console.WriteLine(alc.GetType().ToString()); + Assert(alc.GetType().ToString() == "System.Runtime.Loader.AssemblyLoadContext"); + + Console.WriteLine(alc.ToString()); + Assert(alc.ToString().Contains("\"CustomName")); + Assert(alc.ToString().Contains("\" System.Runtime.Loader.AssemblyLoadContext")); + Assert(alc.ToString().Contains(" #")); + } + catch (Exception e) + { + Assert(false, e.ToString()); + } + } + + public static int Main() + { + foreach (AssemblyLoadContext alc in AssemblyLoadContext.All) + { + Console.WriteLine(alc.ToString()); + foreach (Assembly a in alc.Assemblies) + { + Console.WriteLine(a.ToString()); + } + } + + DefaultName(); + AssemblyLoadFileName(); + AssemblyLoadByteArrayName(); + CustomWOName(); + CustomName(); + + foreach (AssemblyLoadContext alc in AssemblyLoadContext.All) + { + Console.WriteLine(alc.ToString()); + foreach (Assembly a in alc.Assemblies) + { + Console.WriteLine(a.ToString()); + } + } + + if (passed) + { + Console.WriteLine("Test PASSED!"); + return 100; + } + else + { + Console.WriteLine("Test FAILED!"); + return -1; + } + } +} diff --git a/tests/src/Loader/AssemblyLoadContext30Extensions/AssemblyLoadContext30Extensions.csproj b/tests/src/Loader/AssemblyLoadContext30Extensions/AssemblyLoadContext30Extensions.csproj new file mode 100644 index 0000000..5ef86a1 --- /dev/null +++ b/tests/src/Loader/AssemblyLoadContext30Extensions/AssemblyLoadContext30Extensions.csproj @@ -0,0 +1,32 @@ + + + + + Debug + AnyCPU + AssemblyLoadContext30Extensions + 2.0 + {95DFC527-4DC1-495E-97D7-E94EE1F7140D} + Exe + {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + ..\..\ + true + + + + + + + + + False + + + + + + + + + +