From b7444d6de6fa1234314dc69cd4d3a9faef0ea8ed Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Wed, 17 Dec 2025 18:28:13 -0400 Subject: [PATCH 1/7] - Eliminated race conditions when using the LocalAppContextSwitchesHelper. - Removed Tristate in favour of bool?. - Gave the Common test project access to MDS internals and cleaned up the Helper a lot. --- .../src/Microsoft.Data.SqlClient.csproj | 1 + .../netfx/src/Microsoft.Data.SqlClient.csproj | 3 + .../Data/SqlClient/LocalAppContextSwitches.cs | 806 +++++++++--------- .../tests/Common/Common.csproj | 1 + .../Common/Fixtures/CertificateFixtureBase.cs | 4 +- .../ColumnEncryptionCertificateFixture.cs | 20 +- .../ColumnMasterKeyCertificateFixture.cs | 2 +- .../Common/Fixtures/CspCertificateFixture.cs | 8 +- .../Common/LocalAppContextSwitchesHelper.cs | 549 +++--------- .../tests/FunctionalTests/SqlParameterTest.cs | 6 +- .../SQL/DataReaderTest/DataReaderTest.cs | 4 +- .../SQL/ParameterTest/ParametersTest.cs | 6 +- .../Data/SqlClient/SqlConnectionStringTest.cs | 60 +- .../ConnectionFailoverTests.cs | 2 +- .../SimulatedServerTests/ConnectionTests.cs | 4 +- 15 files changed, 594 insertions(+), 882 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index f8fc03f915..512db7418e 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -32,6 +32,7 @@ $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFramework)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) + diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index e0974d0d44..bc14696009 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -33,6 +33,9 @@ <_Parameter1>UnitTests + + <_Parameter1>Common + diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs index 09d84e0ea7..b1441db382 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -4,540 +4,534 @@ using System; -namespace Microsoft.Data.SqlClient -{ - internal static partial class LocalAppContextSwitches - { - // @TODO: Replace with `bool?` since that's exactly how this is being used - private enum Tristate : byte - { - NotInitialized = 0, - False = 1, - True = 2 - } +#nullable enable - private const string MakeReadAsyncBlockingString = @"Switch.Microsoft.Data.SqlClient.MakeReadAsyncBlocking"; - private const string LegacyRowVersionNullString = @"Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior"; - private const string SuppressInsecureTlsWarningString = @"Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning"; - private const string UseMinimumLoginTimeoutString = @"Switch.Microsoft.Data.SqlClient.UseOneSecFloorInTimeoutCalculationDuringLogin"; - private const string LegacyVarTimeZeroScaleBehaviourString = @"Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour"; - private const string UseCompatibilityProcessSniString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni"; - private const string UseCompatibilityAsyncBehaviourString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour"; - private const string UseConnectionPoolV2String = @"Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2"; - private const string TruncateScaledDecimalString = @"Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal"; - private const string IgnoreServerProvidedFailoverPartnerString = @"Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner"; - private const string EnableUserAgentString = @"Switch.Microsoft.Data.SqlClient.EnableUserAgent"; - private const string EnableMultiSubnetFailoverByDefaultString = @"Switch.Microsoft.Data.SqlClient.EnableMultiSubnetFailoverByDefault"; +namespace Microsoft.Data.SqlClient; - #if NET - private const string GlobalizationInvariantModeString = @"System.Globalization.Invariant"; - private const string GlobalizationInvariantModeEnvironmentVariable = "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"; +internal static class LocalAppContextSwitches +{ + private const string MakeReadAsyncBlockingString = @"Switch.Microsoft.Data.SqlClient.MakeReadAsyncBlocking"; + private const string LegacyRowVersionNullString = @"Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior"; + private const string SuppressInsecureTlsWarningString = @"Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning"; + private const string UseMinimumLoginTimeoutString = @"Switch.Microsoft.Data.SqlClient.UseOneSecFloorInTimeoutCalculationDuringLogin"; + private const string LegacyVarTimeZeroScaleBehaviourString = @"Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour"; + private const string UseCompatibilityProcessSniString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni"; + private const string UseCompatibilityAsyncBehaviourString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour"; + private const string UseConnectionPoolV2String = @"Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2"; + private const string TruncateScaledDecimalString = @"Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal"; + private const string IgnoreServerProvidedFailoverPartnerString = @"Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner"; + private const string EnableUserAgentString = @"Switch.Microsoft.Data.SqlClient.EnableUserAgent"; + private const string EnableMultiSubnetFailoverByDefaultString = @"Switch.Microsoft.Data.SqlClient.EnableMultiSubnetFailoverByDefault"; - #if _WINDOWS - private const string UseManagedNetworkingOnWindowsString = "Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows"; - #endif - #else - private const string DisableTnirByDefaultString = @"Switch.Microsoft.Data.SqlClient.DisableTNIRByDefaultInConnectionString"; - #endif + #if NET + private const string GlobalizationInvariantModeString = @"System.Globalization.Invariant"; + private const string GlobalizationInvariantModeEnvironmentVariable = "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"; - // this field is accessed through reflection in tests and should not be renamed or have the type changed without refactoring NullRow related tests - private static Tristate s_legacyRowVersionNullBehavior; - private static Tristate s_suppressInsecureTlsWarning; - private static Tristate s_makeReadAsyncBlocking; - private static Tristate s_useMinimumLoginTimeout; - // this field is accessed through reflection in Microsoft.Data.SqlClient.Tests.SqlParameterTests and should not be renamed or have the type changed without refactoring related tests - private static Tristate s_legacyVarTimeZeroScaleBehaviour; - private static Tristate s_useCompatibilityProcessSni; - private static Tristate s_useCompatibilityAsyncBehaviour; - private static Tristate s_useConnectionPoolV2; - private static Tristate s_truncateScaledDecimal; - private static Tristate s_ignoreServerProvidedFailoverPartner; - private static Tristate s_enableUserAgent; - private static Tristate s_multiSubnetFailoverByDefault; + #if _WINDOWS + private const string UseManagedNetworkingOnWindowsString = "Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows"; + #endif + #else + private const string DisableTnirByDefaultString = @"Switch.Microsoft.Data.SqlClient.DisableTNIRByDefaultInConnectionString"; + #endif - #if NET - private static Tristate s_globalizationInvariantMode; + // this field is accessed through reflection in tests and should not be renamed or have the type changed without refactoring NullRow related tests + internal static bool? s_legacyRowVersionNullBehavior; + internal static bool? s_suppressInsecureTlsWarning; + internal static bool? s_makeReadAsyncBlocking; + internal static bool? s_useMinimumLoginTimeout; + // this field is accessed through reflection in Microsoft.Data.SqlClient.Tests.SqlParameterTests and should not be renamed or have the type changed without refactoring related tests + internal static bool? s_legacyVarTimeZeroScaleBehaviour; + internal static bool? s_useCompatibilityProcessSni; + internal static bool? s_useCompatibilityAsyncBehaviour; + internal static bool? s_useConnectionPoolV2; + internal static bool? s_truncateScaledDecimal; + internal static bool? s_ignoreServerProvidedFailoverPartner; + internal static bool? s_enableUserAgent; + internal static bool? s_multiSubnetFailoverByDefault; - #if _WINDOWS - private static Tristate s_useManagedNetworking; - #endif - #else - private static Tristate s_disableTnirByDefault; - #endif + #if NET + internal static bool? s_globalizationInvariantMode; + #endif + #if NET && _WINDOWS + internal static bool? s_useManagedNetworking; + #endif + #if NETFRAMEWORK + internal static bool? s_disableTnirByDefault; + #endif - #if NET - static LocalAppContextSwitches() - { - IAppContextSwitchOverridesSection appContextSwitch = AppConfigManager.FetchConfigurationSection(AppContextSwitchOverridesSection.Name); + #if NET + static LocalAppContextSwitches() + { + IAppContextSwitchOverridesSection appContextSwitch = AppConfigManager.FetchConfigurationSection(AppContextSwitchOverridesSection.Name); - try - { - SqlAppContextSwitchManager.ApplyContextSwitches(appContextSwitch); - } - catch (Exception e) - { - // @TODO: Adopt netcore style of trace logs - // Don't throw an exception for an invalid config file - SqlClientEventSource.Log.TryTraceEvent(": {1}", nameof(LocalAppContextSwitches), e); - } + try + { + SqlAppContextSwitchManager.ApplyContextSwitches(appContextSwitch); } - #endif + catch (Exception e) + { + // @TODO: Adopt netcore style of trace logs + // Don't throw an exception for an invalid config file + SqlClientEventSource.Log.TryTraceEvent(": {1}", nameof(LocalAppContextSwitches), e); + } + } + #endif - // @TODO: Sort by name + // @TODO: Sort by name - /// - /// In TdsParser, the ProcessSni function changed significantly when the packet - /// multiplexing code needed for high speed multi-packet column values was added. - /// When this switch is set to true (the default), the old ProcessSni design is used. - /// When this switch is set to false, the new experimental ProcessSni behavior using - /// the packet multiplexer is enabled. - /// - public static bool UseCompatibilityProcessSni + /// + /// In TdsParser, the ProcessSni function changed significantly when the packet + /// multiplexing code needed for high speed multi-packet column values was added. + /// When this switch is set to true (the default), the old ProcessSni design is used. + /// When this switch is set to false, the new experimental ProcessSni behavior using + /// the packet multiplexer is enabled. + /// + public static bool UseCompatibilityProcessSni + { + get { - get + if (!s_useCompatibilityProcessSni.HasValue) + { - if (s_useCompatibilityProcessSni == Tristate.NotInitialized) + // Check if the switch has been set by the AppContext switch directly + // If it has not been set, we default to true. + if (!AppContext.TryGetSwitch(UseCompatibilityProcessSniString, out bool returnedValue) || returnedValue) { - // Check if the switch has been set by the AppContext switch directly - // If it has not been set, we default to true. - if (!AppContext.TryGetSwitch(UseCompatibilityProcessSniString, out bool returnedValue) || returnedValue) - { - s_useCompatibilityProcessSni = Tristate.True; - } - else - { - s_useCompatibilityProcessSni = Tristate.False; - } + s_useCompatibilityProcessSni = true; + } + else + { + s_useCompatibilityProcessSni = false; } - return s_useCompatibilityProcessSni == Tristate.True; } + return s_useCompatibilityProcessSni.Value; } + } - /// - /// In TdsParser, the async multi-packet column value fetch behavior can use a continue snapshot state - /// for improved efficiency. When this switch is enabled (the default), the driver preserves the legacy - /// compatibility behavior, which does not use the continue snapshot state. When disabled, the new behavior - /// using the continue snapshot state is enabled. This switch will always return true if - /// is enabled, because the continue state is not stable without - /// the multiplexer. - /// - public static bool UseCompatibilityAsyncBehaviour + /// + /// In TdsParser, the async multi-packet column value fetch behavior can use a continue snapshot state + /// for improved efficiency. When this switch is enabled (the default), the driver preserves the legacy + /// compatibility behavior, which does not use the continue snapshot state. When disabled, the new behavior + /// using the continue snapshot state is enabled. This switch will always return true if + /// is enabled, because the continue state is not stable without + /// the multiplexer. + /// + public static bool UseCompatibilityAsyncBehaviour + { + get { - get + if (UseCompatibilityProcessSni) { - if (UseCompatibilityProcessSni) + // If ProcessSni compatibility mode has been enabled then the packet + // multiplexer has been disabled. The new async behaviour using continue + // point capture is only stable if the multiplexer is enabled so we must + // return true to enable compatibility async behaviour using only restarts. + return true; + } + + if (!s_useCompatibilityAsyncBehaviour.HasValue) + { + if (!AppContext.TryGetSwitch(UseCompatibilityAsyncBehaviourString, out bool returnedValue) || returnedValue) { - // If ProcessSni compatibility mode has been enabled then the packet - // multiplexer has been disabled. The new async behaviour using continue - // point capture is only stable if the multiplexer is enabled so we must - // return true to enable compatibility async behaviour using only restarts. - return true; + s_useCompatibilityAsyncBehaviour = true; } - - if (s_useCompatibilityAsyncBehaviour == Tristate.NotInitialized) + else { - if (!AppContext.TryGetSwitch(UseCompatibilityAsyncBehaviourString, out bool returnedValue) || returnedValue) - { - s_useCompatibilityAsyncBehaviour = Tristate.True; - } - else - { - s_useCompatibilityAsyncBehaviour = Tristate.False; - } + s_useCompatibilityAsyncBehaviour = false; } - return s_useCompatibilityAsyncBehaviour == Tristate.True; } + return s_useCompatibilityAsyncBehaviour.Value; } + } - /// - /// When using Encrypt=false in the connection string, a security warning is output to the console if the TLS version is 1.2 or lower. - /// This warning can be suppressed by enabling this AppContext switch. - /// This app context switch defaults to 'false'. - /// - public static bool SuppressInsecureTlsWarning + /// + /// When using Encrypt=false in the connection string, a security warning is output to the console if the TLS version is 1.2 or lower. + /// This warning can be suppressed by enabling this AppContext switch. + /// This app context switch defaults to 'false'. + /// + public static bool SuppressInsecureTlsWarning + { + get { - get + if (!s_suppressInsecureTlsWarning.HasValue) { - if (s_suppressInsecureTlsWarning == Tristate.NotInitialized) + if (AppContext.TryGetSwitch(SuppressInsecureTlsWarningString, out bool returnedValue) && returnedValue) { - if (AppContext.TryGetSwitch(SuppressInsecureTlsWarningString, out bool returnedValue) && returnedValue) - { - s_suppressInsecureTlsWarning = Tristate.True; - } - else - { - s_suppressInsecureTlsWarning = Tristate.False; - } + s_suppressInsecureTlsWarning = true; + } + else + { + s_suppressInsecureTlsWarning = false; } - return s_suppressInsecureTlsWarning == Tristate.True; } + return s_suppressInsecureTlsWarning.Value; } + } - /// - /// In System.Data.SqlClient and Microsoft.Data.SqlClient prior to 3.0.0 a field with type Timestamp/RowVersion - /// would return an empty byte array. This switch controls whether to preserve that behaviour on newer versions - /// of Microsoft.Data.SqlClient, if this switch returns false an appropriate null value will be returned. - /// This app context switch defaults to 'false'. - /// - public static bool LegacyRowVersionNullBehavior + /// + /// In System.Data.SqlClient and Microsoft.Data.SqlClient prior to 3.0.0 a field with type Timestamp/RowVersion + /// would return an empty byte array. This switch controls whether to preserve that behaviour on newer versions + /// of Microsoft.Data.SqlClient, if this switch returns false an appropriate null value will be returned. + /// This app context switch defaults to 'false'. + /// + public static bool LegacyRowVersionNullBehavior + { + get { - get + if (!s_legacyRowVersionNullBehavior.HasValue) { - if (s_legacyRowVersionNullBehavior == Tristate.NotInitialized) + if (AppContext.TryGetSwitch(LegacyRowVersionNullString, out bool returnedValue) && returnedValue) { - if (AppContext.TryGetSwitch(LegacyRowVersionNullString, out bool returnedValue) && returnedValue) - { - s_legacyRowVersionNullBehavior = Tristate.True; - } - else - { - s_legacyRowVersionNullBehavior = Tristate.False; - } + s_legacyRowVersionNullBehavior = true; + } + else + { + s_legacyRowVersionNullBehavior = false; } - return s_legacyRowVersionNullBehavior == Tristate.True; } + return s_legacyRowVersionNullBehavior.Value; } + } - /// - /// When enabled, ReadAsync runs asynchronously and does not block the calling thread. - /// This app context switch defaults to 'false'. - /// - public static bool MakeReadAsyncBlocking + /// + /// When enabled, ReadAsync runs asynchronously and does not block the calling thread. + /// This app context switch defaults to 'false'. + /// + public static bool MakeReadAsyncBlocking + { + get { - get + if (!s_makeReadAsyncBlocking.HasValue) { - if (s_makeReadAsyncBlocking == Tristate.NotInitialized) + if (AppContext.TryGetSwitch(MakeReadAsyncBlockingString, out bool returnedValue) && returnedValue) { - if (AppContext.TryGetSwitch(MakeReadAsyncBlockingString, out bool returnedValue) && returnedValue) - { - s_makeReadAsyncBlocking = Tristate.True; - } - else - { - s_makeReadAsyncBlocking = Tristate.False; - } + s_makeReadAsyncBlocking = true; + } + else + { + s_makeReadAsyncBlocking = false; } - return s_makeReadAsyncBlocking == Tristate.True; } + return s_makeReadAsyncBlocking.Value; } + } - /// - /// Specifies minimum login timeout to be set to 1 second instead of 0 seconds, - /// to prevent a login attempt from waiting indefinitely. - /// This app context switch defaults to 'true'. - /// - public static bool UseMinimumLoginTimeout + /// + /// Specifies minimum login timeout to be set to 1 second instead of 0 seconds, + /// to prevent a login attempt from waiting indefinitely. + /// This app context switch defaults to 'true'. + /// + public static bool UseMinimumLoginTimeout + { + get { - get + if (!s_useMinimumLoginTimeout.HasValue) { - if (s_useMinimumLoginTimeout == Tristate.NotInitialized) + if (!AppContext.TryGetSwitch(UseMinimumLoginTimeoutString, out bool returnedValue) || returnedValue) { - if (!AppContext.TryGetSwitch(UseMinimumLoginTimeoutString, out bool returnedValue) || returnedValue) - { - s_useMinimumLoginTimeout = Tristate.True; - } - else - { - s_useMinimumLoginTimeout = Tristate.False; - } + s_useMinimumLoginTimeout = true; + } + else + { + s_useMinimumLoginTimeout = false; } - return s_useMinimumLoginTimeout == Tristate.True; } + return s_useMinimumLoginTimeout.Value; } + } - /// - /// When set to 'true' this will output a scale value of 7 (DEFAULT_VARTIME_SCALE) when the scale - /// is explicitly set to zero for VarTime data types ('datetime2', 'datetimeoffset' and 'time') - /// If no scale is set explicitly it will continue to output scale of 7 (DEFAULT_VARTIME_SCALE) - /// regardless of switch value. - /// This app context switch defaults to 'true'. - /// - public static bool LegacyVarTimeZeroScaleBehaviour + /// + /// When set to 'true' this will output a scale value of 7 (DEFAULT_VARTIME_SCALE) when the scale + /// is explicitly set to zero for VarTime data types ('datetime2', 'datetimeoffset' and 'time') + /// If no scale is set explicitly it will continue to output scale of 7 (DEFAULT_VARTIME_SCALE) + /// regardless of switch value. + /// This app context switch defaults to 'true'. + /// + public static bool LegacyVarTimeZeroScaleBehaviour + { + get { - get + if (!s_legacyVarTimeZeroScaleBehaviour.HasValue) { - if (s_legacyVarTimeZeroScaleBehaviour == Tristate.NotInitialized) + if (!AppContext.TryGetSwitch(LegacyVarTimeZeroScaleBehaviourString, out bool returnedValue)) { - if (!AppContext.TryGetSwitch(LegacyVarTimeZeroScaleBehaviourString, out bool returnedValue)) - { - s_legacyVarTimeZeroScaleBehaviour = Tristate.True; - } - else - { - s_legacyVarTimeZeroScaleBehaviour = returnedValue ? Tristate.True : Tristate.False; - } + s_legacyVarTimeZeroScaleBehaviour = true; + } + else + { + s_legacyVarTimeZeroScaleBehaviour = returnedValue ? true : false; } - return s_legacyVarTimeZeroScaleBehaviour == Tristate.True; } + return s_legacyVarTimeZeroScaleBehaviour.Value; } + } - /// - /// When set to true, the connection pool will use the new V2 connection pool implementation. - /// When set to false, the connection pool will use the legacy V1 implementation. - /// This app context switch defaults to 'false'. - /// - public static bool UseConnectionPoolV2 + /// + /// When set to true, the connection pool will use the new V2 connection pool implementation. + /// When set to false, the connection pool will use the legacy V1 implementation. + /// This app context switch defaults to 'false'. + /// + public static bool UseConnectionPoolV2 + { + get { - get + if (!s_useConnectionPoolV2.HasValue) { - if (s_useConnectionPoolV2 == Tristate.NotInitialized) + if (AppContext.TryGetSwitch(UseConnectionPoolV2String, out bool returnedValue) && returnedValue) { - if (AppContext.TryGetSwitch(UseConnectionPoolV2String, out bool returnedValue) && returnedValue) - { - s_useConnectionPoolV2 = Tristate.True; - } - else - { - s_useConnectionPoolV2 = Tristate.False; - } + s_useConnectionPoolV2 = true; + } + else + { + s_useConnectionPoolV2 = false; } - return s_useConnectionPoolV2 == Tristate.True; } + return s_useConnectionPoolV2.Value; } + } - /// - /// When set to true, TdsParser will truncate (rather than round) decimal and SqlDecimal values when scaling them. - /// - public static bool TruncateScaledDecimal + /// + /// When set to true, TdsParser will truncate (rather than round) decimal and SqlDecimal values when scaling them. + /// + public static bool TruncateScaledDecimal + { + get { - get + if (!s_truncateScaledDecimal.HasValue) { - if (s_truncateScaledDecimal == Tristate.NotInitialized) + if (AppContext.TryGetSwitch(TruncateScaledDecimalString, out bool returnedValue) && returnedValue) { - if (AppContext.TryGetSwitch(TruncateScaledDecimalString, out bool returnedValue) && returnedValue) - { - s_truncateScaledDecimal = Tristate.True; - } - else - { - s_truncateScaledDecimal = Tristate.False; - } + s_truncateScaledDecimal = true; + } + else + { + s_truncateScaledDecimal = false; } - return s_truncateScaledDecimal == Tristate.True; } + return s_truncateScaledDecimal.Value; } + } - /// - /// When set to true, the failover partner provided by the server during connection - /// will be ignored. This is useful in scenarios where the application wants to - /// control the failover behavior explicitly (e.g. using a custom port). The application - /// must be kept up to date with the failover configuration of the server. - /// The application will not automatically discover a newly configured failover partner. - /// - /// This app context switch defaults to 'false'. - /// - public static bool IgnoreServerProvidedFailoverPartner + /// + /// When set to true, the failover partner provided by the server during connection + /// will be ignored. This is useful in scenarios where the application wants to + /// control the failover behavior explicitly (e.g. using a custom port). The application + /// must be kept up to date with the failover configuration of the server. + /// The application will not automatically discover a newly configured failover partner. + /// + /// This app context switch defaults to 'false'. + /// + public static bool IgnoreServerProvidedFailoverPartner + { + get { - get + if (!s_ignoreServerProvidedFailoverPartner.HasValue) { - if (s_ignoreServerProvidedFailoverPartner == Tristate.NotInitialized) + if (AppContext.TryGetSwitch(IgnoreServerProvidedFailoverPartnerString, out bool returnedValue) && returnedValue) { - if (AppContext.TryGetSwitch(IgnoreServerProvidedFailoverPartnerString, out bool returnedValue) && returnedValue) - { - s_ignoreServerProvidedFailoverPartner = Tristate.True; - } - else - { - s_ignoreServerProvidedFailoverPartner = Tristate.False; - } + s_ignoreServerProvidedFailoverPartner = true; + } + else + { + s_ignoreServerProvidedFailoverPartner = false; } - return s_ignoreServerProvidedFailoverPartner == Tristate.True; } + return s_ignoreServerProvidedFailoverPartner.Value; } - /// - /// When set to true, the user agent feature is enabled and the driver will send the user agent string to the server. - /// - public static bool EnableUserAgent + } + /// + /// When set to true, the user agent feature is enabled and the driver will send the user agent string to the server. + /// + public static bool EnableUserAgent + { + get { - get + if (!s_enableUserAgent.HasValue) { - if (s_enableUserAgent == Tristate.NotInitialized) + if (AppContext.TryGetSwitch(EnableUserAgentString, out bool returnedValue) && returnedValue) { - if (AppContext.TryGetSwitch(EnableUserAgentString, out bool returnedValue) && returnedValue) - { - s_enableUserAgent = Tristate.True; - } - else - { - s_enableUserAgent = Tristate.False; - } + s_enableUserAgent = true; + } + else + { + s_enableUserAgent = false; } - return s_enableUserAgent == Tristate.True; } + return s_enableUserAgent.Value; } + } - #if NET - /// - /// .NET Core 2.0 and up supports Globalization Invariant mode, which reduces the size of the required libraries for - /// applications which don't need globalization support. SqlClient requires those libraries for core functionality, - /// and will throw exceptions later if they are not present. This switch allows SqlClient to detect this mode early. - /// - public static bool GlobalizationInvariantMode + #if NET + /// + /// .NET Core 2.0 and up supports Globalization Invariant mode, which reduces the size of the required libraries for + /// applications which don't need globalization support. SqlClient requires those libraries for core functionality, + /// and will throw exceptions later if they are not present. This switch allows SqlClient to detect this mode early. + /// + public static bool GlobalizationInvariantMode + { + get { - get + if (!s_globalizationInvariantMode.HasValue) { - if (s_globalizationInvariantMode == Tristate.NotInitialized) + // Check if invariant mode has been set by the AppContext switch directly + if (AppContext.TryGetSwitch(GlobalizationInvariantModeString, out bool returnedValue) && returnedValue) { - // Check if invariant mode has been set by the AppContext switch directly - if (AppContext.TryGetSwitch(GlobalizationInvariantModeString, out bool returnedValue) && returnedValue) + s_globalizationInvariantMode = true; + } + else + { + // If the switch is not set, we check the environment variable as the first fallback + string? envValue = Environment.GetEnvironmentVariable(GlobalizationInvariantModeEnvironmentVariable); + + if (string.Equals(envValue, bool.TrueString, StringComparison.OrdinalIgnoreCase) || string.Equals(envValue, "1", StringComparison.OrdinalIgnoreCase)) { - s_globalizationInvariantMode = Tristate.True; + s_globalizationInvariantMode = true; } else { - // If the switch is not set, we check the environment variable as the first fallback - string envValue = Environment.GetEnvironmentVariable(GlobalizationInvariantModeEnvironmentVariable); - - if (string.Equals(envValue, bool.TrueString, StringComparison.OrdinalIgnoreCase) || string.Equals(envValue, "1", StringComparison.OrdinalIgnoreCase)) + // If this hasn't been manually set, it could still apply if the OS doesn't have ICU libraries installed, + // or if the application is a native binary with ICU support trimmed away. + // .NET 3.1 to 5.0 do not throw in attempting to create en-US in invariant mode, but .NET 6+ does. In + // such cases, catch and infer invariant mode from the exception. + try { - s_globalizationInvariantMode = Tristate.True; + s_globalizationInvariantMode = System.Globalization.CultureInfo.GetCultureInfo("en-US").EnglishName.Contains("Invariant") + ? true + : false; } - else + catch (System.Globalization.CultureNotFoundException) { - // If this hasn't been manually set, it could still apply if the OS doesn't have ICU libraries installed, - // or if the application is a native binary with ICU support trimmed away. - // .NET 3.1 to 5.0 do not throw in attempting to create en-US in invariant mode, but .NET 6+ does. In - // such cases, catch and infer invariant mode from the exception. - try - { - s_globalizationInvariantMode = System.Globalization.CultureInfo.GetCultureInfo("en-US").EnglishName.Contains("Invariant") - ? Tristate.True - : Tristate.False; - } - catch (System.Globalization.CultureNotFoundException) - { - // If the culture is not found, it means we are in invariant mode - s_globalizationInvariantMode = Tristate.True; - } + // If the culture is not found, it means we are in invariant mode + s_globalizationInvariantMode = true; } } } - return s_globalizationInvariantMode == Tristate.True; } + return s_globalizationInvariantMode.Value; } - #else - /// - /// .NET Framework does not support Globalization Invariant mode, so this will always be false. - /// - public static bool GlobalizationInvariantMode - { - get => false; - } - #endif + } + #else + /// + /// .NET Framework does not support Globalization Invariant mode, so this will always be false. + /// + public static bool GlobalizationInvariantMode + { + get => false; + } + #endif - #if NET + #if NET - #if _WINDOWS - /// - /// When set to true, .NET Core will use the managed SNI implementation instead of the native SNI implementation. - /// - /// - /// - /// Non-Windows platforms will always use the managed networking implementation. Windows platforms will use the native SNI - /// implementation by default, but this can be overridden by setting the AppContext switch. - /// - /// - /// ILLink.Substitutions.xml allows the unused SNI implementation to be trimmed away when the corresponding AppContext - /// switch is set at compile time. In such cases, this property will return a constant value, even if the AppContext switch is - /// set or reset at runtime. See the ILLink.Substitutions.Windows.xml and ILLink.Substitutions.Unix.xml resource files for details. - /// - /// - public static bool UseManagedNetworking + #if _WINDOWS + /// + /// When set to true, .NET Core will use the managed SNI implementation instead of the native SNI implementation. + /// + /// + /// + /// Non-Windows platforms will always use the managed networking implementation. Windows platforms will use the native SNI + /// implementation by default, but this can be overridden by setting the AppContext switch. + /// + /// + /// ILLink.Substitutions.xml allows the unused SNI implementation to be trimmed away when the corresponding AppContext + /// switch is set at compile time. In such cases, this property will return a constant value, even if the AppContext switch is + /// set or reset at runtime. See the ILLink.Substitutions.Windows.xml and ILLink.Substitutions.Unix.xml resource files for details. + /// + /// + public static bool UseManagedNetworking + { + get { - get + if (!s_useManagedNetworking.HasValue) { - if (s_useManagedNetworking == Tristate.NotInitialized) + if (!OperatingSystem.IsWindows()) { - if (!OperatingSystem.IsWindows()) - { - s_useManagedNetworking = Tristate.True; - } - else if (AppContext.TryGetSwitch(UseManagedNetworkingOnWindowsString, out bool returnedValue) && returnedValue) - { - s_useManagedNetworking = Tristate.True; - } - else - { - s_useManagedNetworking = Tristate.False; - } + s_useManagedNetworking = true; + } + else if (AppContext.TryGetSwitch(UseManagedNetworkingOnWindowsString, out bool returnedValue) && returnedValue) + { + s_useManagedNetworking = true; + } + else + { + s_useManagedNetworking = false; } - return s_useManagedNetworking == Tristate.True; } + return s_useManagedNetworking.Value; } - #else - /// - /// .NET Core on Unix does not support the native SNI, so this will always be true. - /// - public static bool UseManagedNetworking => true; - #endif + } + #else + /// + /// .NET Core on Unix does not support the native SNI, so this will always be true. + /// + public static bool UseManagedNetworking => true; + #endif - #else - /// - /// .NET Framework does not support the managed SNI, so this will always be false. - /// - public static bool UseManagedNetworking => false; - #endif + #else + /// + /// .NET Framework does not support the managed SNI, so this will always be false. + /// + public static bool UseManagedNetworking => false; + #endif - #if NETFRAMEWORK - /// - /// Transparent Network IP Resolution (TNIR) is a revision of the existing MultiSubnetFailover feature. - /// TNIR affects the connection sequence of the driver in the case where the first resolved IP of the hostname - /// doesn't respond and there are multiple IPs associated with the hostname. - /// - /// TNIR interacts with MultiSubnetFailover to provide the following three connection sequences: - /// 0: One IP is attempted, followed by all IPs in parallel - /// 1: All IPs are attempted in parallel - /// 2: All IPs are attempted one after another - /// - /// TransparentNetworkIPResolution is enabled by default. MultiSubnetFailover is disabled by default. - /// To disable TNIR, you can enable the app context switch. - /// - /// This app context switch defaults to 'false'. - /// - public static bool DisableTnirByDefault + #if NETFRAMEWORK + /// + /// Transparent Network IP Resolution (TNIR) is a revision of the existing MultiSubnetFailover feature. + /// TNIR affects the connection sequence of the driver in the case where the first resolved IP of the hostname + /// doesn't respond and there are multiple IPs associated with the hostname. + /// + /// TNIR interacts with MultiSubnetFailover to provide the following three connection sequences: + /// 0: One IP is attempted, followed by all IPs in parallel + /// 1: All IPs are attempted in parallel + /// 2: All IPs are attempted one after another + /// + /// TransparentNetworkIPResolution is enabled by default. MultiSubnetFailover is disabled by default. + /// To disable TNIR, you can enable the app context switch. + /// + /// This app context switch defaults to 'false'. + /// + public static bool DisableTnirByDefault + { + get { - get + if (!s_disableTnirByDefault.HasValue) { - if (s_disableTnirByDefault == Tristate.NotInitialized) + if (AppContext.TryGetSwitch(DisableTnirByDefaultString, out bool returnedValue) && returnedValue) { - if (AppContext.TryGetSwitch(DisableTnirByDefaultString, out bool returnedValue) && returnedValue) - { - s_disableTnirByDefault = Tristate.True; - } - else - { - s_disableTnirByDefault = Tristate.False; - } + s_disableTnirByDefault = true; + } + else + { + s_disableTnirByDefault = false; } - return s_disableTnirByDefault == Tristate.True; } + return s_disableTnirByDefault.Value; } + } #endif - /// - /// When set to true, the default value for MultiSubnetFailover connection string property - /// will be true instead of false. This enables parallel IP connection attempts for - /// improved connection times in multi-subnet environments. - /// This app context switch defaults to 'false'. - /// - public static bool EnableMultiSubnetFailoverByDefault + /// + /// When set to true, the default value for MultiSubnetFailover connection string property + /// will be true instead of false. This enables parallel IP connection attempts for + /// improved connection times in multi-subnet environments. + /// This app context switch defaults to 'false'. + /// + public static bool EnableMultiSubnetFailoverByDefault + { + get { - get + if (!s_multiSubnetFailoverByDefault.HasValue) { - if (s_multiSubnetFailoverByDefault == Tristate.NotInitialized) + if (AppContext.TryGetSwitch(EnableMultiSubnetFailoverByDefaultString, out bool returnedValue) && returnedValue) { - if (AppContext.TryGetSwitch(EnableMultiSubnetFailoverByDefaultString, out bool returnedValue) && returnedValue) - { - s_multiSubnetFailoverByDefault = Tristate.True; - } - else - { - s_multiSubnetFailoverByDefault = Tristate.False; - } + s_multiSubnetFailoverByDefault = true; + } + else + { + s_multiSubnetFailoverByDefault = false; } - return s_multiSubnetFailoverByDefault == Tristate.True; } + return s_multiSubnetFailoverByDefault.Value; } } } diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj b/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj index ffcd2869f9..ec728d6a98 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj +++ b/src/Microsoft.Data.SqlClient/tests/Common/Common.csproj @@ -8,6 +8,7 @@ $(ObjFolder)$(Configuration).$(Platform).$(AssemblyName) $(BinFolder)$(Configuration).$(Platform).$(AssemblyName) true + enable diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs index f97aedfe4e..e3dd3902f0 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CertificateFixtureBase.cs @@ -67,7 +67,7 @@ protected X509Certificate2 CreateCertificate(string subjectName, IEnumerable csc.Location == storeLocation && csc.Name == storeName); + CertificateStoreContext? storeContext = _certificateStoreModifications.Find(csc => csc.Location == storeLocation && csc.Name == storeName); if (storeContext == null) { diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs index a4aa84842f..05b54ed091 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs @@ -25,7 +25,7 @@ public sealed class ColumnEncryptionCertificateFixture : CertificateFixtureBase public X509Certificate2 CertificateWithoutPrivateKey { get; } private readonly X509Certificate2 _currentUserCertificate; - private readonly X509Certificate2 _localMachineCertificate; + private readonly X509Certificate2? _localMachineCertificate; public ColumnEncryptionCertificateFixture() { @@ -57,11 +57,19 @@ public ColumnEncryptionCertificateFixture() public X509Certificate2 GetCertificate(StoreLocation storeLocation) { - return storeLocation == StoreLocation.CurrentUser - ? _currentUserCertificate - : storeLocation == StoreLocation.LocalMachine && IsAdmin - ? _localMachineCertificate - : throw new InvalidOperationException("Attempted to retrieve the certificate added to the local machine store; this requires administrator rights."); + if (storeLocation == StoreLocation.CurrentUser) + { + return _currentUserCertificate; + } + + if (storeLocation == StoreLocation.LocalMachine && + IsAdmin && + _localMachineCertificate is not null) + { + return _localMachineCertificate!; + } + + throw new InvalidOperationException("Attempted to retrieve the certificate added to the local machine store; this requires administrator rights."); } public static bool IsAdmin diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs index a91ca7a0e0..7b431782af 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnMasterKeyCertificateFixture.cs @@ -21,7 +21,7 @@ public ColumnMasterKeyCertificateFixture() { } - public X509Certificate2 ColumnMasterKeyCertificate { get; } + public X509Certificate2? ColumnMasterKeyCertificate { get; } protected ColumnMasterKeyCertificateFixture(bool createCertificate) { diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs index 74c4ca0325..56463e91b8 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/CspCertificateFixture.cs @@ -32,17 +32,17 @@ public CspCertificateFixture() public string CspCertificatePath { get; } - public string CspKeyPath { get; } + public string? CspKeyPath { get; } - private string GetCspPathFromCertificate() + private string? GetCspPathFromCertificate() { - RSA privateKey = CspCertificate.GetRSAPrivateKey(); + RSA? privateKey = CspCertificate.GetRSAPrivateKey(); if (privateKey is RSACryptoServiceProvider csp) { return string.Concat(csp.CspKeyContainerInfo.ProviderName, @"/", csp.CspKeyContainerInfo.KeyContainerName); } - else if (privateKey is RSACng cng) + else if (privateKey is RSACng cng && cng.Key.Provider is not null) { return string.Concat(cng.Key.Provider.Provider, @"/", cng.Key.KeyName); } diff --git a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs index 7d90df85fe..a04f4ebcf5 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs @@ -1,6 +1,5 @@ using System; -using System.Collections.Generic; -using System.Reflection; +using System.Threading; namespace Microsoft.Data.SqlClient.Tests.Common; @@ -15,388 +14,118 @@ namespace Microsoft.Data.SqlClient.Tests.Common; /// /// https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization /// -/// This class is not thread-aware and should not be used concurrently. +/// Only one instance can exist at a time. Overlapping constructor calls will +/// wait until the previous instance is disposed. /// public sealed class LocalAppContextSwitchesHelper : IDisposable { #region Private Fields - // These fields are used to expose LocalAppContextSwitches's properties. - private readonly PropertyInfo _legacyRowVersionNullBehaviorProperty; - private readonly PropertyInfo _suppressInsecureTlsWarningProperty; - private readonly PropertyInfo _makeReadAsyncBlockingProperty; - private readonly PropertyInfo _useMinimumLoginTimeoutProperty; - private readonly PropertyInfo _legacyVarTimeZeroScaleBehaviourProperty; - private readonly PropertyInfo _useCompatibilityProcessSniProperty; - private readonly PropertyInfo _useCompatibilityAsyncBehaviourProperty; - private readonly PropertyInfo _useConnectionPoolV2Property; - private readonly PropertyInfo _truncateScaledDecimalProperty; - private readonly PropertyInfo _ignoreServerProvidedFailoverPartner; - private readonly PropertyInfo _enableUserAgent; - private readonly PropertyInfo _enableMultiSubnetFailoverByDefaultProperty; -#if NET - private readonly PropertyInfo _globalizationInvariantModeProperty; - #endif - - #if NET && _WINDOWS - private readonly PropertyInfo _useManagedNetworkingProperty; - #endif - - #if NETFRAMEWORK - private readonly PropertyInfo _disableTnirByDefaultProperty; - #endif + // This semaphore ensures that only one instance of this class may exist at + // a time. + private static readonly SemaphoreSlim s_instanceLock = new(1, 1); // These fields are used to capture the original switch values. - private readonly FieldInfo _legacyRowVersionNullBehaviorField; - private readonly Tristate _legacyRowVersionNullBehaviorOriginal; - private readonly FieldInfo _suppressInsecureTlsWarningField; - private readonly Tristate _suppressInsecureTlsWarningOriginal; - private readonly FieldInfo _makeReadAsyncBlockingField; - private readonly Tristate _makeReadAsyncBlockingOriginal; - private readonly FieldInfo _useMinimumLoginTimeoutField; - private readonly Tristate _useMinimumLoginTimeoutOriginal; - private readonly FieldInfo _legacyVarTimeZeroScaleBehaviourField; - private readonly Tristate _legacyVarTimeZeroScaleBehaviourOriginal; - private readonly FieldInfo _useCompatibilityProcessSniField; - private readonly Tristate _useCompatibilityProcessSniOriginal; - private readonly FieldInfo _useCompatibilityAsyncBehaviourField; - private readonly Tristate _useCompatibilityAsyncBehaviourOriginal; - private readonly FieldInfo _useConnectionPoolV2Field; - private readonly Tristate _useConnectionPoolV2Original; - private readonly FieldInfo _truncateScaledDecimalField; - private readonly Tristate _truncateScaledDecimalOriginal; - private readonly FieldInfo _ignoreServerProvidedFailoverPartnerField; - private readonly Tristate _ignoreServerProvidedFailoverPartnerOriginal; - private readonly FieldInfo _enableUserAgentField; - private readonly Tristate _enableUserAgentOriginal; - private readonly FieldInfo _multiSubnetFailoverByDefaultField; - private readonly Tristate _multiSubnetFailoverByDefaultOriginal; -#if NET - private readonly FieldInfo _globalizationInvariantModeField; - private readonly Tristate _globalizationInvariantModeOriginal; + private readonly bool? _legacyRowVersionNullBehaviorOriginal; + private readonly bool? _suppressInsecureTlsWarningOriginal; + private readonly bool? _makeReadAsyncBlockingOriginal; + private readonly bool? _useMinimumLoginTimeoutOriginal; + private readonly bool? _legacyVarTimeZeroScaleBehaviourOriginal; + private readonly bool? _useCompatibilityProcessSniOriginal; + private readonly bool? _useCompatibilityAsyncBehaviourOriginal; + private readonly bool? _useConnectionPoolV2Original; + private readonly bool? _truncateScaledDecimalOriginal; + private readonly bool? _ignoreServerProvidedFailoverPartnerOriginal; + private readonly bool? _enableUserAgentOriginal; + private readonly bool? _multiSubnetFailoverByDefaultOriginal; + + #if NET + private readonly bool? _globalizationInvariantModeOriginal; #endif #if NET && _WINDOWS - private readonly FieldInfo _useManagedNetworkingField; - private readonly Tristate _useManagedNetworkingOriginal; + private readonly bool? _useManagedNetworkingOriginal; #endif #if NETFRAMEWORK - private readonly FieldInfo _disableTnirByDefaultField; - private readonly Tristate _disableTnirByDefaultOriginal; + private readonly bool? _disableTnirByDefaultOriginal; #endif #endregion - #region Public Types - - /// - /// This enum is used to represent the state of a switch. - /// - /// It is a copy of the Tristate enum from LocalAppContextSwitches. - /// - public enum Tristate : byte - { - NotInitialized = 0, - False = 1, - True = 2 - } - - #endregion - #region Construction /// /// Construct to capture all existing switch values. + /// + /// This call will block, waiting for any previous instance to be disposed + /// before completing construction. /// - /// - /// - /// Throws if any values cannot be captured. - /// public LocalAppContextSwitchesHelper() { - // Acquire a handle to the LocalAppContextSwitches type. - var assembly = typeof(SqlCommandBuilder).Assembly; - var switchesType = assembly.GetType( - "Microsoft.Data.SqlClient.LocalAppContextSwitches"); - if (switchesType == null) + // Wait for any previous instance to be disposed. + // + // We are only willing to wait a short time to avoid deadlocks. + // + if (! s_instanceLock.Wait(TimeSpan.FromSeconds(5))) { - throw new Exception("Unable to find LocalAppContextSwitches type."); + throw new InvalidOperationException( + "Timeout waiting for previous LocalAppContextSwitchesHelper " + + "instance to be disposed."); } - // A local helper to acquire a handle to a property. - void InitProperty(string name, out PropertyInfo property) - { - var prop = switchesType.GetProperty( - name, BindingFlags.Public | BindingFlags.Static); - if (prop == null) - { - throw new Exception($"Unable to find {name} property."); - } - property = prop; - } - - // Acquire handles to all of the public properties of - // LocalAppContextSwitches. - InitProperty( - "LegacyRowVersionNullBehavior", - out _legacyRowVersionNullBehaviorProperty); - - InitProperty( - "SuppressInsecureTlsWarning", - out _suppressInsecureTlsWarningProperty); - - InitProperty( - "MakeReadAsyncBlocking", - out _makeReadAsyncBlockingProperty); - - InitProperty( - "UseMinimumLoginTimeout", - out _useMinimumLoginTimeoutProperty); - - InitProperty( - "LegacyVarTimeZeroScaleBehaviour", - out _legacyVarTimeZeroScaleBehaviourProperty); - - InitProperty( - "UseCompatibilityProcessSni", - out _useCompatibilityProcessSniProperty); - - InitProperty( - "UseCompatibilityAsyncBehaviour", - out _useCompatibilityAsyncBehaviourProperty); - - InitProperty( - "UseConnectionPoolV2", - out _useConnectionPoolV2Property); - - InitProperty( - "TruncateScaledDecimal", - out _truncateScaledDecimalProperty); - - InitProperty( - "IgnoreServerProvidedFailoverPartner", - out _ignoreServerProvidedFailoverPartner); - - InitProperty( - "EnableUserAgent", - out _enableUserAgent); - - InitProperty( - "EnableMultiSubnetFailoverByDefault", - out _enableMultiSubnetFailoverByDefaultProperty); - -#if NET - InitProperty( - "GlobalizationInvariantMode", - out _globalizationInvariantModeProperty); + _legacyRowVersionNullBehaviorOriginal = LocalAppContextSwitches.s_legacyRowVersionNullBehavior; + _suppressInsecureTlsWarningOriginal = LocalAppContextSwitches.s_suppressInsecureTlsWarning; + _makeReadAsyncBlockingOriginal = LocalAppContextSwitches.s_makeReadAsyncBlocking; + _useMinimumLoginTimeoutOriginal = LocalAppContextSwitches.s_useMinimumLoginTimeout; + _legacyVarTimeZeroScaleBehaviourOriginal = LocalAppContextSwitches.s_legacyVarTimeZeroScaleBehaviour; + _useCompatibilityProcessSniOriginal = LocalAppContextSwitches.s_useCompatibilityProcessSni; + _useCompatibilityAsyncBehaviourOriginal = LocalAppContextSwitches.s_useCompatibilityAsyncBehaviour; + _useConnectionPoolV2Original = LocalAppContextSwitches.s_useConnectionPoolV2; + _truncateScaledDecimalOriginal = LocalAppContextSwitches.s_truncateScaledDecimal; + _ignoreServerProvidedFailoverPartnerOriginal = LocalAppContextSwitches.s_ignoreServerProvidedFailoverPartner; + _enableUserAgentOriginal = LocalAppContextSwitches.s_enableUserAgent; + _multiSubnetFailoverByDefaultOriginal = LocalAppContextSwitches.s_multiSubnetFailoverByDefault; + #if NET + _globalizationInvariantModeOriginal = LocalAppContextSwitches.s_globalizationInvariantMode; #endif - #if NET && _WINDOWS - InitProperty( - "UseManagedNetworking", - out _useManagedNetworkingProperty); - #endif - - #if NETFRAMEWORK - InitProperty( - "DisableTnirByDefault", - out _disableTnirByDefaultProperty); - #endif - - // A local helper to capture the original value of a switch. - void InitField(string name, out FieldInfo field, out Tristate value) - { - var fieldInfo = - switchesType.GetField( - name, BindingFlags.NonPublic | BindingFlags.Static); - if (fieldInfo == null) - { - throw new Exception($"Unable to find {name} field."); - } - field = fieldInfo; - value = GetValue(field); - } - - // Capture the original value of each switch. - InitField( - "s_legacyRowVersionNullBehavior", - out _legacyRowVersionNullBehaviorField, - out _legacyRowVersionNullBehaviorOriginal); - - InitField( - "s_suppressInsecureTlsWarning", - out _suppressInsecureTlsWarningField, - out _suppressInsecureTlsWarningOriginal); - - InitField( - "s_makeReadAsyncBlocking", - out _makeReadAsyncBlockingField, - out _makeReadAsyncBlockingOriginal); - - InitField( - "s_useMinimumLoginTimeout", - out _useMinimumLoginTimeoutField, - out _useMinimumLoginTimeoutOriginal); - - InitField( - "s_legacyVarTimeZeroScaleBehaviour", - out _legacyVarTimeZeroScaleBehaviourField, - out _legacyVarTimeZeroScaleBehaviourOriginal); - - InitField( - "s_useCompatibilityProcessSni", - out _useCompatibilityProcessSniField, - out _useCompatibilityProcessSniOriginal); - - InitField( - "s_useCompatibilityAsyncBehaviour", - out _useCompatibilityAsyncBehaviourField, - out _useCompatibilityAsyncBehaviourOriginal); - - InitField( - "s_useConnectionPoolV2", - out _useConnectionPoolV2Field, - out _useConnectionPoolV2Original); - - InitField( - "s_truncateScaledDecimal", - out _truncateScaledDecimalField, - out _truncateScaledDecimalOriginal); - - InitField( - "s_ignoreServerProvidedFailoverPartner", - out _ignoreServerProvidedFailoverPartnerField, - out _ignoreServerProvidedFailoverPartnerOriginal); - - InitField( - "s_enableUserAgent", - out _enableUserAgentField, - out _enableUserAgentOriginal); - - InitField( - "s_multiSubnetFailoverByDefault", - out _multiSubnetFailoverByDefaultField, - out _multiSubnetFailoverByDefaultOriginal); - -#if NET - InitField( - "s_globalizationInvariantMode", - out _globalizationInvariantModeField, - out _globalizationInvariantModeOriginal); + _useManagedNetworkingOriginal = LocalAppContextSwitches.s_useManagedNetworking; #endif - - #if NET && _WINDOWS - InitField( - "s_useManagedNetworking", - out _useManagedNetworkingField, - out _useManagedNetworkingOriginal); -#endif - #if NETFRAMEWORK - InitField( - "s_disableTnirByDefault", - out _disableTnirByDefaultField, - out _disableTnirByDefaultOriginal); + _disableTnirByDefaultOriginal = LocalAppContextSwitches.s_disableTnirByDefault; #endif } /// - /// Disposal restores all original switch values as a best effort. + /// Disposal restores all original switch values and releases the instance lock. /// - /// - /// - /// Throws if any values could not be restored after trying to restore all - /// values. - /// public void Dispose() { - List failedFields = new(); - - void RestoreField(FieldInfo field, Tristate value) - { - try - { - SetValue(field, value); - } - catch (Exception) - { - failedFields.Add(field.Name); - } - } - - RestoreField( - _legacyRowVersionNullBehaviorField, - _legacyRowVersionNullBehaviorOriginal); - - RestoreField( - _suppressInsecureTlsWarningField, - _suppressInsecureTlsWarningOriginal); - - RestoreField( - _makeReadAsyncBlockingField, - _makeReadAsyncBlockingOriginal); - - RestoreField( - _useMinimumLoginTimeoutField, - _useMinimumLoginTimeoutOriginal); - - RestoreField( - _legacyVarTimeZeroScaleBehaviourField, - _legacyVarTimeZeroScaleBehaviourOriginal); - - RestoreField( - _useCompatibilityProcessSniField, - _useCompatibilityProcessSniOriginal); - - RestoreField( - _useCompatibilityAsyncBehaviourField, - _useCompatibilityAsyncBehaviourOriginal); - - RestoreField( - _useConnectionPoolV2Field, - _useConnectionPoolV2Original); - - RestoreField( - _truncateScaledDecimalField, - _truncateScaledDecimalOriginal); - - RestoreField( - _ignoreServerProvidedFailoverPartnerField, - _ignoreServerProvidedFailoverPartnerOriginal); - - RestoreField( - _enableUserAgentField, - _enableUserAgentOriginal); - - RestoreField( - _multiSubnetFailoverByDefaultField, - _multiSubnetFailoverByDefaultOriginal); - + LocalAppContextSwitches.s_legacyRowVersionNullBehavior = _legacyRowVersionNullBehaviorOriginal; + LocalAppContextSwitches.s_suppressInsecureTlsWarning = _suppressInsecureTlsWarningOriginal; + LocalAppContextSwitches.s_makeReadAsyncBlocking = _makeReadAsyncBlockingOriginal; + LocalAppContextSwitches.s_useMinimumLoginTimeout = _useMinimumLoginTimeoutOriginal; + LocalAppContextSwitches.s_legacyVarTimeZeroScaleBehaviour = _legacyVarTimeZeroScaleBehaviourOriginal; + LocalAppContextSwitches.s_useCompatibilityProcessSni = _useCompatibilityProcessSniOriginal; + LocalAppContextSwitches.s_useCompatibilityAsyncBehaviour = _useCompatibilityAsyncBehaviourOriginal; + LocalAppContextSwitches.s_useConnectionPoolV2 = _useConnectionPoolV2Original; + LocalAppContextSwitches.s_truncateScaledDecimal = _truncateScaledDecimalOriginal; + LocalAppContextSwitches.s_ignoreServerProvidedFailoverPartner = _ignoreServerProvidedFailoverPartnerOriginal; + LocalAppContextSwitches.s_enableUserAgent = _enableUserAgentOriginal; + LocalAppContextSwitches.s_multiSubnetFailoverByDefault = _multiSubnetFailoverByDefaultOriginal; #if NET - RestoreField( - _globalizationInvariantModeField, - _globalizationInvariantModeOriginal); + LocalAppContextSwitches.s_globalizationInvariantMode = _globalizationInvariantModeOriginal; #endif - #if NET && _WINDOWS - RestoreField( - _useManagedNetworkingField, - _useManagedNetworkingOriginal); + LocalAppContextSwitches.s_useManagedNetworking = _useManagedNetworkingOriginal; #endif - #if NETFRAMEWORK - RestoreField( - _disableTnirByDefaultField, - _disableTnirByDefaultOriginal); + LocalAppContextSwitches.s_disableTnirByDefault = _disableTnirByDefaultOriginal; #endif - if (failedFields.Count > 0) - { - throw new Exception( - "Failed to restore the following fields: " + - string.Join(", ", failedFields)); - } + // Release the lock to allow another instance to be created. + s_instanceLock.Release(); } #endregion @@ -409,7 +138,7 @@ void RestoreField(FieldInfo field, Tristate value) /// public bool LegacyRowVersionNullBehavior { - get => (bool)_legacyRowVersionNullBehaviorProperty.GetValue(null); + get => LocalAppContextSwitches.LegacyRowVersionNullBehavior; } /// @@ -417,7 +146,7 @@ public bool LegacyRowVersionNullBehavior /// public bool SuppressInsecureTlsWarning { - get => (bool)_suppressInsecureTlsWarningProperty.GetValue(null); + get => LocalAppContextSwitches.SuppressInsecureTlsWarning; } /// @@ -425,7 +154,7 @@ public bool SuppressInsecureTlsWarning /// public bool MakeReadAsyncBlocking { - get => (bool)_makeReadAsyncBlockingProperty.GetValue(null); + get => LocalAppContextSwitches.MakeReadAsyncBlocking; } /// @@ -433,7 +162,7 @@ public bool MakeReadAsyncBlocking /// public bool UseMinimumLoginTimeout { - get => (bool)_useMinimumLoginTimeoutProperty.GetValue(null); + get => LocalAppContextSwitches.UseMinimumLoginTimeout; } /// @@ -442,7 +171,7 @@ public bool UseMinimumLoginTimeout /// public bool LegacyVarTimeZeroScaleBehaviour { - get => (bool)_legacyVarTimeZeroScaleBehaviourProperty.GetValue(null); + get => LocalAppContextSwitches.LegacyVarTimeZeroScaleBehaviour; } /// @@ -450,7 +179,7 @@ public bool LegacyVarTimeZeroScaleBehaviour /// public bool UseCompatibilityProcessSni { - get => (bool)_useCompatibilityProcessSniProperty.GetValue(null); + get => LocalAppContextSwitches.UseCompatibilityProcessSni; } /// @@ -459,7 +188,7 @@ public bool UseCompatibilityProcessSni /// public bool UseCompatibilityAsyncBehaviour { - get => (bool)_useCompatibilityAsyncBehaviourProperty.GetValue(null); + get => LocalAppContextSwitches.UseCompatibilityAsyncBehaviour; } /// @@ -467,7 +196,7 @@ public bool UseCompatibilityAsyncBehaviour /// public bool UseConnectionPoolV2 { - get => (bool)_useConnectionPoolV2Property.GetValue(null); + get => LocalAppContextSwitches.UseConnectionPoolV2; } /// @@ -475,22 +204,22 @@ public bool UseConnectionPoolV2 /// public bool TruncateScaledDecimal { - get => (bool)_truncateScaledDecimalProperty.GetValue(null); + get => LocalAppContextSwitches.TruncateScaledDecimal; } public bool IgnoreServerProvidedFailoverPartner { - get => (bool)_ignoreServerProvidedFailoverPartner.GetValue(null); + get => LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner; } public bool EnableUserAgent { - get => (bool)_enableUserAgent.GetValue(null); + get => LocalAppContextSwitches.EnableUserAgent; } public bool EnableMultiSubnetFailoverByDefault { - get => (bool)_enableMultiSubnetFailoverByDefaultProperty.GetValue(null); + get => LocalAppContextSwitches.EnableMultiSubnetFailoverByDefault; } #if NET @@ -499,7 +228,7 @@ public bool EnableMultiSubnetFailoverByDefault /// public bool GlobalizationInvariantMode { - get => (bool)_globalizationInvariantModeProperty.GetValue(null); + get => LocalAppContextSwitches.GlobalizationInvariantMode; } #endif @@ -509,7 +238,7 @@ public bool GlobalizationInvariantMode /// public bool UseManagedNetworking { - get => (bool)_useManagedNetworkingProperty.GetValue(null); + get => LocalAppContextSwitches.UseManagedNetworking; } #endif @@ -519,7 +248,7 @@ public bool UseManagedNetworking /// public bool DisableTnirByDefault { - get => (bool)_disableTnirByDefaultProperty.GetValue(null); + get => LocalAppContextSwitches.DisableTnirByDefault; } #endif @@ -531,116 +260,116 @@ public bool DisableTnirByDefault /// Get or set the LocalAppContextSwitches.LegacyRowVersionNullBehavior /// switch value. /// - public Tristate LegacyRowVersionNullBehaviorField + public bool? LegacyRowVersionNullBehaviorValue { - get => GetValue(_legacyRowVersionNullBehaviorField); - set => SetValue(_legacyRowVersionNullBehaviorField, value); + get => LocalAppContextSwitches.s_legacyRowVersionNullBehavior; + set => LocalAppContextSwitches.s_legacyRowVersionNullBehavior = value; } /// /// Get or set the LocalAppContextSwitches.SuppressInsecureTlsWarning /// switch value. /// - public Tristate SuppressInsecureTlsWarningField + public bool? SuppressInsecureTlsWarningValue { - get => GetValue(_suppressInsecureTlsWarningField); - set => SetValue(_suppressInsecureTlsWarningField, value); + get => LocalAppContextSwitches.s_suppressInsecureTlsWarning; + set => LocalAppContextSwitches.s_suppressInsecureTlsWarning = value; } /// /// Get or set the LocalAppContextSwitches.MakeReadAsyncBlocking switch /// value. /// - public Tristate MakeReadAsyncBlockingField + public bool? MakeReadAsyncBlockingValue { - get => GetValue(_makeReadAsyncBlockingField); - set => SetValue(_makeReadAsyncBlockingField, value); + get => LocalAppContextSwitches.s_makeReadAsyncBlocking; + set => LocalAppContextSwitches.s_makeReadAsyncBlocking = value; } /// /// Get or set the LocalAppContextSwitches.UseMinimumLoginTimeout switch /// value. /// - public Tristate UseMinimumLoginTimeoutField + public bool? UseMinimumLoginTimeoutValue { - get => GetValue(_useMinimumLoginTimeoutField); - set => SetValue(_useMinimumLoginTimeoutField, value); + get => LocalAppContextSwitches.s_useMinimumLoginTimeout; + set => LocalAppContextSwitches.s_useMinimumLoginTimeout = value; } /// /// Get or set the LocalAppContextSwitches.LegacyVarTimeZeroScaleBehaviour /// switch value. /// - public Tristate LegacyVarTimeZeroScaleBehaviourField + public bool? LegacyVarTimeZeroScaleBehaviourValue { - get => GetValue(_legacyVarTimeZeroScaleBehaviourField); - set => SetValue(_legacyVarTimeZeroScaleBehaviourField, value); + get => LocalAppContextSwitches.s_legacyVarTimeZeroScaleBehaviour; + set => LocalAppContextSwitches.s_legacyVarTimeZeroScaleBehaviour = value; } /// /// Get or set the LocalAppContextSwitches.UseCompatibilityProcessSni switch /// value. /// - public Tristate UseCompatibilityProcessSniField + public bool? UseCompatibilityProcessSniValue { - get => GetValue(_useCompatibilityProcessSniField); - set => SetValue(_useCompatibilityProcessSniField, value); + get => LocalAppContextSwitches.s_useCompatibilityProcessSni; + set => LocalAppContextSwitches.s_useCompatibilityProcessSni = value; } /// /// Get or set the LocalAppContextSwitches.UseCompatibilityAsyncBehaviour /// switch value. /// - public Tristate UseCompatibilityAsyncBehaviourField + public bool? UseCompatibilityAsyncBehaviourValue { - get => GetValue(_useCompatibilityAsyncBehaviourField); - set => SetValue(_useCompatibilityAsyncBehaviourField, value); + get => LocalAppContextSwitches.s_useCompatibilityAsyncBehaviour; + set => LocalAppContextSwitches.s_useCompatibilityAsyncBehaviour = value; } /// /// Get or set the LocalAppContextSwitches.UseConnectionPoolV2 switch value. /// - public Tristate UseConnectionPoolV2Field + public bool? UseConnectionPoolV2Value { - get => GetValue(_useConnectionPoolV2Field); - set => SetValue(_useConnectionPoolV2Field, value); + get => LocalAppContextSwitches.s_useConnectionPoolV2; + set => LocalAppContextSwitches.s_useConnectionPoolV2 = value; } /// /// Get or set the LocalAppContextSwitches.TruncateScaledDecimal switch value. /// - public Tristate TruncateScaledDecimalField + public bool? TruncateScaledDecimalValue { - get => GetValue(_truncateScaledDecimalField); - set => SetValue(_truncateScaledDecimalField, value); + get => LocalAppContextSwitches.s_truncateScaledDecimal; + set => LocalAppContextSwitches.s_truncateScaledDecimal = value; } - public Tristate IgnoreServerProvidedFailoverPartnerField + public bool? IgnoreServerProvidedFailoverPartnerValue { - get => GetValue(_ignoreServerProvidedFailoverPartnerField); - set => SetValue(_ignoreServerProvidedFailoverPartnerField, value); + get => LocalAppContextSwitches.s_ignoreServerProvidedFailoverPartner; + set => LocalAppContextSwitches.s_ignoreServerProvidedFailoverPartner = value; } - public Tristate EnableUserAgentField + public bool? EnableUserAgentValue { - get => GetValue(_enableUserAgentField); - set => SetValue(_enableUserAgentField, value); + get => LocalAppContextSwitches.s_enableUserAgent; + set => LocalAppContextSwitches.s_enableUserAgent = value; } - public Tristate EnableMultiSubnetFailoverByDefaultField + public bool? EnableMultiSubnetFailoverByDefaultValue { - get => GetValue(_multiSubnetFailoverByDefaultField); - set => SetValue(_multiSubnetFailoverByDefaultField, value); + get => LocalAppContextSwitches.s_multiSubnetFailoverByDefault; + set => LocalAppContextSwitches.s_multiSubnetFailoverByDefault = value; } #if NET /// /// Get or set the LocalAppContextSwitches.GlobalizationInvariantMode switch value. /// - public Tristate GlobalizationInvariantModeField + public bool? GlobalizationInvariantModeValue { - get => GetValue(_globalizationInvariantModeField); - set => SetValue(_globalizationInvariantModeField, value); + get => LocalAppContextSwitches.s_globalizationInvariantMode; + set => LocalAppContextSwitches.s_globalizationInvariantMode = value; } #endif @@ -648,10 +377,10 @@ public Tristate GlobalizationInvariantModeField /// /// Get or set the LocalAppContextSwitches.UseManagedNetworking switch value. /// - public Tristate UseManagedNetworkingField + public bool? UseManagedNetworkingValue { - get => GetValue(_useManagedNetworkingField); - set => SetValue(_useManagedNetworkingField, value); + get => LocalAppContextSwitches.s_useManagedNetworking; + set => LocalAppContextSwitches.s_useManagedNetworking = value; } #endif @@ -660,34 +389,12 @@ public Tristate UseManagedNetworkingField /// Get or set the LocalAppContextSwitches.DisableTnirByDefault switch /// value. /// - public Tristate DisableTnirByDefaultField + public bool? DisableTnirByDefaultValue { - get => GetValue(_disableTnirByDefaultField); - set => SetValue(_disableTnirByDefaultField, value); + get => LocalAppContextSwitches.s_disableTnirByDefault; + set => LocalAppContextSwitches.s_disableTnirByDefault = value; } #endif #endregion - - #region Private Helpers - - // Get the value of the given field, or throw if it is null. - private static Tristate GetValue(FieldInfo field) - { - var value = field.GetValue(null); - if (value is null) - { - throw new Exception($"Field {field.Name} has a null value."); - } - - return (Tristate)value; - } - - // Set the value of the given field. - private static void SetValue(FieldInfo field, Tristate value) - { - field.SetValue(null, (byte)value); - } - - #endregion } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs index f62c5a70c8..8506e35e98 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs @@ -1952,10 +1952,8 @@ public void SqlDateTime2Scale_Legacy(int? setScale, byte outputScale, bool legac lock (_parameterLegacyScaleLock) { using SwitchesHelper switches = new SwitchesHelper(); - switches.LegacyVarTimeZeroScaleBehaviourField = - legacyVarTimeZeroScaleSwitchValue - ? SwitchesHelper.Tristate.True - : SwitchesHelper.Tristate.False; + switches.LegacyVarTimeZeroScaleBehaviourValue = + legacyVarTimeZeroScaleSwitchValue; var parameter = new SqlParameter { diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs index b071eeaf5a..4b7ac38cfa 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs @@ -268,7 +268,7 @@ public static void CheckNullRowVersionIsDBNull() lock (s_rowVersionLock) { using SwitchesHelper helper = new(); - helper.LegacyRowVersionNullBehaviorField = SwitchesHelper.Tristate.False; + helper.LegacyRowVersionNullBehaviorValue = false; using SqlConnection con = new(DataTestUtility.TCPConnectionString); con.Open(); @@ -869,7 +869,7 @@ public static void CheckLegacyNullRowVersionIsEmptyArray() lock (s_rowVersionLock) { using SwitchesHelper helper = new(); - helper.LegacyRowVersionNullBehaviorField = SwitchesHelper.Tristate.True; + helper.LegacyRowVersionNullBehaviorValue = true; using SqlConnection con = new(DataTestUtility.TCPConnectionString); con.Open(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs index 7ae4a82fb9..b16368a9c0 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -589,7 +589,7 @@ public static void TestScaledDecimalParameter_CommandInsert(string connectionStr { using (SqlCommand cmd = connection.CreateCommand()) { - appContextSwitchesHelper.TruncateScaledDecimalField = truncateScaledDecimal ? LocalAppContextSwitchesHelper.Tristate.True : LocalAppContextSwitchesHelper.Tristate.False; + appContextSwitchesHelper.TruncateScaledDecimalValue = truncateScaledDecimal; var p = new SqlParameter("@Value", null) { @@ -636,7 +636,7 @@ public static void TestScaledDecimalParameter_BulkCopy(string connectionString, } bulkCopy.DestinationTableName = tableName; - appContextSwitchesHelper.TruncateScaledDecimalField = truncateScaledDecimal ? LocalAppContextSwitchesHelper.Tristate.True : LocalAppContextSwitchesHelper.Tristate.False; + appContextSwitchesHelper.TruncateScaledDecimalValue = truncateScaledDecimal; bulkCopy.WriteToServer(table); } Assert.True(ValidateInsertedValues(connection, tableName, truncateScaledDecimal), $"Invalid test happened with connection string [{connection.ConnectionString}]"); @@ -681,7 +681,7 @@ public static void TestScaledDecimalTVP_CommandSP(string connectionString, bool table.Rows.Add(newRow); } p.Value = table; - appContextSwitchesHelper.TruncateScaledDecimalField = truncateScaledDecimal ? LocalAppContextSwitchesHelper.Tristate.True : LocalAppContextSwitchesHelper.Tristate.False; + appContextSwitchesHelper.TruncateScaledDecimalValue = truncateScaledDecimal; cmd.ExecuteNonQuery(); } // TVP always rounds data without attention to the configuration. diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs index e413821932..6133671881 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs @@ -16,25 +16,25 @@ public SqlConnectionStringTest() #if NETFRAMEWORK [Theory] - [InlineData("test.database.windows.net", true, Tristate.True, true)] - [InlineData("test.database.windows.net", false, Tristate.True, false)] - [InlineData("test.database.windows.net", null, Tristate.True, false)] - [InlineData("test.database.windows.net", true, Tristate.False, true)] - [InlineData("test.database.windows.net", false, Tristate.False, false)] - [InlineData("test.database.windows.net", null, Tristate.False, true)] - [InlineData("test.database.windows.net", true, Tristate.NotInitialized, true)] - [InlineData("test.database.windows.net", false, Tristate.NotInitialized, false)] - [InlineData("test.database.windows.net", null, Tristate.NotInitialized, true)] - [InlineData("my.test.server", true, Tristate.True, true)] - [InlineData("my.test.server", false, Tristate.True, false)] - [InlineData("my.test.server", null, Tristate.True, false)] - [InlineData("my.test.server", true, Tristate.False, true)] - [InlineData("my.test.server", false, Tristate.False, false)] - [InlineData("my.test.server", null, Tristate.False, true)] - [InlineData("my.test.server", true, Tristate.NotInitialized, true)] - [InlineData("my.test.server", false, Tristate.NotInitialized, false)] - [InlineData("my.test.server", null, Tristate.NotInitialized, true)] - public void TestDefaultTnir(string dataSource, bool? tnirEnabledInConnString, Tristate tnirDisabledAppContext, bool expectedValue) + [InlineData("test.database.windows.net", true, true, true)] + [InlineData("test.database.windows.net", false, true, false)] + [InlineData("test.database.windows.net", null, true, false)] + [InlineData("test.database.windows.net", true, false, true)] + [InlineData("test.database.windows.net", false, false, false)] + [InlineData("test.database.windows.net", null, false, true)] + [InlineData("test.database.windows.net", true, null, true)] + [InlineData("test.database.windows.net", false, null, false)] + [InlineData("test.database.windows.net", null, null, true)] + [InlineData("my.test.server", true, true, true)] + [InlineData("my.test.server", false, true, false)] + [InlineData("my.test.server", null, true, false)] + [InlineData("my.test.server", true, false, true)] + [InlineData("my.test.server", false, false, false)] + [InlineData("my.test.server", null, false, true)] + [InlineData("my.test.server", true, null, true)] + [InlineData("my.test.server", false, null, false)] + [InlineData("my.test.server", null, null, true)] + public void TestDefaultTnir(string dataSource, bool? tnirEnabledInConnString, bool? tnirDisabledAppContext, bool expectedValue) { // Note: TNIR is only supported on .NET Framework. // Note: TNIR is disabled by default for Azure SQL Database servers (i.e. *.database.windows.net) @@ -43,7 +43,7 @@ public void TestDefaultTnir(string dataSource, bool? tnirEnabledInConnString, Tr // the value of TransparentNetworkIPResolution property in SqlConnectionString. // Arrange - _appContextSwitchHelper.DisableTnirByDefaultField = tnirDisabledAppContext; + _appContextSwitchHelper.DisableTnirByDefaultValue = tnirDisabledAppContext; // Act SqlConnectionStringBuilder builder = new(); @@ -62,16 +62,16 @@ public void TestDefaultTnir(string dataSource, bool? tnirEnabledInConnString, Tr /// Test MSF values when set through connection string and through app context switch. /// [Theory] - [InlineData(true, Tristate.True, true)] - [InlineData(false, Tristate.True, false)] - [InlineData(null, Tristate.True, true)] - [InlineData(true, Tristate.False, true)] - [InlineData(false, Tristate.False, false)] - [InlineData(null, Tristate.False, false)] - [InlineData(null, Tristate.NotInitialized, false)] - public void TestDefaultMultiSubnetFailover(bool? msfInConnString, Tristate msfEnabledAppContext, bool expectedValue) + [InlineData(true, true, true)] + [InlineData(false, true, false)] + [InlineData(null, true, true)] + [InlineData(true, false, true)] + [InlineData(false, false, false)] + [InlineData(null, false, false)] + [InlineData(null, null, false)] + public void TestDefaultMultiSubnetFailover(bool? msfInConnString, bool? msfEnabledAppContext, bool expectedValue) { - _appContextSwitchHelper.EnableMultiSubnetFailoverByDefaultField = msfEnabledAppContext; + _appContextSwitchHelper.EnableMultiSubnetFailoverByDefaultValue = msfEnabledAppContext; SqlConnectionStringBuilder builder = new(); if (msfInConnString.HasValue) @@ -89,7 +89,7 @@ public void TestDefaultMultiSubnetFailover(bool? msfInConnString, Tristate msfEn [Fact] public void TestMultiSubnetFailoverWithFailoverPartnerThrows() { - _appContextSwitchHelper.EnableMultiSubnetFailoverByDefaultField = Tristate.True; + _appContextSwitchHelper.EnableMultiSubnetFailoverByDefaultValue = true; SqlConnectionStringBuilder builder = new() { diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs index dfc37d2720..f815a909d8 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs @@ -529,7 +529,7 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs { // Arrange using LocalAppContextSwitchesHelper switchesHelper = new(); - switchesHelper.IgnoreServerProvidedFailoverPartnerField = LocalAppContextSwitchesHelper.Tristate.True; + switchesHelper.IgnoreServerProvidedFailoverPartnerValue = true; using TdsServer failoverServer = new( new TdsServerArguments diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs index 9dad33aa1d..daae6d7598 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs @@ -839,7 +839,7 @@ public void TestConnWithUserAgentFeatureExtension(bool forceAck) { // Make sure needed switch is enabled using LocalAppContextSwitchesHelper switchesHelper = new(); - switchesHelper.EnableUserAgentField = LocalAppContextSwitchesHelper.Tristate.True; + switchesHelper.EnableUserAgentValue = true; using var server = new TdsServer(); server.Start(); @@ -935,7 +935,7 @@ public void TestConnWithoutUserAgentFeatureExtension() { // Disable the client-side UserAgent field entirely using LocalAppContextSwitchesHelper switchesHelper = new(); - switchesHelper.EnableUserAgentField = LocalAppContextSwitchesHelper.Tristate.False; + switchesHelper.EnableUserAgentValue = false; using var server = new TdsServer(); server.Start(); From ae224616d05e708c1fea71b18b38012d65c243c0 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 18 Dec 2025 11:21:07 -0400 Subject: [PATCH 2/7] - Reverted back to an atomic byte for the switch values. --- .../Data/SqlClient/LocalAppContextSwitches.cs | 189 ++++++++++-------- .../Common/LocalAppContextSwitchesHelper.cs | 170 ++++++++++------ 2 files changed, 218 insertions(+), 141 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs index b1441db382..e01422a875 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -10,6 +10,8 @@ namespace Microsoft.Data.SqlClient; internal static class LocalAppContextSwitches { + #region Switch Names + private const string MakeReadAsyncBlockingString = @"Switch.Microsoft.Data.SqlClient.MakeReadAsyncBlocking"; private const string LegacyRowVersionNullString = @"Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior"; private const string SuppressInsecureTlsWarningString = @"Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning"; @@ -34,31 +36,50 @@ internal static class LocalAppContextSwitches private const string DisableTnirByDefaultString = @"Switch.Microsoft.Data.SqlClient.DisableTNIRByDefaultInConnectionString"; #endif - // this field is accessed through reflection in tests and should not be renamed or have the type changed without refactoring NullRow related tests - internal static bool? s_legacyRowVersionNullBehavior; - internal static bool? s_suppressInsecureTlsWarning; - internal static bool? s_makeReadAsyncBlocking; - internal static bool? s_useMinimumLoginTimeout; - // this field is accessed through reflection in Microsoft.Data.SqlClient.Tests.SqlParameterTests and should not be renamed or have the type changed without refactoring related tests - internal static bool? s_legacyVarTimeZeroScaleBehaviour; - internal static bool? s_useCompatibilityProcessSni; - internal static bool? s_useCompatibilityAsyncBehaviour; - internal static bool? s_useConnectionPoolV2; - internal static bool? s_truncateScaledDecimal; - internal static bool? s_ignoreServerProvidedFailoverPartner; - internal static bool? s_enableUserAgent; - internal static bool? s_multiSubnetFailoverByDefault; + #endregion + + #region Switch Values + + // We use a byte-based enum to track the value of each switch. This plays + // nicely with threaded access. A nullable bool would seem to be the + // obvious choice, but the way nullable bools are implemented in the CLR + // makes them not thread-safe without using locks (the HasValue and Value + // properties can get out of sync if one thread is writing while another is + // reading). + internal enum SwitchValue : byte + { + None = 0, + True = 1, + False = 2 + } + + internal static SwitchValue s_legacyRowVersionNullBehavior = SwitchValue.None; + internal static SwitchValue s_suppressInsecureTlsWarning = SwitchValue.None; + internal static SwitchValue s_makeReadAsyncBlocking = SwitchValue.None; + internal static SwitchValue s_useMinimumLoginTimeout = SwitchValue.None; + internal static SwitchValue s_legacyVarTimeZeroScaleBehaviour = SwitchValue.None; + internal static SwitchValue s_useCompatibilityProcessSni = SwitchValue.None; + internal static SwitchValue s_useCompatibilityAsyncBehaviour = SwitchValue.None; + internal static SwitchValue s_useConnectionPoolV2 = SwitchValue.None; + internal static SwitchValue s_truncateScaledDecimal = SwitchValue.None; + internal static SwitchValue s_ignoreServerProvidedFailoverPartner = SwitchValue.None; + internal static SwitchValue s_enableUserAgent = SwitchValue.None; + internal static SwitchValue s_multiSubnetFailoverByDefault = SwitchValue.None; #if NET - internal static bool? s_globalizationInvariantMode; + internal static SwitchValue s_globalizationInvariantMode; #endif #if NET && _WINDOWS - internal static bool? s_useManagedNetworking; + internal static TriState s_useManagedNetworking; #endif #if NETFRAMEWORK - internal static bool? s_disableTnirByDefault; + internal static SwitchValue s_disableTnirByDefault; #endif + #endregion + + #region Static Initialization + #if NET static LocalAppContextSwitches() { @@ -77,6 +98,10 @@ static LocalAppContextSwitches() } #endif + #endregion + + #region Switch Properties + // @TODO: Sort by name /// @@ -90,21 +115,21 @@ public static bool UseCompatibilityProcessSni { get { - if (!s_useCompatibilityProcessSni.HasValue) + if (s_useCompatibilityProcessSni == SwitchValue.None) { // Check if the switch has been set by the AppContext switch directly // If it has not been set, we default to true. if (!AppContext.TryGetSwitch(UseCompatibilityProcessSniString, out bool returnedValue) || returnedValue) { - s_useCompatibilityProcessSni = true; + s_useCompatibilityProcessSni = SwitchValue.True; } else { - s_useCompatibilityProcessSni = false; + s_useCompatibilityProcessSni = SwitchValue.False; } } - return s_useCompatibilityProcessSni.Value; + return s_useCompatibilityProcessSni == SwitchValue.True; } } @@ -129,18 +154,18 @@ public static bool UseCompatibilityAsyncBehaviour return true; } - if (!s_useCompatibilityAsyncBehaviour.HasValue) + if (s_useCompatibilityAsyncBehaviour == SwitchValue.None) { if (!AppContext.TryGetSwitch(UseCompatibilityAsyncBehaviourString, out bool returnedValue) || returnedValue) { - s_useCompatibilityAsyncBehaviour = true; + s_useCompatibilityAsyncBehaviour = SwitchValue.True; } else { - s_useCompatibilityAsyncBehaviour = false; + s_useCompatibilityAsyncBehaviour = SwitchValue.False; } } - return s_useCompatibilityAsyncBehaviour.Value; + return s_useCompatibilityAsyncBehaviour == SwitchValue.True; } } @@ -153,18 +178,18 @@ public static bool SuppressInsecureTlsWarning { get { - if (!s_suppressInsecureTlsWarning.HasValue) + if (s_suppressInsecureTlsWarning == SwitchValue.None) { if (AppContext.TryGetSwitch(SuppressInsecureTlsWarningString, out bool returnedValue) && returnedValue) { - s_suppressInsecureTlsWarning = true; + s_suppressInsecureTlsWarning = SwitchValue.True; } else { - s_suppressInsecureTlsWarning = false; + s_suppressInsecureTlsWarning = SwitchValue.False; } } - return s_suppressInsecureTlsWarning.Value; + return s_suppressInsecureTlsWarning == SwitchValue.True; } } @@ -178,18 +203,18 @@ public static bool LegacyRowVersionNullBehavior { get { - if (!s_legacyRowVersionNullBehavior.HasValue) + if (s_legacyRowVersionNullBehavior == SwitchValue.None) { if (AppContext.TryGetSwitch(LegacyRowVersionNullString, out bool returnedValue) && returnedValue) { - s_legacyRowVersionNullBehavior = true; + s_legacyRowVersionNullBehavior = SwitchValue.True; } else { - s_legacyRowVersionNullBehavior = false; + s_legacyRowVersionNullBehavior = SwitchValue.False; } } - return s_legacyRowVersionNullBehavior.Value; + return s_legacyRowVersionNullBehavior == SwitchValue.True; } } @@ -201,18 +226,18 @@ public static bool MakeReadAsyncBlocking { get { - if (!s_makeReadAsyncBlocking.HasValue) + if (s_makeReadAsyncBlocking == SwitchValue.None) { if (AppContext.TryGetSwitch(MakeReadAsyncBlockingString, out bool returnedValue) && returnedValue) { - s_makeReadAsyncBlocking = true; + s_makeReadAsyncBlocking = SwitchValue.True; } else { - s_makeReadAsyncBlocking = false; + s_makeReadAsyncBlocking = SwitchValue.False; } } - return s_makeReadAsyncBlocking.Value; + return s_makeReadAsyncBlocking == SwitchValue.True; } } @@ -225,18 +250,18 @@ public static bool UseMinimumLoginTimeout { get { - if (!s_useMinimumLoginTimeout.HasValue) + if (s_useMinimumLoginTimeout == SwitchValue.None) { if (!AppContext.TryGetSwitch(UseMinimumLoginTimeoutString, out bool returnedValue) || returnedValue) { - s_useMinimumLoginTimeout = true; + s_useMinimumLoginTimeout = SwitchValue.True; } else { - s_useMinimumLoginTimeout = false; + s_useMinimumLoginTimeout = SwitchValue.False; } } - return s_useMinimumLoginTimeout.Value; + return s_useMinimumLoginTimeout == SwitchValue.True; } } @@ -252,18 +277,18 @@ public static bool LegacyVarTimeZeroScaleBehaviour { get { - if (!s_legacyVarTimeZeroScaleBehaviour.HasValue) + if (s_legacyVarTimeZeroScaleBehaviour == SwitchValue.None) { if (!AppContext.TryGetSwitch(LegacyVarTimeZeroScaleBehaviourString, out bool returnedValue)) { - s_legacyVarTimeZeroScaleBehaviour = true; + s_legacyVarTimeZeroScaleBehaviour = SwitchValue.True; } else { - s_legacyVarTimeZeroScaleBehaviour = returnedValue ? true : false; + s_legacyVarTimeZeroScaleBehaviour = returnedValue ? SwitchValue.True : SwitchValue.False; } } - return s_legacyVarTimeZeroScaleBehaviour.Value; + return s_legacyVarTimeZeroScaleBehaviour == SwitchValue.True; } } @@ -276,18 +301,18 @@ public static bool UseConnectionPoolV2 { get { - if (!s_useConnectionPoolV2.HasValue) + if (s_useConnectionPoolV2 == SwitchValue.None) { if (AppContext.TryGetSwitch(UseConnectionPoolV2String, out bool returnedValue) && returnedValue) { - s_useConnectionPoolV2 = true; + s_useConnectionPoolV2 = SwitchValue.True; } else { - s_useConnectionPoolV2 = false; + s_useConnectionPoolV2 = SwitchValue.False; } } - return s_useConnectionPoolV2.Value; + return s_useConnectionPoolV2 == SwitchValue.True; } } @@ -298,18 +323,18 @@ public static bool TruncateScaledDecimal { get { - if (!s_truncateScaledDecimal.HasValue) + if (s_truncateScaledDecimal == SwitchValue.None) { if (AppContext.TryGetSwitch(TruncateScaledDecimalString, out bool returnedValue) && returnedValue) { - s_truncateScaledDecimal = true; + s_truncateScaledDecimal = SwitchValue.True; } else { - s_truncateScaledDecimal = false; + s_truncateScaledDecimal = SwitchValue.False; } } - return s_truncateScaledDecimal.Value; + return s_truncateScaledDecimal == SwitchValue.True; } } @@ -326,18 +351,18 @@ public static bool IgnoreServerProvidedFailoverPartner { get { - if (!s_ignoreServerProvidedFailoverPartner.HasValue) + if (s_ignoreServerProvidedFailoverPartner == SwitchValue.None) { if (AppContext.TryGetSwitch(IgnoreServerProvidedFailoverPartnerString, out bool returnedValue) && returnedValue) { - s_ignoreServerProvidedFailoverPartner = true; + s_ignoreServerProvidedFailoverPartner = SwitchValue.True; } else { - s_ignoreServerProvidedFailoverPartner = false; + s_ignoreServerProvidedFailoverPartner = SwitchValue.False; } } - return s_ignoreServerProvidedFailoverPartner.Value; + return s_ignoreServerProvidedFailoverPartner == SwitchValue.True; } } /// @@ -347,18 +372,18 @@ public static bool EnableUserAgent { get { - if (!s_enableUserAgent.HasValue) + if (s_enableUserAgent == SwitchValue.None) { if (AppContext.TryGetSwitch(EnableUserAgentString, out bool returnedValue) && returnedValue) { - s_enableUserAgent = true; + s_enableUserAgent = SwitchValue.True; } else { - s_enableUserAgent = false; + s_enableUserAgent = SwitchValue.False; } } - return s_enableUserAgent.Value; + return s_enableUserAgent == SwitchValue.True; } } @@ -372,12 +397,12 @@ public static bool GlobalizationInvariantMode { get { - if (!s_globalizationInvariantMode.HasValue) + if (s_globalizationInvariantMode == SwitchValue.None) { // Check if invariant mode has been set by the AppContext switch directly if (AppContext.TryGetSwitch(GlobalizationInvariantModeString, out bool returnedValue) && returnedValue) { - s_globalizationInvariantMode = true; + s_globalizationInvariantMode = SwitchValue.True; } else { @@ -386,7 +411,7 @@ public static bool GlobalizationInvariantMode if (string.Equals(envValue, bool.TrueString, StringComparison.OrdinalIgnoreCase) || string.Equals(envValue, "1", StringComparison.OrdinalIgnoreCase)) { - s_globalizationInvariantMode = true; + s_globalizationInvariantMode = SwitchValue.True; } else { @@ -397,18 +422,18 @@ public static bool GlobalizationInvariantMode try { s_globalizationInvariantMode = System.Globalization.CultureInfo.GetCultureInfo("en-US").EnglishName.Contains("Invariant") - ? true - : false; + ? SwitchValue.True + : SwitchValue.False; } catch (System.Globalization.CultureNotFoundException) { // If the culture is not found, it means we are in invariant mode - s_globalizationInvariantMode = true; + s_globalizationInvariantMode = SwitchValue.True; } } } } - return s_globalizationInvariantMode.Value; + return s_globalizationInvariantMode == SwitchValue.True; } } #else @@ -442,22 +467,22 @@ public static bool UseManagedNetworking { get { - if (!s_useManagedNetworking.HasValue) + if (s_useManagedNetworking == TriState.None) { if (!OperatingSystem.IsWindows()) { - s_useManagedNetworking = true; + s_useManagedNetworking = TriState.True; } else if (AppContext.TryGetSwitch(UseManagedNetworkingOnWindowsString, out bool returnedValue) && returnedValue) { - s_useManagedNetworking = true; + s_useManagedNetworking = TriState.True; } else { - s_useManagedNetworking = false; + s_useManagedNetworking = TriState.False; } } - return s_useManagedNetworking.Value; + return s_useManagedNetworking == TriState.True; } } #else @@ -494,18 +519,18 @@ public static bool DisableTnirByDefault { get { - if (!s_disableTnirByDefault.HasValue) + if (s_disableTnirByDefault == SwitchValue.None) { if (AppContext.TryGetSwitch(DisableTnirByDefaultString, out bool returnedValue) && returnedValue) { - s_disableTnirByDefault = true; + s_disableTnirByDefault = SwitchValue.True; } else { - s_disableTnirByDefault = false; + s_disableTnirByDefault = SwitchValue.False; } } - return s_disableTnirByDefault.Value; + return s_disableTnirByDefault == SwitchValue.True; } } #endif @@ -520,18 +545,20 @@ public static bool EnableMultiSubnetFailoverByDefault { get { - if (!s_multiSubnetFailoverByDefault.HasValue) + if (s_multiSubnetFailoverByDefault == SwitchValue.None) { if (AppContext.TryGetSwitch(EnableMultiSubnetFailoverByDefaultString, out bool returnedValue) && returnedValue) { - s_multiSubnetFailoverByDefault = true; + s_multiSubnetFailoverByDefault = SwitchValue.True; } else { - s_multiSubnetFailoverByDefault = false; + s_multiSubnetFailoverByDefault = SwitchValue.False; } } - return s_multiSubnetFailoverByDefault.Value; + return s_multiSubnetFailoverByDefault == SwitchValue.True; } } + + #endregion } diff --git a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs index a04f4ebcf5..5182e7b7e7 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs @@ -74,26 +74,41 @@ public LocalAppContextSwitchesHelper() "instance to be disposed."); } - _legacyRowVersionNullBehaviorOriginal = LocalAppContextSwitches.s_legacyRowVersionNullBehavior; - _suppressInsecureTlsWarningOriginal = LocalAppContextSwitches.s_suppressInsecureTlsWarning; - _makeReadAsyncBlockingOriginal = LocalAppContextSwitches.s_makeReadAsyncBlocking; - _useMinimumLoginTimeoutOriginal = LocalAppContextSwitches.s_useMinimumLoginTimeout; - _legacyVarTimeZeroScaleBehaviourOriginal = LocalAppContextSwitches.s_legacyVarTimeZeroScaleBehaviour; - _useCompatibilityProcessSniOriginal = LocalAppContextSwitches.s_useCompatibilityProcessSni; - _useCompatibilityAsyncBehaviourOriginal = LocalAppContextSwitches.s_useCompatibilityAsyncBehaviour; - _useConnectionPoolV2Original = LocalAppContextSwitches.s_useConnectionPoolV2; - _truncateScaledDecimalOriginal = LocalAppContextSwitches.s_truncateScaledDecimal; - _ignoreServerProvidedFailoverPartnerOriginal = LocalAppContextSwitches.s_ignoreServerProvidedFailoverPartner; - _enableUserAgentOriginal = LocalAppContextSwitches.s_enableUserAgent; - _multiSubnetFailoverByDefaultOriginal = LocalAppContextSwitches.s_multiSubnetFailoverByDefault; + _legacyRowVersionNullBehaviorOriginal = + Convert(LocalAppContextSwitches.s_legacyRowVersionNullBehavior); + _suppressInsecureTlsWarningOriginal = + Convert(LocalAppContextSwitches.s_suppressInsecureTlsWarning); + _makeReadAsyncBlockingOriginal = + Convert(LocalAppContextSwitches.s_makeReadAsyncBlocking); + _useMinimumLoginTimeoutOriginal = + Convert(LocalAppContextSwitches.s_useMinimumLoginTimeout); + _legacyVarTimeZeroScaleBehaviourOriginal = + Convert(LocalAppContextSwitches.s_legacyVarTimeZeroScaleBehaviour); + _useCompatibilityProcessSniOriginal = + Convert(LocalAppContextSwitches.s_useCompatibilityProcessSni); + _useCompatibilityAsyncBehaviourOriginal = + Convert(LocalAppContextSwitches.s_useCompatibilityAsyncBehaviour); + _useConnectionPoolV2Original = + Convert(LocalAppContextSwitches.s_useConnectionPoolV2); + _truncateScaledDecimalOriginal = + Convert(LocalAppContextSwitches.s_truncateScaledDecimal); + _ignoreServerProvidedFailoverPartnerOriginal = + Convert(LocalAppContextSwitches.s_ignoreServerProvidedFailoverPartner); + _enableUserAgentOriginal = + Convert(LocalAppContextSwitches.s_enableUserAgent); + _multiSubnetFailoverByDefaultOriginal = + Convert(LocalAppContextSwitches.s_multiSubnetFailoverByDefault); #if NET - _globalizationInvariantModeOriginal = LocalAppContextSwitches.s_globalizationInvariantMode; + _globalizationInvariantModeOriginal = + Convert(LocalAppContextSwitches.s_globalizationInvariantMode); #endif #if NET && _WINDOWS - _useManagedNetworkingOriginal = LocalAppContextSwitches.s_useManagedNetworking; + _useManagedNetworkingOriginal = + Convert(LocalAppContextSwitches.s_useManagedNetworking); #endif #if NETFRAMEWORK - _disableTnirByDefaultOriginal = LocalAppContextSwitches.s_disableTnirByDefault; + _disableTnirByDefaultOriginal = + Convert(LocalAppContextSwitches.s_disableTnirByDefault); #endif } @@ -102,26 +117,41 @@ public LocalAppContextSwitchesHelper() /// public void Dispose() { - LocalAppContextSwitches.s_legacyRowVersionNullBehavior = _legacyRowVersionNullBehaviorOriginal; - LocalAppContextSwitches.s_suppressInsecureTlsWarning = _suppressInsecureTlsWarningOriginal; - LocalAppContextSwitches.s_makeReadAsyncBlocking = _makeReadAsyncBlockingOriginal; - LocalAppContextSwitches.s_useMinimumLoginTimeout = _useMinimumLoginTimeoutOriginal; - LocalAppContextSwitches.s_legacyVarTimeZeroScaleBehaviour = _legacyVarTimeZeroScaleBehaviourOriginal; - LocalAppContextSwitches.s_useCompatibilityProcessSni = _useCompatibilityProcessSniOriginal; - LocalAppContextSwitches.s_useCompatibilityAsyncBehaviour = _useCompatibilityAsyncBehaviourOriginal; - LocalAppContextSwitches.s_useConnectionPoolV2 = _useConnectionPoolV2Original; - LocalAppContextSwitches.s_truncateScaledDecimal = _truncateScaledDecimalOriginal; - LocalAppContextSwitches.s_ignoreServerProvidedFailoverPartner = _ignoreServerProvidedFailoverPartnerOriginal; - LocalAppContextSwitches.s_enableUserAgent = _enableUserAgentOriginal; - LocalAppContextSwitches.s_multiSubnetFailoverByDefault = _multiSubnetFailoverByDefaultOriginal; + LocalAppContextSwitches.s_legacyRowVersionNullBehavior = + Convert(_legacyRowVersionNullBehaviorOriginal); + LocalAppContextSwitches.s_suppressInsecureTlsWarning = + Convert(_suppressInsecureTlsWarningOriginal); + LocalAppContextSwitches.s_makeReadAsyncBlocking = + Convert(_makeReadAsyncBlockingOriginal); + LocalAppContextSwitches.s_useMinimumLoginTimeout = + Convert(_useMinimumLoginTimeoutOriginal); + LocalAppContextSwitches.s_legacyVarTimeZeroScaleBehaviour = + Convert(_legacyVarTimeZeroScaleBehaviourOriginal); + LocalAppContextSwitches.s_useCompatibilityProcessSni = + Convert(_useCompatibilityProcessSniOriginal); + LocalAppContextSwitches.s_useCompatibilityAsyncBehaviour = + Convert(_useCompatibilityAsyncBehaviourOriginal); + LocalAppContextSwitches.s_useConnectionPoolV2 = + Convert(_useConnectionPoolV2Original); + LocalAppContextSwitches.s_truncateScaledDecimal = + Convert(_truncateScaledDecimalOriginal); + LocalAppContextSwitches.s_ignoreServerProvidedFailoverPartner = + Convert(_ignoreServerProvidedFailoverPartnerOriginal); + LocalAppContextSwitches.s_enableUserAgent = + Convert(_enableUserAgentOriginal); + LocalAppContextSwitches.s_multiSubnetFailoverByDefault = + Convert(_multiSubnetFailoverByDefaultOriginal); #if NET - LocalAppContextSwitches.s_globalizationInvariantMode = _globalizationInvariantModeOriginal; + LocalAppContextSwitches.s_globalizationInvariantMode = + Convert(_globalizationInvariantModeOriginal); #endif #if NET && _WINDOWS - LocalAppContextSwitches.s_useManagedNetworking = _useManagedNetworkingOriginal; + LocalAppContextSwitches.s_useManagedNetworking = + Convert(_useManagedNetworkingOriginal); #endif #if NETFRAMEWORK - LocalAppContextSwitches.s_disableTnirByDefault = _disableTnirByDefaultOriginal; + LocalAppContextSwitches.s_disableTnirByDefault = + Convert(_disableTnirByDefaultOriginal); #endif // Release the lock to allow another instance to be created. @@ -262,8 +292,8 @@ public bool DisableTnirByDefault /// public bool? LegacyRowVersionNullBehaviorValue { - get => LocalAppContextSwitches.s_legacyRowVersionNullBehavior; - set => LocalAppContextSwitches.s_legacyRowVersionNullBehavior = value; + get => Convert(LocalAppContextSwitches.s_legacyRowVersionNullBehavior); + set => LocalAppContextSwitches.s_legacyRowVersionNullBehavior = Convert(value); } /// @@ -272,8 +302,8 @@ public bool? LegacyRowVersionNullBehaviorValue /// public bool? SuppressInsecureTlsWarningValue { - get => LocalAppContextSwitches.s_suppressInsecureTlsWarning; - set => LocalAppContextSwitches.s_suppressInsecureTlsWarning = value; + get => Convert(LocalAppContextSwitches.s_suppressInsecureTlsWarning); + set => LocalAppContextSwitches.s_suppressInsecureTlsWarning = Convert(value); } /// @@ -282,8 +312,8 @@ public bool? SuppressInsecureTlsWarningValue /// public bool? MakeReadAsyncBlockingValue { - get => LocalAppContextSwitches.s_makeReadAsyncBlocking; - set => LocalAppContextSwitches.s_makeReadAsyncBlocking = value; + get => Convert(LocalAppContextSwitches.s_makeReadAsyncBlocking); + set => LocalAppContextSwitches.s_makeReadAsyncBlocking = Convert(value); } /// @@ -292,8 +322,8 @@ public bool? MakeReadAsyncBlockingValue /// public bool? UseMinimumLoginTimeoutValue { - get => LocalAppContextSwitches.s_useMinimumLoginTimeout; - set => LocalAppContextSwitches.s_useMinimumLoginTimeout = value; + get => Convert(LocalAppContextSwitches.s_useMinimumLoginTimeout); + set => LocalAppContextSwitches.s_useMinimumLoginTimeout = Convert(value); } /// @@ -302,8 +332,8 @@ public bool? UseMinimumLoginTimeoutValue /// public bool? LegacyVarTimeZeroScaleBehaviourValue { - get => LocalAppContextSwitches.s_legacyVarTimeZeroScaleBehaviour; - set => LocalAppContextSwitches.s_legacyVarTimeZeroScaleBehaviour = value; + get => Convert(LocalAppContextSwitches.s_legacyVarTimeZeroScaleBehaviour); + set => LocalAppContextSwitches.s_legacyVarTimeZeroScaleBehaviour = Convert(value); } /// @@ -312,8 +342,8 @@ public bool? LegacyVarTimeZeroScaleBehaviourValue /// public bool? UseCompatibilityProcessSniValue { - get => LocalAppContextSwitches.s_useCompatibilityProcessSni; - set => LocalAppContextSwitches.s_useCompatibilityProcessSni = value; + get => Convert(LocalAppContextSwitches.s_useCompatibilityProcessSni); + set => LocalAppContextSwitches.s_useCompatibilityProcessSni = Convert(value); } /// @@ -322,8 +352,8 @@ public bool? UseCompatibilityProcessSniValue /// public bool? UseCompatibilityAsyncBehaviourValue { - get => LocalAppContextSwitches.s_useCompatibilityAsyncBehaviour; - set => LocalAppContextSwitches.s_useCompatibilityAsyncBehaviour = value; + get => Convert(LocalAppContextSwitches.s_useCompatibilityAsyncBehaviour); + set => LocalAppContextSwitches.s_useCompatibilityAsyncBehaviour = Convert(value); } /// @@ -331,8 +361,8 @@ public bool? UseCompatibilityAsyncBehaviourValue /// public bool? UseConnectionPoolV2Value { - get => LocalAppContextSwitches.s_useConnectionPoolV2; - set => LocalAppContextSwitches.s_useConnectionPoolV2 = value; + get => Convert(LocalAppContextSwitches.s_useConnectionPoolV2); + set => LocalAppContextSwitches.s_useConnectionPoolV2 = Convert(value); } /// @@ -340,26 +370,26 @@ public bool? UseConnectionPoolV2Value /// public bool? TruncateScaledDecimalValue { - get => LocalAppContextSwitches.s_truncateScaledDecimal; - set => LocalAppContextSwitches.s_truncateScaledDecimal = value; + get => Convert(LocalAppContextSwitches.s_truncateScaledDecimal); + set => LocalAppContextSwitches.s_truncateScaledDecimal = Convert(value); } public bool? IgnoreServerProvidedFailoverPartnerValue { - get => LocalAppContextSwitches.s_ignoreServerProvidedFailoverPartner; - set => LocalAppContextSwitches.s_ignoreServerProvidedFailoverPartner = value; + get => Convert(LocalAppContextSwitches.s_ignoreServerProvidedFailoverPartner); + set => LocalAppContextSwitches.s_ignoreServerProvidedFailoverPartner = Convert(value); } public bool? EnableUserAgentValue { - get => LocalAppContextSwitches.s_enableUserAgent; - set => LocalAppContextSwitches.s_enableUserAgent = value; + get => Convert(LocalAppContextSwitches.s_enableUserAgent); + set => LocalAppContextSwitches.s_enableUserAgent = Convert(value); } public bool? EnableMultiSubnetFailoverByDefaultValue { - get => LocalAppContextSwitches.s_multiSubnetFailoverByDefault; - set => LocalAppContextSwitches.s_multiSubnetFailoverByDefault = value; + get => Convert(LocalAppContextSwitches.s_multiSubnetFailoverByDefault); + set => LocalAppContextSwitches.s_multiSubnetFailoverByDefault = Convert(value); } #if NET @@ -368,8 +398,8 @@ public bool? EnableMultiSubnetFailoverByDefaultValue /// public bool? GlobalizationInvariantModeValue { - get => LocalAppContextSwitches.s_globalizationInvariantMode; - set => LocalAppContextSwitches.s_globalizationInvariantMode = value; + get => Convert(LocalAppContextSwitches.s_globalizationInvariantMode); + set => LocalAppContextSwitches.s_globalizationInvariantMode = Convert(value); } #endif @@ -379,8 +409,8 @@ public bool? GlobalizationInvariantModeValue /// public bool? UseManagedNetworkingValue { - get => LocalAppContextSwitches.s_useManagedNetworking; - set => LocalAppContextSwitches.s_useManagedNetworking = value; + get => Convert(LocalAppContextSwitches.s_useManagedNetworking); + set => LocalAppContextSwitches.s_useManagedNetworking = Convert(value); } #endif @@ -391,10 +421,30 @@ public bool? UseManagedNetworkingValue /// public bool? DisableTnirByDefaultValue { - get => LocalAppContextSwitches.s_disableTnirByDefault; - set => LocalAppContextSwitches.s_disableTnirByDefault = value; + get => Convert(LocalAppContextSwitches.s_disableTnirByDefault); + set => LocalAppContextSwitches.s_disableTnirByDefault = Convert(value); } #endif #endregion + + #region Helpers + + private static bool? Convert(LocalAppContextSwitches.SwitchValue value) + { + return value == LocalAppContextSwitches.SwitchValue.None + ? null + : value == LocalAppContextSwitches.SwitchValue.True; + } + + private static LocalAppContextSwitches.SwitchValue Convert(bool? value) + { + return !value.HasValue + ? LocalAppContextSwitches.SwitchValue.None + : value.Value + ? LocalAppContextSwitches.SwitchValue.True + : LocalAppContextSwitches.SwitchValue.False; + } + + #endregion } From 6bf4eaadfdf1f41142b01b5f358cc4b66ee06bc9 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 18 Dec 2025 13:34:58 -0400 Subject: [PATCH 3/7] - Reverted to reflection since the Common project is used by other test projects that don't have access to MDS internals. --- .../src/Microsoft.Data.SqlClient.csproj | 1 - .../netfx/src/Microsoft.Data.SqlClient.csproj | 3 - .../Data/SqlClient/LocalAppContextSwitches.cs | 12 +- .../Common/LocalAppContextSwitchesHelper.cs | 518 +++++++++--------- .../tests/FunctionalTests/SqlParameterTest.cs | 2 +- .../TdsParserStateObject.TestHarness.cs | 24 +- .../SQL/DataReaderTest/DataReaderTest.cs | 4 +- .../SQL/ParameterTest/ParametersTest.cs | 6 +- .../AdjustPrecScaleForBulkCopy.cs | 4 +- .../SqlClient/LocalAppContextSwitchesTest.cs | 15 +- .../Data/SqlClient/SqlConnectionStringTest.cs | 23 +- .../ConnectionFailoverTests.cs | 2 +- .../SimulatedServerTests/ConnectionTests.cs | 4 +- 13 files changed, 313 insertions(+), 305 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj index 512db7418e..f8fc03f915 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft.Data.SqlClient.csproj @@ -32,7 +32,6 @@ $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFramework)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) - diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj index bc14696009..e0974d0d44 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft.Data.SqlClient.csproj @@ -33,9 +33,6 @@ <_Parameter1>UnitTests - - <_Parameter1>Common - diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs index e01422a875..a32ae5ba8e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -70,7 +70,7 @@ internal enum SwitchValue : byte internal static SwitchValue s_globalizationInvariantMode; #endif #if NET && _WINDOWS - internal static TriState s_useManagedNetworking; + internal static SwitchValue s_useManagedNetworking; #endif #if NETFRAMEWORK internal static SwitchValue s_disableTnirByDefault; @@ -467,22 +467,22 @@ public static bool UseManagedNetworking { get { - if (s_useManagedNetworking == TriState.None) + if (s_useManagedNetworking == SwitchValue.None) { if (!OperatingSystem.IsWindows()) { - s_useManagedNetworking = TriState.True; + s_useManagedNetworking = SwitchValue.True; } else if (AppContext.TryGetSwitch(UseManagedNetworkingOnWindowsString, out bool returnedValue) && returnedValue) { - s_useManagedNetworking = TriState.True; + s_useManagedNetworking = SwitchValue.True; } else { - s_useManagedNetworking = TriState.False; + s_useManagedNetworking = SwitchValue.False; } } - return s_useManagedNetworking == TriState.True; + return s_useManagedNetworking == SwitchValue.True; } } #else diff --git a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs index 5182e7b7e7..7919ecb8f9 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs @@ -1,31 +1,40 @@ using System; +using System.Reflection; using System.Threading; namespace Microsoft.Data.SqlClient.Tests.Common; /// -/// This class provides read/write access to LocalAppContextSwitches values -/// for the duration of a test. It is intended to be constructed at the start -/// of a test and disposed of at the end. It captures the original values of -/// the switches and restores them when disposed. +/// This class provides read/write access to LocalAppContextSwitches values for +/// the duration of a test. It is intended to be constructed at the start of a +/// test and disposed of at the end. It captures the original values of the +/// switches and restores them when disposed. /// /// This follows the RAII pattern to ensure that the switches are always /// restored, which is important for global state like LocalAppContextSwitches. /// /// https://en.wikipedia.org/wiki/Resource_acquisition_is_initialization -/// -/// Only one instance can exist at a time. Overlapping constructor calls will -/// wait until the previous instance is disposed. +/// +/// As with all global state, care must be taken when using this class in tests +/// that may run in parallel. This class enforces a single instance policy +/// using a semaphore. Overlapping constructor calls will wait up to 5 seconds +/// for the previous instance to be disposed. Any tests that use this class +/// should not keep an instance alive for longer than 5 seconds, or they risk +/// causing failures in other tests. /// public sealed class LocalAppContextSwitchesHelper : IDisposable { #region Private Fields - // This semaphore ensures that only one instance of this class may exist at - // a time. + /// + /// This semaphore ensures that only one instance of this class may exist at + /// a time. + /// private static readonly SemaphoreSlim s_instanceLock = new(1, 1); - // These fields are used to capture the original switch values. + /// + /// These fields are used to capture the original switch values. + /// private readonly bool? _legacyRowVersionNullBehaviorOriginal; private readonly bool? _suppressInsecureTlsWarningOriginal; private readonly bool? _makeReadAsyncBlockingOriginal; @@ -74,42 +83,52 @@ public LocalAppContextSwitchesHelper() "instance to be disposed."); } - _legacyRowVersionNullBehaviorOriginal = - Convert(LocalAppContextSwitches.s_legacyRowVersionNullBehavior); - _suppressInsecureTlsWarningOriginal = - Convert(LocalAppContextSwitches.s_suppressInsecureTlsWarning); - _makeReadAsyncBlockingOriginal = - Convert(LocalAppContextSwitches.s_makeReadAsyncBlocking); - _useMinimumLoginTimeoutOriginal = - Convert(LocalAppContextSwitches.s_useMinimumLoginTimeout); - _legacyVarTimeZeroScaleBehaviourOriginal = - Convert(LocalAppContextSwitches.s_legacyVarTimeZeroScaleBehaviour); - _useCompatibilityProcessSniOriginal = - Convert(LocalAppContextSwitches.s_useCompatibilityProcessSni); - _useCompatibilityAsyncBehaviourOriginal = - Convert(LocalAppContextSwitches.s_useCompatibilityAsyncBehaviour); - _useConnectionPoolV2Original = - Convert(LocalAppContextSwitches.s_useConnectionPoolV2); - _truncateScaledDecimalOriginal = - Convert(LocalAppContextSwitches.s_truncateScaledDecimal); - _ignoreServerProvidedFailoverPartnerOriginal = - Convert(LocalAppContextSwitches.s_ignoreServerProvidedFailoverPartner); - _enableUserAgentOriginal = - Convert(LocalAppContextSwitches.s_enableUserAgent); - _multiSubnetFailoverByDefaultOriginal = - Convert(LocalAppContextSwitches.s_multiSubnetFailoverByDefault); - #if NET - _globalizationInvariantModeOriginal = - Convert(LocalAppContextSwitches.s_globalizationInvariantMode); - #endif - #if NET && _WINDOWS - _useManagedNetworkingOriginal = - Convert(LocalAppContextSwitches.s_useManagedNetworking); - #endif - #if NETFRAMEWORK - _disableTnirByDefaultOriginal = - Convert(LocalAppContextSwitches.s_disableTnirByDefault); - #endif + try + { + _legacyRowVersionNullBehaviorOriginal = + GetSwitchValue("s_legacyRowVersionNullBehavior"); + _suppressInsecureTlsWarningOriginal = + GetSwitchValue("s_suppressInsecureTlsWarning"); + _makeReadAsyncBlockingOriginal = + GetSwitchValue("s_makeReadAsyncBlocking"); + _useMinimumLoginTimeoutOriginal = + GetSwitchValue("s_useMinimumLoginTimeout"); + _legacyVarTimeZeroScaleBehaviourOriginal = + GetSwitchValue("s_legacyVarTimeZeroScaleBehaviour"); + _useCompatibilityProcessSniOriginal = + GetSwitchValue("s_useCompatibilityProcessSni"); + _useCompatibilityAsyncBehaviourOriginal = + GetSwitchValue("s_useCompatibilityAsyncBehaviour"); + _useConnectionPoolV2Original = + GetSwitchValue("s_useConnectionPoolV2"); + _truncateScaledDecimalOriginal = + GetSwitchValue("s_truncateScaledDecimal"); + _ignoreServerProvidedFailoverPartnerOriginal = + GetSwitchValue("s_ignoreServerProvidedFailoverPartner"); + _enableUserAgentOriginal = + GetSwitchValue("s_enableUserAgent"); + _multiSubnetFailoverByDefaultOriginal = + GetSwitchValue("s_multiSubnetFailoverByDefault"); + #if NET + _globalizationInvariantModeOriginal = + GetSwitchValue("s_globalizationInvariantMode"); + #endif + #if NET && _WINDOWS + _useManagedNetworkingOriginal = + GetSwitchValue("s_useManagedNetworking"); + #endif + #if NETFRAMEWORK + _disableTnirByDefaultOriginal = + GetSwitchValue("s_disableTnirByDefault"); + #endif + } + catch + { + // If we fail to capture the original values, release the lock + // immediately to avoid deadlocks. + s_instanceLock.Release(); + throw; + } } /// @@ -117,289 +136,189 @@ public LocalAppContextSwitchesHelper() /// public void Dispose() { - LocalAppContextSwitches.s_legacyRowVersionNullBehavior = - Convert(_legacyRowVersionNullBehaviorOriginal); - LocalAppContextSwitches.s_suppressInsecureTlsWarning = - Convert(_suppressInsecureTlsWarningOriginal); - LocalAppContextSwitches.s_makeReadAsyncBlocking = - Convert(_makeReadAsyncBlockingOriginal); - LocalAppContextSwitches.s_useMinimumLoginTimeout = - Convert(_useMinimumLoginTimeoutOriginal); - LocalAppContextSwitches.s_legacyVarTimeZeroScaleBehaviour = - Convert(_legacyVarTimeZeroScaleBehaviourOriginal); - LocalAppContextSwitches.s_useCompatibilityProcessSni = - Convert(_useCompatibilityProcessSniOriginal); - LocalAppContextSwitches.s_useCompatibilityAsyncBehaviour = - Convert(_useCompatibilityAsyncBehaviourOriginal); - LocalAppContextSwitches.s_useConnectionPoolV2 = - Convert(_useConnectionPoolV2Original); - LocalAppContextSwitches.s_truncateScaledDecimal = - Convert(_truncateScaledDecimalOriginal); - LocalAppContextSwitches.s_ignoreServerProvidedFailoverPartner = - Convert(_ignoreServerProvidedFailoverPartnerOriginal); - LocalAppContextSwitches.s_enableUserAgent = - Convert(_enableUserAgentOriginal); - LocalAppContextSwitches.s_multiSubnetFailoverByDefault = - Convert(_multiSubnetFailoverByDefaultOriginal); - #if NET - LocalAppContextSwitches.s_globalizationInvariantMode = - Convert(_globalizationInvariantModeOriginal); - #endif - #if NET && _WINDOWS - LocalAppContextSwitches.s_useManagedNetworking = - Convert(_useManagedNetworkingOriginal); - #endif - #if NETFRAMEWORK - LocalAppContextSwitches.s_disableTnirByDefault = - Convert(_disableTnirByDefaultOriginal); - #endif - - // Release the lock to allow another instance to be created. - s_instanceLock.Release(); + try + { + SetSwitchValue( + "s_legacyRowVersionNullBehavior", + _legacyRowVersionNullBehaviorOriginal); + SetSwitchValue( + "s_suppressInsecureTlsWarning", + _suppressInsecureTlsWarningOriginal); + SetSwitchValue( + "s_makeReadAsyncBlocking", + _makeReadAsyncBlockingOriginal); + SetSwitchValue( + "s_useMinimumLoginTimeout", + _useMinimumLoginTimeoutOriginal); + SetSwitchValue( + "s_legacyVarTimeZeroScaleBehaviour", + _legacyVarTimeZeroScaleBehaviourOriginal); + SetSwitchValue( + "s_useCompatibilityProcessSni", + _useCompatibilityProcessSniOriginal); + SetSwitchValue( + "s_useCompatibilityAsyncBehaviour", + _useCompatibilityAsyncBehaviourOriginal); + SetSwitchValue( + "s_useConnectionPoolV2", + _useConnectionPoolV2Original); + SetSwitchValue( + "s_truncateScaledDecimal", + _truncateScaledDecimalOriginal); + SetSwitchValue( + "s_ignoreServerProvidedFailoverPartner", + _ignoreServerProvidedFailoverPartnerOriginal); + SetSwitchValue( + "s_enableUserAgent", + _enableUserAgentOriginal); + SetSwitchValue( + "s_multiSubnetFailoverByDefault", + _multiSubnetFailoverByDefaultOriginal); + #if NET + SetSwitchValue( + "s_globalizationInvariantMode", + _globalizationInvariantModeOriginal); + #endif + #if NET && _WINDOWS + SetSwitchValue( + "s_useManagedNetworking", + _useManagedNetworkingOriginal); + #endif + #if NETFRAMEWORK + SetSwitchValue( + "s_disableTnirByDefault", + _disableTnirByDefaultOriginal); + #endif + } + finally + { + // Release the lock to allow another instance to be created. + s_instanceLock.Release(); + } } #endregion - #region Public Properties - - /// - /// Access the LocalAppContextSwitches.LegacyRowVersionNullBehavior - /// property. - /// - public bool LegacyRowVersionNullBehavior - { - get => LocalAppContextSwitches.LegacyRowVersionNullBehavior; - } - - /// - /// Access the LocalAppContextSwitches.SuppressInsecureTlsWarning property. - /// - public bool SuppressInsecureTlsWarning - { - get => LocalAppContextSwitches.SuppressInsecureTlsWarning; - } - - /// - /// Access the LocalAppContextSwitches.MakeReadAsyncBlocking property. - /// - public bool MakeReadAsyncBlocking - { - get => LocalAppContextSwitches.MakeReadAsyncBlocking; - } - - /// - /// Access the LocalAppContextSwitches.UseMinimumLoginTimeout property. - /// - public bool UseMinimumLoginTimeout - { - get => LocalAppContextSwitches.UseMinimumLoginTimeout; - } - - /// - /// Access the LocalAppContextSwitches.LegacyVarTimeZeroScaleBehaviour - /// property. - /// - public bool LegacyVarTimeZeroScaleBehaviour - { - get => LocalAppContextSwitches.LegacyVarTimeZeroScaleBehaviour; - } - - /// - /// Access the LocalAppContextSwitches.UseCompatibilityProcessSni property. - /// - public bool UseCompatibilityProcessSni - { - get => LocalAppContextSwitches.UseCompatibilityProcessSni; - } - - /// - /// Access the LocalAppContextSwitches.UseCompatibilityAsyncBehaviour - /// property. - /// - public bool UseCompatibilityAsyncBehaviour - { - get => LocalAppContextSwitches.UseCompatibilityAsyncBehaviour; - } - - /// - /// Access the LocalAppContextSwitches.UseConnectionPoolV2 property. - /// - public bool UseConnectionPoolV2 - { - get => LocalAppContextSwitches.UseConnectionPoolV2; - } - - /// - /// Access the LocalAppContextSwitches.TruncateScaledDecimal property. - /// - public bool TruncateScaledDecimal - { - get => LocalAppContextSwitches.TruncateScaledDecimal; - } - - public bool IgnoreServerProvidedFailoverPartner - { - get => LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner; - } - - public bool EnableUserAgent - { - get => LocalAppContextSwitches.EnableUserAgent; - } - - public bool EnableMultiSubnetFailoverByDefault - { - get => LocalAppContextSwitches.EnableMultiSubnetFailoverByDefault; - } - - #if NET - /// - /// Access the LocalAppContextSwitches.GlobalizationInvariantMode property. - /// - public bool GlobalizationInvariantMode - { - get => LocalAppContextSwitches.GlobalizationInvariantMode; - } - #endif - - #if NET && _WINDOWS - /// - /// Access the LocalAppContextSwitches.UseManagedNetworking property. - /// - public bool UseManagedNetworking - { - get => LocalAppContextSwitches.UseManagedNetworking; - } - #endif - - #if NETFRAMEWORK - /// - /// Access the LocalAppContextSwitches.DisableTnirByDefault property. - /// - public bool DisableTnirByDefault - { - get => LocalAppContextSwitches.DisableTnirByDefault; - } - #endif + #region Switch Value Getters and Setters // These properties get or set the like-named underlying switch field value. // - // They all fail the test if the value cannot be retrieved or set. + // They all throw if the value cannot be retrieved or set. /// /// Get or set the LocalAppContextSwitches.LegacyRowVersionNullBehavior /// switch value. /// - public bool? LegacyRowVersionNullBehaviorValue + public bool? LegacyRowVersionNullBehavior { - get => Convert(LocalAppContextSwitches.s_legacyRowVersionNullBehavior); - set => LocalAppContextSwitches.s_legacyRowVersionNullBehavior = Convert(value); + get => GetSwitchValue("s_legacyRowVersionNullBehavior"); + set => SetSwitchValue("s_legacyRowVersionNullBehavior", value); } /// /// Get or set the LocalAppContextSwitches.SuppressInsecureTlsWarning /// switch value. /// - public bool? SuppressInsecureTlsWarningValue + public bool? SuppressInsecureTlsWarning { - get => Convert(LocalAppContextSwitches.s_suppressInsecureTlsWarning); - set => LocalAppContextSwitches.s_suppressInsecureTlsWarning = Convert(value); + get => GetSwitchValue("s_suppressInsecureTlsWarning"); + set => SetSwitchValue("s_suppressInsecureTlsWarning", value); } /// /// Get or set the LocalAppContextSwitches.MakeReadAsyncBlocking switch /// value. /// - public bool? MakeReadAsyncBlockingValue + public bool? MakeReadAsyncBlocking { - get => Convert(LocalAppContextSwitches.s_makeReadAsyncBlocking); - set => LocalAppContextSwitches.s_makeReadAsyncBlocking = Convert(value); + get => GetSwitchValue("s_makeReadAsyncBlocking"); + set => SetSwitchValue("s_makeReadAsyncBlocking", value); } /// /// Get or set the LocalAppContextSwitches.UseMinimumLoginTimeout switch /// value. /// - public bool? UseMinimumLoginTimeoutValue + public bool? UseMinimumLoginTimeout { - get => Convert(LocalAppContextSwitches.s_useMinimumLoginTimeout); - set => LocalAppContextSwitches.s_useMinimumLoginTimeout = Convert(value); + get => GetSwitchValue("s_useMinimumLoginTimeout"); + set => SetSwitchValue("s_useMinimumLoginTimeout", value); } /// /// Get or set the LocalAppContextSwitches.LegacyVarTimeZeroScaleBehaviour /// switch value. /// - public bool? LegacyVarTimeZeroScaleBehaviourValue + public bool? LegacyVarTimeZeroScaleBehaviour { - get => Convert(LocalAppContextSwitches.s_legacyVarTimeZeroScaleBehaviour); - set => LocalAppContextSwitches.s_legacyVarTimeZeroScaleBehaviour = Convert(value); + get => GetSwitchValue("s_legacyVarTimeZeroScaleBehaviour"); + set => SetSwitchValue("s_legacyVarTimeZeroScaleBehaviour", value); } /// /// Get or set the LocalAppContextSwitches.UseCompatibilityProcessSni switch /// value. /// - public bool? UseCompatibilityProcessSniValue + public bool? UseCompatibilityProcessSni { - get => Convert(LocalAppContextSwitches.s_useCompatibilityProcessSni); - set => LocalAppContextSwitches.s_useCompatibilityProcessSni = Convert(value); + get => GetSwitchValue("s_useCompatibilityProcessSni"); + set => SetSwitchValue("s_useCompatibilityProcessSni", value); } /// /// Get or set the LocalAppContextSwitches.UseCompatibilityAsyncBehaviour /// switch value. /// - public bool? UseCompatibilityAsyncBehaviourValue + public bool? UseCompatibilityAsyncBehaviour { - get => Convert(LocalAppContextSwitches.s_useCompatibilityAsyncBehaviour); - set => LocalAppContextSwitches.s_useCompatibilityAsyncBehaviour = Convert(value); + get => GetSwitchValue("s_useCompatibilityAsyncBehaviour"); + set => SetSwitchValue("s_useCompatibilityAsyncBehaviour", value); } /// /// Get or set the LocalAppContextSwitches.UseConnectionPoolV2 switch value. /// - public bool? UseConnectionPoolV2Value + public bool? UseConnectionPoolV2 { - get => Convert(LocalAppContextSwitches.s_useConnectionPoolV2); - set => LocalAppContextSwitches.s_useConnectionPoolV2 = Convert(value); + get => GetSwitchValue("s_useConnectionPoolV2"); + set => SetSwitchValue("s_useConnectionPoolV2", value); } /// /// Get or set the LocalAppContextSwitches.TruncateScaledDecimal switch value. /// - public bool? TruncateScaledDecimalValue + public bool? TruncateScaledDecimal { - get => Convert(LocalAppContextSwitches.s_truncateScaledDecimal); - set => LocalAppContextSwitches.s_truncateScaledDecimal = Convert(value); + get => GetSwitchValue("s_truncateScaledDecimal"); + set => SetSwitchValue("s_truncateScaledDecimal", value); } - public bool? IgnoreServerProvidedFailoverPartnerValue + public bool? IgnoreServerProvidedFailoverPartner { - get => Convert(LocalAppContextSwitches.s_ignoreServerProvidedFailoverPartner); - set => LocalAppContextSwitches.s_ignoreServerProvidedFailoverPartner = Convert(value); + get => GetSwitchValue("s_ignoreServerProvidedFailoverPartner"); + set => SetSwitchValue("s_ignoreServerProvidedFailoverPartner", value); } - public bool? EnableUserAgentValue + public bool? EnableUserAgent { - get => Convert(LocalAppContextSwitches.s_enableUserAgent); - set => LocalAppContextSwitches.s_enableUserAgent = Convert(value); + get => GetSwitchValue("s_enableUserAgent"); + set => SetSwitchValue("s_enableUserAgent", value); } - public bool? EnableMultiSubnetFailoverByDefaultValue + public bool? EnableMultiSubnetFailoverByDefault { - get => Convert(LocalAppContextSwitches.s_multiSubnetFailoverByDefault); - set => LocalAppContextSwitches.s_multiSubnetFailoverByDefault = Convert(value); + get => GetSwitchValue("s_multiSubnetFailoverByDefault"); + set => SetSwitchValue("s_multiSubnetFailoverByDefault", value); } #if NET /// /// Get or set the LocalAppContextSwitches.GlobalizationInvariantMode switch value. /// - public bool? GlobalizationInvariantModeValue + public bool? GlobalizationInvariantMode { - get => Convert(LocalAppContextSwitches.s_globalizationInvariantMode); - set => LocalAppContextSwitches.s_globalizationInvariantMode = Convert(value); + get => GetSwitchValue("s_globalizationInvariantMode"); + set => SetSwitchValue("s_globalizationInvariantMode", value); } #endif @@ -407,10 +326,10 @@ public bool? GlobalizationInvariantModeValue /// /// Get or set the LocalAppContextSwitches.UseManagedNetworking switch value. /// - public bool? UseManagedNetworkingValue + public bool? UseManagedNetworking { - get => Convert(LocalAppContextSwitches.s_useManagedNetworking); - set => LocalAppContextSwitches.s_useManagedNetworking = Convert(value); + get => GetSwitchValue("s_useManagedNetworking"); + set => SetSwitchValue("s_useManagedNetworking", value); } #endif @@ -419,10 +338,10 @@ public bool? UseManagedNetworkingValue /// Get or set the LocalAppContextSwitches.DisableTnirByDefault switch /// value. /// - public bool? DisableTnirByDefaultValue + public bool? DisableTnirByDefault { - get => Convert(LocalAppContextSwitches.s_disableTnirByDefault); - set => LocalAppContextSwitches.s_disableTnirByDefault = Convert(value); + get => GetSwitchValue("s_disableTnirByDefault"); + set => SetSwitchValue("s_disableTnirByDefault", value); } #endif @@ -430,20 +349,95 @@ public bool? DisableTnirByDefaultValue #region Helpers - private static bool? Convert(LocalAppContextSwitches.SwitchValue value) + /// + /// Use reflection to get a switch field value from LocalAppContextSwitches. + /// + private static bool? GetSwitchValue(string fieldName) { - return value == LocalAppContextSwitches.SwitchValue.None - ? null - : value == LocalAppContextSwitches.SwitchValue.True; + var assembly = Assembly.GetAssembly(typeof(SqlConnection)); + if (assembly is null) + { + throw new InvalidOperationException( + "Could not get assembly for Microsoft.Data.SqlClient"); + } + + var type = assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches"); + if (type is null) + { + throw new InvalidOperationException( + "Could not get type LocalAppContextSwitches"); + } + + var field = type.GetField( + fieldName, + BindingFlags.Static | BindingFlags.NonPublic); + if (field == null) + { + throw new InvalidOperationException( + $"Field '{fieldName}' not found in LocalAppContextSwitches"); + } + + var value = field.GetValue(null); + if (value is byte switchValue) + { + // GOTCHA: This assumes that switch values map to bytes as: + // + // None = 0 + // True = 1 + // False = 2 + // + // See the LocalAppContextSwitches.SwitchValue enum definition. + // + return switchValue == 0 ? null : switchValue == 1; + } + + throw new InvalidOperationException( + $"Field '{fieldName}' is not of type byte"); } - private static LocalAppContextSwitches.SwitchValue Convert(bool? value) + /// + /// Use reflection to set a switch field value in LocalAppContextSwitches. + /// + private static void SetSwitchValue(string fieldName, bool? value) { - return !value.HasValue - ? LocalAppContextSwitches.SwitchValue.None - : value.Value - ? LocalAppContextSwitches.SwitchValue.True - : LocalAppContextSwitches.SwitchValue.False; + var assembly = Assembly.GetAssembly(typeof(SqlConnection)); + if (assembly is null) + { + throw new InvalidOperationException( + "Could not get assembly for Microsoft.Data.SqlClient"); + } + + var type = assembly.GetType("Microsoft.Data.SqlClient.LocalAppContextSwitches"); + if (type is null) + { + throw new InvalidOperationException( + "Could not get type LocalAppContextSwitches"); + } + + var field = type.GetField( + fieldName, + BindingFlags.Static | BindingFlags.NonPublic); + if (field == null) + { + throw new InvalidOperationException( + $"Field '{fieldName}' not found in LocalAppContextSwitches"); + } + + field.SetValue( + null, + // GOTCHA: This assumes that switch values map to bytes as: + // + // None = 0 + // True = 1 + // False = 2 + // + // See the LocalAppContextSwitches.SwitchValue enum definition. + // + !value.HasValue + ? (byte)0 + : value.Value + ? (byte)1 + : (byte)2); } #endregion diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs index 8506e35e98..8465d894a9 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/SqlParameterTest.cs @@ -1952,7 +1952,7 @@ public void SqlDateTime2Scale_Legacy(int? setScale, byte outputScale, bool legac lock (_parameterLegacyScaleLock) { using SwitchesHelper switches = new SwitchesHelper(); - switches.LegacyVarTimeZeroScaleBehaviourValue = + switches.LegacyVarTimeZeroScaleBehaviour = legacyVarTimeZeroScaleSwitchValue; var parameter = new SqlParameter diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs index 6e8f64fcc8..377be3e511 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs @@ -5,10 +5,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Reflection; using Microsoft.Data.SqlClient.Tests; - -using SwitchesHelper = Microsoft.Data.SqlClient.Tests.Common.LocalAppContextSwitchesHelper; +using Microsoft.Data.SqlClient.Tests.Common; namespace Microsoft.Data.SqlClient { @@ -163,7 +161,25 @@ private void AssertValidState() { } [DebuggerStepThrough] private void AddError(object value) => throw new Exception(value as string ?? "AddError"); - private SwitchesHelper LocalAppContextSwitches = new(); + private class SwitchesHelper : IDisposable + { + private LocalAppContextSwitchesHelper _helper = new(); + + public void Dispose() + { + _helper.Dispose(); + } + + public bool UseCompatibilityProcessSni + { + get + { + var value = _helper.UseCompatibilityProcessSni; + return value.HasValue && value.Value; + } + } + } + private SwitchesHelper LocalAppContextSwitches = new SwitchesHelper(); #if NETFRAMEWORK private SniNativeWrapperImpl _native; diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs index 4b7ac38cfa..040119616e 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/DataReaderTest/DataReaderTest.cs @@ -268,7 +268,7 @@ public static void CheckNullRowVersionIsDBNull() lock (s_rowVersionLock) { using SwitchesHelper helper = new(); - helper.LegacyRowVersionNullBehaviorValue = false; + helper.LegacyRowVersionNullBehavior = false; using SqlConnection con = new(DataTestUtility.TCPConnectionString); con.Open(); @@ -869,7 +869,7 @@ public static void CheckLegacyNullRowVersionIsEmptyArray() lock (s_rowVersionLock) { using SwitchesHelper helper = new(); - helper.LegacyRowVersionNullBehaviorValue = true; + helper.LegacyRowVersionNullBehavior = true; using SqlConnection con = new(DataTestUtility.TCPConnectionString); con.Open(); diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs index b16368a9c0..4574f81a93 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/ParameterTest/ParametersTest.cs @@ -589,7 +589,7 @@ public static void TestScaledDecimalParameter_CommandInsert(string connectionStr { using (SqlCommand cmd = connection.CreateCommand()) { - appContextSwitchesHelper.TruncateScaledDecimalValue = truncateScaledDecimal; + appContextSwitchesHelper.TruncateScaledDecimal = truncateScaledDecimal; var p = new SqlParameter("@Value", null) { @@ -636,7 +636,7 @@ public static void TestScaledDecimalParameter_BulkCopy(string connectionString, } bulkCopy.DestinationTableName = tableName; - appContextSwitchesHelper.TruncateScaledDecimalValue = truncateScaledDecimal; + appContextSwitchesHelper.TruncateScaledDecimal = truncateScaledDecimal; bulkCopy.WriteToServer(table); } Assert.True(ValidateInsertedValues(connection, tableName, truncateScaledDecimal), $"Invalid test happened with connection string [{connection.ConnectionString}]"); @@ -681,7 +681,7 @@ public static void TestScaledDecimalTVP_CommandSP(string connectionString, bool table.Rows.Add(newRow); } p.Value = table; - appContextSwitchesHelper.TruncateScaledDecimalValue = truncateScaledDecimal; + appContextSwitchesHelper.TruncateScaledDecimal = truncateScaledDecimal; cmd.ExecuteNonQuery(); } // TVP always rounds data without attention to the configuration. diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs index 5a335e957e..49912202d6 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/SqlBulkCopyTest/AdjustPrecScaleForBulkCopy.cs @@ -29,7 +29,9 @@ public static void RunTest() Assert.Equal("12.3", value.ToString()); value = BulkCopySqlDecimalToTable(new SqlDecimal(123.45), 10, 2, 4, 1); - if (appContextSwitches.TruncateScaledDecimal) + + bool? truncate = appContextSwitches.TruncateScaledDecimal; + if (truncate.HasValue && truncate.Value) { Assert.Equal("123.4", value.ToString()); } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs index c92402af41..59445a26b6 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/LocalAppContextSwitchesTest.cs @@ -27,12 +27,17 @@ public void TestDefaultAppContextSwitchValues() Assert.True(LocalAppContextSwitches.UseCompatibilityAsyncBehaviour); Assert.False(LocalAppContextSwitches.UseConnectionPoolV2); Assert.False(LocalAppContextSwitches.TruncateScaledDecimal); + Assert.False(LocalAppContextSwitches.IgnoreServerProvidedFailoverPartner); + Assert.False(LocalAppContextSwitches.EnableUserAgent); Assert.False(LocalAppContextSwitches.EnableMultiSubnetFailoverByDefault); -#if NETFRAMEWORK - Assert.False(LocalAppContextSwitches.DisableTnirByDefault); + #if NET + Assert.False(LocalAppContextSwitches.GlobalizationInvariantMode); + #endif + #if NET && _WINDOWS Assert.False(LocalAppContextSwitches.UseManagedNetworking); -#else - Assert.Equal(!OperatingSystem.IsWindows(), LocalAppContextSwitches.UseManagedNetworking); -#endif + #endif + #if NETFRAMEWORK + Assert.False(LocalAppContextSwitches.DisableTnirByDefault); + #endif } } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs index 6133671881..8c2f879041 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs @@ -1,17 +1,18 @@ using System; using Microsoft.Data.SqlClient.Tests.Common; using Xunit; -using static Microsoft.Data.SqlClient.Tests.Common.LocalAppContextSwitchesHelper; namespace Microsoft.Data.SqlClient.UnitTests.Microsoft.Data.SqlClient { public class SqlConnectionStringTest : IDisposable { - private LocalAppContextSwitchesHelper _appContextSwitchHelper; - public SqlConnectionStringTest() + // Ensure we restore the original app context switch values after each + // test. + private LocalAppContextSwitchesHelper _appContextSwitchHelper = new(); + + public void Dispose() { - // Ensure that the app context switch is set to the default value - _appContextSwitchHelper = new LocalAppContextSwitchesHelper(); + _appContextSwitchHelper.Dispose(); } #if NETFRAMEWORK @@ -43,7 +44,7 @@ public void TestDefaultTnir(string dataSource, bool? tnirEnabledInConnString, bo // the value of TransparentNetworkIPResolution property in SqlConnectionString. // Arrange - _appContextSwitchHelper.DisableTnirByDefaultValue = tnirDisabledAppContext; + _appContextSwitchHelper.DisableTnirByDefault = tnirDisabledAppContext; // Act SqlConnectionStringBuilder builder = new(); @@ -71,7 +72,7 @@ public void TestDefaultTnir(string dataSource, bool? tnirEnabledInConnString, bo [InlineData(null, null, false)] public void TestDefaultMultiSubnetFailover(bool? msfInConnString, bool? msfEnabledAppContext, bool expectedValue) { - _appContextSwitchHelper.EnableMultiSubnetFailoverByDefaultValue = msfEnabledAppContext; + _appContextSwitchHelper.EnableMultiSubnetFailoverByDefault = msfEnabledAppContext; SqlConnectionStringBuilder builder = new(); if (msfInConnString.HasValue) @@ -89,7 +90,7 @@ public void TestDefaultMultiSubnetFailover(bool? msfInConnString, bool? msfEnabl [Fact] public void TestMultiSubnetFailoverWithFailoverPartnerThrows() { - _appContextSwitchHelper.EnableMultiSubnetFailoverByDefaultValue = true; + _appContextSwitchHelper.EnableMultiSubnetFailoverByDefault = true; SqlConnectionStringBuilder builder = new() { @@ -100,11 +101,5 @@ public void TestMultiSubnetFailoverWithFailoverPartnerThrows() Assert.Throws(() => new SqlConnectionString(builder.ConnectionString)); } - - public void Dispose() - { - // Clean up any resources if necessary - _appContextSwitchHelper.Dispose(); - } } } diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs index f815a909d8..5b294ff20e 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionFailoverTests.cs @@ -529,7 +529,7 @@ public void TransientFault_IgnoreServerProvidedFailoverPartner_ShouldConnectToUs { // Arrange using LocalAppContextSwitchesHelper switchesHelper = new(); - switchesHelper.IgnoreServerProvidedFailoverPartnerValue = true; + switchesHelper.IgnoreServerProvidedFailoverPartner = true; using TdsServer failoverServer = new( new TdsServerArguments diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs index daae6d7598..a6d2cac9fd 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/SimulatedServerTests/ConnectionTests.cs @@ -839,7 +839,7 @@ public void TestConnWithUserAgentFeatureExtension(bool forceAck) { // Make sure needed switch is enabled using LocalAppContextSwitchesHelper switchesHelper = new(); - switchesHelper.EnableUserAgentValue = true; + switchesHelper.EnableUserAgent = true; using var server = new TdsServer(); server.Start(); @@ -935,7 +935,7 @@ public void TestConnWithoutUserAgentFeatureExtension() { // Disable the client-side UserAgent field entirely using LocalAppContextSwitchesHelper switchesHelper = new(); - switchesHelper.EnableUserAgentValue = false; + switchesHelper.EnableUserAgent = false; using var server = new TdsServer(); server.Start(); From aaf5a80acf0daccc3fcdda0035def24e475774bf Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 18 Dec 2025 14:59:15 -0400 Subject: [PATCH 4/7] - Fixed reflection-based enum value setting. --- .../ColumnEncryptionCertificateFixture.cs | 2 +- .../Common/LocalAppContextSwitchesHelper.cs | 32 +++++++++---------- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs index 05b54ed091..0c51a7e17b 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/Fixtures/ColumnEncryptionCertificateFixture.cs @@ -66,7 +66,7 @@ public X509Certificate2 GetCertificate(StoreLocation storeLocation) IsAdmin && _localMachineCertificate is not null) { - return _localMachineCertificate!; + return _localMachineCertificate; } throw new InvalidOperationException("Attempted to retrieve the certificate added to the local machine store; this requires administrator rights."); diff --git a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs index 7919ecb8f9..4c77c4dd26 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs @@ -378,7 +378,7 @@ public bool? DisableTnirByDefault } var value = field.GetValue(null); - if (value is byte switchValue) + if (value is not null) { // GOTCHA: This assumes that switch values map to bytes as: // @@ -388,7 +388,8 @@ public bool? DisableTnirByDefault // // See the LocalAppContextSwitches.SwitchValue enum definition. // - return switchValue == 0 ? null : switchValue == 1; + byte underlyingValue = (byte)value; + return underlyingValue == 0 ? null : underlyingValue == 1; } throw new InvalidOperationException( @@ -423,21 +424,18 @@ private static void SetSwitchValue(string fieldName, bool? value) $"Field '{fieldName}' not found in LocalAppContextSwitches"); } - field.SetValue( - null, - // GOTCHA: This assumes that switch values map to bytes as: - // - // None = 0 - // True = 1 - // False = 2 - // - // See the LocalAppContextSwitches.SwitchValue enum definition. - // - !value.HasValue - ? (byte)0 - : value.Value - ? (byte)1 - : (byte)2); + // GOTCHA: This assumes that switch values map to bytes as: + // + // None = 0 + // True = 1 + // False = 2 + // + // See the LocalAppContextSwitches.SwitchValue enum definition. + // + byte byteValue = + (byte)(!value.HasValue ? 0 : value.Value ? 1 : 2); + + field.SetValue(null, Enum.ToObject(field.FieldType, byteValue)); } #endregion From 7a5e8a04970b5a6d36c94894b8fd1fbe4ee53861 Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 18 Dec 2025 15:47:12 -0400 Subject: [PATCH 5/7] - Consolidated most switch acquisition into a helper to reduce copy-pasta. - Addressed some Copilot comments. --- .../Data/SqlClient/LocalAppContextSwitches.cs | 391 +++++++----------- .../TdsParserStateObject.TestHarness.cs | 6 +- .../Data/SqlClient/SqlConnectionStringTest.cs | 2 +- 3 files changed, 144 insertions(+), 255 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs index a32ae5ba8e..35fd0981c5 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -67,13 +67,13 @@ internal enum SwitchValue : byte internal static SwitchValue s_multiSubnetFailoverByDefault = SwitchValue.None; #if NET - internal static SwitchValue s_globalizationInvariantMode; + internal static SwitchValue s_globalizationInvariantMode = SwitchValue.None; #endif #if NET && _WINDOWS - internal static SwitchValue s_useManagedNetworking; + internal static SwitchValue s_useManagedNetworking; = SwitchValue.None; #endif #if NETFRAMEWORK - internal static SwitchValue s_disableTnirByDefault; + internal static SwitchValue s_disableTnirByDefault = SwitchValue.None; #endif #endregion @@ -111,27 +111,11 @@ static LocalAppContextSwitches() /// When this switch is set to false, the new experimental ProcessSni behavior using /// the packet multiplexer is enabled. /// - public static bool UseCompatibilityProcessSni - { - get - { - if (s_useCompatibilityProcessSni == SwitchValue.None) - - { - // Check if the switch has been set by the AppContext switch directly - // If it has not been set, we default to true. - if (!AppContext.TryGetSwitch(UseCompatibilityProcessSniString, out bool returnedValue) || returnedValue) - { - s_useCompatibilityProcessSni = SwitchValue.True; - } - else - { - s_useCompatibilityProcessSni = SwitchValue.False; - } - } - return s_useCompatibilityProcessSni == SwitchValue.True; - } - } + public static bool UseCompatibilityProcessSni => + AcquireAndReturn( + UseCompatibilityProcessSniString, + defaultValue: true, + ref s_useCompatibilityProcessSni); /// /// In TdsParser, the async multi-packet column value fetch behavior can use a continue snapshot state @@ -154,18 +138,10 @@ public static bool UseCompatibilityAsyncBehaviour return true; } - if (s_useCompatibilityAsyncBehaviour == SwitchValue.None) - { - if (!AppContext.TryGetSwitch(UseCompatibilityAsyncBehaviourString, out bool returnedValue) || returnedValue) - { - s_useCompatibilityAsyncBehaviour = SwitchValue.True; - } - else - { - s_useCompatibilityAsyncBehaviour = SwitchValue.False; - } - } - return s_useCompatibilityAsyncBehaviour == SwitchValue.True; + return AcquireAndReturn( + UseCompatibilityAsyncBehaviourString, + defaultValue: true, + ref s_useCompatibilityAsyncBehaviour); } } @@ -174,24 +150,11 @@ public static bool UseCompatibilityAsyncBehaviour /// This warning can be suppressed by enabling this AppContext switch. /// This app context switch defaults to 'false'. /// - public static bool SuppressInsecureTlsWarning - { - get - { - if (s_suppressInsecureTlsWarning == SwitchValue.None) - { - if (AppContext.TryGetSwitch(SuppressInsecureTlsWarningString, out bool returnedValue) && returnedValue) - { - s_suppressInsecureTlsWarning = SwitchValue.True; - } - else - { - s_suppressInsecureTlsWarning = SwitchValue.False; - } - } - return s_suppressInsecureTlsWarning == SwitchValue.True; - } - } + public static bool SuppressInsecureTlsWarning => + AcquireAndReturn( + SuppressInsecureTlsWarningString, + defaultValue: false, + ref s_suppressInsecureTlsWarning); /// /// In System.Data.SqlClient and Microsoft.Data.SqlClient prior to 3.0.0 a field with type Timestamp/RowVersion @@ -199,72 +162,32 @@ public static bool SuppressInsecureTlsWarning /// of Microsoft.Data.SqlClient, if this switch returns false an appropriate null value will be returned. /// This app context switch defaults to 'false'. /// - public static bool LegacyRowVersionNullBehavior - { - get - { - if (s_legacyRowVersionNullBehavior == SwitchValue.None) - { - if (AppContext.TryGetSwitch(LegacyRowVersionNullString, out bool returnedValue) && returnedValue) - { - s_legacyRowVersionNullBehavior = SwitchValue.True; - } - else - { - s_legacyRowVersionNullBehavior = SwitchValue.False; - } - } - return s_legacyRowVersionNullBehavior == SwitchValue.True; - } - } + public static bool LegacyRowVersionNullBehavior => + AcquireAndReturn( + LegacyRowVersionNullString, + defaultValue: false, + ref s_legacyRowVersionNullBehavior); /// /// When enabled, ReadAsync runs asynchronously and does not block the calling thread. /// This app context switch defaults to 'false'. /// - public static bool MakeReadAsyncBlocking - { - get - { - if (s_makeReadAsyncBlocking == SwitchValue.None) - { - if (AppContext.TryGetSwitch(MakeReadAsyncBlockingString, out bool returnedValue) && returnedValue) - { - s_makeReadAsyncBlocking = SwitchValue.True; - } - else - { - s_makeReadAsyncBlocking = SwitchValue.False; - } - } - return s_makeReadAsyncBlocking == SwitchValue.True; - } - } + public static bool MakeReadAsyncBlocking => + AcquireAndReturn( + MakeReadAsyncBlockingString, + defaultValue: false, + ref s_makeReadAsyncBlocking); /// /// Specifies minimum login timeout to be set to 1 second instead of 0 seconds, /// to prevent a login attempt from waiting indefinitely. /// This app context switch defaults to 'true'. /// - public static bool UseMinimumLoginTimeout - { - get - { - if (s_useMinimumLoginTimeout == SwitchValue.None) - { - if (!AppContext.TryGetSwitch(UseMinimumLoginTimeoutString, out bool returnedValue) || returnedValue) - { - s_useMinimumLoginTimeout = SwitchValue.True; - } - else - { - s_useMinimumLoginTimeout = SwitchValue.False; - } - } - return s_useMinimumLoginTimeout == SwitchValue.True; - } - } - + public static bool UseMinimumLoginTimeout => + AcquireAndReturn( + UseMinimumLoginTimeoutString, + defaultValue: true, + ref s_useMinimumLoginTimeout); /// /// When set to 'true' this will output a scale value of 7 (DEFAULT_VARTIME_SCALE) when the scale @@ -273,70 +196,31 @@ public static bool UseMinimumLoginTimeout /// regardless of switch value. /// This app context switch defaults to 'true'. /// - public static bool LegacyVarTimeZeroScaleBehaviour - { - get - { - if (s_legacyVarTimeZeroScaleBehaviour == SwitchValue.None) - { - if (!AppContext.TryGetSwitch(LegacyVarTimeZeroScaleBehaviourString, out bool returnedValue)) - { - s_legacyVarTimeZeroScaleBehaviour = SwitchValue.True; - } - else - { - s_legacyVarTimeZeroScaleBehaviour = returnedValue ? SwitchValue.True : SwitchValue.False; - } - } - return s_legacyVarTimeZeroScaleBehaviour == SwitchValue.True; - } - } + public static bool LegacyVarTimeZeroScaleBehaviour => + AcquireAndReturn( + LegacyVarTimeZeroScaleBehaviourString, + defaultValue: true, + ref s_legacyVarTimeZeroScaleBehaviour); /// /// When set to true, the connection pool will use the new V2 connection pool implementation. /// When set to false, the connection pool will use the legacy V1 implementation. /// This app context switch defaults to 'false'. /// - public static bool UseConnectionPoolV2 - { - get - { - if (s_useConnectionPoolV2 == SwitchValue.None) - { - if (AppContext.TryGetSwitch(UseConnectionPoolV2String, out bool returnedValue) && returnedValue) - { - s_useConnectionPoolV2 = SwitchValue.True; - } - else - { - s_useConnectionPoolV2 = SwitchValue.False; - } - } - return s_useConnectionPoolV2 == SwitchValue.True; - } - } + public static bool UseConnectionPoolV2 => + AcquireAndReturn( + UseConnectionPoolV2String, + defaultValue: false, + ref s_useConnectionPoolV2); /// /// When set to true, TdsParser will truncate (rather than round) decimal and SqlDecimal values when scaling them. /// - public static bool TruncateScaledDecimal - { - get - { - if (s_truncateScaledDecimal == SwitchValue.None) - { - if (AppContext.TryGetSwitch(TruncateScaledDecimalString, out bool returnedValue) && returnedValue) - { - s_truncateScaledDecimal = SwitchValue.True; - } - else - { - s_truncateScaledDecimal = SwitchValue.False; - } - } - return s_truncateScaledDecimal == SwitchValue.True; - } - } + public static bool TruncateScaledDecimal => + AcquireAndReturn( + TruncateScaledDecimalString, + defaultValue: false, + ref s_truncateScaledDecimal); /// /// When set to true, the failover partner provided by the server during connection @@ -347,45 +231,20 @@ public static bool TruncateScaledDecimal /// /// This app context switch defaults to 'false'. /// - public static bool IgnoreServerProvidedFailoverPartner - { - get - { - if (s_ignoreServerProvidedFailoverPartner == SwitchValue.None) - { - if (AppContext.TryGetSwitch(IgnoreServerProvidedFailoverPartnerString, out bool returnedValue) && returnedValue) - { - s_ignoreServerProvidedFailoverPartner = SwitchValue.True; - } - else - { - s_ignoreServerProvidedFailoverPartner = SwitchValue.False; - } - } - return s_ignoreServerProvidedFailoverPartner == SwitchValue.True; - } - } + public static bool IgnoreServerProvidedFailoverPartner => + AcquireAndReturn( + IgnoreServerProvidedFailoverPartnerString, + defaultValue: false, + ref s_ignoreServerProvidedFailoverPartner); + /// /// When set to true, the user agent feature is enabled and the driver will send the user agent string to the server. /// - public static bool EnableUserAgent - { - get - { - if (s_enableUserAgent == SwitchValue.None) - { - if (AppContext.TryGetSwitch(EnableUserAgentString, out bool returnedValue) && returnedValue) - { - s_enableUserAgent = SwitchValue.True; - } - else - { - s_enableUserAgent = SwitchValue.False; - } - } - return s_enableUserAgent == SwitchValue.True; - } - } + public static bool EnableUserAgent => + AcquireAndReturn( + EnableUserAgentString, + defaultValue: false, + ref s_enableUserAgent); #if NET /// @@ -406,6 +265,17 @@ public static bool GlobalizationInvariantMode } else { + // TODO(https://github.com/dotnet/SqlClient/pull/3853): + // + // The comment's intention doesn't match the code. + // + // The comment claims to fallback to the environment + // variable if the switch is not set. However, it actually + // falls-back if the switch is not set _OR_ it is set to + // false. + // + // Should we update the comment or fix the code to match? + // If the switch is not set, we check the environment variable as the first fallback string? envValue = Environment.GetEnvironmentVariable(GlobalizationInvariantModeEnvironmentVariable); @@ -440,10 +310,7 @@ public static bool GlobalizationInvariantMode /// /// .NET Framework does not support Globalization Invariant mode, so this will always be false. /// - public static bool GlobalizationInvariantMode - { - get => false; - } + public static bool GlobalizationInvariantMode => false; #endif #if NET @@ -467,22 +334,25 @@ public static bool UseManagedNetworking { get { - if (s_useManagedNetworking == SwitchValue.None) + if (s_useManagedNetworking != SwitchValue.None) { - if (!OperatingSystem.IsWindows()) - { - s_useManagedNetworking = SwitchValue.True; - } - else if (AppContext.TryGetSwitch(UseManagedNetworkingOnWindowsString, out bool returnedValue) && returnedValue) - { - s_useManagedNetworking = SwitchValue.True; - } - else - { - s_useManagedNetworking = SwitchValue.False; - } + return s_useManagedNetworking == SwitchValue.True; + } + + if (!OperatingSystem.IsWindows()) + { + s_useManagedNetworking = SwitchValue.True; + return true; + } + + if (AppContext.TryGetSwitch(UseManagedNetworkingOnWindowsString, out bool returnedValue) && returnedValue) + { + s_useManagedNetworking = SwitchValue.True; + return true; } - return s_useManagedNetworking == SwitchValue.True; + + s_useManagedNetworking = SwitchValue.False; + return false; } } #else @@ -515,25 +385,12 @@ public static bool UseManagedNetworking /// /// This app context switch defaults to 'false'. /// - public static bool DisableTnirByDefault - { - get - { - if (s_disableTnirByDefault == SwitchValue.None) - { - if (AppContext.TryGetSwitch(DisableTnirByDefaultString, out bool returnedValue) && returnedValue) - { - s_disableTnirByDefault = SwitchValue.True; - } - else - { - s_disableTnirByDefault = SwitchValue.False; - } - } - return s_disableTnirByDefault == SwitchValue.True; - } - } -#endif + public static bool DisableTnirByDefault => + AcquireAndReturn( + DisableTnirByDefaultString, + defaultValue: false, + ref s_disableTnirByDefault); + #endif /// /// When set to true, the default value for MultiSubnetFailover connection string property @@ -541,24 +398,56 @@ public static bool DisableTnirByDefault /// improved connection times in multi-subnet environments. /// This app context switch defaults to 'false'. /// - public static bool EnableMultiSubnetFailoverByDefault + public static bool EnableMultiSubnetFailoverByDefault => + AcquireAndReturn( + EnableMultiSubnetFailoverByDefaultString, + defaultValue: false, + ref s_multiSubnetFailoverByDefault); + + #endregion + + #region Helpers + + /// + /// Acquires the value of the specified app context switch and stores it + /// in the given reference. Applies the default value if the switch isn't + /// set. + /// + /// If the cached value is already set, it is returned immediately without + /// attempting to re-acquire it. + /// + /// No attempt is made to prevent multiple threads from acquiring the same + /// switch value simultaneously. The worst that can happen is that the + /// switch is acquired more than once, and the last acquired value wins. + /// + /// The name of the app context switch. + /// The default value to use if the switch is not set. + /// A reference to variable to store the switch value in. + /// Returns the acquired value as a bool. + private static bool AcquireAndReturn( + string switchName, + bool defaultValue, + ref SwitchValue switchValue) { - get + // Refuse to re-acquire a switch value. Simply return whatever value + // was previously acquired. + if (switchValue != SwitchValue.None) { - if (s_multiSubnetFailoverByDefault == SwitchValue.None) - { - if (AppContext.TryGetSwitch(EnableMultiSubnetFailoverByDefaultString, out bool returnedValue) && returnedValue) - { - s_multiSubnetFailoverByDefault = SwitchValue.True; - } - else - { - s_multiSubnetFailoverByDefault = SwitchValue.False; - } - } - return s_multiSubnetFailoverByDefault == SwitchValue.True; + return switchValue == SwitchValue.True; } - } + // Attempt to acquire the switch value from AppContext. + if (! AppContext.TryGetSwitch(switchName, out bool acquiredValue)) + { + // The switch has no value, so use the given default. + switchValue = defaultValue ? SwitchValue.True : SwitchValue.False; + return defaultValue; + } + + // Assign the appropriate SwitchValue based on the acquired value. + switchValue = acquiredValue ? SwitchValue.True : SwitchValue.False; + return acquiredValue; + } + #endregion } diff --git a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs index 377be3e511..4e70eaae57 100644 --- a/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs +++ b/src/Microsoft.Data.SqlClient/tests/FunctionalTests/TdsParserStateObject.TestHarness.cs @@ -163,13 +163,13 @@ private void AssertValidState() { } private class SwitchesHelper : IDisposable { - private LocalAppContextSwitchesHelper _helper = new(); + private readonly LocalAppContextSwitchesHelper _helper = new(); public void Dispose() { _helper.Dispose(); } - + public bool UseCompatibilityProcessSni { get @@ -179,7 +179,7 @@ public bool UseCompatibilityProcessSni } } } - private SwitchesHelper LocalAppContextSwitches = new SwitchesHelper(); + private readonly SwitchesHelper LocalAppContextSwitches = new SwitchesHelper(); #if NETFRAMEWORK private SniNativeWrapperImpl _native; diff --git a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs index 8c2f879041..844506e92f 100644 --- a/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/UnitTests/Microsoft/Data/SqlClient/SqlConnectionStringTest.cs @@ -8,7 +8,7 @@ public class SqlConnectionStringTest : IDisposable { // Ensure we restore the original app context switch values after each // test. - private LocalAppContextSwitchesHelper _appContextSwitchHelper = new(); + private readonly LocalAppContextSwitchesHelper _appContextSwitchHelper = new(); public void Dispose() { From 6db4226b146c55b45943b5b6ea5ebcd34b014cfc Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Thu, 18 Dec 2025 16:32:22 -0400 Subject: [PATCH 6/7] - Sorted all switch names alphabetically. --- .../Data/SqlClient/LocalAppContextSwitches.cs | 382 +++++++++--------- .../Common/LocalAppContextSwitchesHelper.cs | 274 +++++++------ 2 files changed, 325 insertions(+), 331 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs index 35fd0981c5..da88c225be 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -12,30 +12,29 @@ internal static class LocalAppContextSwitches { #region Switch Names - private const string MakeReadAsyncBlockingString = @"Switch.Microsoft.Data.SqlClient.MakeReadAsyncBlocking"; - private const string LegacyRowVersionNullString = @"Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior"; - private const string SuppressInsecureTlsWarningString = @"Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning"; - private const string UseMinimumLoginTimeoutString = @"Switch.Microsoft.Data.SqlClient.UseOneSecFloorInTimeoutCalculationDuringLogin"; - private const string LegacyVarTimeZeroScaleBehaviourString = @"Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour"; - private const string UseCompatibilityProcessSniString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni"; - private const string UseCompatibilityAsyncBehaviourString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour"; - private const string UseConnectionPoolV2String = @"Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2"; - private const string TruncateScaledDecimalString = @"Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal"; - private const string IgnoreServerProvidedFailoverPartnerString = @"Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner"; - private const string EnableUserAgentString = @"Switch.Microsoft.Data.SqlClient.EnableUserAgent"; + #if NETFRAMEWORK + private const string DisableTnirByDefaultString = @"Switch.Microsoft.Data.SqlClient.DisableTNIRByDefaultInConnectionString"; + #endif private const string EnableMultiSubnetFailoverByDefaultString = @"Switch.Microsoft.Data.SqlClient.EnableMultiSubnetFailoverByDefault"; - + private const string EnableUserAgentString = @"Switch.Microsoft.Data.SqlClient.EnableUserAgent"; #if NET private const string GlobalizationInvariantModeString = @"System.Globalization.Invariant"; private const string GlobalizationInvariantModeEnvironmentVariable = "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"; - - #if _WINDOWS - private const string UseManagedNetworkingOnWindowsString = "Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows"; #endif - #else - private const string DisableTnirByDefaultString = @"Switch.Microsoft.Data.SqlClient.DisableTNIRByDefaultInConnectionString"; + private const string IgnoreServerProvidedFailoverPartnerString = @"Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner"; + private const string LegacyRowVersionNullString = @"Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior"; + private const string LegacyVarTimeZeroScaleBehaviourString = @"Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour"; + private const string MakeReadAsyncBlockingString = @"Switch.Microsoft.Data.SqlClient.MakeReadAsyncBlocking"; + private const string SuppressInsecureTlsWarningString = @"Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning"; + private const string TruncateScaledDecimalString = @"Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal"; + private const string UseCompatibilityAsyncBehaviourString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour"; + private const string UseCompatibilityProcessSniString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni"; + private const string UseConnectionPoolV2String = @"Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2"; + #if NET && _WINDOWS + private const string UseManagedNetworkingOnWindowsString = "Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows"; #endif - + private const string UseMinimumLoginTimeoutString = @"Switch.Microsoft.Data.SqlClient.UseOneSecFloorInTimeoutCalculationDuringLogin"; + #endregion #region Switch Values @@ -53,28 +52,31 @@ internal enum SwitchValue : byte False = 2 } - internal static SwitchValue s_legacyRowVersionNullBehavior = SwitchValue.None; - internal static SwitchValue s_suppressInsecureTlsWarning = SwitchValue.None; - internal static SwitchValue s_makeReadAsyncBlocking = SwitchValue.None; - internal static SwitchValue s_useMinimumLoginTimeout = SwitchValue.None; - internal static SwitchValue s_legacyVarTimeZeroScaleBehaviour = SwitchValue.None; - internal static SwitchValue s_useCompatibilityProcessSni = SwitchValue.None; - internal static SwitchValue s_useCompatibilityAsyncBehaviour = SwitchValue.None; - internal static SwitchValue s_useConnectionPoolV2 = SwitchValue.None; - internal static SwitchValue s_truncateScaledDecimal = SwitchValue.None; - internal static SwitchValue s_ignoreServerProvidedFailoverPartner = SwitchValue.None; + // GOTCHA: These fields are accessed via reflection by the + // LocalAppContextSwitchesHelper test helper class. If you rename them, be + // sure to update the test helper as well. + + #if NETFRAMEWORK + internal static SwitchValue s_disableTnirByDefault = SwitchValue.None; + #endif + internal static SwitchValue s_enableMultiSubnetFailoverByDefault = SwitchValue.None; internal static SwitchValue s_enableUserAgent = SwitchValue.None; - internal static SwitchValue s_multiSubnetFailoverByDefault = SwitchValue.None; - #if NET internal static SwitchValue s_globalizationInvariantMode = SwitchValue.None; #endif + internal static SwitchValue s_ignoreServerProvidedFailoverPartner = SwitchValue.None; + internal static SwitchValue s_legacyRowVersionNullBehavior = SwitchValue.None; + internal static SwitchValue s_legacyVarTimeZeroScaleBehaviour = SwitchValue.None; + internal static SwitchValue s_makeReadAsyncBlocking = SwitchValue.None; + internal static SwitchValue s_suppressInsecureTlsWarning = SwitchValue.None; + internal static SwitchValue s_truncateScaledDecimal = SwitchValue.None; + internal static SwitchValue s_useCompatibilityAsyncBehaviour = SwitchValue.None; + internal static SwitchValue s_useCompatibilityProcessSni = SwitchValue.None; + internal static SwitchValue s_useConnectionPoolV2 = SwitchValue.None; #if NET && _WINDOWS - internal static SwitchValue s_useManagedNetworking; = SwitchValue.None; - #endif - #if NETFRAMEWORK - internal static SwitchValue s_disableTnirByDefault = SwitchValue.None; + internal static SwitchValue s_useManagedNetworking = SwitchValue.None; #endif + internal static SwitchValue s_useMinimumLoginTimeout = SwitchValue.None; #endregion @@ -102,140 +104,40 @@ static LocalAppContextSwitches() #region Switch Properties - // @TODO: Sort by name - - /// - /// In TdsParser, the ProcessSni function changed significantly when the packet - /// multiplexing code needed for high speed multi-packet column values was added. - /// When this switch is set to true (the default), the old ProcessSni design is used. - /// When this switch is set to false, the new experimental ProcessSni behavior using - /// the packet multiplexer is enabled. - /// - public static bool UseCompatibilityProcessSni => - AcquireAndReturn( - UseCompatibilityProcessSniString, - defaultValue: true, - ref s_useCompatibilityProcessSni); - - /// - /// In TdsParser, the async multi-packet column value fetch behavior can use a continue snapshot state - /// for improved efficiency. When this switch is enabled (the default), the driver preserves the legacy - /// compatibility behavior, which does not use the continue snapshot state. When disabled, the new behavior - /// using the continue snapshot state is enabled. This switch will always return true if - /// is enabled, because the continue state is not stable without - /// the multiplexer. - /// - public static bool UseCompatibilityAsyncBehaviour - { - get - { - if (UseCompatibilityProcessSni) - { - // If ProcessSni compatibility mode has been enabled then the packet - // multiplexer has been disabled. The new async behaviour using continue - // point capture is only stable if the multiplexer is enabled so we must - // return true to enable compatibility async behaviour using only restarts. - return true; - } - - return AcquireAndReturn( - UseCompatibilityAsyncBehaviourString, - defaultValue: true, - ref s_useCompatibilityAsyncBehaviour); - } - } - - /// - /// When using Encrypt=false in the connection string, a security warning is output to the console if the TLS version is 1.2 or lower. - /// This warning can be suppressed by enabling this AppContext switch. - /// This app context switch defaults to 'false'. - /// - public static bool SuppressInsecureTlsWarning => - AcquireAndReturn( - SuppressInsecureTlsWarningString, - defaultValue: false, - ref s_suppressInsecureTlsWarning); - - /// - /// In System.Data.SqlClient and Microsoft.Data.SqlClient prior to 3.0.0 a field with type Timestamp/RowVersion - /// would return an empty byte array. This switch controls whether to preserve that behaviour on newer versions - /// of Microsoft.Data.SqlClient, if this switch returns false an appropriate null value will be returned. - /// This app context switch defaults to 'false'. - /// - public static bool LegacyRowVersionNullBehavior => - AcquireAndReturn( - LegacyRowVersionNullString, - defaultValue: false, - ref s_legacyRowVersionNullBehavior); - - /// - /// When enabled, ReadAsync runs asynchronously and does not block the calling thread. - /// This app context switch defaults to 'false'. - /// - public static bool MakeReadAsyncBlocking => - AcquireAndReturn( - MakeReadAsyncBlockingString, - defaultValue: false, - ref s_makeReadAsyncBlocking); - - /// - /// Specifies minimum login timeout to be set to 1 second instead of 0 seconds, - /// to prevent a login attempt from waiting indefinitely. - /// This app context switch defaults to 'true'. - /// - public static bool UseMinimumLoginTimeout => - AcquireAndReturn( - UseMinimumLoginTimeoutString, - defaultValue: true, - ref s_useMinimumLoginTimeout); - - /// - /// When set to 'true' this will output a scale value of 7 (DEFAULT_VARTIME_SCALE) when the scale - /// is explicitly set to zero for VarTime data types ('datetime2', 'datetimeoffset' and 'time') - /// If no scale is set explicitly it will continue to output scale of 7 (DEFAULT_VARTIME_SCALE) - /// regardless of switch value. - /// This app context switch defaults to 'true'. - /// - public static bool LegacyVarTimeZeroScaleBehaviour => - AcquireAndReturn( - LegacyVarTimeZeroScaleBehaviourString, - defaultValue: true, - ref s_legacyVarTimeZeroScaleBehaviour); - + #if NETFRAMEWORK /// - /// When set to true, the connection pool will use the new V2 connection pool implementation. - /// When set to false, the connection pool will use the legacy V1 implementation. + /// Transparent Network IP Resolution (TNIR) is a revision of the existing MultiSubnetFailover feature. + /// TNIR affects the connection sequence of the driver in the case where the first resolved IP of the hostname + /// doesn't respond and there are multiple IPs associated with the hostname. + /// + /// TNIR interacts with MultiSubnetFailover to provide the following three connection sequences: + /// 0: One IP is attempted, followed by all IPs in parallel + /// 1: All IPs are attempted in parallel + /// 2: All IPs are attempted one after another + /// + /// TransparentNetworkIPResolution is enabled by default. MultiSubnetFailover is disabled by default. + /// To disable TNIR, you can enable the app context switch. + /// /// This app context switch defaults to 'false'. /// - public static bool UseConnectionPoolV2 => - AcquireAndReturn( - UseConnectionPoolV2String, - defaultValue: false, - ref s_useConnectionPoolV2); - - /// - /// When set to true, TdsParser will truncate (rather than round) decimal and SqlDecimal values when scaling them. - /// - public static bool TruncateScaledDecimal => + public static bool DisableTnirByDefault => AcquireAndReturn( - TruncateScaledDecimalString, + DisableTnirByDefaultString, defaultValue: false, - ref s_truncateScaledDecimal); + ref s_disableTnirByDefault); + #endif /// - /// When set to true, the failover partner provided by the server during connection - /// will be ignored. This is useful in scenarios where the application wants to - /// control the failover behavior explicitly (e.g. using a custom port). The application - /// must be kept up to date with the failover configuration of the server. - /// The application will not automatically discover a newly configured failover partner. - /// + /// When set to true, the default value for MultiSubnetFailover connection string property + /// will be true instead of false. This enables parallel IP connection attempts for + /// improved connection times in multi-subnet environments. /// This app context switch defaults to 'false'. /// - public static bool IgnoreServerProvidedFailoverPartner => + public static bool EnableMultiSubnetFailoverByDefault => AcquireAndReturn( - IgnoreServerProvidedFailoverPartnerString, + EnableMultiSubnetFailoverByDefaultString, defaultValue: false, - ref s_ignoreServerProvidedFailoverPartner); + ref s_enableMultiSubnetFailoverByDefault); /// /// When set to true, the user agent feature is enabled and the driver will send the user agent string to the server. @@ -313,9 +215,129 @@ public static bool GlobalizationInvariantMode public static bool GlobalizationInvariantMode => false; #endif - #if NET + /// + /// When set to true, the failover partner provided by the server during connection + /// will be ignored. This is useful in scenarios where the application wants to + /// control the failover behavior explicitly (e.g. using a custom port). The application + /// must be kept up to date with the failover configuration of the server. + /// The application will not automatically discover a newly configured failover partner. + /// + /// This app context switch defaults to 'false'. + /// + public static bool IgnoreServerProvidedFailoverPartner => + AcquireAndReturn( + IgnoreServerProvidedFailoverPartnerString, + defaultValue: false, + ref s_ignoreServerProvidedFailoverPartner); + + /// + /// In System.Data.SqlClient and Microsoft.Data.SqlClient prior to 3.0.0 a field with type Timestamp/RowVersion + /// would return an empty byte array. This switch controls whether to preserve that behaviour on newer versions + /// of Microsoft.Data.SqlClient, if this switch returns false an appropriate null value will be returned. + /// This app context switch defaults to 'false'. + /// + public static bool LegacyRowVersionNullBehavior => + AcquireAndReturn( + LegacyRowVersionNullString, + defaultValue: false, + ref s_legacyRowVersionNullBehavior); + + /// + /// When set to 'true' this will output a scale value of 7 (DEFAULT_VARTIME_SCALE) when the scale + /// is explicitly set to zero for VarTime data types ('datetime2', 'datetimeoffset' and 'time') + /// If no scale is set explicitly it will continue to output scale of 7 (DEFAULT_VARTIME_SCALE) + /// regardless of switch value. + /// This app context switch defaults to 'true'. + /// + public static bool LegacyVarTimeZeroScaleBehaviour => + AcquireAndReturn( + LegacyVarTimeZeroScaleBehaviourString, + defaultValue: true, + ref s_legacyVarTimeZeroScaleBehaviour); + + /// + /// When enabled, ReadAsync runs asynchronously and does not block the calling thread. + /// This app context switch defaults to 'false'. + /// + public static bool MakeReadAsyncBlocking => + AcquireAndReturn( + MakeReadAsyncBlockingString, + defaultValue: false, + ref s_makeReadAsyncBlocking); + + /// + /// When using Encrypt=false in the connection string, a security warning is output to the console if the TLS version is 1.2 or lower. + /// This warning can be suppressed by enabling this AppContext switch. + /// This app context switch defaults to 'false'. + /// + public static bool SuppressInsecureTlsWarning => + AcquireAndReturn( + SuppressInsecureTlsWarningString, + defaultValue: false, + ref s_suppressInsecureTlsWarning); + + /// + /// When set to true, TdsParser will truncate (rather than round) decimal and SqlDecimal values when scaling them. + /// + public static bool TruncateScaledDecimal => + AcquireAndReturn( + TruncateScaledDecimalString, + defaultValue: false, + ref s_truncateScaledDecimal); + + /// + /// In TdsParser, the async multi-packet column value fetch behavior can use a continue snapshot state + /// for improved efficiency. When this switch is enabled (the default), the driver preserves the legacy + /// compatibility behavior, which does not use the continue snapshot state. When disabled, the new behavior + /// using the continue snapshot state is enabled. This switch will always return true if + /// is enabled, because the continue state is not stable without + /// the multiplexer. + /// + public static bool UseCompatibilityAsyncBehaviour + { + get + { + if (UseCompatibilityProcessSni) + { + // If ProcessSni compatibility mode has been enabled then the packet + // multiplexer has been disabled. The new async behaviour using continue + // point capture is only stable if the multiplexer is enabled so we must + // return true to enable compatibility async behaviour using only restarts. + return true; + } + + return AcquireAndReturn( + UseCompatibilityAsyncBehaviourString, + defaultValue: true, + ref s_useCompatibilityAsyncBehaviour); + } + } + + /// + /// In TdsParser, the ProcessSni function changed significantly when the packet + /// multiplexing code needed for high speed multi-packet column values was added. + /// When this switch is set to true (the default), the old ProcessSni design is used. + /// When this switch is set to false, the new experimental ProcessSni behavior using + /// the packet multiplexer is enabled. + /// + public static bool UseCompatibilityProcessSni => + AcquireAndReturn( + UseCompatibilityProcessSniString, + defaultValue: true, + ref s_useCompatibilityProcessSni); + + /// + /// When set to true, the connection pool will use the new V2 connection pool implementation. + /// When set to false, the connection pool will use the legacy V1 implementation. + /// This app context switch defaults to 'false'. + /// + public static bool UseConnectionPoolV2 => + AcquireAndReturn( + UseConnectionPoolV2String, + defaultValue: false, + ref s_useConnectionPoolV2); - #if _WINDOWS + #if NET && _WINDOWS /// /// When set to true, .NET Core will use the managed SNI implementation instead of the native SNI implementation. /// @@ -355,13 +377,11 @@ public static bool UseManagedNetworking return false; } } - #else + #elif NET /// /// .NET Core on Unix does not support the native SNI, so this will always be true. /// public static bool UseManagedNetworking => true; - #endif - #else /// /// .NET Framework does not support the managed SNI, so this will always be false. @@ -369,40 +389,16 @@ public static bool UseManagedNetworking public static bool UseManagedNetworking => false; #endif - #if NETFRAMEWORK - /// - /// Transparent Network IP Resolution (TNIR) is a revision of the existing MultiSubnetFailover feature. - /// TNIR affects the connection sequence of the driver in the case where the first resolved IP of the hostname - /// doesn't respond and there are multiple IPs associated with the hostname. - /// - /// TNIR interacts with MultiSubnetFailover to provide the following three connection sequences: - /// 0: One IP is attempted, followed by all IPs in parallel - /// 1: All IPs are attempted in parallel - /// 2: All IPs are attempted one after another - /// - /// TransparentNetworkIPResolution is enabled by default. MultiSubnetFailover is disabled by default. - /// To disable TNIR, you can enable the app context switch. - /// - /// This app context switch defaults to 'false'. - /// - public static bool DisableTnirByDefault => - AcquireAndReturn( - DisableTnirByDefaultString, - defaultValue: false, - ref s_disableTnirByDefault); - #endif - /// - /// When set to true, the default value for MultiSubnetFailover connection string property - /// will be true instead of false. This enables parallel IP connection attempts for - /// improved connection times in multi-subnet environments. - /// This app context switch defaults to 'false'. + /// Specifies minimum login timeout to be set to 1 second instead of 0 seconds, + /// to prevent a login attempt from waiting indefinitely. + /// This app context switch defaults to 'true'. /// - public static bool EnableMultiSubnetFailoverByDefault => + public static bool UseMinimumLoginTimeout => AcquireAndReturn( - EnableMultiSubnetFailoverByDefaultString, - defaultValue: false, - ref s_multiSubnetFailoverByDefault); + UseMinimumLoginTimeoutString, + defaultValue: true, + ref s_useMinimumLoginTimeout); #endregion diff --git a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs index 4c77c4dd26..507fc53a27 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs @@ -35,30 +35,27 @@ public sealed class LocalAppContextSwitchesHelper : IDisposable /// /// These fields are used to capture the original switch values. /// - private readonly bool? _legacyRowVersionNullBehaviorOriginal; - private readonly bool? _suppressInsecureTlsWarningOriginal; - private readonly bool? _makeReadAsyncBlockingOriginal; - private readonly bool? _useMinimumLoginTimeoutOriginal; - private readonly bool? _legacyVarTimeZeroScaleBehaviourOriginal; - private readonly bool? _useCompatibilityProcessSniOriginal; - private readonly bool? _useCompatibilityAsyncBehaviourOriginal; - private readonly bool? _useConnectionPoolV2Original; - private readonly bool? _truncateScaledDecimalOriginal; - private readonly bool? _ignoreServerProvidedFailoverPartnerOriginal; + #if NETFRAMEWORK + private readonly bool? _disableTnirByDefaultOriginal; + #endif + private readonly bool? _enableMultiSubnetFailoverByDefaultOriginal; private readonly bool? _enableUserAgentOriginal; - private readonly bool? _multiSubnetFailoverByDefaultOriginal; - #if NET private readonly bool? _globalizationInvariantModeOriginal; #endif - + private readonly bool? _ignoreServerProvidedFailoverPartnerOriginal; + private readonly bool? _legacyRowVersionNullBehaviorOriginal; + private readonly bool? _legacyVarTimeZeroScaleBehaviourOriginal; + private readonly bool? _makeReadAsyncBlockingOriginal; + private readonly bool? _suppressInsecureTlsWarningOriginal; + private readonly bool? _truncateScaledDecimalOriginal; + private readonly bool? _useCompatibilityAsyncBehaviourOriginal; + private readonly bool? _useCompatibilityProcessSniOriginal; + private readonly bool? _useConnectionPoolV2Original; #if NET && _WINDOWS private readonly bool? _useManagedNetworkingOriginal; #endif - - #if NETFRAMEWORK - private readonly bool? _disableTnirByDefaultOriginal; - #endif + private readonly bool? _useMinimumLoginTimeoutOriginal; #endregion @@ -85,42 +82,42 @@ public LocalAppContextSwitchesHelper() try { + #if NETFRAMEWORK + _disableTnirByDefaultOriginal = + GetSwitchValue("s_disableTnirByDefault"); + #endif + _enableMultiSubnetFailoverByDefaultOriginal = + GetSwitchValue("s_enableMultiSubnetFailoverByDefault"); + _enableUserAgentOriginal = + GetSwitchValue("s_enableUserAgent"); + #if NET + _globalizationInvariantModeOriginal = + GetSwitchValue("s_globalizationInvariantMode"); + #endif + _ignoreServerProvidedFailoverPartnerOriginal = + GetSwitchValue("s_ignoreServerProvidedFailoverPartner"); _legacyRowVersionNullBehaviorOriginal = GetSwitchValue("s_legacyRowVersionNullBehavior"); - _suppressInsecureTlsWarningOriginal = - GetSwitchValue("s_suppressInsecureTlsWarning"); - _makeReadAsyncBlockingOriginal = - GetSwitchValue("s_makeReadAsyncBlocking"); - _useMinimumLoginTimeoutOriginal = - GetSwitchValue("s_useMinimumLoginTimeout"); _legacyVarTimeZeroScaleBehaviourOriginal = GetSwitchValue("s_legacyVarTimeZeroScaleBehaviour"); - _useCompatibilityProcessSniOriginal = - GetSwitchValue("s_useCompatibilityProcessSni"); + _makeReadAsyncBlockingOriginal = + GetSwitchValue("s_makeReadAsyncBlocking"); + _suppressInsecureTlsWarningOriginal = + GetSwitchValue("s_suppressInsecureTlsWarning"); + _truncateScaledDecimalOriginal = + GetSwitchValue("s_truncateScaledDecimal"); _useCompatibilityAsyncBehaviourOriginal = GetSwitchValue("s_useCompatibilityAsyncBehaviour"); + _useCompatibilityProcessSniOriginal = + GetSwitchValue("s_useCompatibilityProcessSni"); _useConnectionPoolV2Original = GetSwitchValue("s_useConnectionPoolV2"); - _truncateScaledDecimalOriginal = - GetSwitchValue("s_truncateScaledDecimal"); - _ignoreServerProvidedFailoverPartnerOriginal = - GetSwitchValue("s_ignoreServerProvidedFailoverPartner"); - _enableUserAgentOriginal = - GetSwitchValue("s_enableUserAgent"); - _multiSubnetFailoverByDefaultOriginal = - GetSwitchValue("s_multiSubnetFailoverByDefault"); - #if NET - _globalizationInvariantModeOriginal = - GetSwitchValue("s_globalizationInvariantMode"); - #endif #if NET && _WINDOWS _useManagedNetworkingOriginal = GetSwitchValue("s_useManagedNetworking"); #endif - #if NETFRAMEWORK - _disableTnirByDefaultOriginal = - GetSwitchValue("s_disableTnirByDefault"); - #endif + _useMinimumLoginTimeoutOriginal = + GetSwitchValue("s_useMinimumLoginTimeout"); } catch { @@ -138,57 +135,57 @@ public void Dispose() { try { + #if NETFRAMEWORK SetSwitchValue( - "s_legacyRowVersionNullBehavior", - _legacyRowVersionNullBehaviorOriginal); + "s_disableTnirByDefault", + _disableTnirByDefaultOriginal); + #endif SetSwitchValue( - "s_suppressInsecureTlsWarning", - _suppressInsecureTlsWarningOriginal); + "s_enableMultiSubnetFailoverByDefault", + _enableMultiSubnetFailoverByDefaultOriginal); SetSwitchValue( - "s_makeReadAsyncBlocking", - _makeReadAsyncBlockingOriginal); + "s_enableUserAgent", + _enableUserAgentOriginal); + #if NET SetSwitchValue( - "s_useMinimumLoginTimeout", - _useMinimumLoginTimeoutOriginal); + "s_globalizationInvariantMode", + _globalizationInvariantModeOriginal); + #endif + SetSwitchValue( + "s_ignoreServerProvidedFailoverPartner", + _ignoreServerProvidedFailoverPartnerOriginal); + SetSwitchValue( + "s_legacyRowVersionNullBehavior", + _legacyRowVersionNullBehaviorOriginal); SetSwitchValue( "s_legacyVarTimeZeroScaleBehaviour", _legacyVarTimeZeroScaleBehaviourOriginal); SetSwitchValue( - "s_useCompatibilityProcessSni", - _useCompatibilityProcessSniOriginal); - SetSwitchValue( - "s_useCompatibilityAsyncBehaviour", - _useCompatibilityAsyncBehaviourOriginal); + "s_makeReadAsyncBlocking", + _makeReadAsyncBlockingOriginal); SetSwitchValue( - "s_useConnectionPoolV2", - _useConnectionPoolV2Original); + "s_suppressInsecureTlsWarning", + _suppressInsecureTlsWarningOriginal); SetSwitchValue( "s_truncateScaledDecimal", _truncateScaledDecimalOriginal); SetSwitchValue( - "s_ignoreServerProvidedFailoverPartner", - _ignoreServerProvidedFailoverPartnerOriginal); - SetSwitchValue( - "s_enableUserAgent", - _enableUserAgentOriginal); + "s_useCompatibilityAsyncBehaviour", + _useCompatibilityAsyncBehaviourOriginal); SetSwitchValue( - "s_multiSubnetFailoverByDefault", - _multiSubnetFailoverByDefaultOriginal); - #if NET + "s_useCompatibilityProcessSni", + _useCompatibilityProcessSniOriginal); SetSwitchValue( - "s_globalizationInvariantMode", - _globalizationInvariantModeOriginal); - #endif + "s_useConnectionPoolV2", + _useConnectionPoolV2Original); #if NET && _WINDOWS SetSwitchValue( "s_useManagedNetworking", _useManagedNetworkingOriginal); #endif - #if NETFRAMEWORK SetSwitchValue( - "s_disableTnirByDefault", - _disableTnirByDefaultOriginal); - #endif + "s_useMinimumLoginTimeout", + _useMinimumLoginTimeoutOriginal); } finally { @@ -205,126 +202,130 @@ public void Dispose() // // They all throw if the value cannot be retrieved or set. + #if NETFRAMEWORK /// - /// Get or set the LocalAppContextSwitches.LegacyRowVersionNullBehavior - /// switch value. + /// Get or set the DisableTnirByDefault switch value. /// - public bool? LegacyRowVersionNullBehavior + public bool? DisableTnirByDefault { - get => GetSwitchValue("s_legacyRowVersionNullBehavior"); - set => SetSwitchValue("s_legacyRowVersionNullBehavior", value); + get => GetSwitchValue("s_disableTnirByDefault"); + set => SetSwitchValue("s_disableTnirByDefault", value); } + #endif /// - /// Get or set the LocalAppContextSwitches.SuppressInsecureTlsWarning - /// switch value. + /// Get or set the EnableMultiSubnetFailoverByDefault switch value. /// - public bool? SuppressInsecureTlsWarning + public bool? EnableMultiSubnetFailoverByDefault { - get => GetSwitchValue("s_suppressInsecureTlsWarning"); - set => SetSwitchValue("s_suppressInsecureTlsWarning", value); + get => GetSwitchValue("s_enableMultiSubnetFailoverByDefault"); + set => SetSwitchValue("s_enableMultiSubnetFailoverByDefault", value); } /// - /// Get or set the LocalAppContextSwitches.MakeReadAsyncBlocking switch - /// value. + /// Get or set the EnableUserAgent switch value. /// - public bool? MakeReadAsyncBlocking + public bool? EnableUserAgent { - get => GetSwitchValue("s_makeReadAsyncBlocking"); - set => SetSwitchValue("s_makeReadAsyncBlocking", value); + get => GetSwitchValue("s_enableUserAgent"); + set => SetSwitchValue("s_enableUserAgent", value); } + #if NET /// - /// Get or set the LocalAppContextSwitches.UseMinimumLoginTimeout switch - /// value. + /// Get or set the GlobalizationInvariantMode switch value. /// - public bool? UseMinimumLoginTimeout + public bool? GlobalizationInvariantMode { - get => GetSwitchValue("s_useMinimumLoginTimeout"); - set => SetSwitchValue("s_useMinimumLoginTimeout", value); + get => GetSwitchValue("s_globalizationInvariantMode"); + set => SetSwitchValue("s_globalizationInvariantMode", value); } + #endif /// - /// Get or set the LocalAppContextSwitches.LegacyVarTimeZeroScaleBehaviour - /// switch value. + /// Get or set the IgnoreServerProvidedFailoverPartner switch value. /// - public bool? LegacyVarTimeZeroScaleBehaviour + public bool? IgnoreServerProvidedFailoverPartner { - get => GetSwitchValue("s_legacyVarTimeZeroScaleBehaviour"); - set => SetSwitchValue("s_legacyVarTimeZeroScaleBehaviour", value); + get => GetSwitchValue("s_ignoreServerProvidedFailoverPartner"); + set => SetSwitchValue("s_ignoreServerProvidedFailoverPartner", value); } /// - /// Get or set the LocalAppContextSwitches.UseCompatibilityProcessSni switch - /// value. + /// Get or set the LegacyRowVersionNullBehavior switch value. /// - public bool? UseCompatibilityProcessSni + public bool? LegacyRowVersionNullBehavior { - get => GetSwitchValue("s_useCompatibilityProcessSni"); - set => SetSwitchValue("s_useCompatibilityProcessSni", value); + get => GetSwitchValue("s_legacyRowVersionNullBehavior"); + set => SetSwitchValue("s_legacyRowVersionNullBehavior", value); } /// - /// Get or set the LocalAppContextSwitches.UseCompatibilityAsyncBehaviour - /// switch value. + /// Get or set the LegacyVarTimeZeroScaleBehaviour switch value. /// - public bool? UseCompatibilityAsyncBehaviour + public bool? LegacyVarTimeZeroScaleBehaviour { - get => GetSwitchValue("s_useCompatibilityAsyncBehaviour"); - set => SetSwitchValue("s_useCompatibilityAsyncBehaviour", value); + get => GetSwitchValue("s_legacyVarTimeZeroScaleBehaviour"); + set => SetSwitchValue("s_legacyVarTimeZeroScaleBehaviour", value); } /// - /// Get or set the LocalAppContextSwitches.UseConnectionPoolV2 switch value. + /// Get or set the MakeReadAsyncBlocking switch value. /// - public bool? UseConnectionPoolV2 + public bool? MakeReadAsyncBlocking { - get => GetSwitchValue("s_useConnectionPoolV2"); - set => SetSwitchValue("s_useConnectionPoolV2", value); + get => GetSwitchValue("s_makeReadAsyncBlocking"); + set => SetSwitchValue("s_makeReadAsyncBlocking", value); } /// - /// Get or set the LocalAppContextSwitches.TruncateScaledDecimal switch value. + /// Get or set the SuppressInsecureTlsWarning switch value. /// - public bool? TruncateScaledDecimal + public bool? SuppressInsecureTlsWarning { - get => GetSwitchValue("s_truncateScaledDecimal"); - set => SetSwitchValue("s_truncateScaledDecimal", value); + get => GetSwitchValue("s_suppressInsecureTlsWarning"); + set => SetSwitchValue("s_suppressInsecureTlsWarning", value); } - public bool? IgnoreServerProvidedFailoverPartner + /// + /// Get or set the TruncateScaledDecimal switch value. + /// + public bool? TruncateScaledDecimal { - get => GetSwitchValue("s_ignoreServerProvidedFailoverPartner"); - set => SetSwitchValue("s_ignoreServerProvidedFailoverPartner", value); + get => GetSwitchValue("s_truncateScaledDecimal"); + set => SetSwitchValue("s_truncateScaledDecimal", value); } - public bool? EnableUserAgent + /// + /// Get or set the UseCompatibilityAsyncBehaviour switch value. + /// + public bool? UseCompatibilityAsyncBehaviour { - get => GetSwitchValue("s_enableUserAgent"); - set => SetSwitchValue("s_enableUserAgent", value); + get => GetSwitchValue("s_useCompatibilityAsyncBehaviour"); + set => SetSwitchValue("s_useCompatibilityAsyncBehaviour", value); } - public bool? EnableMultiSubnetFailoverByDefault + /// + /// Get or set the UseCompatibilityProcessSni switch value. + /// + public bool? UseCompatibilityProcessSni { - get => GetSwitchValue("s_multiSubnetFailoverByDefault"); - set => SetSwitchValue("s_multiSubnetFailoverByDefault", value); + get => GetSwitchValue("s_useCompatibilityProcessSni"); + set => SetSwitchValue("s_useCompatibilityProcessSni", value); } -#if NET /// - /// Get or set the LocalAppContextSwitches.GlobalizationInvariantMode switch value. + /// Get or set the UseConnectionPoolV2 switch value. /// - public bool? GlobalizationInvariantMode + public bool? UseConnectionPoolV2 { - get => GetSwitchValue("s_globalizationInvariantMode"); - set => SetSwitchValue("s_globalizationInvariantMode", value); + get => GetSwitchValue("s_useConnectionPoolV2"); + set => SetSwitchValue("s_useConnectionPoolV2", value); } - #endif #if NET && _WINDOWS /// - /// Get or set the LocalAppContextSwitches.UseManagedNetworking switch value. + /// Get or set the UseManagedNetworking switch value. /// public bool? UseManagedNetworking { @@ -332,18 +333,15 @@ public bool? UseManagedNetworking set => SetSwitchValue("s_useManagedNetworking", value); } #endif - - #if NETFRAMEWORK + /// - /// Get or set the LocalAppContextSwitches.DisableTnirByDefault switch - /// value. + /// Get or set the UseMinimumLoginTimeout switch value. /// - public bool? DisableTnirByDefault + public bool? UseMinimumLoginTimeout { - get => GetSwitchValue("s_disableTnirByDefault"); - set => SetSwitchValue("s_disableTnirByDefault", value); + get => GetSwitchValue("s_useMinimumLoginTimeout"); + set => SetSwitchValue("s_useMinimumLoginTimeout", value); } - #endif #endregion From a50cefceda72c779cf41a61f5fa0951b42c1d7cc Mon Sep 17 00:00:00 2001 From: Paul Medynski <31868385+paulmedynski@users.noreply.github.com> Date: Mon, 22 Dec 2025 08:24:02 -0400 Subject: [PATCH 7/7] - Added XML comments throughout the LocalAppContextSwitches class. - Re-formatted existing XML comments for consistency. --- .../Data/SqlClient/LocalAppContextSwitches.cs | 545 +++++++++++++----- .../Common/LocalAppContextSwitchesHelper.cs | 8 +- 2 files changed, 391 insertions(+), 162 deletions(-) diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs index da88c225be..74fd226728 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/LocalAppContextSwitches.cs @@ -8,44 +8,152 @@ namespace Microsoft.Data.SqlClient; +/// +/// This class provides immutable access to the app context switches used +/// throughout the codebase. Each switch value is read once (on first access) +/// and cached for future access. Most switch values are obtained solely from +/// the AppContext, but some switches may use other mechanisms to determine +/// their values as well (e.g. environment variables). +/// internal static class LocalAppContextSwitches { #region Switch Names #if NETFRAMEWORK - private const string DisableTnirByDefaultString = @"Switch.Microsoft.Data.SqlClient.DisableTNIRByDefaultInConnectionString"; + /// + /// The name of the app context switch that controls whether TNIR is + /// disabled by default in the connection string. + /// + private const string DisableTnirByDefaultString = + "Switch.Microsoft.Data.SqlClient.DisableTNIRByDefaultInConnectionString"; #endif - private const string EnableMultiSubnetFailoverByDefaultString = @"Switch.Microsoft.Data.SqlClient.EnableMultiSubnetFailoverByDefault"; - private const string EnableUserAgentString = @"Switch.Microsoft.Data.SqlClient.EnableUserAgent"; + + /// + /// The name of the app context switch that controls whether + /// MultiSubnetFailover is enabled by default in the connection string. + /// + private const string EnableMultiSubnetFailoverByDefaultString = + "Switch.Microsoft.Data.SqlClient.EnableMultiSubnetFailoverByDefault"; + + /// + /// The name of the app context switch that controls whether + /// the user agent feature is enabled. + /// + private const string EnableUserAgentString = + "Switch.Microsoft.Data.SqlClient.EnableUserAgent"; + #if NET - private const string GlobalizationInvariantModeString = @"System.Globalization.Invariant"; - private const string GlobalizationInvariantModeEnvironmentVariable = "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"; + /// + /// The name of the app context switch that controls whether + /// Globalization Invariant mode is enabled. + /// + private const string GlobalizationInvariantModeString = + "System.Globalization.Invariant"; + /// + /// The name of the environment variable that controls whether + /// Globalization Invariant mode is enabled. + /// + private const string GlobalizationInvariantModeEnvironmentVariable = + "DOTNET_SYSTEM_GLOBALIZATION_INVARIANT"; #endif - private const string IgnoreServerProvidedFailoverPartnerString = @"Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner"; - private const string LegacyRowVersionNullString = @"Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior"; - private const string LegacyVarTimeZeroScaleBehaviourString = @"Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour"; - private const string MakeReadAsyncBlockingString = @"Switch.Microsoft.Data.SqlClient.MakeReadAsyncBlocking"; - private const string SuppressInsecureTlsWarningString = @"Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning"; - private const string TruncateScaledDecimalString = @"Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal"; - private const string UseCompatibilityAsyncBehaviourString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour"; - private const string UseCompatibilityProcessSniString = @"Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni"; - private const string UseConnectionPoolV2String = @"Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2"; + + /// + /// The name of the app context switch that controls whether the failover + /// partner provided by the server during connection will be ignored. + /// + private const string IgnoreServerProvidedFailoverPartnerString = + "Switch.Microsoft.Data.SqlClient.IgnoreServerProvidedFailoverPartner"; + + /// + /// The name of the app context switch that controls whether to preserve + /// legacy behavior where Timestamp/RowVersion fields return empty byte + /// arrays instead of null. + /// + private const string LegacyRowVersionNullString = + "Switch.Microsoft.Data.SqlClient.LegacyRowVersionNullBehavior"; + + /// + /// The name of the app context switch that controls whether to preserve + /// legacy VarTime zero scale behavior for datetime2, datetimeoffset, and + /// time data types. + /// + private const string LegacyVarTimeZeroScaleBehaviourString = + "Switch.Microsoft.Data.SqlClient.LegacyVarTimeZeroScaleBehaviour"; + + /// + /// The name of the app context switch that controls whether + /// ReadAsync operations run synchronously and block the calling thread. + /// + private const string MakeReadAsyncBlockingString = + "Switch.Microsoft.Data.SqlClient.MakeReadAsyncBlocking"; + + /// + /// The name of the app context switch that controls whether to suppress + /// the security warning when using Encrypt=false with TLS 1.2 or lower. + /// + private const string SuppressInsecureTlsWarningString = + "Switch.Microsoft.Data.SqlClient.SuppressInsecureTLSWarning"; + + /// + /// The name of the app context switch that controls whether TdsParser + /// truncates (rather than rounds) decimal and SqlDecimal values when + /// scaling them. + /// + private const string TruncateScaledDecimalString = + "Switch.Microsoft.Data.SqlClient.TruncateScaledDecimal"; + + /// + /// The name of the app context switch that controls whether to use legacy + /// async multi-packet column value fetch behavior without continue snapshot + /// state. + /// + private const string UseCompatibilityAsyncBehaviourString = + "Switch.Microsoft.Data.SqlClient.UseCompatibilityAsyncBehaviour"; + + /// + /// The name of the app context switch that controls whether to use the old + /// ProcessSni design instead of the packet multiplexing implementation. + /// + private const string UseCompatibilityProcessSniString = + "Switch.Microsoft.Data.SqlClient.UseCompatibilityProcessSni"; + + /// + /// The name of the app context switch that controls whether to use the new + /// V2 connection pool implementation or the legacy V1 implementation. + /// + private const string UseConnectionPoolV2String = + "Switch.Microsoft.Data.SqlClient.UseConnectionPoolV2"; + #if NET && _WINDOWS - private const string UseManagedNetworkingOnWindowsString = "Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows"; + /// + /// The name of the app context switch that controls whether to use the + /// managed SNI implementation instead of the native SNI implementation on + /// Windows. + /// + private const string UseManagedNetworkingOnWindowsString = + "Switch.Microsoft.Data.SqlClient.UseManagedNetworkingOnWindows"; #endif - private const string UseMinimumLoginTimeoutString = @"Switch.Microsoft.Data.SqlClient.UseOneSecFloorInTimeoutCalculationDuringLogin"; + + /// + /// The name of the app context switch that controls whether to enforce + /// a minimum login timeout of 1 second instead of 0 seconds. + /// + private const string UseMinimumLoginTimeoutString = + "Switch.Microsoft.Data.SqlClient.UseOneSecFloorInTimeoutCalculationDuringLogin"; #endregion #region Switch Values - // We use a byte-based enum to track the value of each switch. This plays - // nicely with threaded access. A nullable bool would seem to be the - // obvious choice, but the way nullable bools are implemented in the CLR - // makes them not thread-safe without using locks (the HasValue and Value - // properties can get out of sync if one thread is writing while another is - // reading). - internal enum SwitchValue : byte + /// + /// We use a byte-based enum to track the value of each switch. This plays + /// nicely with threaded access. A nullable bool would seem to be the + /// obvious choice, but the way nullable bools are implemented in the CLR + /// makes them not thread-safe without using locks (the HasValue and Value + /// properties can get out of sync if one thread is writing while another is + /// reading). + /// + private enum SwitchValue : byte { None = 0, True = 1, @@ -57,32 +165,97 @@ internal enum SwitchValue : byte // sure to update the test helper as well. #if NETFRAMEWORK - internal static SwitchValue s_disableTnirByDefault = SwitchValue.None; + /// + /// The cached value of the DisableTnirByDefault switch. + /// + private static SwitchValue s_disableTnirByDefault = SwitchValue.None; #endif - internal static SwitchValue s_enableMultiSubnetFailoverByDefault = SwitchValue.None; - internal static SwitchValue s_enableUserAgent = SwitchValue.None; + + /// + /// The cached value of the EnableMultiSubnetFailoverByDefault switch. + /// + private static SwitchValue s_enableMultiSubnetFailoverByDefault = SwitchValue.None; + + /// + /// The cached value of the EnableUserAgent switch. + /// + private static SwitchValue s_enableUserAgent = SwitchValue.None; + #if NET - internal static SwitchValue s_globalizationInvariantMode = SwitchValue.None; + /// + /// The cached value of the GlobalizationInvariantMode switch. + /// + private static SwitchValue s_globalizationInvariantMode = SwitchValue.None; #endif - internal static SwitchValue s_ignoreServerProvidedFailoverPartner = SwitchValue.None; - internal static SwitchValue s_legacyRowVersionNullBehavior = SwitchValue.None; - internal static SwitchValue s_legacyVarTimeZeroScaleBehaviour = SwitchValue.None; - internal static SwitchValue s_makeReadAsyncBlocking = SwitchValue.None; - internal static SwitchValue s_suppressInsecureTlsWarning = SwitchValue.None; - internal static SwitchValue s_truncateScaledDecimal = SwitchValue.None; - internal static SwitchValue s_useCompatibilityAsyncBehaviour = SwitchValue.None; - internal static SwitchValue s_useCompatibilityProcessSni = SwitchValue.None; - internal static SwitchValue s_useConnectionPoolV2 = SwitchValue.None; + + /// + /// The cached value of the IgnoreServerProvidedFailoverPartner switch. + /// + private static SwitchValue s_ignoreServerProvidedFailoverPartner = SwitchValue.None; + + /// + /// The cached value of the LegacyRowVersionNullBehavior switch. + /// + private static SwitchValue s_legacyRowVersionNullBehavior = SwitchValue.None; + + /// + /// The cached value of the LegacyVarTimeZeroScaleBehaviour switch. + /// + private static SwitchValue s_legacyVarTimeZeroScaleBehaviour = SwitchValue.None; + + /// + /// The cached value of the MakeReadAsyncBlocking switch. + /// + private static SwitchValue s_makeReadAsyncBlocking = SwitchValue.None; + + /// + /// The cached value of the SuppressInsecureTlsWarning switch. + /// + private static SwitchValue s_suppressInsecureTlsWarning = SwitchValue.None; + + /// + /// The cached value of the TruncateScaledDecimal switch. + /// + private static SwitchValue s_truncateScaledDecimal = SwitchValue.None; + + /// + /// The cached value of the UseCompatibilityAsyncBehaviour switch. + /// + private static SwitchValue s_useCompatibilityAsyncBehaviour = SwitchValue.None; + + /// + /// The cached value of the UseCompatibilityProcessSni switch. + /// + private static SwitchValue s_useCompatibilityProcessSni = SwitchValue.None; + + /// + /// The cached value of the UseConnectionPoolV2 switch. + /// + private static SwitchValue s_useConnectionPoolV2 = SwitchValue.None; + #if NET && _WINDOWS - internal static SwitchValue s_useManagedNetworking = SwitchValue.None; + /// + /// The cached value of the UseManagedNetworking switch. + /// + private static SwitchValue s_useManagedNetworking = SwitchValue.None; #endif - internal static SwitchValue s_useMinimumLoginTimeout = SwitchValue.None; + + /// + /// The cached value of the UseMinimumLoginTimeout switch. + /// + private static SwitchValue s_useMinimumLoginTimeout = SwitchValue.None; #endregion #region Static Initialization #if NET + /// + /// Static construction for .NET reads the AppContextSwitchOverridesSection + /// from the default app config file and applies any switch values found + /// there to the AppContext. These values are then later read and cached by + /// the individual switch properties on first access. + /// static LocalAppContextSwitches() { IAppContextSwitchOverridesSection appContextSwitch = AppConfigManager.FetchConfigurationSection(AppContextSwitchOverridesSection.Name); @@ -106,19 +279,23 @@ static LocalAppContextSwitches() #if NETFRAMEWORK /// - /// Transparent Network IP Resolution (TNIR) is a revision of the existing MultiSubnetFailover feature. - /// TNIR affects the connection sequence of the driver in the case where the first resolved IP of the hostname + /// Transparent Network IP Resolution (TNIR) is a revision of the existing + /// MultiSubnetFailover feature. TNIR affects the connection sequence of + /// the driver in the case where the first resolved IP of the hostname /// doesn't respond and there are multiple IPs associated with the hostname. /// - /// TNIR interacts with MultiSubnetFailover to provide the following three connection sequences: - /// 0: One IP is attempted, followed by all IPs in parallel - /// 1: All IPs are attempted in parallel - /// 2: All IPs are attempted one after another + /// TNIR interacts with MultiSubnetFailover to provide the following three + /// connection sequences: /// - /// TransparentNetworkIPResolution is enabled by default. MultiSubnetFailover is disabled by default. - /// To disable TNIR, you can enable the app context switch. + /// 0: One IP is attempted, followed by all IPs in parallel + /// 1: All IPs are attempted in parallel + /// 2: All IPs are attempted one after another /// - /// This app context switch defaults to 'false'. + /// TransparentNetworkIPResolution is enabled by default. + /// MultiSubnetFailover is disabled by default. To disable TNIR, you can + /// enable the app context switch. + /// + /// The default value of this switch is false. /// public static bool DisableTnirByDefault => AcquireAndReturn( @@ -128,10 +305,12 @@ static LocalAppContextSwitches() #endif /// - /// When set to true, the default value for MultiSubnetFailover connection string property - /// will be true instead of false. This enables parallel IP connection attempts for - /// improved connection times in multi-subnet environments. - /// This app context switch defaults to 'false'. + /// When set to true, the default value for MultiSubnetFailover connection + /// string property will be true instead of false. This enables parallel IP + /// connection attempts for improved connection times in multi-subnet + /// environments. + /// + /// The default value of this switch is false. /// public static bool EnableMultiSubnetFailoverByDefault => AcquireAndReturn( @@ -140,7 +319,10 @@ static LocalAppContextSwitches() ref s_enableMultiSubnetFailoverByDefault); /// - /// When set to true, the user agent feature is enabled and the driver will send the user agent string to the server. + /// When set to true, the user agent feature is enabled and the driver will + /// send the user agent string to the server. + /// + /// The default value of this switch is false. /// public static bool EnableUserAgent => AcquireAndReturn( @@ -150,79 +332,99 @@ static LocalAppContextSwitches() #if NET /// - /// .NET Core 2.0 and up supports Globalization Invariant mode, which reduces the size of the required libraries for - /// applications which don't need globalization support. SqlClient requires those libraries for core functionality, - /// and will throw exceptions later if they are not present. This switch allows SqlClient to detect this mode early. + /// .NET Core 2.0 and up supports Globalization Invariant mode, which + /// reduces the size of the required libraries for applications which don't + /// need globalization support. SqlClient requires those libraries for core + /// functionality, and will throw exceptions later if they are not present. + /// This switch allows SqlClient to detect this mode early. + /// + /// The value of this switch is determined first by checking the AppContext. + /// If not set there, it falls back to checking the environment variable + /// DOTNET_SYSTEM_GLOBALIZATION_INVARIANT. If neither is set, it attempts to + /// create the "en-US" culture and infers invariant mode from whether that + /// operation throws an exception. + /// + /// The default value of this switch is false. /// public static bool GlobalizationInvariantMode { get { - if (s_globalizationInvariantMode == SwitchValue.None) + if (s_globalizationInvariantMode != SwitchValue.None) + { + return s_globalizationInvariantMode == SwitchValue.True; + } + + // Check if invariant mode has been set by the AppContext switch directly + if (AppContext.TryGetSwitch(GlobalizationInvariantModeString, out bool returnedValue) && returnedValue) + { + s_globalizationInvariantMode = SwitchValue.True; + return true; + } + + // TODO(https://github.com/dotnet/SqlClient/pull/3853): + // + // The intention of the comment below doesn't match the code. + // + // The comment claims to fallback to the environment variable if the + // switch is not set. However, it actually falls-back if the switch + // is not set _OR_ it is set to false. + // + // Should we update the comment or fix the code to match? + + // If the switch is not set, we check the environment variable as the first fallback + string? envValue = Environment.GetEnvironmentVariable(GlobalizationInvariantModeEnvironmentVariable); + + if (string.Equals(envValue, bool.TrueString, StringComparison.OrdinalIgnoreCase) || + string.Equals(envValue, "1", StringComparison.OrdinalIgnoreCase)) + { + s_globalizationInvariantMode = SwitchValue.True; + return true; + } + + // TODO(https://github.com/dotnet/SqlClient/pull/3853): + // + // What if the environment variable is set to false? Why are we + // ignoring that case? + + // If this hasn't been manually set, it could still apply if the OS + // doesn't have ICU libraries installed, or if the application is a + // native binary with ICU support trimmed away. .NET 3.1 to 5.0 do + // not throw in attempting to create en-US in invariant mode, but + // .NET 6+ does. In such cases, catch and infer invariant mode from + // the exception. + try + { + s_globalizationInvariantMode = System.Globalization.CultureInfo.GetCultureInfo("en-US").EnglishName.Contains("Invariant") + ? SwitchValue.True + : SwitchValue.False; + } + catch (System.Globalization.CultureNotFoundException) { - // Check if invariant mode has been set by the AppContext switch directly - if (AppContext.TryGetSwitch(GlobalizationInvariantModeString, out bool returnedValue) && returnedValue) - { - s_globalizationInvariantMode = SwitchValue.True; - } - else - { - // TODO(https://github.com/dotnet/SqlClient/pull/3853): - // - // The comment's intention doesn't match the code. - // - // The comment claims to fallback to the environment - // variable if the switch is not set. However, it actually - // falls-back if the switch is not set _OR_ it is set to - // false. - // - // Should we update the comment or fix the code to match? - - // If the switch is not set, we check the environment variable as the first fallback - string? envValue = Environment.GetEnvironmentVariable(GlobalizationInvariantModeEnvironmentVariable); - - if (string.Equals(envValue, bool.TrueString, StringComparison.OrdinalIgnoreCase) || string.Equals(envValue, "1", StringComparison.OrdinalIgnoreCase)) - { - s_globalizationInvariantMode = SwitchValue.True; - } - else - { - // If this hasn't been manually set, it could still apply if the OS doesn't have ICU libraries installed, - // or if the application is a native binary with ICU support trimmed away. - // .NET 3.1 to 5.0 do not throw in attempting to create en-US in invariant mode, but .NET 6+ does. In - // such cases, catch and infer invariant mode from the exception. - try - { - s_globalizationInvariantMode = System.Globalization.CultureInfo.GetCultureInfo("en-US").EnglishName.Contains("Invariant") - ? SwitchValue.True - : SwitchValue.False; - } - catch (System.Globalization.CultureNotFoundException) - { - // If the culture is not found, it means we are in invariant mode - s_globalizationInvariantMode = SwitchValue.True; - } - } - } + // If the culture is not found, it means we are in invariant mode + s_globalizationInvariantMode = SwitchValue.True; } + return s_globalizationInvariantMode == SwitchValue.True; } } #else /// - /// .NET Framework does not support Globalization Invariant mode, so this will always be false. + /// .NET Framework does not support Globalization Invariant mode, so this + /// will always be false. /// public static bool GlobalizationInvariantMode => false; #endif /// - /// When set to true, the failover partner provided by the server during connection - /// will be ignored. This is useful in scenarios where the application wants to - /// control the failover behavior explicitly (e.g. using a custom port). The application - /// must be kept up to date with the failover configuration of the server. - /// The application will not automatically discover a newly configured failover partner. - /// - /// This app context switch defaults to 'false'. + /// When set to true, the failover partner provided by the server during + /// connection will be ignored. This is useful in scenarios where the + /// application wants to control the failover behavior explicitly (e.g. + /// using a custom port). The application must be kept up to date with the + /// failover configuration of the server. The application will not + /// automatically discover a newly configured failover partner. + /// + /// The default value of this switch is false. /// public static bool IgnoreServerProvidedFailoverPartner => AcquireAndReturn( @@ -231,10 +433,13 @@ public static bool GlobalizationInvariantMode ref s_ignoreServerProvidedFailoverPartner); /// - /// In System.Data.SqlClient and Microsoft.Data.SqlClient prior to 3.0.0 a field with type Timestamp/RowVersion - /// would return an empty byte array. This switch controls whether to preserve that behaviour on newer versions - /// of Microsoft.Data.SqlClient, if this switch returns false an appropriate null value will be returned. - /// This app context switch defaults to 'false'. + /// In System.Data.SqlClient and Microsoft.Data.SqlClient prior to 3.0.0 a + /// field with type Timestamp/RowVersion would return an empty byte array. + /// This switch controls whether to preserve that behaviour on newer + /// versions of Microsoft.Data.SqlClient, if this switch returns false an + /// appropriate null value will be returned. + /// + /// The default value of this switch is false. /// public static bool LegacyRowVersionNullBehavior => AcquireAndReturn( @@ -243,11 +448,13 @@ public static bool GlobalizationInvariantMode ref s_legacyRowVersionNullBehavior); /// - /// When set to 'true' this will output a scale value of 7 (DEFAULT_VARTIME_SCALE) when the scale - /// is explicitly set to zero for VarTime data types ('datetime2', 'datetimeoffset' and 'time') - /// If no scale is set explicitly it will continue to output scale of 7 (DEFAULT_VARTIME_SCALE) - /// regardless of switch value. - /// This app context switch defaults to 'true'. + /// When set to 'true' this will output a scale value of 7 + /// (DEFAULT_VARTIME_SCALE) when the scale is explicitly set to zero for + /// VarTime data types ('datetime2', 'datetimeoffset' and 'time') If no + /// scale is set explicitly it will continue to output scale of 7 + /// (DEFAULT_VARTIME_SCALE) regardless of switch value. + /// + /// The default value of this switch is true. /// public static bool LegacyVarTimeZeroScaleBehaviour => AcquireAndReturn( @@ -256,8 +463,10 @@ public static bool GlobalizationInvariantMode ref s_legacyVarTimeZeroScaleBehaviour); /// - /// When enabled, ReadAsync runs asynchronously and does not block the calling thread. - /// This app context switch defaults to 'false'. + /// When enabled, ReadAsync runs asynchronously and does not block the + /// calling thread. + /// + /// The default value of this switch is false. /// public static bool MakeReadAsyncBlocking => AcquireAndReturn( @@ -266,9 +475,11 @@ public static bool GlobalizationInvariantMode ref s_makeReadAsyncBlocking); /// - /// When using Encrypt=false in the connection string, a security warning is output to the console if the TLS version is 1.2 or lower. - /// This warning can be suppressed by enabling this AppContext switch. - /// This app context switch defaults to 'false'. + /// When using Encrypt=false in the connection string, a security warning is + /// output to the console if the TLS version is 1.2 or lower. This warning + /// can be suppressed by enabling this AppContext switch. + /// + /// The default value of this switch is false. /// public static bool SuppressInsecureTlsWarning => AcquireAndReturn( @@ -277,7 +488,10 @@ public static bool GlobalizationInvariantMode ref s_suppressInsecureTlsWarning); /// - /// When set to true, TdsParser will truncate (rather than round) decimal and SqlDecimal values when scaling them. + /// When set to true, TdsParser will truncate (rather than round) decimal + /// and SqlDecimal values when scaling them. + /// + /// The default value of this switch is false. /// public static bool TruncateScaledDecimal => AcquireAndReturn( @@ -286,12 +500,17 @@ public static bool GlobalizationInvariantMode ref s_truncateScaledDecimal); /// - /// In TdsParser, the async multi-packet column value fetch behavior can use a continue snapshot state - /// for improved efficiency. When this switch is enabled (the default), the driver preserves the legacy - /// compatibility behavior, which does not use the continue snapshot state. When disabled, the new behavior - /// using the continue snapshot state is enabled. This switch will always return true if - /// is enabled, because the continue state is not stable without - /// the multiplexer. + /// In TdsParser, the async multi-packet column value fetch behavior can use + /// a continue snapshot state for improved efficiency. When this switch is + /// enabled (the default), the driver preserves the legacy compatibility + /// behavior, which does not use the continue snapshot state. When disabled, + /// the new behavior using the continue snapshot state is enabled. + /// + /// This switch will always return true if + /// is enabled, because the + /// continue state is not stable without the multiplexer. + /// + /// The default value of this switch is true. /// public static bool UseCompatibilityAsyncBehaviour { @@ -299,10 +518,11 @@ public static bool UseCompatibilityAsyncBehaviour { if (UseCompatibilityProcessSni) { - // If ProcessSni compatibility mode has been enabled then the packet - // multiplexer has been disabled. The new async behaviour using continue - // point capture is only stable if the multiplexer is enabled so we must - // return true to enable compatibility async behaviour using only restarts. + // If ProcessSni compatibility mode has been enabled then the + // packet multiplexer has been disabled. The new async behaviour + // using continue point capture is only stable if the + // multiplexer is enabled so we must return true to enable + // compatibility async behaviour using only restarts. return true; } @@ -314,11 +534,14 @@ public static bool UseCompatibilityAsyncBehaviour } /// - /// In TdsParser, the ProcessSni function changed significantly when the packet - /// multiplexing code needed for high speed multi-packet column values was added. - /// When this switch is set to true (the default), the old ProcessSni design is used. - /// When this switch is set to false, the new experimental ProcessSni behavior using - /// the packet multiplexer is enabled. + /// In TdsParser, the ProcessSni function changed significantly when the + /// packet multiplexing code needed for high speed multi-packet column + /// values was added. When this switch is set to true (the default), the + /// old ProcessSni design is used. When this switch is set to false, the + /// new experimental ProcessSni behavior using the packet multiplexer is + /// enabled. + /// + /// The default value of this switch is true. /// public static bool UseCompatibilityProcessSni => AcquireAndReturn( @@ -327,9 +550,11 @@ public static bool UseCompatibilityAsyncBehaviour ref s_useCompatibilityProcessSni); /// - /// When set to true, the connection pool will use the new V2 connection pool implementation. - /// When set to false, the connection pool will use the legacy V1 implementation. - /// This app context switch defaults to 'false'. + /// When set to true, the connection pool will use the new V2 connection + /// pool implementation. When set to false, the connection pool will use + /// the legacy V1 implementation. + /// + /// The default value of this switch is false. /// public static bool UseConnectionPoolV2 => AcquireAndReturn( @@ -339,19 +564,18 @@ public static bool UseCompatibilityAsyncBehaviour #if NET && _WINDOWS /// - /// When set to true, .NET Core will use the managed SNI implementation instead of the native SNI implementation. - /// - /// - /// - /// Non-Windows platforms will always use the managed networking implementation. Windows platforms will use the native SNI - /// implementation by default, but this can be overridden by setting the AppContext switch. - /// - /// - /// ILLink.Substitutions.xml allows the unused SNI implementation to be trimmed away when the corresponding AppContext - /// switch is set at compile time. In such cases, this property will return a constant value, even if the AppContext switch is - /// set or reset at runtime. See the ILLink.Substitutions.Windows.xml and ILLink.Substitutions.Unix.xml resource files for details. - /// - /// + /// When set to true, .NET on Windows will use the managed SNI + /// implementation instead of the native SNI implementation. + /// + /// ILLink.Substitutions.xml allows the unused SNI implementation to be + /// trimmed away when the corresponding AppContext switch is set at compile + /// time. In such cases, this property will return a constant value, even if + /// the AppContext switch is set or reset at runtime. See the + /// ILLink.Substitutions.Windows.xml and ILLink.Substitutions.Unix.xml + /// resource files for details. + /// + /// The default value of this switch is false. + /// public static bool UseManagedNetworking { get @@ -379,20 +603,23 @@ public static bool UseManagedNetworking } #elif NET /// - /// .NET Core on Unix does not support the native SNI, so this will always be true. + /// .NET Core on Unix does not support native SNI, so this will always be + /// true. /// public static bool UseManagedNetworking => true; #else /// - /// .NET Framework does not support the managed SNI, so this will always be false. + /// .NET Framework does not support the managed SNI, so this will always be + /// false. /// public static bool UseManagedNetworking => false; #endif /// - /// Specifies minimum login timeout to be set to 1 second instead of 0 seconds, - /// to prevent a login attempt from waiting indefinitely. - /// This app context switch defaults to 'true'. + /// Specifies minimum login timeout to be set to 1 second instead of 0 + /// seconds, to prevent a login attempt from waiting indefinitely. + /// + /// The default value of this switch is true. /// public static bool UseMinimumLoginTimeout => AcquireAndReturn( diff --git a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs index 507fc53a27..e71d82b398 100644 --- a/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs +++ b/src/Microsoft.Data.SqlClient/tests/Common/LocalAppContextSwitchesHelper.cs @@ -64,8 +64,9 @@ public sealed class LocalAppContextSwitchesHelper : IDisposable /// /// Construct to capture all existing switch values. /// - /// This call will block, waiting for any previous instance to be disposed - /// before completing construction. + /// This call will block for at most 5 seconds, waiting for any previous + /// instance to be disposed before completing construction. Failure to + /// acquire the lock in that time will result in an exception being thrown. /// public LocalAppContextSwitchesHelper() { @@ -129,7 +130,8 @@ public LocalAppContextSwitchesHelper() } /// - /// Disposal restores all original switch values and releases the instance lock. + /// Disposal restores all original switch values and releases the instance + /// lock. /// public void Dispose() {