Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/Microsoft.Data.SqlClient.slnx
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,10 @@
<Project Path="Microsoft.SqlServer.Server/Microsoft.SqlServer.Server.csproj" />
</Folder>
<Folder Name="/test/">
<Project Path="Microsoft.Data.SqlClient/tests/UnloadableLibrary/UnloadableLibrary.csproj">
<Platform Solution="*|x64" Project="x64" />
<Platform Solution="*|x86" Project="x86" />
</Project>
<Project Path="Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj">
<Platform Solution="*|x64" Project="x64" />
<Platform Solution="*|x86" Project="x86" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// 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.

Expand Down Expand Up @@ -357,28 +357,30 @@ internal sealed class AssemblyResolutionSubscription : IDisposable
{
#if NET
private bool _isSubscribed;
private readonly AssemblyLoadContext _currentAssemblyLoadContext;
Comment thread
mdaigle marked this conversation as resolved.
#endif

internal AssemblyResolutionSubscription(bool subscribe)
{
#if NET
#if NET
if (subscribe)
{
AssemblyLoadContext.Default.Resolving += Default_Resolving;
_currentAssemblyLoadContext = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly());
_currentAssemblyLoadContext?.Resolving += Default_Resolving;
_isSubscribed = true;
}
#endif
#endif
}

public void Dispose()
{
#if NET
#if NET
if (_isSubscribed)
{
AssemblyLoadContext.Default.Resolving -= Default_Resolving;
_currentAssemblyLoadContext?.Resolving -= Default_Resolving;
_isSubscribed = false;
}
#endif
#endif
}
}

Expand All @@ -399,10 +401,11 @@ private static Assembly AssemblyResolver(AssemblyName arg)
string methodName = nameof(AssemblyResolver);

string fullPath = MakeFullPath(ProbingDirectory, arg.Name);
AssemblyLoadContext currentAlc = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly());
SqlClientEventSource.Log.TryTraceEvent("<sc.{0}.{1}|INFO> Looking for '{2}' assembly by '{3}' full path."
, TypeName, methodName, arg, fullPath);

return fullPath == null ? null : AssemblyLoadContext.Default.LoadFromAssemblyPath(fullPath);
return fullPath != null && currentAlc != null ? currentAlc.LoadFromAssemblyPath(fullPath) : null;
}

