Skip to content

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

Merged
merged 5 commits into from
Mar 1, 2019

Conversation

janvorli
Copy link
Member

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.

@janvorli janvorli requested a review from mairaw as a code owner January 31, 2019 21:54
@janvorli
Copy link
Member Author

```
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Copy link
Member

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?

Copy link
Member Author

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.

Copy link
Member

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.

Copy link
Member Author

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

Copy link
Member

@jkoritzinsky jkoritzinsky Feb 1, 2019

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.

Copy link
Member Author

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.

Copy link
Member Author

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]

Copy link
Member

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.
Copy link
Member

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.
Copy link
Member

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.
Copy link
Member

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.

Copy link
Member Author

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:
Copy link
Member

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
Copy link
Member

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
Copy link
Member

@AaronRobinsonMSFT AaronRobinsonMSFT Feb 1, 2019

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.
Copy link
Member

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."

@janvorli
Copy link
Member Author

janvorli commented Feb 1, 2019

@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.

@janvorli
Copy link
Member Author

janvorli commented Feb 1, 2019

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.

@janvorli
Copy link
Member Author

janvorli commented Feb 1, 2019

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.

Copy link
Contributor

@rpetrusha rpetrusha left a 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?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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.

Copy link
Contributor

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.

@janvorli
Copy link
Member Author

janvorli commented Feb 4, 2019

@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.
I've applied all of your suggestions except for changing one of the titles and also changed "unloadable AssemblyLoadContext" at few places to "collectible AssemblyLoadContext".
I have also created a PR containing all the code snippets from this document here: dotnet/samples#617. I will update this doc once that PR goes in to reference the snippets instead of having them inline.

@AaronRobinsonMSFT
Copy link
Member

LGTM.

@mairaw
Copy link
Contributor

mairaw commented Feb 5, 2019

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)

@rpetrusha
Copy link
Contributor

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.

Copy link
Contributor

@mairaw mairaw left a 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
Copy link
Contributor

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.

Copy link
Member Author

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.

@mairaw
Copy link
Contributor

mairaw commented Feb 5, 2019

Sounds good @rpetrusha about the link to the API!

@janvorli
Copy link
Member Author

janvorli commented Feb 5, 2019

@mairaw thank you so much for the review and the info on the docs authoring pack. I didn't know such a thing exists.

@janvorli
Copy link
Member Author

janvorli commented Feb 5, 2019

@rpetrusha, regarding the broken link - I wonder if the link is really correct, as it contains %2A in the middle. Or does it have a special meaning?
<xref:System.Runtime.Loader.AssemblyLoadContext.Unload%2A?displayProperty=nameWithType>

@janvorli janvorli force-pushed the add-unloadability-howto branch from 017f06b to c6ec9b9 Compare February 5, 2019 13:23
@rpetrusha
Copy link
Contributor

@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.

@rpetrusha
Copy link
Contributor

Closing and reopening to begin new build after dotnet/samples#617 was merged.

@rpetrusha rpetrusha closed this Feb 5, 2019
@rpetrusha rpetrusha reopened this Feb 5, 2019
@janvorli
Copy link
Member Author

janvorli commented Feb 5, 2019

@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.
Do you have a preference w.r.t. that?

@janvorli
Copy link
Member Author

@rpetrusha, @mairaw can you please reply to my previous comment? I would like to merge the doc in soon.

@rpetrusha
Copy link
Contributor

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

for (int i = 0; testAlcWeakRef.IsAlive && (i < 10); i++)
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
}

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):

// <Snippet1>
for (int i = 0; testAlcWeakRef.IsAlive && (i < 10); i++)
{
    GC.Collect();
    GC.WaitForPendingFinalizers();
}
// </Snippet1>

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.

@janvorli
Copy link
Member Author

I've somehow screwed the commits. Will fix it soon.

@janvorli janvorli force-pushed the add-unloadability-howto branch from 8ee97ca to 079e51e Compare February 28, 2019 22:13
@janvorli
Copy link
Member Author

@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.

@janvorli janvorli force-pushed the add-unloadability-howto branch from 079e51e to f33d98b Compare February 28, 2019 22:43
@janvorli
Copy link
Member Author

@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)]
Copy link
Contributor

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

Suggested change
!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)]

Copy link
Member Author

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.
@janvorli janvorli force-pushed the add-unloadability-howto branch from f33d98b to b4dd93a Compare March 1, 2019 00:00
@mairaw
Copy link
Contributor

mairaw commented Mar 1, 2019

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.

Copy link
Contributor

@mairaw mairaw left a 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.

mairaw and others added 4 commits March 1, 2019 01:47
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>
@janvorli
Copy link
Member Author

janvorli commented Mar 1, 2019

Closing and reopeing so that it picks a typo fix in a snippet tag in the samples repo

@rpetrusha
Copy link
Contributor

This looks good, @janvorli. I'll merge your PR now.

@rpetrusha rpetrusha merged commit a6d44e2 into dotnet:master Mar 1, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants