Implemented assembly redirect for non-default AppDomain#4728
Implemented assembly redirect for non-default AppDomain#4728iskiselev wants to merge 17 commits intoopen-telemetry:mainfrom
Conversation
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.
|
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; |
There was a problem hiding this comment.
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
# 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
There was a problem hiding this comment.
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_STRATEGYwith 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.
| the application. In that mode you accept the risk that secondary | ||
| `AppDomain` creation may make instrumentation incomplete or unstable. | ||
|
|
||
| ## Scope of this document |
There was a problem hiding this comment.
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)] |
There was a problem hiding this comment.
Is there a reason to not test AppPoolMode.Integrated with all possibilities of Gac and AppDomainWorkaround?
There was a problem hiding this comment.
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;` |
There was a problem hiding this comment.
I don't understand what these changes are for, can you explain?
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
Can we inline this where it's called?
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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
zacharycmontoya
left a comment
There was a problem hiding this comment.
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}}" /> |
There was a problem hiding this comment.
Is this typical? I'd expect the user config to only provide a codebase with the version matching the original redirect version
zacharycmontoya
left a comment
There was a problem hiding this comment.
Overall the changes and testing look good, thanks!
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_STRATEGYthat 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 useLoaderOptimization.SingleDomain- used by defaultAssemblyRedirect- 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.mdis updated.How new setting should be integrated with file-based config? - documented as not supported