-
Notifications
You must be signed in to change notification settings - Fork 6k
Add unloadability howto document #10256
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
``` | ||
As you can see, the `Load` method returns `null`. That means that all the dependency assemblies are loaded into the default context and only the assemblies explicitly loaded into the new context are in this context. | ||
|
||
In case you want to load some or all of the dependencies into the `AssemblyLoadContext` too, you can use the `AssemblyDependencyResolver` in the `Load` method. The `AssemblyDependencyResolver` resolves the assembly names to absolute assembly file paths using the *.deps.json file contained in the directory of the main assembly loaded into the context and using assembly files in that directory. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In case you want to load some or all of the dependencies into the `AssemblyLoadContext` too, you can use the `AssemblyDependencyResolver` in the `Load` method. The `AssemblyDependencyResolver` resolves the assembly names to absolute assembly file paths using the *.deps.json file contained in the directory of the main assembly loaded into the context and using assembly files in that directory. | |
In case you want to load some or all of the dependencies into the `AssemblyLoadContext` too, you can use the `AssemblyDependencyResolver` in the `Load` method. The `AssemblyDependencyResolver` resolves the assembly names to absolute assembly file paths using the `*.deps.json` file contained in the directory of the main assembly loaded into the context and using assembly files in that directory. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jkoritzinsky added a plugin sample for this. Reference it?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you mean a sample that uses *.deps.json in some interesting way or a just a sample that uses AssemblyDependencyResolver? If it is the latter, I already have a link above to my own unloadability sample that uses it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is basically a sample that is the plugin scenario - don't think that it does anything clever. However it is in the official samples repo, which seems like what we should reference instead of having additional examples of the same concept? Does your example do anything different than https://github.com/dotnet/samples/tree/master/core/extensions/AppWithPlugin with respect to AssemblyDependencyResolver
? The unload concept is definitely important for the AssemblyLoadContext
class, but seems like AssemblyDependencyResolver
is orthogonal or rather an implementation detail of using AssemblyLoadContext
in general and doesn't relate to unloading.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My sample is also in that official sample repo. I've merged it in two days ago. It covers unloadability and since I wanted to make it a boiler plate that people can use, I've used the AssemblyDependencyResolver there too. If you've missed the link in the text above, it is here:
https://github.com/dotnet/samples/tree/master/core/tutorials/Unloading
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In that case you should use a code reference tag to automatically import the code from the sample like the following
[!code-csharp[Assembly-load-context-with-assembly-dependency-resolver](~/samples/core/tutorials/Unloading/Host/Program.cs)]
You might also want to seperate out the ALC into a different file to make the code reference work a little cleaner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't want to include the code from my sample here. I see this doc and the sample as separate entities. This doc focuses on a simple version where the Load method always returns NULL and just mentions the more advanced way briefly so that people know it exist.
The sample is a separate full blown end to end example of using unloadable ALC that complements this article. I didn't actually know you were working on your sample and frankly even didn't know about the AssemblyDependencyResolver existence when I've completed the first version of my sample and I was originally using just the simple ALC returning NULL from the Load overried. @jkotas then pointed out during review of that sample that it might be nice to make it more complete by adding the usage of the AssemblyDependencyResolver. Thus I've added it there and mentioned it in this doc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jkoritzinsky btw, the code inclusion tag you've suggested doesn't seem to work. Looking at your doc PR and viewing the file rendered, it shows just the following:
[!code-csharpthe-plugin-interface]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That tag is a specially handled by the markdown engine that docs.microsoft.com uses. Github doesn't understand it fully.
|
||
With the new way that uses `AssemblyLoadContext`, the unload is "cooperative". Calling the `Unload` method on the `AssemblyLoadContext` just initiates the unloading. But the unload will not complete until there are no threads having code from the assemblies loaded into the `AssemblyLoadContext` on their call stacks and until there are no strong references to types from the assemblies loaded into the `AssemblyLoadContext`, their instances and the assemblies themselves. And that means that GC needs to collect those first. | ||
## Using unloadable AssemblyLoadContext | ||
You can find a complete sample [at this link](https://github.com/dotnet/samples/tree/master/core/tutorials/Unloading). This section contains step by step detailed tutorial on a simplest way how to load a .NET Core application into an unloadable AssemblyLoadContext, execute its entry point and then unload it. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"simplest way how" -> "simple way to"
Comma after "entry point,"
WeakReference testAlcWeakRef; | ||
int result = ExecuteAndUnload("absolute/path/to/your/assembly", out testAlcWeakRef); | ||
``` | ||
However, the unloading doesn't complete immediately. As I've already mentioned, it relies on GC to collect all the objects from the test assembly (etc.). In many cases, it is not necessary to wait for the unload completion. However there are cases where it is useful to know that the unload has finished. For example, you may want to delete the assembly file that was loaded into the custom context from disk. In such case, the following code snippet can be used. It triggers a GC 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. Please note that 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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove all uses of "I". In this case, As I've already mentioned, -> "As previously mentioned,"
} | ||
``` | ||
### The Unloading event | ||
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, cleanup some strong GC handles, etc. The `Unloading` event can be used in such cases. A handler that performs the necessary cleanup can be hooked to this event. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unloading should be quoted not back-ticked.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unloading is the name of the event, hence the back-ticks.
### The Unloading event | ||
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, cleanup some strong GC handles, etc. The `Unloading` event can be used in such cases. A handler that performs the necessary cleanup can be hooked to this event. | ||
### Troubleshooting unloadability issues | ||
While the previous paragraphs may sound like it is all easy and simple, it is often not the case. Due to the cooperative nature of the unloading, it is easy to forget about references keeping the stuff in the custom AssemblyLoadContext alive and preventing unload. Here is a summary of things (some of them non-obvious) that can hold the references: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a technical document, commentary can be removed.
While the previous paragraphs may sound like it is all easy and simple, it is often not the case.
* Type from such an assembly | ||
* Instance of a type from such an assembly | ||
* Threads running code from an assembly loaded into the custom AssemblyLoadContext | ||
* Instances of non-collectible AssemblyLoadContext |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are inconsistent on back-ticks around AssemblyLoadContext
and types in general.
* Threads running code from an assembly loaded into the custom AssemblyLoadContext | ||
* Instances of non-collectible AssemblyLoadContext | ||
* Pending RegisteredWaitHandle instances | ||
The above mentioned reference can come from any object with root in a stack slot or processor register (method locals, either explicitly created by the user code or implicitly by the JIT), a static variable or strong / pinning GC handle. TODO: explain what root means |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TODO: explain what root means
Is there a nice definition in the BOTR we can reference? If not, I would add one there.
In LLDB: | ||
plugin load /path/to/libsosplugin.so | ||
``` | ||
Let's try to debug an example program that has problems with unloading. I have included its source code below. When you run it under WinDbg, the program breaks into the debugger right after attempting to check for the unload success. We can then start looking for the culprits. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have included its source code below -> "Source code is included below."
@AaronRobinsonMSFT I've updated the doc based on your feedback. The only place where I've left the type names without backticks is in the section titles, as it looks awkward in those. |
Oh, I've forgotten about the TODO about explaining gc root. I am going to add that. I have not found it in any doc. |
I've actually reworded the text that was mentioning the roots before and got rid of the "roots" name there as it doesn't seem to be necessary for the explanation. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good, @janvorli. I've left a number of questions, comments, and suggestions for you to consider. You can bulk-review my suggested changes by reviewing from the Files changes tab.
@@ -0,0 +1,359 @@ | |||
# Using and debugging unloadability in .NET Core | |||
An ability load and later unload a set of assemblies is one of the features that were missing until now in the .NET Core. The desktop .NET has AppDomains that people can use for the purpose of the unloading. Since AppDomains were removed from the .NET Core, how can we achieve that goal there? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An ability load and later unload a set of assemblies is one of the features that were missing until now in the .NET Core. The desktop .NET has AppDomains that people can use for the purpose of the unloading. Since AppDomains were removed from the .NET Core, how can we achieve that goal there? | |
An ability to load and later unload a set of assemblies is one of the features that was missing until now in .NET Core. In .NET Framework, custom app domains are used for this purpose. But since .NET Core supports only a single default .app domain, how can we achieve that goal in .NET Core? |
The .NET Core 3.0 brings support of unloadability using `AssemblyLoadContext`. You can now load a set of assemblies into an `AssemblyLoadContext`, execute methods in them or just inspect them using reflection and finally unload the `AssemblyLoadContext`. That results in unloading of the assemblies loaded into that `AssemblyLoadContext`. | ||
|
||
There is one noteworthy difference between the unloading using `AssemblyLoadContext` and using AppDomains. With AppDomains, the unloading is forced. At the unload time, all threads running in the target AppDomain are aborted, managed COM objects created in the AppDomain that's being unloaded are destroyed, etc. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No paragraph break here. This should be a paragraph about the noteworthy difference.
@rpetrusha thank you so much for the detailed review and all the suggested corrections of the grammar. I can see I should get better especially in the comma usage. |
LGTM. |
There's a broken link in the build report and the article needs to be added to the TOC file as well (https://github.com/dotnet/docs/blob/master/docs/toc.md) |
The broken link in the build report is to a .NET Core 3.0 API, @mairaw. I thought that it would be better to leave the xref than fence it and have to change it later. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this @janvorli. I've added some initial comments to get this going.
## Example source with unloadability issues | ||
This example is used in the debugging above. | ||
### Main testing program | ||
```csharp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do you have this in the dotnet/samples repo? if so, we should load the file directly from there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a pending PR to put all the snippets into that repo. After it is approved and merged, I'll update this doc to not to have any inline code.
Sounds good @rpetrusha about the link to the API! |
@mairaw thank you so much for the review and the info on the docs authoring pack. I didn't know such a thing exists. |
@rpetrusha, regarding the broken link - I wonder if the link is really correct, as it contains |
017f06b
to
c6ec9b9
Compare
@janvorli, ordinarily, links have to be fully qualified with parameter types. %2A indicates that the link should be to the overload page rather than to a specific overload. |
Closing and reopening to begin new build after dotnet/samples#617 was merged. |
@rpetrusha, @mairaw I've updated the doc so that the last two code blocks are not inline anymore and point to the samples repo. As for the rest of those, I am actually wondering whether to leave them all inline or whether to update the doc to replace the ones that are compilable with a reference to the samples and only keep the snippets that cannot be compiled on their own inline. |
@rpetrusha, @mairaw can you please reply to my previous comment? I would like to merge the doc in soon. |
I apologize for the long delay in responding, @janvorli. The best option is to place all compilable samples in the samples repo, with small snippets inline. In addition, though, some of the inline snippets can be referenced from the samples repo. For example, the currently inline code
is included in your main testing program. So we'd want to pull that snippet into the build, instead of having two versions existing independently. The way to do that is to add a sniippet tag with a label (the snippet tags are removed from the documentation when it's built):
Then add the reference to the snippet to the topic by including the snippet label without the string "snippet": [!code-csharp[Main testing program](~/samples/snippets/core/tutorials/unloading/unloadability_issues_example_main.cs#1)] But it's OK to leave most the remaining one- or two-line snippets in the "Use a custom collectible AssemblyLoadContext" section inline, since they don't exactly correspond to code that would go in the repo. |
I've somehow screwed the commits. Will fix it soon. |
8ee97ca
to
079e51e
Compare
@karelz, @JRAlexander, @BillWagner, @cartermp, @Lxiamail - please ignore the request to review. I have not sent them, they somehow got added automatically based on a bad merge commit that I've fixed. |
079e51e
to
f33d98b
Compare
@rpetrusha I've tried to reference the snippets, but it works only for the snippet #1, for the rest it complains that the links are invalid. I have no idea what could be causing that. Do you have any idea what can be wrong? |
|
||
You can create an instance of the custom `AssemblyLoadContext` and load an assembly into it as follows: | ||
|
||
!code-csharp[Part 1](~/samples/snippets/standard/assembly/unloading/simple_example.cs#3)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're just missing part of the syntax
!code-csharp[Part 1](~/samples/snippets/standard/assembly/unloading/simple_example.cs#3)] | |
[!code-csharp[Part 1](~/samples/snippets/standard/assembly/unloading/simple_example.cs#3)] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, it was right under my nose! Thank you!
This document describes how to use unloadability in .NET Core 3.0 and how to debug issues with unloading. It also contains a link to a complete sample in dotnet/samples repo.
f33d98b
to
b4dd93a
Compare
Also, feel free to just push new commits, since we squash all of them when we merge. We like to have each PR as a single commit in our history. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm approving the PR but left a few suggestions for you to consider. @rpetrusha will have to approve it as well since he requested some changes earlier.
Co-Authored-By: janvorli <janvorli@microsoft.com>
Co-Authored-By: janvorli <janvorli@microsoft.com>
Co-Authored-By: janvorli <janvorli@microsoft.com>
Co-Authored-By: janvorli <janvorli@microsoft.com>
Closing and reopeing so that it picks a typo fix in a snippet tag in the samples repo |
This looks good, @janvorli. I'll merge your PR now. |
Summary
This document describes how to use unloadability in .NET Core 3.0 and
how to debug issues with unloading.
It also contains a link to a complete sample in dotnet/samples repo.