Skip to content

Implemented assembly redirect for non-default AppDomain#4728

Open
iskiselev wants to merge 17 commits intoopen-telemetry:mainfrom
iskiselev:redirect
Open

Implemented assembly redirect for non-default AppDomain#4728
iskiselev wants to merge 17 commits intoopen-telemetry:mainfrom
iskiselev:redirect

Conversation

@iskiselev
Copy link
Copy Markdown
Contributor

@iskiselev iskiselev commented Dec 24, 2025

Why

Exisitng solution #4187 force LoaderOptimization.SingleDomain for every additional app domains. It is not standard behaviour and may result in increased memory consumption and slower warm-up for process hosting multiple web-applications - and other processes that use additional AppDomains.

Implement second stage of fix for #4186.

Parts of implementation uses code generated by Codex with GPT-5.4 model

What

Added environment variable OTEL_DOTNET_AUTO_APP_DOMAIN_STRATEGY that controls strategy of fixing assembly conflicts in non-default app domains.
Possible values:

  • None - do not apply anything. Works with test app and OTEL 1.14, crash with Loading this assembly would produce a different grant set from other instances #4186 on OTEL 1.13. May be useful for custom host that creates AppDomains.
  • LoaderOptimizationSingleDomain - exisiting solution implemented in Automatic LoaderOptimization.SingleDomain #4187 - all additional AppDomains use LoaderOptimization.SingleDomain - used by default
  • AssemblyRedirect - new stategy - patch assemblyBindings/dependentAssembly/bindingRedirect in memory-loaded app.config. On test app docker requires app pool to loading user profile if assemblies loaded from folder (and has no issues when assemblies are registered in GAC). On my local computer works even without loading user profile for app pool.

Tests

AspNetTests.SubmitsTraces now test additional modes.
AssemblyBindingUpdaterTests - unit test coverage
Tested all strategies with OTEL assemblies in GAC and not in GAC on web sites that does/does not use newer version of dependent assemblies. For dependent assembly with newer version System.Diagnostics.DignosticsSource 11 was used.

Checklist

  • CHANGELOG.md is updated.
  • Documentation is updated.
  • New features are covered by tests.
  • How new setting should be integrated with file-based config? - documented as not supported

Introduce OTEL_APP_DOMAIN_STRATEGY to control AppDomain config update strategies for .NET Framework, supporting LoaderOptimization, assembly redirects, or no action. Add dynamic app.config modification for assembly binding redirects via AppConfigUpdater and new AssemblyCatalog. Expand IIS tests to cover all strategies.
@iskiselev
Copy link
Copy Markdown
Contributor Author

iskiselev commented Dec 24, 2025

Some special processing required for MongoDB - should be validated. Probably we should not include MongoDB dll in our distro at all, if we are not loading it?

SOLVED: We do not use any special processing for MongoDB anymore and do not include it in distro

