Repository navigation
Fix | Scope configurable retry logic assembly resolution to opt-in callers #4547
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
priyankatiwari08
merged 8 commits into
main
from
dev/prtiwar/fix-configurable-retry-assembly-resolution
Sep 8, 2026
Merged
Changes from 2 commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
9754eb0
Scope configurable retry logic assembly resolution
priyankatiwari08 6476d99
Test | Address review feedback on retry logic assembly probing tests
priyankatiwari08 4b8de9f
Test | Cover the successful retry logic type resolution path
priyankatiwari08 7ce702b
Test | Widen probe file cleanup to non-IO failures
priyankatiwari08 efc0d7a
Keep retry logic assembly probing active during provider construction
priyankatiwari08 bedc369
Treat a whitespace-only retryLogicType as not configured
priyankatiwari08 740223f
Remove the UseLegacyRetryLogicAssemblyResolution app context switch
priyankatiwari08 42319f2
Refactor retry assembly resolution subscription
priyankatiwari08 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
145 changes: 145 additions & 0 deletions
145
...SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConfigurableRetryLogicLoaderTest.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,145 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| #if NET | ||
|
|
||
| using System; | ||
| using System.IO; | ||
| using System.Reflection; | ||
| using System.Runtime.Loader; | ||
| using Xunit; | ||
|
|
||
| namespace Microsoft.Data.SqlClient.UnitTests; | ||
|
|
||
| /// <summary> | ||
| /// Unit tests validating that <see cref="SqlConfigurableRetryLogicLoader"/> never leaves a | ||
| /// process-wide assembly resolving handler attached to | ||
| /// <see cref="AssemblyLoadContext.Default"/>. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// A handler left attached there participates in resolution of every assembly the host | ||
| /// application fails to find, not just the retry logic assembly the loader was interested in. | ||
| /// That silently changes assembly loading behaviour for code that never opted in to this | ||
| /// feature, and can serve unrelated assemblies out of this component's probing directory. | ||
| /// </remarks> | ||
| public class SqlConfigurableRetryLogicLoaderTest | ||
| { | ||
| /// <summary> | ||
| /// The default code path: no configuration at all. The loader must not subscribe to the | ||
| /// default load context. | ||
| /// </summary> | ||
| [Fact] | ||
| public void Constructor_WithNoConfiguration_DoesNotLeaveAssemblyProbingEnabled() | ||
| { | ||
| _ = new SqlConfigurableRetryLogicLoader(null, null); | ||
|
|
||
| AssertNoAssemblyProbingHandlerInstalled(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// A configuration that supplies only a retry method, which is the documented way to select | ||
| /// one of the built-in retry providers. No custom type is being requested, so no assembly | ||
| /// resolution is required and no handler may be subscribed - not even transiently. | ||
| /// </summary> | ||
| [Theory] | ||
| [InlineData(null)] | ||
| [InlineData("")] | ||
| public void Constructor_WithoutRetryLogicType_DoesNotLeaveAssemblyProbingEnabled(string? retryLogicType) | ||
| { | ||
| TestRetryConnectionSection section = CreateSection(retryLogicType); | ||
|
|
||
| SqlConfigurableRetryLogicLoader loader = new(section, null); | ||
|
|
||
| // The built-in factory still resolves the requested method. | ||
| Assert.NotNull(loader.ConnectionProvider); | ||
| AssertNoAssemblyProbingHandlerInstalled(); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// A configuration that requests a custom retry logic type legitimately needs assembly | ||
| /// resolution, but the handler must be removed again once type resolution has finished. | ||
| /// </summary> | ||
| [Fact] | ||
| public void Constructor_WithUnresolvableRetryLogicType_DoesNotLeaveAssemblyProbingEnabled() | ||
| { | ||
| TestRetryConnectionSection section = | ||
| CreateSection("Some.Namespace.NoSuchType, Some.Assembly.That.Does.Not.Exist"); | ||
|
|
||
| SqlConfigurableRetryLogicLoader loader = new(section, null); | ||
|
|
||
| // Resolution fails and falls back to the built-in factory rather than throwing. | ||
| Assert.NotNull(loader.ConnectionProvider); | ||
| AssertNoAssemblyProbingHandlerInstalled(); | ||
| } | ||
|
|
||
| private static TestRetryConnectionSection CreateSection(string? retryLogicType) => | ||
| new() | ||
| { | ||
| RetryLogicType = retryLogicType!, | ||
| RetryMethod = nameof(SqlConfigurableRetryFactory.CreateFixedRetryProvider), | ||
| NumberOfTries = 2, | ||
| DeltaTime = TimeSpan.FromSeconds(1), | ||
| MinTimeInterval = TimeSpan.Zero, | ||
| MaxTimeInterval = TimeSpan.FromSeconds(10), | ||
| }; | ||
|
|
||
| /// <summary> | ||
| /// Asserts that a failed assembly load is not served out of the loader's probing directory, | ||
| /// which can only happen while a resolving handler installed by | ||
| /// <see cref="SqlConfigurableRetryLogicLoader"/> is subscribed to | ||
| /// <see cref="AssemblyLoadContext.Default"/>. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// A file that is not a valid assembly is planted in the probing directory under a name no | ||
| /// other component could be asking for. If a handler is still subscribed it finds that file | ||
| /// and tries to load it, which surfaces as <see cref="BadImageFormatException"/>. With no | ||
| /// handler subscribed the runtime never looks there and reports the assembly as simply not | ||
| /// found. This asserts the behaviour that actually matters to a host application rather than | ||
| /// inspecting loader or runtime internals. | ||
| /// </remarks> | ||
| private static void AssertNoAssemblyProbingHandlerInstalled() | ||
|
paulmedynski marked this conversation as resolved.
|
||
| { | ||
| // The probing directory is the application base directory, which for a test run is the | ||
| // directory the test assembly was loaded from. | ||
| string assemblySimpleName = "MdsProbeAssembly_" + Guid.NewGuid().ToString("N"); | ||
| string plantedFile = Path.Combine(AppContext.BaseDirectory, assemblySimpleName + ".dll"); | ||
|
|
||
| File.WriteAllText(plantedFile, "not an assembly"); | ||
| try | ||
| { | ||
| Assert.Throws<FileNotFoundException>( | ||
| () => Assembly.Load(new AssemblyName(assemblySimpleName))); | ||
| } | ||
| finally | ||
| { | ||
| try | ||
| { | ||
| File.Delete(plantedFile); | ||
| } | ||
| catch (IOException) | ||
| { | ||
| // Best effort cleanup. | ||
| } | ||
| } | ||
| } | ||
|
priyankatiwari08 marked this conversation as resolved.
|
||
|
|
||
| private sealed class TestRetryConnectionSection : ISqlConfigurableRetryConnectionSection | ||
| { | ||
| public TimeSpan DeltaTime { get; set; } | ||
|
|
||
| public TimeSpan MaxTimeInterval { get; set; } | ||
|
|
||
| public TimeSpan MinTimeInterval { get; set; } | ||
|
|
||
| public int NumberOfTries { get; set; } | ||
|
|
||
| public string RetryLogicType { get; set; } = string.Empty; | ||
|
|
||
| public string RetryMethod { get; set; } = string.Empty; | ||
|
|
||
| public string TransientErrors { get; set; } = string.Empty; | ||
| } | ||
| } | ||
|
|
||
| #endif | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.