Skip to content

Add versioning details for AssemblyLoadContext #30748

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 2 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 14 additions & 22 deletions docs/core/dependency-loading/loading-managed.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ author: sdmaclea
---
# Managed assembly loading algorithm

Managed assemblies are located and loaded with an algorithm involving various stages.
Managed assemblies are located and loaded with an algorithm that has various stages.

All managed assemblies except satellite assemblies and `WinRT` assemblies use the same algorithm.

## When are managed assemblies loaded?

The most common mechanism to trigger a managed assembly load is a static assembly reference. These references are inserted by the compiler whenever code uses a type defined in another assembly. These assemblies are loaded (`load-by-name`) as needed by the runtime. The exact timing of when the static assembly references are loaded is unspecified. It can vary between runtime versions and it is influenced by optimizations like inlining.
The most common mechanism to trigger a managed assembly load is a static assembly reference. These references are inserted by the compiler whenever code uses a type defined in another assembly. These assemblies are loaded (`load-by-name`) as needed by the runtime. The exact timing of when the static assembly references are loaded is unspecified. It can vary between runtime versions and is influenced by optimizations like inlining.

The direct use of specific APIs will also trigger loads:
The direct use of the following APIs will also trigger loads:

|API |Description |`Active` <xref:System.Runtime.Loader.AssemblyLoadContext> |
|---------|---------|---------|
Expand All @@ -38,27 +38,19 @@ The following algorithm describes how the runtime loads a managed assembly.
- For a static assembly reference, the `active` <xref:System.Runtime.Loader.AssemblyLoadContext> is the instance that loaded the referring assembly.
- Preferred APIs make the `active` <xref:System.Runtime.Loader.AssemblyLoadContext> explicit.
- Other APIs infer the `active` <xref:System.Runtime.Loader.AssemblyLoadContext>. For these APIs, the <xref:System.Runtime.Loader.AssemblyLoadContext.CurrentContextualReflectionContext?displayProperty=nameWithType> property is used. If its value is `null`, then the inferred <xref:System.Runtime.Loader.AssemblyLoadContext> instance is used.
- See table above.
- See the table in the [When are managed assemblies loaded?](#when-are-managed-assemblies-loaded) section.

2. For the `Load-by-name` methods, the `active` <xref:System.Runtime.Loader.AssemblyLoadContext> loads the assembly. In priority order by:
- Checking its `cache-by-name`.
2. For the `Load-by-name` methods, the `active` <xref:System.Runtime.Loader.AssemblyLoadContext> loads the assembly in the following priority order:

- Calling the <xref:System.Runtime.Loader.AssemblyLoadContext.Load%2A?displayProperty=nameWithType> function.
- Check its `cache-by-name`.
- Call the <xref:System.Runtime.Loader.AssemblyLoadContext.Load%2A?displayProperty=nameWithType> function.
- Check the <xref:System.Runtime.Loader.AssemblyLoadContext.Default%2A?displayProperty=nameWithType> instance's cache and run [managed assembly default probing](default-probing.md#managed-assembly-default-probing) logic. If an assembly is newly loaded, a reference is added to the <xref:System.Runtime.Loader.AssemblyLoadContext.Default%2A?displayProperty=nameWithType> instance's `cache-by-name`.
- Raise the <xref:System.Runtime.Loader.AssemblyLoadContext.Resolving?displayProperty=nameWithType> event for the active AssemblyLoadContext.
- Raise the <xref:System.AppDomain.AssemblyResolve?displayProperty=nameWithType> event.

- Checking the <xref:System.Runtime.Loader.AssemblyLoadContext.Default%2A?displayProperty=nameWithType> instance's cache and running [managed assembly default probing](default-probing.md#managed-assembly-default-probing) logic.
3. For the other types of loads, the `active` <xref:System.Runtime.Loader.AssemblyLoadContext> loads the assembly in the following priority order:

- If an assembly is newly loaded, a reference is added to the <xref:System.Runtime.Loader.AssemblyLoadContext.Default%2A?displayProperty=nameWithType> instance's `cache-by-name`.
- Check its `cache-by-name`.
- Load from the specified path or raw assembly object. If an assembly is newly loaded, a reference is added to the `active` <xref:System.Runtime.Loader.AssemblyLoadContext> instance's `cache-by-name`.

- Raising the <xref:System.Runtime.Loader.AssemblyLoadContext.Resolving?displayProperty=nameWithType> event for the active AssemblyLoadContext.

- Raising the <xref:System.AppDomain.AssemblyResolve?displayProperty=nameWithType> event.

3. For the other types of loads, the `active` <xref:System.Runtime.Loader.AssemblyLoadContext> loads the assembly. In priority order by:
- Checking its `cache-by-name`.

- Loading from the specified path or raw assembly object.

- If an assembly is newly loaded, a reference is added to `active` <xref:System.Runtime.Loader.AssemblyLoadContext> instance's `cache-by-name`.

4. In either case, if an assembly is newly loaded, then:
- The <xref:System.AppDomain.AssemblyLoad?displayProperty=nameWithType> event is raised.
4. In either case, if an assembly is newly loaded, then the <xref:System.AppDomain.AssemblyLoad?displayProperty=nameWithType> event is raised.
8 changes: 4 additions & 4 deletions docs/core/dependency-loading/overview.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
---
title: Dependency loading - .NET
description: Overview of managed and unmanaged dependency loading in .NET 5+ and .NET Core
ms.date: 08/09/2019
ms.date: 08/18/2022
author: sdmaclea
ms.topic: overview
---
# Dependency loading in .NET

Every .NET application has dependencies. Even the simple `hello world` app has dependencies on portions of the .NET class libraries.

Understanding .NET default assembly loading logic can help you troubleshoot typical deployment issues.
Understanding the default assembly loading logic in .NET can help you troubleshoot typical deployment issues.

In some applications, dependencies are dynamically determined at run time. In these situations, it's critical to understand how managed assemblies and unmanaged dependencies are loaded.

Expand All @@ -28,11 +28,11 @@ The loading algorithm details are covered briefly in several articles:

## Create an app with plugins

The tutorial [Create a .NET Core application with plugins](../tutorials/creating-app-with-plugin-support.md) describes how to create a custom AssemblyLoadContext. It uses an <xref:System.Runtime.Loader.AssemblyDependencyResolver> to resolve the dependencies of the plugin. The tutorial correctly isolates the plugin's dependencies from the hosting application.
The tutorial [Create a .NET application with plugins](../tutorials/creating-app-with-plugin-support.md) describes how to create a custom AssemblyLoadContext. It uses an <xref:System.Runtime.Loader.AssemblyDependencyResolver> to resolve the dependencies of the plugin. The tutorial correctly isolates the plugin's dependencies from the hosting application.

## Assembly unloadability

The [How to use and debug assembly unloadability in .NET Core](../../standard/assembly/unloadability.md) article is a step-by-step tutorial. It shows how to load a .NET Core application, execute it, and then unload it. The article also provides debugging tips.
The [How to use and debug assembly unloadability in .NET](../../standard/assembly/unloadability.md) article is a step-by-step tutorial. It shows how to load a .NET application, execute it, and then unload it. The article also provides debugging tips.

## Collect detailed assembly loading information

Expand Down
35 changes: 17 additions & 18 deletions docs/core/dependency-loading/understanding-assemblyloadcontext.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
title: Understanding AssemblyLoadContext - .NET
title: About AssemblyLoadContext - .NET
description: Key concepts to understand the purpose and behavior of AssemblyLoadContext in .NET.
ms.date: 08/09/2019
ms.date: 08/18/2022
author: sdmaclea
---
# Understanding System.Runtime.Loader.AssemblyLoadContext
# About System.Runtime.Loader.AssemblyLoadContext

The <xref:System.Runtime.Loader.AssemblyLoadContext> class was introduced in .NET Core and is not available in .NET Framework. This article supplements the <xref:System.Runtime.Loader.AssemblyLoadContext> API documentation with conceptual information.

Expand All @@ -15,27 +15,28 @@ This article is relevant to developers implementing dynamic loading, especially
Every .NET 5+ and .NET Core application implicitly uses <xref:System.Runtime.Loader.AssemblyLoadContext>.
It's the runtime's provider for locating and loading dependencies. Whenever a dependency is loaded, an <xref:System.Runtime.Loader.AssemblyLoadContext> instance is invoked to locate it.

- It provides a service of locating, loading, and caching managed assemblies and other dependencies.

- AssemblyLoadContext provides a service of locating, loading, and caching managed assemblies and other dependencies.
- To support dynamic code loading and unloading, it creates an isolated context for loading code and its dependencies in their own <xref:System.Runtime.Loader.AssemblyLoadContext> instance.

## When do you need multiple AssemblyLoadContext instances?
## Versioning rules

A single <xref:System.Runtime.Loader.AssemblyLoadContext> instance is limited to loading exactly one version of an <xref:System.Reflection.Assembly> per simple assembly name, <xref:System.Reflection.AssemblyName.Name?displayProperty=nameWithType>.
A single <xref:System.Runtime.Loader.AssemblyLoadContext> instance is limited to loading exactly one version of an <xref:System.Reflection.Assembly> per [simple assembly name](xref:System.Reflection.AssemblyName.Name?displayProperty=nameWithType). When an assembly reference is resolved against an <xref:System.Runtime.Loader.AssemblyLoadContext> instance that already has an assembly of that name loaded, the requested version is compared to the loaded version. The resolution will succeed only if the loaded version is equal or higher to the requested version.

This restriction can become a problem when loading code modules dynamically. Each module is independently compiled and may depend on different versions of an <xref:System.Reflection.Assembly>. This problem commonly occurs when different modules depend on different versions of a commonly used library.
## When do you need multiple AssemblyLoadContext instances?

To support dynamically loading code, the <xref:System.Runtime.Loader.AssemblyLoadContext> API provides for loading conflicting versions of an <xref:System.Reflection.Assembly> in the same application. Each <xref:System.Runtime.Loader.AssemblyLoadContext> instance provides a unique dictionary mapping each <xref:System.Reflection.AssemblyName.Name?displayProperty=nameWithType> to a specific <xref:System.Reflection.Assembly> instance.
The restriction that a single <xref:System.Runtime.Loader.AssemblyLoadContext> instance can load only one version of an assembly can become a problem when loading code modules dynamically. Each module is independently compiled, and the modules may depend on different versions of an <xref:System.Reflection.Assembly>. This is often a problem when different modules depend on different versions of a commonly used library.

To support dynamically loading code, the <xref:System.Runtime.Loader.AssemblyLoadContext> API provides for loading conflicting versions of an <xref:System.Reflection.Assembly> in the same application. Each <xref:System.Runtime.Loader.AssemblyLoadContext> instance provides a unique dictionary that maps each <xref:System.Reflection.AssemblyName.Name?displayProperty=nameWithType> to a specific <xref:System.Reflection.Assembly> instance.

It also provides a convenient mechanism for grouping dependencies related to a code module for later unload.

## What is special about the AssemblyLoadContext.Default instance?
## The AssemblyLoadContext.Default instance

The <xref:System.Runtime.Loader.AssemblyLoadContext.Default?displayProperty=nameWithType> instance is automatically populated by the runtime at startup. It uses [default probing](default-probing.md) to locate and find all static dependencies.

It solves the most common dependency loading scenarios.

## How does AssemblyLoadContext support dynamic dependencies?
## Dynamic dependencies

<xref:System.Runtime.Loader.AssemblyLoadContext> has various events and virtual functions that can be overridden.

Expand Down Expand Up @@ -65,7 +66,7 @@ In each <xref:System.Runtime.Loader.AssemblyLoadContext>:
- <xref:System.Reflection.AssemblyName.Name?displayProperty=nameWithType> may refer to a different <xref:System.Reflection.Assembly> instance.
- <xref:System.Type.GetType%2A?displayProperty=nameWithType> may return a different type instance for the same type `name`.

## How are dependencies shared?
## Shared dependencies

Dependencies can easily be shared between <xref:System.Runtime.Loader.AssemblyLoadContext> instances. The general model is for one <xref:System.Runtime.Loader.AssemblyLoadContext> to load a dependency. The other shares the dependency by using a reference to the loaded assembly.

Expand All @@ -75,17 +76,15 @@ It's recommended that shared dependencies be loaded into <xref:System.Runtime.Lo

Sharing is implemented in the coding of the custom <xref:System.Runtime.Loader.AssemblyLoadContext> instance. <xref:System.Runtime.Loader.AssemblyLoadContext> has various events and virtual functions that can be overridden. When any of these functions return a reference to an <xref:System.Reflection.Assembly> instance that was loaded in another <xref:System.Runtime.Loader.AssemblyLoadContext> instance, the <xref:System.Reflection.Assembly> instance is shared. The standard load algorithm defers to <xref:System.Runtime.Loader.AssemblyLoadContext.Default?displayProperty=nameWithType> for loading to simplify the common sharing pattern. For more information, see [Managed assembly loading algorithm](loading-managed.md).

## Complications

### Type conversion issues
## Type-conversion issues

When two <xref:System.Runtime.Loader.AssemblyLoadContext> instances contain type definitions with the same `name`, they're not the same type. They're the same type if and only if they come from the same <xref:System.Reflection.Assembly> instance.

To complicate matters, exception messages about these mismatched types can be confusing. The types are referred to in the exception messages by their simple type names. The common exception message in this case is of the form:

> Object of type 'IsolatedType' cannot be converted to type 'IsolatedType'.

### Debugging type conversion issues
### Debug type-conversion issues

Given a pair of mismatched types, it's important to also know:

Expand All @@ -103,10 +102,10 @@ System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(a.GetType().Assembly)
System.Runtime.Loader.AssemblyLoadContext.GetLoadContext(b.GetType().Assembly)
```

### Resolving type conversion issues
### Resolve type-conversion issues

There are two design patterns for solving these type conversion issues.

1. Use common shared types. This shared type can either be a primitive runtime type, or it can involve creating a new shared type in a shared assembly. Often the shared type is an [interface](../../csharp/language-reference/keywords/interface.md) defined in an application assembly. For more information, read about [how dependencies are shared](#how-are-dependencies-shared).
1. Use common shared types. This shared type can either be a primitive runtime type, or it can involve creating a new shared type in a shared assembly. Often the shared type is an [interface](../../csharp/language-reference/keywords/interface.md) defined in an application assembly. For more information, read about [how dependencies are shared](#shared-dependencies).

2. Use marshalling techniques to convert from one type to another.