Skip to content

Commit bb8b25f

Browse files
authored
remove -all option from gcroot (dotnet#38038)
1 parent b0508a2 commit bb8b25f

File tree

1 file changed

+23
-23
lines changed

1 file changed

+23
-23
lines changed

docs/standard/assembly/unloadability.md

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@ title: "How to use and debug assembly unloadability in .NET"
33
description: "Learn how to use collectible AssemblyLoadContext for loading and unloading managed assemblies and how to debug issues preventing the unloading success."
44
author: "janvorli"
55
ms.author: "janvorli"
6-
ms.date: 09/16/2022
6+
ms.date: 11/13/2023
77
ms.topic: how-to
88
---
99
# How to use and debug assembly unloadability in .NET
1010

11-
.NET Core 3.0 introduced the ability to load and later unload a set of assemblies. In .NET Framework, custom app domains were used for this purpose, but .NET \[Core] only supports a single default app domain.
11+
.NET (Core) introduced the ability to load and later unload a set of assemblies. In .NET Framework, custom app domains were used for this purpose, but .NET (Core) only supports a single default app domain.
1212

1313
Unloadability is supported through <xref:System.Runtime.Loader.AssemblyLoadContext>. You can load a set of assemblies into a collectible `AssemblyLoadContext`, execute methods in them or just inspect them using reflection, and finally unload the `AssemblyLoadContext`. That unloads the assemblies loaded into the `AssemblyLoadContext`.
1414

@@ -21,7 +21,7 @@ There's one noteworthy difference between the unloading using `AssemblyLoadConte
2121

2222
## Use collectible AssemblyLoadContext
2323

24-
This section contains a detailed step-by-step tutorial that shows a simple way to load a .NET Core application into a collectible `AssemblyLoadContext`, execute its entry point, and then unload it. You can find a complete sample at [https://github.com/dotnet/samples/tree/main/core/tutorials/Unloading](https://github.com/dotnet/samples/tree/main/core/tutorials/Unloading).
24+
This section contains a detailed step-by-step tutorial that shows a simple way to load a .NET (Core) application into a collectible `AssemblyLoadContext`, execute its entry point, and then unload it. You can find a complete sample at <https://github.com/dotnet/samples/tree/main/core/tutorials/Unloading>.
2525

2626
### Create a collectible AssemblyLoadContext
2727

@@ -45,17 +45,17 @@ You can create an instance of the custom `AssemblyLoadContext` and load an assem
4545

4646
[!code-csharp[Part 1](~/samples/snippets/standard/assembly/unloading/simple_example.cs#3)]
4747

48-
For each of the assemblies referenced by the loaded assembly, the `TestAssemblyLoadContext.Load` method is called so that the `TestAssemblyLoadContext` can decide where to get the assembly from. In our case, it returns `null` to indicate that it should be loaded into the default context from locations that the runtime uses to load assemblies by default.
48+
For each of the assemblies referenced by the loaded assembly, the `TestAssemblyLoadContext.Load` method is called so that the `TestAssemblyLoadContext` can decide where to get the assembly from. In this case, it returns `null` to indicate that it should be loaded into the default context from locations that the runtime uses to load assemblies by default.
4949

5050
Now that an assembly was loaded, you can execute a method from it. Run the `Main` method:
5151

5252
[!code-csharp[Part 2](~/samples/snippets/standard/assembly/unloading/simple_example.cs#4)]
5353

54-
After the `Main` method returns, you can initiate unloading by either calling the `Unload` method on the custom `AssemblyLoadContext` or getting rid of the reference you have to the `AssemblyLoadContext`:
54+
After the `Main` method returns, you can initiate unloading by either calling the `Unload` method on the custom `AssemblyLoadContext` or removing the reference you have to the `AssemblyLoadContext`:
5555

5656
[!code-csharp[Part 3](~/samples/snippets/standard/assembly/unloading/simple_example.cs#5)]
5757

58-
This is sufficient to unload the test assembly. Let's actually put all of this into a separate non-inlineable method to ensure that the `TestAssemblyLoadContext`, `Assembly`, and `MethodInfo` (the `Assembly.EntryPoint`) can't be kept alive by stack slot references (real- or JIT-introduced locals). That could keep the `TestAssemblyLoadContext` alive and prevent the unload.
58+
This is sufficient to unload the test assembly. Next, you'll put all of this into a separate noninlineable method to ensure that the `TestAssemblyLoadContext`, `Assembly`, and `MethodInfo` (the `Assembly.EntryPoint`) can't be kept alive by stack slot references (real- or JIT-introduced locals). That could keep the `TestAssemblyLoadContext` alive and prevent the unload.
5959

6060
Also, return a weak reference to the `AssemblyLoadContext` so that you can use it later to detect unload completion.
6161

@@ -65,24 +65,24 @@ Now you can run this function to load, execute, and unload the assembly.
6565

6666
[!code-csharp[Part 5](~/samples/snippets/standard/assembly/unloading/simple_example.cs#6)]
6767

68-
However, the unload doesn't complete immediately. As previously mentioned, it relies on the garbage collector to collect all the objects from the test assembly. In many cases, it isn't necessary to wait for the unload completion. However, there are cases where it's useful to know that the unload has finished. For example, you may want to delete the assembly file that was loaded into the custom `AssemblyLoadContext` from disk. In such a case, the following code snippet can be used. It triggers garbage collection and waits for pending finalizers in a loop until the weak reference to the custom `AssemblyLoadContext` is set to `null`, indicating the target object was collected. In most cases, just one pass through the loop is required. However, for more complex cases where objects created by the code running in the `AssemblyLoadContext` have finalizers, more passes may be needed.
68+
However, the unload doesn't complete immediately. As previously mentioned, it relies on the garbage collector to collect all the objects from the test assembly. In many cases, it isn't necessary to wait for the unload completion. However, there are cases where it's useful to know that the unload has finished. For example, you might want to delete the assembly file that was loaded into the custom `AssemblyLoadContext` from disk. In such a case, the following code snippet can be used. It triggers garbage collection and waits for pending finalizers in a loop until the weak reference to the custom `AssemblyLoadContext` is set to `null`, indicating the target object was collected. In most cases, just one pass through the loop is required. However, for more complex cases where objects created by the code running in the `AssemblyLoadContext` have finalizers, more passes might be needed.
6969

7070
[!code-csharp[Part 6](~/samples/snippets/standard/assembly/unloading/simple_example.cs#7)]
7171

7272
### The Unloading event
7373

74-
In some cases, it may be necessary for the code loaded into a custom `AssemblyLoadContext` to perform some cleanup when the unloading is initiated. For example, it may need to stop threads or clean up strong GC handles. The `Unloading` event can be used in such cases. A handler that performs the necessary cleanup can be hooked to this event.
74+
In some cases, it might be necessary for the code loaded into a custom `AssemblyLoadContext` to perform some cleanup when the unloading is initiated. For example, it might need to stop threads or clean up strong GC handles. The `Unloading` event can be used in such cases. You can hook a handler that performs the necessary cleanup to this event.
7575

7676
### Troubleshoot unloadability issues
7777

78-
Due to the cooperative nature of the unloading, it's easy to forget about references that may be keeping the stuff in a collectible `AssemblyLoadContext` alive and preventing unload. Here is a summary of entities (some of them non-obvious) that can hold the references:
78+
Due to the cooperative nature of the unloading, it's easy to forget about references that might be keeping the stuff in a collectible `AssemblyLoadContext` alive and preventing unload. Here's a summary of entities (some of them nonobvious) that can hold the references:
7979

8080
- Regular references held from outside of the collectible `AssemblyLoadContext` that are stored in a stack slot or a processor register (method locals, either explicitly created by the user code or implicitly by the just-in-time (JIT) compiler), a static variable, or a strong (pinning) GC handle, and transitively pointing to:
8181
- An assembly loaded into the collectible `AssemblyLoadContext`.
8282
- A type from such an assembly.
8383
- An instance of a type from such an assembly.
8484
- Threads running code from an assembly loaded into the collectible `AssemblyLoadContext`.
85-
- Instances of custom, non-collectible `AssemblyLoadContext` types created inside of the collectible `AssemblyLoadContext`.
85+
- Instances of custom, noncollectible `AssemblyLoadContext` types created inside of the collectible `AssemblyLoadContext`.
8686
- Pending <xref:System.Threading.RegisteredWaitHandle> instances with callbacks set to methods in the custom `AssemblyLoadContext`.
8787

8888
> [!TIP]
@@ -93,11 +93,11 @@ Due to the cooperative nature of the unloading, it's easy to forget about refere
9393
9494
## Debug unloading issues
9595

96-
Debugging issues with unloading can be tedious. You can get into situations where you don't know what can be holding an `AssemblyLoadContext` alive, but the unload fails. The best weapon to help with that is WinDbg (LLDB on Unix) with the SOS plugin. You need to find what's keeping a `LoaderAllocator` belonging to the specific `AssemblyLoadContext` alive. The SOS plugin allows you to look at GC heap objects, their hierarchies, and roots.
96+
Debugging issues with unloading can be tedious. You can get into situations where you don't know what can be holding an `AssemblyLoadContext` alive, but the unload fails. The best tool to help with that is WinDbg (or LLDB on Unix) with the SOS plugin. You need to find what's keeping a `LoaderAllocator` that belongs to the specific `AssemblyLoadContext` alive. The SOS plugin lets you look at GC heap objects, their hierarchies, and roots.
9797

98-
To load the plugin into the debugger, enter the following command in the debugger command line:
98+
To load the SOS plugin into the debugger, enter one of the following commands in the debugger command line.
9999

100-
In WinDbg (it seems WinDbg does that automatically when breaking into .NET Core application):
100+
In WinDbg (if it's not already loaded):
101101

102102
```console
103103
.loadby sos coreclr
@@ -109,7 +109,7 @@ In LLDB:
109109
plugin load /path/to/libsosplugin.so
110110
```
111111

112-
Let's debug an example program that has problems with unloading. Source code is included below. When you run it under WinDbg, the program breaks into the debugger right after attempting to check for the unload success. You can then start looking for the culprits.
112+
Now you'll debug an example program that has problems with unloading. The source code is available in the [Example source code](#example-source-code) section. When you run it under WinDbg, the program breaks into the debugger right after attempting to check for the unload success. You can then start looking for the culprits.
113113

114114
> [!TIP]
115115
> If you debug using LLDB on Unix, the SOS commands in the following examples don't have the `!` in front of them.
@@ -118,7 +118,7 @@ Let's debug an example program that has problems with unloading. Source code is
118118
!dumpheap -type LoaderAllocator
119119
```
120120

121-
This command dumps all objects with a type name containing `LoaderAllocator` that are in the GC heap. Here is an example:
121+
This command dumps all objects with a type name containing `LoaderAllocator` that are in the GC heap. Here's an example:
122122

123123
```console
124124
Address MT Size
@@ -132,17 +132,17 @@ Statistics:
132132
Total 2 objects
133133
```
134134

135-
In the "Statistics:" part below, check the `MT` (`MethodTable`) belonging to the `System.Reflection.LoaderAllocator`, which is the object we care about. Then, in the list at the beginning, find the entry with `MT` matching that one and get the address of the object itself. In our case, it is "000002b78000ce40".
135+
In the "Statistics:" part, check the `MT` (`MethodTable`) that belongs to the `System.Reflection.LoaderAllocator`, which is the object you care about. Then, in the list at the beginning, find the entry with `MT` that matches that one, and get the address of the object itself. In this case, it's "000002b78000ce40".
136136

137-
Now that we know the address of the `LoaderAllocator` object, we can use another command to find its GC roots:
137+
Now that you know the address of the `LoaderAllocator` object, you can use another command to find its GC roots:
138138

139139
```console
140-
!gcroot -all 0x000002b78000ce40
140+
!gcroot 0x000002b78000ce40
141141
```
142142

143-
This command dumps the chain of object references that lead to the `LoaderAllocator` instance. The list starts with the root, which is the entity that keeps our `LoaderAllocator` alive and thus is the core of the problem. The root can be a stack slot, a processor register, a GC handle, or a static variable.
143+
This command dumps the chain of object references that lead to the `LoaderAllocator` instance. The list starts with the root, which is the entity that keeps the `LoaderAllocator` alive and thus is the core of the problem. The root can be a stack slot, a processor register, a GC handle, or a static variable.
144144

145-
Here is an example of the output of the `gcroot` command:
145+
Here's an example of the output of the `gcroot` command:
146146

147147
```console
148148
Thread 4ac:
@@ -175,7 +175,7 @@ In the previous example, the first root is a local of type `System.Reflection.Ru
175175

176176
The second root is a normal (strong) `GCHandle` that holds a reference to an instance of the `test.Test` class.
177177

178-
The third root is a pinned `GCHandle`. This one is actually a static variable, but unfortunately, there is no way to tell. Statics for reference types are stored in a managed object array in internal runtime structures.
178+
The third root is a pinned `GCHandle`. This one is actually a static variable, but unfortunately, there's no way to tell. Statics for reference types are stored in a managed object array in internal runtime structures.
179179

180180
Another case that can prevent unloading of an `AssemblyLoadContext` is when a thread has a frame of a method from an assembly loaded into the `AssemblyLoadContext` on its stack. You can check that by dumping managed call stacks of all threads:
181181

@@ -233,9 +233,9 @@ OS Thread Id: 0x60bc (7)
233233

234234
As you can see, the last thread has `test.Program.ThreadProc()`. This is a function from the assembly loaded into the `AssemblyLoadContext`, and so it keeps the `AssemblyLoadContext` alive.
235235

236-
## Example source with unloadability issues
236+
## Example source code
237237

238-
The following code is used in the previous debugging example.
238+
The following code that contains unloadability issues is used in the previous debugging example.
239239

240240
### Main testing program
241241

0 commit comments

Comments
 (0)