Skip to content

Fix | Load and resolve retry logic providers into the current AssemblyLoadContext - #4588

Open
edwardneal wants to merge 19 commits into
dotnet:mainfrom
edwardneal:feat/retrylogic-current-alc
Open

edwardneal wants to merge 19 commits into
dotnet:mainfrom
edwardneal:feat/retrylogic-current-alc

Conversation

@edwardneal

Copy link
Copy Markdown
Contributor

Description

This PR addresses two interrelated bugs, both of which are triggered when SqlClient is loaded into an alternate AssemblyLoadContext. Both of these relate to SqlConfigurableRetryLogicLoader.

The first bug is that when using the managed SNI, SqlClient blocks an unloadable AssemblyLoadContext from being unloaded. This is completely due to the event handlers: it adds a handler to the default ALC's Resolving event, and this strong reference from the default ALC to a type loaded into a secondary ALC prevents that secondary ALC from being unloaded. This is fixed by making sure that the currently-executing ALC unhooks the Resolving handler when it unloads.

The second, related, bug is that we load a configuration's RetryLogicType into the default AssemblyLoadContext. This means that the type will never be unloaded with the secondary AssemblyLoadContext - and because it needs to reference a type in that secondary ALC, the secondary ALC won't unload either. Fixing this means that we need to load RetryLogicType into the current AssemblyLoadContext, not the default one.

To avoid a regression in fixing the second bug, we must also hook to the current AssemblyLoadContext's Resolving handler. This is called to resolve transitive dependencies of the assembly which has to be loaded to locate a configuration's RetryLogicType. The nuance here is that only the current AssemblyLoadContext's Resolving handler is called - not the default handler. If those two differ, we'll find ourselves in a position where a transitive dependency can fail to load (or worse, the wrong dependency may load - different ALCs may have different base paths.)

With these bugs resolved, #1687 is fixed when using the managed SNI.

I don't think that SqlClient can implement a fix to make the native SNI compatible with unloadable AssemblyLoadContexts - there's a rooted SNILoadHandle which calls SniInitialize and SniTerminate. Unless these two methods are signalling and waiting a semaphore in native code, they seem to perform process-wide initialization and teardown. I think having an AssemblyLoadContext remain uncollectible is safer behaviour than having multiple ALCs referring to the process-wide SNI; one might have an in-flight async request, while the other might be unloading and performing SNI teardown.

Issues

Contributes to #1687.

Testing

Existing automated tests (SqlConfigurationManagerReliabilityTest) continue to pass. I've also added a new test, UnloadableAssemblyLoadContextTest. This test covers the scenario where users are loading and unloading AssemblyLoadContexts.

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
There may be pipelines that require an authorized user to comment /azp run to run.

@mdaigle mdaigle added this to the 8.0.0-preview1 milestone Sep 18, 2026
@cheenamalhotra cheenamalhotra moved this from To triage to In review in SqlClient Board Sep 23, 2026
@paulmedynski

Copy link
Copy Markdown
Contributor

/azp run

@paulmedynski
paulmedynski requested a balanced review from Copilot September 28, 2026 14:25
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).
1 pipeline(s) were filtered out due to trigger conditions.

Copilot AI left a comment

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.

Copilot review overview

🟡 Changes recommended

The new regression test uses default-context SqlClient and never exercises the changed retry-provider resolution paths.

Review effort: Balanced
Findings: 1 Medium severity · 2 Low severity

Open (3)
What changed in this PR

Updates configurable retry assembly resolution to use SqlClient’s current AssemblyLoadContext and adds unloadability coverage.

Changes:

  • Resolve retry providers and dependencies in the current load context.
  • Add a collectible-context integration test and helper library.
  • Update existing retry-loader test documentation.
File Description
UnloadableLibrary.csproj Defines the collectible test library.
EntryPoint.cs Exercises synchronous and asynchronous SqlClient operations.
SqlConfigurableRetryLogicLoaderTest.cs Updates load-context terminology.
UnloadableAssemblyLoadContextTest.cs Adds collectible-context regression coverage.
Microsoft.Data.SqlClient.ManualTests.csproj References the new test library.
SqlConfigurableRetryLogicTest.cs Updates resolving-handler documentation.
SqlConfigurableRetryLogicLoader.cs Resolves assemblies in the current load context.
Microsoft.Data.SqlClient.slnx Adds the test library to the solution.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +61 to +63
AssemblyLoadContext alc = new(nameof(SecondaryAssemblyLoadContext_Unloads), isCollectible: true);
WeakReference weakRef = new(alc, trackResurrection: true);
Assembly loadedAsm = alc.LoadFromAssemblyPath(assemblyPath);

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.