/// <summary>
Expand All @@ -411,8 +414,8 @@ private static Assembly AssemblyResolver(AssemblyName arg)
/// <remarks>
/// This handler is only subscribed while a configured retry logic provider is being
/// resolved and constructed, and only when a custom retry logic type has been configured.
/// It must not remain subscribed to <see cref="AssemblyLoadContext.Default"/> after that,
/// because doing so changes assembly resolution behavior for the entire application.
/// It must not remain subscribed to the calling <see cref="AssemblyLoadContext"/> after that,
/// because doing so can change assembly resolution behavior for the entire application.
/// </remarks>
private static Assembly Default_Resolving(AssemblyLoadContext arg1, AssemblyName arg2)
{
Expand All @@ -426,8 +429,8 @@ private static Assembly Default_Resolving(AssemblyLoadContext arg1, AssemblyName
}

/// <summary>
/// Performs a case-sensitive search to resolve the specified type name
/// and its related assemblies in default assembly load context if they aren't loaded yet.
/// Performs a case-sensitive search to resolve the specified type name and its related
/// assemblies in the calling assembly load context if they aren't loaded yet.
/// </summary>
/// <returns>Resolved type if it could resolve the type; otherwise, the `SqlConfigurableRetryFactory` type.</returns>
private static Type LoadType(string fullyQualifiedName)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// 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.

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

/// <summary>
/// Transport object for retry logic configuration parameters. This is used to pass
/// values which would ordinarily be part of the SqlConfigurableRetryLogicConnection
/// or SqlConfigurableRetryLogicCommand section to <see cref="RetryLogicLoaderHelper.CreateLoader"/>
/// </summary>
public sealed class RetryLogicConfigs
{
/// <summary>
/// Equivalent of the deltaTime value in an app.config file.
/// </summary>
public TimeSpan DeltaTime { get; set; }

/// <summary>
/// Equivalent of the maxTime value in an app.config file.
/// </summary>
public TimeSpan MaxTimeInterval { get; set; }

/// <summary>
/// Equivalent of the minTime value in an app.config file.
/// </summary>
public TimeSpan MinTimeInterval { get; set; }

/// <summary>
/// Equivalent of the numberOfTries value in an app.config file.
/// </summary>
public int NumberOfTries { get; set; }

/// <summary>
/// Equivalent of the transientErrors value in an app.config file.
/// </summary>
public string? TransientErrors { get; set; }

/// <summary>
/// Equivalent of the authorizedSqlCondition value in an app.config file.
/// </summary>
public string? AuthorizedSqlCondition { get; set; }

/// <summary>
/// Equivalent of the retryLogicType value in an app.config file.
/// </summary>
public string? RetryLogicType { get; set; }

/// <summary>
/// Equivalent of the retryMethod value in an app.config file.
/// </summary>
public string? RetryMethodName { get; set; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
// 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.

using System.Reflection;

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

/// <summary>
/// Contains helper methods for creating and configuring instances of
/// SqlConfigurableRetryLogicLoader and its associated configuration objects without needing
/// to populate an app.config file.
/// </summary>
public static class RetryLogicLoaderHelper
{
private const string ConfigurationLoaderTypeName = "Microsoft.Data.SqlClient.SqlConfigurableRetryLogicLoader";
private const string InterfaceCnnCfgTypeName = "Microsoft.Data.SqlClient.ISqlConfigurableRetryConnectionSection";
private const string InterfaceCmdCfgTypeName = "Microsoft.Data.SqlClient.ISqlConfigurableRetryCommandSection";
private const string CnnCfgTypeName = "Microsoft.Data.SqlClient.SqlConfigurableRetryConnectionSection";
private const string CmdCfgTypeName = "Microsoft.Data.SqlClient.SqlConfigurableRetryCommandSection";
private const string DefaultTransientErrors = "1204, 1205, 1222, 49918, 49919, 49920, 4060, 4221, 40143, 40613, 40501, 40540, 40197, 42108, 42109, 10929, 10928, 10060, 10054, 10053, 997, 233, 64, 20, 0, -2, 207, 102, 2812";

private static readonly Random s_random = new();

private static readonly Assembly s_sqlClientAssembly = typeof(SqlConnection).Assembly;
private static readonly Type s_configurationLoaderType = s_sqlClientAssembly.GetType(ConfigurationLoaderTypeName)!;
private static readonly Type s_interfaceCnnCfgType = s_sqlClientAssembly.GetType(InterfaceCnnCfgTypeName)!;
private static readonly Type s_interfaceCmdCfgType = s_sqlClientAssembly.GetType(InterfaceCmdCfgTypeName)!;
private static readonly Type s_cnnCfgType = s_sqlClientAssembly.GetType(CnnCfgTypeName)!;
private static readonly Type s_cmdCfgType = s_sqlClientAssembly.GetType(CmdCfgTypeName)!;

private static readonly Type[] s_cfgLoaderParamsType =
[
s_interfaceCnnCfgType,
s_interfaceCmdCfgType,
typeof(string), typeof(string)
];
private static readonly ConstructorInfo s_loaderCtorInfo = s_configurationLoaderType.GetConstructor(s_cfgLoaderParamsType)!;

/// <summary>
/// Creates and returns an instance of SqlConfigurableRetryLogicLoader with the specified
/// connection and command retry logic configurations.
/// </summary>
/// <param name="cnnConfig">The configuration for the connection retry logic.</param>
/// <param name="cmdConfig">The configuration for the command retry logic.</param>
/// <returns>The created loader instance.</returns>
public static object CreateLoader(RetryLogicConfigs cnnConfig, RetryLogicConfigs cmdConfig)
{
object cnnCfgObj = Activator.CreateInstance(s_cnnCfgType)!;
SetValue(cnnCfgObj, s_cnnCfgType, "DeltaTime", cnnConfig.DeltaTime);
SetValue(cnnCfgObj, s_cnnCfgType, "MinTimeInterval", cnnConfig.MinTimeInterval);
SetValue(cnnCfgObj, s_cnnCfgType, "MaxTimeInterval", cnnConfig.MaxTimeInterval);
SetValue(cnnCfgObj, s_cnnCfgType, "NumberOfTries", cnnConfig.NumberOfTries);
SetValue(cnnCfgObj, s_cnnCfgType, "AuthorizedSqlCondition", cnnConfig.AuthorizedSqlCondition);
SetValue(cnnCfgObj, s_cnnCfgType, "TransientErrors", cnnConfig.TransientErrors);
SetValue(cnnCfgObj, s_cnnCfgType, "RetryLogicType", cnnConfig.RetryLogicType);
SetValue(cnnCfgObj, s_cnnCfgType, "RetryMethod", cnnConfig.RetryMethodName);

object cmdCfgObj = Activator.CreateInstance(s_cmdCfgType)!;
SetValue(cmdCfgObj, s_cmdCfgType, "DeltaTime", cmdConfig.DeltaTime);
SetValue(cmdCfgObj, s_cmdCfgType, "MinTimeInterval", cmdConfig.MinTimeInterval);
SetValue(cmdCfgObj, s_cmdCfgType, "MaxTimeInterval", cmdConfig.MaxTimeInterval);
SetValue(cmdCfgObj, s_cmdCfgType, "NumberOfTries", cmdConfig.NumberOfTries);
SetValue(cmdCfgObj, s_cmdCfgType, "AuthorizedSqlCondition", cmdConfig.AuthorizedSqlCondition);
SetValue(cmdCfgObj, s_cmdCfgType, "TransientErrors", cmdConfig.TransientErrors);
SetValue(cmdCfgObj, s_cmdCfgType, "RetryLogicType", cmdConfig.RetryLogicType);
SetValue(cmdCfgObj, s_cmdCfgType, "RetryMethod", cmdConfig.RetryMethodName);

return s_loaderCtorInfo.Invoke([cnnCfgObj, cmdCfgObj, default!, default!]);
}

/// <summary>
/// Creates and returns an instance of SqlConfigurableRetryLogicLoader with the specified
/// connection and command retry logic configurations, and also retrieves the associated
/// connection and command providers.
/// </summary>
/// <param name="cnnCfg">The configuration for the connection retry logic.</param>
/// <param name="cmdCfg">The configuration for the command retry logic.</param>
/// <param name="cnnProvider">When this method returns, contains the connection provider.</param>
/// <param name="cmdProvider">When this method returns, contains the command provider.</param>
/// <returns>The created loader instance.</returns>
public static object ReturnLoaderAndProviders(RetryLogicConfigs cnnCfg, RetryLogicConfigs cmdCfg, out SqlRetryLogicBaseProvider cnnProvider, out SqlRetryLogicBaseProvider cmdProvider)
{
object loaderObj = CreateLoader(cnnCfg, cmdCfg);

cnnProvider = GetValue<SqlRetryLogicBaseProvider>(loaderObj, s_configurationLoaderType, "ConnectionProvider");
cmdProvider = GetValue<SqlRetryLogicBaseProvider>(loaderObj, s_configurationLoaderType, "CommandProvider");
return loaderObj;
}

/// <summary>
/// Creates a RetryLogicConfigs object with random values for its properties, optionally specifying
/// default values for certain mandatory properties.
/// </summary>
/// <param name="method">The retry method to use.</param>
/// <param name="authorizedSqlCondition">The authorized SQL condition.</param>
/// <param name="transientErrors">The transient errors.</param>
/// <returns>The created retry logic configuration.</returns>
public static RetryLogicConfigs CreateRandomConfig(string? method, string? authorizedSqlCondition = null, string transientErrors = DefaultTransientErrors)
{
TimeSpan start = TimeSpan.Zero;
TimeSpan end = TimeSpan.FromSeconds(60);
TimeSpan min = GenerateTimeSpan(start, end);

return new RetryLogicConfigs()
{
DeltaTime = GenerateTimeSpan(start, end),
MinTimeInterval = min,
MaxTimeInterval = GenerateTimeSpan(min, end),
NumberOfTries = s_random.Next(1, 60),
AuthorizedSqlCondition = authorizedSqlCondition,
TransientErrors = transientErrors,
RetryMethodName = method
};
}

private static T GetValue<T>(object obj, Type type, string propName, BindingFlags flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) =>
(T)type.GetProperty(propName, flags)?.GetValue(obj)!;

private static void SetValue<T>(object obj, Type type, string propName, T value, BindingFlags flags = BindingFlags.Public | BindingFlags.Instance) =>
type.GetProperty(propName, flags)?.SetValue(obj, value);

private static TimeSpan GenerateTimeSpan(TimeSpan start, TimeSpan end)
{
int max = (int)(end - start).TotalSeconds;
return start.Add(TimeSpan.FromSeconds(s_random.Next(max)));
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Licensed to the .NET Foundation under one or more agreements.
// 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.

Expand Down Expand Up @@ -86,11 +86,10 @@ public void ValidateRetryParameters()
#if NET
/// <summary>
/// Regression test: triggering the configurable retry logic loader through its normal
/// entry points must not leave a process-wide
/// <see cref="System.Runtime.Loader.AssemblyLoadContext.Default"/> resolving handler
/// installed. Such a handler participates in resolution of every assembly the host
/// application fails to find, and serves them out of this component's probing directory,
/// which can load code from an unintended location.
/// entry points must not leave a resolving handler subscribed to the current
/// <see cref="System.Runtime.Loader.AssemblyLoadContext" />. Such a handler participates
/// in resolution of every assembly the host application fails to find, and serves them
/// out of this component's probing directory, which can load code from an unintended location.
/// </summary>
[Fact]
public void RetryLogicProviderDoesNotLeaveAssemblyProbingEnabled()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@

<ProjectReference Include="$(RepoRoot)src/Microsoft.Data.SqlClient/tests/Common/Microsoft.Data.SqlClient.TestCommon.csproj" />
<ProjectReference Include="$(RepoRoot)src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomRetryLogicProvider.csproj" />
<ProjectReference Include="$(RepoRoot)src/Microsoft.Data.SqlClient/tests/UnloadableLibrary/UnloadableLibrary.csproj" />
<ProjectReference Include="$(RepoRoot)src/Microsoft.Data.SqlClient/tests/TestUdts/Address/Address.csproj" />
<ProjectReference Include="$(RepoRoot)src/Microsoft.Data.SqlClient/tests/TestUdts/Circle/Circle.csproj" />
<ProjectReference Include="$(RepoRoot)src/Microsoft.Data.SqlClient/tests/TestUdts/Shapes/Shapes.csproj" />
Expand Down Expand Up @@ -134,6 +135,7 @@
<ProjectReference Include="$(RepoRoot)src/Microsoft.Data.SqlClient/tests/TestUdts/Shapes/Shapes.csproj" />
<ProjectReference Include="$(RepoRoot)src/Microsoft.Data.SqlClient/tests/TestUdts/Utf8String/Utf8String.csproj" />
<ProjectReference Include="$(RepoRoot)src/Microsoft.Data.SqlClient/tests/CustomConfigurableRetryLogic/CustomRetryLogicProvider.csproj" />
<ProjectReference Include="$(RepoRoot)src/Microsoft.Data.SqlClient/tests/UnloadableLibrary/UnloadableLibrary.csproj" />
<ProjectReference Include="$(RepoRoot)src/Microsoft.Data.SqlClient/tests/tools/Microsoft.Data.SqlClient.TestUtilities/Microsoft.Data.SqlClient.TestUtilities.csproj" />
<ProjectReference Include="$(RepoRoot)src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS/TDS.csproj" />
<ProjectReference Include="$(RepoRoot)src/Microsoft.Data.SqlClient/tests/tools/TDS/TDS.EndPoint/TDS.EndPoint.csproj" />
Expand Down
Loading
Loading