public static void ModifyConfig(AppDomainSetup appDomainSetup)
{
appDomainSetup.LoaderOptimization = LoaderOptimization.SingleDomain;
var patchMode = Environment.GetEnvironmentVariable("OTEL_APP_DOMAIN_STRATEGY") ?? string.Empty;
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Should we try to parse app.config when we check it? Which app.config should be used - creator domain or target domain?
How should be file-based config be integrated here?
For now, I plan to add it to the list of settings supported only by environment variables.

…AIN_STRATEGY`

Added documentation and changlog information.
Handled creation of config when not provided by AppDomain
Comment thread src/OpenTelemetry.AutoInstrumentation.Loader/AppConfigUpdater.cs Outdated
# Conflicts:
#	CHANGELOG.md
#	docs/config.md
#	src/OpenTelemetry.AutoInstrumentation.Loader/AssemblyResolver.Net.cs
#	src/OpenTelemetry.AutoInstrumentation.Loader/AssemblyResolver.cs
#	src/OpenTelemetry.AutoInstrumentation.Loader/Loader.cs
#	test/IntegrationTests/AspNetTests.cs
@zacharycmontoya zacharycmontoya self-requested a review April 22, 2026 16:05
@iskiselev iskiselev marked this pull request as ready for review April 24, 2026 06:20
@iskiselev iskiselev requested a review from a team as a code owner April 24, 2026 06:20
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds a new .NET Framework AppDomain handling strategy to reduce assembly conflicts in non-default AppDomains without always forcing LoaderOptimization.SingleDomain, addressing the follow-up to #4186/#4187.

Changes:

  • Introduces OTEL_DOTNET_AUTO_APP_DOMAIN_STRATEGY with strategies (None, LoaderOptimizationSingleDomain, AssemblyRedirect) and integrates strategy selection into the loader.
  • Implements in-memory config patching for assembly binding redirects/codeBase via new AssemblyBindingUpdater + AssemblyCatalog, with accompanying unit tests.
  • Updates documentation, changelog, and ASP.NET integration tests/Docker setup to exercise the new modes.

Reviewed changes

Copilot reviewed 13 out of 13 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
test/test-applications/integrations/TestApplication.AspNet.NetFramework/Dockerfile Adjusts IIS app pool settings used by containerized ASP.NET .NETFX tests.
test/OpenTelemetry.AutoInstrumentation.Loader.Tests/AssemblyBindingUpdaterTests.cs Adds unit tests covering assembly binding redirect/codeBase rewrite behavior.
test/IntegrationTests/AspNetTests.cs Extends end-to-end ASP.NET tests to run with multiple AppDomain strategies.
src/OpenTelemetry.AutoInstrumentation/Util/ManagedProfilerLocationHelper.cs Refactors .link resolution into a reusable helper with validation.
src/OpenTelemetry.AutoInstrumentation.Loader/AssemblyResolver.NetFramework.cs Improves loader debug logging for Assembly.LoadFrom.
src/OpenTelemetry.AutoInstrumentation.Loader/AssemblyCatalog.cs New catalog that discovers profiler/shared runtime assemblies (incl. .link) for redirect decisions.
src/OpenTelemetry.AutoInstrumentation.Loader/AssemblyBindingUpdater.cs New updater that rewrites/augments assemblyBinding for non-default AppDomains.
src/OpenTelemetry.AutoInstrumentation.Loader/AppConfigUpdater.cs Adds env-var driven strategy selection and hooks logger lifecycle.
docs/netfx-appdomain-strategy.md New doc explaining the AppDomain problem space and strategy tradeoffs.
docs/config.md Documents the new environment variable and its supported values.
docs/assembly-conflict-resolution.md Links AppDomain strategy doc and clarifies .NETFX GAC precedence behavior.
CHANGELOG.md Notes the addition of OTEL_DOTNET_AUTO_APP_DOMAIN_STRATEGY.
.cspell/dot-net.txt Adds appdomain to spell-check dictionary.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/config.md Outdated
Comment thread src/OpenTelemetry.AutoInstrumentation/Util/ManagedProfilerLocationHelper.cs Outdated
Comment thread src/OpenTelemetry.AutoInstrumentation.Loader/AssemblyCatalog.cs Outdated
the application. In that mode you accept the risk that secondary
`AppDomain` creation may make instrumentation incomplete or unstable.

## Scope of this document
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the great detail in this markdown. I think if you move this ## Scope of this document section before the ## Why secondary AppDomains are special section, that could be helpful

[InlineData(AppPoolMode.Integrated, Gac.UseGac)]
[InlineData(AppPoolMode.Integrated, Gac.UseLocal)]
public async Task SubmitsTraces(AppPoolMode appPoolMode, Gac useGac)
[InlineData(AppPoolMode.Integrated, Gac.UseGac, AppDomainWorkaround.LoaderOptimizationSingleDomain)]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there a reason to not test AppPoolMode.Integrated with all possibilities of Gac and AppDomainWorkaround?

Copy link
Copy Markdown
Contributor Author

@iskiselev iskiselev Apr 28, 2026

Choose a reason for hiding this comment

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

Saving of electric eneregy :)
I do not expect that changing app pool mode between ingerated and classic will chenge anything in assembly loading. So, checking it only once will work (or checking classic only once).
I will add a comment to explain it.

COPY bin/${configuration}/app.publish .

FROM integrated-base AS integrated-nogac
RUN Start-IISCommitDelay;`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I don't understand what these changes are for, can you explain?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

AssemblyRedirect - ... On test app docker requires app pool to loading user profile if assemblies loaded from folder...

So, here I enable loading of user profile for both IIS when assemlbies are not installed in GAC to make it compatible with AssemblyRedirect mode

}