Excellent point - my original test case actually had that, but I stripped it out by mistake. Fixed in a495eab, with a specific assertion in 79f51b9 to prove that it doesn't regress.

mdaigle
mdaigle previously approved these changes Oct 1, 2026
Comment thread src/Microsoft.Data.SqlClient/tests/UnloadableLibrary/EntryPoint.cs
@mdaigle

mdaigle commented Oct 7, 2026

Copy link
Copy Markdown
Contributor

/azp run

@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 2 pipeline(s).
1 pipeline(s) were filtered out due to trigger conditions.

Copilot AI left a comment

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.

🟡 Changes recommended

The regression test bypasses the changed retry-type loading path and leaks a process-wide AppContext switch.

6 open findings
2 resolved since last review
Previously missed (2)

In code that hasn't changed since last review

Low severity Update LoadType summary for current load context

src/​Microsoft.Data.SqlClient/​src/​Microsoft/​Data/​SqlClient/​Reliability/​SqlConfigurableRetryLogicLoader.cs:417

The LoadType XML summary immediately below still says assemblies are resolved in the default load context, which is no longer true after this change. Update it to describe resolution in SqlClient's current load context.

Low severity Remove UTF-8 BOM from project file

src/​Microsoft.Data.SqlClient/​tests/​UnloadableLibrary/​UnloadableLibrary.csproj:1

Remove the UTF-8 BOM from this new project file. Repository policy requires UTF-8 without BOM (policy/coding-style.md:16-17), and the other touched source files explicitly remove their BOMs.

🧠 Review effort: Balanced


public EntryPoint(string connectionString)
{
AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", true);

namespace Microsoft.Data.SqlClient.Tests.Common.ConfigurableRetryLogic;

public static class RetryLogicLoaderHelper
return weakRef;
}

private sealed class UnloadableAssemblyLoadContext : AssemblyLoadContext

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.

Simplified in bd0ba5b and fixed in ad8edb9 and e2c04e3.

/// <remarks>
/// This is organised into a separate class and assembly to provide isolation from the ManualTesting
/// project. Loading an assembly loads all of its dependencies, so this isolation means that if the
/// test fails, we can be confident that the failure is due to SqlClient itself rather that to another

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.

Fixed in ad8edb9.

InitializeRetryProviders();
}

private void InitializeRetryProviders()

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.

Fixed in ad8edb9.


public EntryPoint(string connectionString)
{
AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows", true);

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.

It looks like this will leak out of the test into the switch dictionary in the default ALC. Can double check that we're not polluting that?

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.

It did, yes - thanks for spotting that @mdaigle. I'd guarded it with a LocalAppContextSwitchesHelper, but that doesn't help when I'm setting the underlying AppContext switch directly! The test code now uses LocalAppContextSwitchesHelper within the unloadable ALC, so we never tamper with these switches in the first place.

@cheenamalhotra cheenamalhotra added the Author attention needed PRs that require author to respond or make updates to PR. label Oct 8, 2026
@github-actions

github-actions Bot commented Oct 8, 2026

Copy link
Copy Markdown

@edwardneal This pull request has been marked as Author attention needed.

When you have addressed the reviewer feedback and are ready for another review, please post a comment with /ready to remove the label and re-engage reviewers.

This removes an unnecessary piece of infrastructure: the AssemblyName-to-path mappings. AssemblyDependencyResolver handles this automatically, and it ensures that the test reflects a real-world environment.
This prevents a cross-ALC leak of the AppContext switch
These are largely in the test code, with the exception of one in SqlConfigurableRetryLogicLoader.
@edwardneal

Copy link
Copy Markdown
Contributor Author

/ready

Apologies for the delay here.

@github-actions github-actions Bot removed the Author attention needed PRs that require author to respond or make updates to PR. label Oct 9, 2026
@github-actions
github-actions Bot requested a review from mdaigle October 9, 2026 18:24
This still compiled to net8.0/net9.0, which pulled in the netstandard2.0 dependencies.
This point is from private review. It's designed to make sure we exercise the full set of type/assembly resolution

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Waiting for customer

Development

Successfully merging this pull request may close these issues.

5 participants