diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs index b48ea36958..0f40a70df7 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNINpHandle.cs @@ -37,7 +37,7 @@ internal sealed class SNINpHandle : SNIPhysicalHandle private int _bufferSize = TdsEnums.DEFAULT_LOGIN_PACKET_SIZE; private readonly Guid _connectionId = Guid.NewGuid(); - public SNINpHandle(string serverName, string pipeName, long timerExpire, bool tlsFirst) + public SNINpHandle(string serverName, string pipeName, long timerExpire, bool tlsFirst, bool isAsyncOption) { using (TrySNIEventScope.Create(nameof(SNINpHandle))) { @@ -48,11 +48,13 @@ public SNINpHandle(string serverName, string pipeName, long timerExpire, bool tl _tlsFirst = tlsFirst; try { + PipeOptions pipeOptions = isAsyncOption ? PipeOptions.Asynchronous | PipeOptions.WriteThrough : PipeOptions.WriteThrough; + _pipeStream = new NamedPipeClientStream( serverName, pipeName, PipeDirection.InOut, - PipeOptions.Asynchronous | PipeOptions.WriteThrough); + pipeOptions); bool isInfiniteTimeOut = long.MaxValue == timerExpire; if (isInfiniteTimeOut) diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs index ac4d3599dd..7a1d1083e1 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SNI/SNIProxy.cs @@ -192,7 +192,7 @@ internal static SNIHandle CreateConnectionHandle( tlsFirst, hostNameInCertificate, serverCertificateFilename); break; case DataSource.Protocol.NP: - sniHandle = CreateNpHandle(details, timerExpire, parallel, tlsFirst); + sniHandle = CreateNpHandle(details, timerExpire, parallel, tlsFirst, isAsyncOption : async); break; default: Debug.Fail($"Unexpected connection protocol: {details._connectionProtocol}"); @@ -348,8 +348,9 @@ private static SNITCPHandle CreateTcpHandle( /// Timer expiration /// Should MultiSubnetFailover be used. Only returns an error for named pipes. /// + /// /// SNINpHandle - private static SNINpHandle CreateNpHandle(DataSource details, long timerExpire, bool parallel, bool tlsFirst) + private static SNINpHandle CreateNpHandle(DataSource details, long timerExpire, bool parallel, bool tlsFirst, bool isAsyncOption) { if (parallel) { @@ -357,7 +358,7 @@ private static SNINpHandle CreateNpHandle(DataSource details, long timerExpire, SNICommon.ReportSNIError(SNIProviders.NP_PROV, 0, SNICommon.MultiSubnetFailoverWithNonTcpProtocol, Strings.SNI_ERROR_49); return null; } - return new SNINpHandle(details.PipeHostName, details.PipeName, timerExpire, tlsFirst); + return new SNINpHandle(details.PipeHostName, details.PipeName, timerExpire, tlsFirst, isAsyncOption); } /// diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs index 660b5934e0..5f6427aa25 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnection.cs @@ -38,6 +38,7 @@ private enum CultureCheckState : uint } private bool _AsyncCommandInProgress; + private bool _isAsyncPipeOption = false; // SQLStatistics support internal SqlStatistics _statistics; @@ -496,6 +497,11 @@ internal bool AsyncCommandInProgress set => _AsyncCommandInProgress = value; } + internal bool IsAsyncPipeOption + { + get => _isAsyncPipeOption; + } + private bool UsesActiveDirectoryIntegrated(SqlConnectionString opt) { return opt != null && opt.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated; @@ -1589,6 +1595,8 @@ private Task InternalOpenAsync(CancellationToken cancellationToken) { long scopeID = SqlClientEventSource.Log.TryPoolerScopeEnterEvent("SqlConnection.InternalOpenAsync | API | Object Id {0}", ObjectID); SqlClientEventSource.Log.TryCorrelationTraceEvent("SqlConnection.InternalOpenAsync | API | Correlation | Object Id {0}, Activity Id {1}", ObjectID, ActivityCorrelator.Current); + _isAsyncPipeOption = true; + try { Guid operationId = s_diagnosticListener.WriteConnectionOpenBefore(this); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index e977641175..4e0035f020 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -1911,7 +1911,8 @@ private void AttemptOneLogin( ignoreSniOpenTimeout, timeout.LegacyTimerExpire, ConnectionOptions, - withFailover); + withFailover, + Connection.IsAsyncPipeOption); _timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake); _timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.LoginBegin); diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs index 8217604c72..913377adea 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/TdsParser.cs @@ -364,7 +364,8 @@ internal void Connect( bool ignoreSniOpenTimeout, long timerExpire, SqlConnectionString connectionOptions, - bool withFailover) + bool withFailover, + bool isAsyncPipeOption) { SqlConnectionEncryptOption encrypt = connectionOptions.Encrypt; bool isTlsFirst = (encrypt == SqlConnectionEncryptOption.Strict); @@ -443,6 +444,7 @@ internal void Connect( _connHandler.pendingSQLDNSObject = null; // AD Integrated behaves like Windows integrated when connecting to a non-fedAuth server + _physicalStateObj.CreatePhysicalSNIHandle( serverInfo.ExtendedServerName, ignoreSniOpenTimeout, @@ -450,7 +452,7 @@ internal void Connect( out instanceName, ref _sniSpnBuffer, false, - true, + async: isAsyncPipeOption, fParallel, _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCache, @@ -545,10 +547,11 @@ internal void Connect( _physicalStateObj.CreatePhysicalSNIHandle( serverInfo.ExtendedServerName, ignoreSniOpenTimeout, - timerExpire, out instanceName, + timerExpire, + out instanceName, ref _sniSpnBuffer, true, - true, + async: isAsyncPipeOption, fParallel, _connHandler.ConnectionOptions.IPAddressPreference, FQDNforDNSCache, diff --git a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs index 624912f260..1aea785935 100644 --- a/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs +++ b/src/Microsoft.Data.SqlClient/tests/ManualTests/SQL/RetryLogic/SqlConnectionReliabilityTest.cs @@ -130,6 +130,41 @@ public void ConcurrentExecution(string cnnString, SqlRetryLogicBaseProvider prov Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); } +#if NETCOREAPP + + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] + [MemberData(nameof(RetryLogicTestHelper.GetConnectionAndRetryStrategyInvalidCatalog), parameters: new object[] { 2 }, MemberType = typeof(RetryLogicTestHelper), DisableDiscoveryEnumeration = true)] + public async Task ConcurrentExecutionAsync(string cnnString, SqlRetryLogicBaseProvider provider) + { + int numberOfTries = provider.RetryLogic.NumberOfTries; + int cancelAfterRetries = numberOfTries + 1; + int retriesCount = 0; + int concurrentExecution = 5; + provider.Retrying += (s, e) => Interlocked.Increment(ref retriesCount); + + await Parallel.ForEachAsync(System.Linq.Enumerable.Range(0, concurrentExecution), + async (i, c) => + { + using (var cnn = CreateConnectionWithInvalidCatalog(cnnString, provider, cancelAfterRetries)) + { + await Assert.ThrowsAsync(async () => await cnn.OpenAsync()); + } + }); + Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); + + retriesCount = 0; + Parallel.For(0, concurrentExecution, + i => + { + using (var cnn = CreateConnectionWithInvalidCatalog(cnnString, provider, cancelAfterRetries)) + { + Assert.ThrowsAsync(() => cnn.OpenAsync()).Wait(); + } + }); + Assert.Equal(numberOfTries * concurrentExecution, retriesCount + concurrentExecution); + } +#endif + [ConditionalTheory(typeof(DataTestUtility), nameof(DataTestUtility.AreConnStringsSetup))] [MemberData(nameof(RetryLogicTestHelper.GetNoneRetriableCondition), MemberType = typeof(RetryLogicTestHelper), DisableDiscoveryEnumeration = true)] public void DefaultOpenWithoutRetry(string connectionString, SqlRetryLogicBaseProvider cnnProvider)