#pragma warning disable SA1202
protected virtual string? GetCreatorApplicationBase()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we inline this where it's called?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Oh I see that this is used for testing. Can you comment the virtual / internal methods that are intended to be used only for testing?

=> AppDomain.CurrentDomain.BaseDirectory;
#pragma warning restore SA1202

private static XElement CreateDependentAssembly(AssemblyCatalog.AssemblyInfo assemblyInfo)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can you isolate the helper methods like this, creating versions, etc. into another C# file? This file is very complex so keeping only the necessary loading logic in this file would be very helpful

return null;
}

private List<string> GetProbeDirectories(XElement assemblyBinding, AppDomainSetup appDomainSetup)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I had a hard time following this method (maybe because of the inner static methods) so I fed it into Claude and it gave me the following summary, which I found helpful for giving some high-level context first then it was easier to look into each step. Do you think any of this is worth adding as a high-level comment?

  GetProbeDirectories (line 388)

  Replicates Fusion's probe path order:
  1. If DisallowApplicationBaseProbing → return empty.
  2. Resolves ApplicationBase; if missing, falls back to GetCreatorApplicationBase() (virtual, returns AppDomain.CurrentDomain.BaseDirectory) and writes it back into the setup. (:430-445, 565-566)
  3. Adds the application base — unless PrivateBinPathProbe != null, which disables it (matches CLR semantics).
  4. For web.config, adds {base}/bin. (:534-537)
  5. Splits PrivateBinPath on ; — for rooted entries, only keeps them if they're under ApplicationBase (security: prevents probing outside the app), warns otherwise. Relative entries are joined to the base.
  (:466-481)
  6. Same treatment for <probing privatePath="..."> from the config.
  7. Dedupes by full path, case-insensitive. Logs the resolved set with full context (config file, base, flags). (:393-428)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This file was rather difficult to review because there's a lot of Fusion / probing logic that I wasn't already familiar with, so I have two requests to hopefully help other reviewers/maintainers:

  • If there are any public docs or links that explain some of the behaviors, can you link to it?
  • If reasonable, could you provide code comments to explain the high-level "what" is the goal/logic of a given method? No need to explain the "how" as that's in code.

When reviewing this file, I told Claude verbatim Walk me through the changes introduced in src/OpenTelemetry.AutoInstrumentation.Loader/AssemblyBindingUpdater.cs starting from ModifyAssemblyRedirectConfig and it was surprisingly insightful and provided a helpful method-by-method breakdown. This could also be a good source of method-level comments so I'd be happy to share if you'd like

Copy link
Copy Markdown
Contributor

@zacharycmontoya zacharycmontoya left a comment

Choose a reason for hiding this comment

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

I've reviewed everything but the AssemblyBindingUpdaterTests.cs, which I can continue tomorrow

<dependentAssembly>
<assemblyIdentity name="{{TestAssemblyName}}" publicKeyToken="{TOKEN}" culture="neutral" />
<bindingRedirect oldVersion="0.0.0.0-{{lowerRedirectVersion}}" newVersion="{{lowerRedirectVersion}}" />
<codeBase version="{{customerVersion}}" href="{{customerHref}}" />
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is this typical? I'd expect the user config to only provide a codebase with the version matching the original redirect version

Copy link
Copy Markdown
Contributor

@zacharycmontoya zacharycmontoya left a comment

Choose a reason for hiding this comment

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

Overall the changes and testing look good, thanks!

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.

4 participants