Remove s_isProcessExiting and Clear of ALC list at exit
[platform/upstream/coreclr.git] / src / System.Private.CoreLib / shared / System / Runtime / Loader / AssemblyLoadContext.cs
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 // See the LICENSE file in the project root for more information.
4
5 using System.Collections.Generic;
6 using System.Diagnostics;
7 using System.IO;
8 using System.Reflection;
9 using System.Runtime.CompilerServices;
10 using System.Runtime.InteropServices;
11 using System.Threading;
12
13 namespace System.Runtime.Loader
14 {
15     public partial class AssemblyLoadContext
16     {
17         private enum InternalState
18         {
19             /// <summary>
20             /// The ALC is alive (default)
21             /// </summary>
22             Alive,
23
24             /// <summary>
25             /// The unload process has started, the Unloading event will be called
26             /// once the underlying LoaderAllocator has been finalized
27             /// </summary>
28             Unloading
29         }
30
31         private static readonly Dictionary<long, WeakReference<AssemblyLoadContext>> s_allContexts = new Dictionary<long, WeakReference<AssemblyLoadContext>>();
32         private static long s_nextId;
33
34         // Indicates the state of this ALC (Alive or in Unloading state)
35         private InternalState _state;
36
37         // Id used by s_allContexts
38         private readonly long _id;
39
40         // synchronization primitive to protect against usage of this instance while unloading
41         private readonly object _unloadLock;
42
43         // Contains the reference to VM's representation of the AssemblyLoadContext
44         private readonly IntPtr _nativeAssemblyLoadContext;
45
46         protected AssemblyLoadContext() : this(false, false, null)
47         {
48         }
49
50         protected AssemblyLoadContext(bool isCollectible) : this(false, isCollectible, null)
51         {
52         }
53
54         public AssemblyLoadContext(string name, bool isCollectible = false) : this(false, isCollectible, name)
55         {
56         }
57
58         private protected AssemblyLoadContext(bool representsTPALoadContext, bool isCollectible, string name)
59         {
60             // Initialize the VM side of AssemblyLoadContext if not already done.
61             IsCollectible = isCollectible;
62
63             Name = name;
64
65             // The _unloadLock needs to be assigned after the IsCollectible to ensure proper behavior of the finalizer
66             // even in case the following allocation fails or the thread is aborted between these two lines.
67             _unloadLock = new object();
68
69             if (!isCollectible)
70             {
71                 // For non collectible AssemblyLoadContext, the finalizer should never be called and thus the AssemblyLoadContext should not
72                 // be on the finalizer queue.
73                 GC.SuppressFinalize(this);
74             }
75
76             // If this is a collectible ALC, we are creating a weak handle tracking resurrection otherwise we use a strong handle
77             var thisHandle = GCHandle.Alloc(this, IsCollectible ? GCHandleType.WeakTrackResurrection : GCHandleType.Normal);
78             var thisHandlePtr = GCHandle.ToIntPtr(thisHandle);
79             _nativeAssemblyLoadContext = InitializeAssemblyLoadContext(thisHandlePtr, representsTPALoadContext, isCollectible);
80
81             // Add this instance to the list of alive ALC
82             lock (s_allContexts)
83             {
84                 _id = s_nextId++;
85                 s_allContexts.Add(_id, new WeakReference<AssemblyLoadContext>(this, true));
86             }
87         }
88
89         ~AssemblyLoadContext()
90         {
91             // Use the _unloadLock as a guard to detect the corner case when the constructor of the AssemblyLoadContext was not executed
92             // e.g. due to the JIT failing to JIT it.
93             if (_unloadLock != null)
94             {
95                 // Only valid for a Collectible ALC. Non-collectible ALCs have the finalizer suppressed.
96                 Debug.Assert(IsCollectible);
97                 // We get here only in case the explicit Unload was not initiated.
98                 Debug.Assert(_state != InternalState.Unloading);
99                 InitiateUnload();
100             }
101         }
102
103         private void RaiseUnloadEvent()
104         {
105             // Ensure that we raise the Unload event only once
106             Interlocked.Exchange(ref Unloading, null)?.Invoke(this);
107         }
108
109         private void InitiateUnload()
110         {
111             RaiseUnloadEvent();
112
113             // When in Unloading state, we are not supposed to be called on the finalizer
114             // as the native side is holding a strong reference after calling Unload
115             lock (_unloadLock)
116             {
117                 Debug.Assert(_state == InternalState.Alive);
118
119                 var thisStrongHandle = GCHandle.Alloc(this, GCHandleType.Normal);
120                 var thisStrongHandlePtr = GCHandle.ToIntPtr(thisStrongHandle);
121                 // The underlying code will transform the original weak handle
122                 // created by InitializeLoadContext to a strong handle
123                 PrepareForAssemblyLoadContextRelease(_nativeAssemblyLoadContext, thisStrongHandlePtr);
124
125                 _state = InternalState.Unloading;
126             }
127
128             lock (s_allContexts)
129             {
130                 s_allContexts.Remove(_id);
131             }
132         }
133
134         public IEnumerable<Assembly> Assemblies
135         {
136             get
137             {
138                 foreach (Assembly a in GetLoadedAssemblies())
139                 {
140                     AssemblyLoadContext alc = GetLoadContext(a);
141
142                     if (alc == this)
143                     {
144                         yield return a;
145                     }
146                 }
147             }
148         }
149
150         // Event handler for resolving native libraries.
151         // This event is raised if the native library could not be resolved via
152         // the default resolution logic [including AssemblyLoadContext.LoadUnmanagedDll()]
153         // 
154         // Inputs: Invoking assembly, and library name to resolve
155         // Returns: A handle to the loaded native library
156         public event Func<Assembly, string, IntPtr> ResolvingUnmanagedDll;
157
158         // Event handler for resolving managed assemblies.
159         // This event is raised if the managed assembly could not be resolved via
160         // the default resolution logic [including AssemblyLoadContext.Load()]
161         // 
162         // Inputs: The AssemblyLoadContext and AssemblyName to be loaded
163         // Returns: The Loaded assembly object.
164         public event Func<AssemblyLoadContext, AssemblyName, Assembly> Resolving;
165
166         public event Action<AssemblyLoadContext> Unloading;
167
168         // Occurs when an Assembly is loaded
169         public static event AssemblyLoadEventHandler AssemblyLoad;
170
171         // Occurs when resolution of type fails
172         public static event ResolveEventHandler TypeResolve;
173
174         // Occurs when resolution of resource fails
175         public static event ResolveEventHandler ResourceResolve;
176
177         // Occurs when resolution of assembly fails
178         // This event is fired after resolve events of AssemblyLoadContext fails
179         public static event ResolveEventHandler AssemblyResolve;
180
181         public static AssemblyLoadContext Default => DefaultAssemblyLoadContext.s_loadContext;
182
183         public bool IsCollectible { get; }
184
185         public string Name { get; }
186
187         public override string ToString() => "\"" + Name + "\" " + GetType().ToString() + " #" + _id;
188
189         public static IEnumerable<AssemblyLoadContext> All
190         {
191             get
192             {
193                 AssemblyLoadContext d = AssemblyLoadContext.Default; // Ensure default is initialized
194
195                 List<WeakReference<AssemblyLoadContext>> alcList = null;
196                 lock (s_allContexts)
197                 {
198                     // To make this thread safe we need a quick snapshot while locked
199                     alcList = new List<WeakReference<AssemblyLoadContext>>(s_allContexts.Values);
200                 }
201
202                 foreach (WeakReference<AssemblyLoadContext> weakAlc in alcList)
203                 {
204                     AssemblyLoadContext alc = null;
205
206                     weakAlc.TryGetTarget(out alc);
207
208                     if (alc != null)
209                     {
210                         yield return alc;
211                     }
212                 }
213             }
214         }
215
216         // Helper to return AssemblyName corresponding to the path of an IL assembly
217         public static AssemblyName GetAssemblyName(string assemblyPath)
218         {
219             if (assemblyPath == null)
220             {
221                 throw new ArgumentNullException(nameof(assemblyPath));
222             }
223
224             return AssemblyName.GetAssemblyName(assemblyPath);
225         }
226
227         // Custom AssemblyLoadContext implementations can override this
228         // method to perform custom processing and use one of the protected
229         // helpers above to load the assembly.
230         protected virtual Assembly Load(AssemblyName assemblyName)
231         {
232             return null;
233         }
234
235         [System.Security.DynamicSecurityMethod] // Methods containing StackCrawlMark local var has to be marked DynamicSecurityMethod
236         public Assembly LoadFromAssemblyName(AssemblyName assemblyName)
237         {
238             if (assemblyName == null)
239                 throw new ArgumentNullException(nameof(assemblyName));
240
241             // Attempt to load the assembly, using the same ordering as static load, in the current load context.
242             StackCrawlMark stackMark = StackCrawlMark.LookForMyCaller;
243             return Assembly.Load(assemblyName, ref stackMark, _nativeAssemblyLoadContext);
244         }
245
246         // These methods load assemblies into the current AssemblyLoadContext 
247         // They may be used in the implementation of an AssemblyLoadContext derivation
248         public Assembly LoadFromAssemblyPath(string assemblyPath)
249         {
250             if (assemblyPath == null)
251             {
252                 throw new ArgumentNullException(nameof(assemblyPath));
253             }
254
255             if (PathInternal.IsPartiallyQualified(assemblyPath))
256             {
257                 throw new ArgumentException(SR.Argument_AbsolutePathRequired, nameof(assemblyPath));
258             }
259
260             lock (_unloadLock)
261             {
262                 VerifyIsAlive();
263
264                 return InternalLoadFromPath(assemblyPath, null);
265             }
266         }
267
268         public Assembly LoadFromNativeImagePath(string nativeImagePath, string assemblyPath)
269         {
270             if (nativeImagePath == null)
271             {
272                 throw new ArgumentNullException(nameof(nativeImagePath));
273             }
274
275             if (PathInternal.IsPartiallyQualified(nativeImagePath))
276             {
277                 throw new ArgumentException(SR.Argument_AbsolutePathRequired, nameof(nativeImagePath));
278             }
279
280             if (assemblyPath != null && PathInternal.IsPartiallyQualified(assemblyPath))
281             {
282                 throw new ArgumentException(SR.Argument_AbsolutePathRequired, nameof(assemblyPath));
283             }
284
285             lock (_unloadLock)
286             {
287                 VerifyIsAlive();
288
289                 return InternalLoadFromPath(assemblyPath, nativeImagePath);
290             }
291         }        
292
293         public Assembly LoadFromStream(Stream assembly)
294         {
295             return LoadFromStream(assembly, null);
296         }
297
298         public Assembly LoadFromStream(Stream assembly, Stream assemblySymbols)
299         {
300             if (assembly == null)
301             {
302                 throw new ArgumentNullException(nameof(assembly));
303             }
304
305             int iAssemblyStreamLength = (int)assembly.Length;
306
307             if (iAssemblyStreamLength <= 0)
308             {
309                 throw new BadImageFormatException(SR.BadImageFormat_BadILFormat);
310             }
311
312             // Allocate the byte[] to hold the assembly
313             byte[] arrAssembly = new byte[iAssemblyStreamLength];
314
315             // Copy the assembly to the byte array
316             assembly.Read(arrAssembly, 0, iAssemblyStreamLength);
317
318             // Get the symbol stream in byte[] if provided
319             byte[] arrSymbols = null;
320             if (assemblySymbols != null)
321             {
322                 var iSymbolLength = (int)assemblySymbols.Length;
323                 arrSymbols = new byte[iSymbolLength];
324
325                 assemblySymbols.Read(arrSymbols, 0, iSymbolLength);
326             }
327
328             lock (_unloadLock)
329             {
330                 VerifyIsAlive();
331
332                 return InternalLoad(arrAssembly, arrSymbols);
333             }
334         }
335
336         // This method provides a way for overriders of LoadUnmanagedDll() to load an unmanaged DLL from a specific path in a
337         // platform-independent way. The DLL is loaded with default load flags.
338         protected IntPtr LoadUnmanagedDllFromPath(string unmanagedDllPath)
339         {
340             if (unmanagedDllPath == null)
341             {
342                 throw new ArgumentNullException(nameof(unmanagedDllPath));
343             }
344
345             if (unmanagedDllPath.Length == 0)
346             {
347                 throw new ArgumentException(SR.Argument_EmptyPath, nameof(unmanagedDllPath));
348             }
349
350             if (PathInternal.IsPartiallyQualified(unmanagedDllPath))
351             {
352                 throw new ArgumentException(SR.Argument_AbsolutePathRequired, nameof(unmanagedDllPath));
353             }
354
355             return InternalLoadUnmanagedDllFromPath(unmanagedDllPath);
356         }
357
358         // Custom AssemblyLoadContext implementations can override this
359         // method to perform the load of unmanaged native dll
360         // This function needs to return the HMODULE of the dll it loads
361         protected virtual IntPtr LoadUnmanagedDll(string unmanagedDllName)
362         {
363             //defer to default coreclr policy of loading unmanaged dll
364             return IntPtr.Zero;
365         }        
366
367         public void Unload()
368         {
369             if (!IsCollectible)
370             {
371                 throw new InvalidOperationException(SR.AssemblyLoadContext_Unload_CannotUnloadIfNotCollectible);
372             }
373
374             GC.SuppressFinalize(this);
375             InitiateUnload();
376         }
377
378         internal static void OnProcessExit()
379         {
380             lock (s_allContexts)
381             {
382                 foreach (var alcAlive in s_allContexts)
383                 {
384                     if (alcAlive.Value.TryGetTarget(out AssemblyLoadContext alc))
385                     {
386                         alc.RaiseUnloadEvent();
387                     }
388                 }
389             }
390         }        
391
392         private void VerifyIsAlive()
393         {
394             if (_state != InternalState.Alive)
395             {
396                 throw new InvalidOperationException(SR.AssemblyLoadContext_Verify_NotUnloading);
397             }
398         }
399     }
400
401     internal sealed class DefaultAssemblyLoadContext : AssemblyLoadContext
402     {
403         internal static readonly AssemblyLoadContext s_loadContext = new DefaultAssemblyLoadContext();
404
405         internal DefaultAssemblyLoadContext() : base(true, false, "Default")
406         {
407         }
408     }
409
410     internal sealed class IndividualAssemblyLoadContext : AssemblyLoadContext
411     {
412         internal IndividualAssemblyLoadContext(string name) : base(false, false, name)
413         {
414         }
415     }
416 }