-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
API proposal AssemblyLoadContext.ActiveForContextSensitiveReflection #29042
Comments
/cc @vitek-karas @jkotas @jeffschwMSFT @jkoritzinsky @AaronRobinsonMSFT @swaroop-sridhar I'll update the proposal based on the minimized comments in #28491. |
I have updated the proposal to make it more self sufficient and readable. I incorporated @josalem's xunit experience as part of the justification. |
I am marking this api ready for review.
|
Couple comments:
In all API proposals, include the surrounding |
@vitek-karas I have made the grammatical/editorial corrections you suggested. |
namespace System.Runtime.Loader
{
public partial class AssemblyLoadContext
{
public static AssemblyLoadContext CurrentContextualReflectionContext { get; }
public ContextualReflectionScope EnterContextualReflection();
static public ContextualReflectionScope EnterContextualReflection(Assembly activating);
[EditorBrowsable(Never)]
public struct ContextualReflectionScope : IDisposable
{
}
}
} |
Maybe |
Add ContextualReflection APIs approved in dotnet/corefx#36236 Fix issue #22213
Add ContextualReflection APIs approved in dotnet/corefx#36236 Fix issue #22213
Add ContextualReflection APIs approved in dotnet/corefx#36236 Fix issue #22213
Add ContextualReflection APIs approved in dotnet/corefx#36236 Fix issue #22213
* Add ContextualReflection APIs Add ContextualReflection APIs approved in dotnet/corefx#36236 Fix issue #22213 * SetParentAssembly even when IsCollectible() * ContextualReflection tests * PR Feedback * Add more usage tests Add using statement tests Add bad usage tests including Assert.Throws<> * Only initialize on set * Add XML API comments * Unify VerifyIsolation * Fix unused expectedAssembly * Remove ContextualReflectionScope throw * Clean up TestResolveMissingAssembly et. al * Remove unused QCall::AppDomainHandle * Remove AppDomainBaseObject * Pass AssemblyLoadContext as managed object to native * Fix AssemblyLoadContextBaseObject packing * AssemblyLoadContext backing stores Use explicit backing stores for events and properties * Remove StaticAsyncLocalCurrentContextualReflectionContext * Remove PermissionSetObject
* Add ContextualReflection APIs Add ContextualReflection APIs approved in dotnet/corefx#36236 Fix issue dotnet#22213 * SetParentAssembly even when IsCollectible() * ContextualReflection tests * PR Feedback * Add more usage tests Add using statement tests Add bad usage tests including Assert.Throws<> * Only initialize on set * Add XML API comments * Unify VerifyIsolation * Fix unused expectedAssembly * Remove ContextualReflectionScope throw * Clean up TestResolveMissingAssembly et. al * Remove unused QCall::AppDomainHandle * Remove AppDomainBaseObject * Pass AssemblyLoadContext as managed object to native * Fix AssemblyLoadContextBaseObject packing * AssemblyLoadContext backing stores Use explicit backing stores for events and properties * Remove StaticAsyncLocalCurrentContextualReflectionContext * Remove PermissionSetObject Signed-off-by: dotnet-bot <[email protected]>
* Add ContextualReflection APIs Add ContextualReflection APIs approved in dotnet/corefx#36236 Fix issue #22213 * SetParentAssembly even when IsCollectible() * ContextualReflection tests * PR Feedback * Add more usage tests Add using statement tests Add bad usage tests including Assert.Throws<> * Only initialize on set * Add XML API comments * Unify VerifyIsolation * Fix unused expectedAssembly * Remove ContextualReflectionScope throw * Clean up TestResolveMissingAssembly et. al * Remove unused QCall::AppDomainHandle * Remove AppDomainBaseObject * Pass AssemblyLoadContext as managed object to native * Fix AssemblyLoadContextBaseObject packing * AssemblyLoadContext backing stores Use explicit backing stores for events and properties * Remove StaticAsyncLocalCurrentContextualReflectionContext * Remove PermissionSetObject Signed-off-by: dotnet-bot <[email protected]>
* Add ContextualReflection APIs Add ContextualReflection APIs approved in dotnet/corefx#36236 Fix issue #22213 * SetParentAssembly even when IsCollectible() * ContextualReflection tests * PR Feedback * Add more usage tests Add using statement tests Add bad usage tests including Assert.Throws<> * Only initialize on set * Add XML API comments * Unify VerifyIsolation * Fix unused expectedAssembly * Remove ContextualReflectionScope throw * Clean up TestResolveMissingAssembly et. al * Remove unused QCall::AppDomainHandle * Remove AppDomainBaseObject * Pass AssemblyLoadContext as managed object to native * Fix AssemblyLoadContextBaseObject packing * AssemblyLoadContext backing stores Use explicit backing stores for events and properties * Remove StaticAsyncLocalCurrentContextualReflectionContext * Remove PermissionSetObject Signed-off-by: dotnet-bot <[email protected]>
* Add ContextualReflection APIs Add ContextualReflection APIs approved in dotnet/corefx#36236 Fix issue #22213 * SetParentAssembly even when IsCollectible() * ContextualReflection tests * PR Feedback * Add more usage tests Add using statement tests Add bad usage tests including Assert.Throws<> * Only initialize on set * Add XML API comments * Unify VerifyIsolation * Fix unused expectedAssembly * Remove ContextualReflectionScope throw * Clean up TestResolveMissingAssembly et. al * Remove unused QCall::AppDomainHandle * Remove AppDomainBaseObject * Pass AssemblyLoadContext as managed object to native * Fix AssemblyLoadContextBaseObject packing * AssemblyLoadContext backing stores Use explicit backing stores for events and properties * Remove StaticAsyncLocalCurrentContextualReflectionContext * Remove PermissionSetObject Signed-off-by: dotnet-bot <[email protected]>
* Add ContextualReflection APIs Add ContextualReflection APIs approved in dotnet/corefx#36236 Fix issue #22213 * SetParentAssembly even when IsCollectible() * ContextualReflection tests * PR Feedback * Add more usage tests Add using statement tests Add bad usage tests including Assert.Throws<> * Only initialize on set * Add XML API comments * Unify VerifyIsolation * Fix unused expectedAssembly * Remove ContextualReflectionScope throw * Clean up TestResolveMissingAssembly et. al * Remove unused QCall::AppDomainHandle * Remove AppDomainBaseObject * Pass AssemblyLoadContext as managed object to native * Fix AssemblyLoadContextBaseObject packing * AssemblyLoadContext backing stores Use explicit backing stores for events and properties * Remove StaticAsyncLocalCurrentContextualReflectionContext * Remove PermissionSetObject Signed-off-by: dotnet-bot <[email protected]>
* Add ContextualReflection APIs Add ContextualReflection APIs approved in dotnet/corefx#36236 Fix issue #22213 * SetParentAssembly even when IsCollectible() * ContextualReflection tests * PR Feedback * Add more usage tests Add using statement tests Add bad usage tests including Assert.Throws<> * Only initialize on set * Add XML API comments * Unify VerifyIsolation * Fix unused expectedAssembly * Remove ContextualReflectionScope throw * Clean up TestResolveMissingAssembly et. al * Remove unused QCall::AppDomainHandle * Remove AppDomainBaseObject * Pass AssemblyLoadContext as managed object to native * Fix AssemblyLoadContextBaseObject packing * AssemblyLoadContext backing stores Use explicit backing stores for events and properties * Remove StaticAsyncLocalCurrentContextualReflectionContext * Remove PermissionSetObject Signed-off-by: dotnet-bot <[email protected]>
* Add ContextualReflection APIs Add ContextualReflection APIs approved in dotnet/corefx#36236 Fix issue #22213 * SetParentAssembly even when IsCollectible() * ContextualReflection tests * PR Feedback * Add more usage tests Add using statement tests Add bad usage tests including Assert.Throws<> * Only initialize on set * Add XML API comments * Unify VerifyIsolation * Fix unused expectedAssembly * Remove ContextualReflectionScope throw * Clean up TestResolveMissingAssembly et. al * Remove unused QCall::AppDomainHandle * Remove AppDomainBaseObject * Pass AssemblyLoadContext as managed object to native * Fix AssemblyLoadContextBaseObject packing * AssemblyLoadContext backing stores Use explicit backing stores for events and properties * Remove StaticAsyncLocalCurrentContextualReflectionContext * Remove PermissionSetObject
Problem
.NET Core 3.0 is trying to enable a simple isolated plugin loading model.
The issue is that the existing reflection API surface changes behavior depending on how the plugin dependencies are loaded. For the problematic APIs, the location of the
Assembly
directly calling the reflection API, is used to infer theAssemblyLoadContext
for reflection loads.Consider the following set of dependencies:
The .NET Core isolation model allows
pluginDependency
to be loaded into three distinct places in order to satisfy the dependency ofplugin
:AssemblyLoadContext.Default
AssemblyLoadContext
asplugin
AssemblyLoadContext
asplugin
(unusual, but allowed)Using
pluginDependency
to determine theAssemblyLoadContext
used for loading leads to inconsistent behavior. Theplugin
expectspluginDependency
to execute code on its behalf. Therefore it reasonably expectspluginDependency
to useplugin
'sAssemblyLoadContext
. It leads to unexpected behavior except when loaded in the "Same customAssemblyLoadContext
asplugin
."Failing Scenarios
Xunit story
We have been working on building a test harness in Xunit for running the CoreFX test suite inside
AssemblyLoadContext
s (each test case in its own context). This has proven to be somewhat difficult due to Xunit being a very reflection heavy codebase with tons of instances of types, assemblies, etc. being converted to strings and then fed throughActivator
. One of the main learnings is that it is not always obvious what will stay inside the “bounds” of anAssemblyLoadContext
and what won’t. The basic rule of thumb is that anyAssembly.Load()
will result in the assembly being loaded onto theAssemblyLoadContext
of the calling code, so if code loaded by an ALC callsAssembly.Load(...)
, the resulting assembly will be within the “bounds” of the ALC. This unfortunately breaks down in some cases, specifically when code callsActivator
which lives inSystem.Private.CoreLib
which is always shared.System.Xaml
This problem also manifests when using an
Object
deserialization framework which allows specifying assembly qualified type names.We have seen this issue when porting WPF tests to run in a component in an isolation context. These tests are using
System.Xaml
for deserialization. During deserialization,System.Xaml
is using the affected APIs to create object instances using assembly-qualified type names.Scope of affected APIs
The problem exists whenever a reflection API can trigger a load or bind of an
Assembly
and the intendedAssemblyLoadContext
is ambiguous.Currently affected APIs
These APIs are using the immediate caller to determine the
AssemblyLoadContext
to use. As shown above the immediate caller is not necessarily the desired context.These always trigger assembly loads and are always affected:
These are only affected when they trigger assembly loads. Assembly loads for these occur when
typeName
includes a assembly-qualified type reference:Unamiguous APIs related to affected APIs
In this case,
assemblyResolver
functionally specifies the explicit mechanism to load. This indicates the current assembly'sAssmblyLoadContext
is not being used. If theassemblyResolver
is only serving as a first or last chance resolver, then these would also be in the set of affected APIs.Should be affected APIs
Issue https://github.com/dotnet/coreclr/issues/22213, discusses scenarios in which various flavors of the API
GetType()
is not functioning correctly. As part of the analysis and fix of that issue, the set of affected APIs may increase.Root cause analysis
In .NET Framework, plugin isolation was provided by creating multiple
AppDomain
instances. .NET Core dropped support for multipleAppDomain
instances. Instead we introducedAssemblyLoadContext
.The isolation model for
AssemblyLoadContext
is very different fromAppDomain
. One major distinction was the existence of an ambient propertyAppDomain.CurrentDomain
associated with the running code and its dependents. There is no equivalent ambient property forAssemblyLoadContext
.The issue is that the existing reflection API surface design was based on the existence of an ambient
AppDomain.CurrentDomain
associated with the current isolation environment. TheAppDomain.CurrentDomain
acted as theAssembly
loader. (In .NET Core the loader function is conceptually attached toAssemblyLoadContext
.)Options
There are two main options:
Add APIs which allow specifying an explicit callback to load assemblies. Guide customers to avoid using the APIs which just infer assembly loading semantics on their own.
Add an ambient property which corresponds to the active
AssemblyLoadContext
.We are already pursuing the first option. It is insufficient. For existing code with existing APIs this approach can be problematic.
The second option allows logical the separation of concerns. Code loaded into an isolation context does not really need to be concerned with how it was loaded. It should expect APIs to logically behave in the same way independent of loading.
This proposal is recommending pursuing the second option while continuing to pursue the first.
Proposed Solution
This proposal is for a mechanism for code to explicitly set a specific
AssemblyLoadContext
as theActiveForContextSensitiveReflection
for a using block and its asynchronous flow of control. Previous context is restored upon exiting the using block. Blocks can be nested.AssemblyLoadContext.ActiveForContextSensitiveReflection
AssemblyLoadContext.ActiveForContextSensitiveReflection
is a static read only property. Its value is changed through the API below.AssemblyLoadContext.ActiveForContextSensitiveReflection
property is anAsyncLocal<T>
. This means there is a distinct value which is associated with each asynchronous control flow.The initial value at application startup is
null
. The value for a new async block will be inherited from its parent.When
AssemblyLoadContext.ActiveForContextSensitiveReflection != null
When
AssemblyLoadContext.ActiveForContextSensitiveReflection != null
,ActiveForContextSensitiveReflection
will act as the primaryAssemblyLoadContext
for the affected APIs.When used in an affected API, the primary, will:
AssemblyLoadContext
eachAssembly
is loaded.AssemblyLoadContext.Load(...)
before falling back toAssemblyLoadContext.Default
to try to load from its TPA list.AssemblyLoadContext.Resolving
event if the both of the preceding have failedKey concepts
AssemblyLoadContext
is required to be idempotent. This means when it is asked to load a specificAssembly
by name, it must always return the same result. The result would include whether anAssembly
load occurred and into whichAssemblyLoadContext
it was loaded.Assemblies
related to anAssemblyLoadContext
are not all loaded by the sameAssemblyLoadContext
. They collaborate. An assembly loaded into oneAssemblyLoadContext
, can resolve its dependentAssembly
references from anotherAssemblyLoadContext
.System.Private.Corelib.dll
) is required to be loaded into theAssemblyLoadContext.Default
. This means all customAssemblyLoadContext
depend on this code to implement fundamental code including the primitive types.Assembly
has static state, its state will be associated with its load location. Each load location will have its own static state. This can guide and constrain the isolation strategy.AssemblyLoadContext
loads lazily. Loads can be triggered for various reasons. Loads are often triggered as code begins to need the dependentAssembly
. Triggers can come from any thread. Code usingAssemblyLoadContext
does not require external synchronization. Inherently this means thatAssemblyLoadContext
are required to load in a thread safe way.When
AssemblyLoadContext.ActiveForContextSensitiveReflection == null
The behavior of .NET Core will be unchanged. Specifically, the effective
AssemblyLoadContext
will continued to be inferred to be the ALC of the currentcaller's
Assembly
.AssemblyLoadContext.ActivateForContextSensitiveReflection()
The API for setting
ActiveForContextSensitiveReflection
is intended to be used in a using block.Two methods are proposed.
this
AssemblyLoadContext
AssemblyLoadContext
containingAssembly
. This also serves as a mechanism to deactivate within a using block (ActivateForContextSensitiveReflection(null)
).Basic Usage
Maintaining and restoring original behavior
Proposed API changes
Design doc
Detailed design doc is still in early review dotnet/coreclr#23335.
The text was updated successfully, but these errors were encountered: