diff --git a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index ca9bcc2cd9..bfda03ae42 100644 --- a/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -136,7 +136,7 @@ override protected DbConnectionInternal CreateConnection(DbConnectionOptions opt opt = new SqlConnectionString(opt, instanceName, userInstance: false, setEnlistValue: null); poolGroupProviderInfo = null; // null so we do not pass to constructor below... } - return new SqlInternalConnectionTds(identity, opt, key.Credential, poolGroupProviderInfo, "", null, redirectedUserInstance, userOpt, recoverySessionData, applyTransientFaultHandling: applyTransientFaultHandling, key.AccessToken, pool, key.AccessTokenCallback); + return new SqlInternalConnectionTds(identity, opt, key.Credential, poolGroupProviderInfo, "", null, redirectedUserInstance, userOpt, recoverySessionData, applyTransientFaultHandling, key.AccessToken, pool, key.AccessTokenCallback); } protected override DbConnectionOptions CreateConnectionOptions(string connectionString, DbConnectionOptions previous) 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 d128268185..7d6d2b51f6 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 @@ -106,14 +106,13 @@ internal sealed class SqlInternalConnectionTds : SqlInternalConnection, IDisposa { // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/retry-after#simple-retry-for-errors-with-http-error-codes-500-600 internal const int MsalHttpRetryStatusCode = 429; + // Connection re-route limit + internal const int MaxNumberOfRedirectRoute = 10; // CONNECTION AND STATE VARIABLES private readonly SqlConnectionPoolGroupProviderInfo _poolGroupProviderInfo; // will only be null when called for ChangePassword, or creating SSE User Instance private TdsParser _parser; - // Connection re-route limit - internal const int _maxNumberOfRedirectRoute = 10; - private SqlLoginAck _loginAck; private SqlCredential _credential; private FederatedAuthenticationFeatureExtensionData _fedAuthFeatureExtensionData; @@ -186,7 +185,7 @@ internal bool IsSQLDNSRetryEnabled } } - private bool _DNSCachingBeforeRedirect = false; + private bool _dnsCachingBeforeRedirect = false; /// /// Get or set if the control ring send redirect token and feature ext ack with true for DNSCaching @@ -195,11 +194,11 @@ internal bool IsDNSCachingBeforeRedirectSupported { get { - return _DNSCachingBeforeRedirect; + return _dnsCachingBeforeRedirect; } set { - _DNSCachingBeforeRedirect = value; + _dnsCachingBeforeRedirect = value; } } @@ -432,8 +431,9 @@ internal SqlConnectionTimeoutErrorInternal TimeoutErrorInternal } // OTHER STATE VARIABLES AND REFERENCES - internal Guid _clientConnectionId = Guid.Empty; + + // Routing information (ROR) private Guid _originalClientConnectionId = Guid.Empty; private string _routingDestination = null; private readonly TimeoutTimer _timeout; @@ -456,13 +456,32 @@ internal SqlInternalConnectionTds( DbConnectionPool pool = null, Func> accessTokenCallback = null) : base(connectionOptions) - { #if DEBUG if (reconnectSessionData != null) { reconnectSessionData._debugReconnectDataApplied = true; } +#if NETFRAMEWORK + try + { + // use this to help validate this object is only created after the following permission has been previously demanded in the current codepath + if (userConnectionOptions != null) + { + // As mentioned above, userConnectionOptions may be different to connectionOptions, so we need to demand on the correct connection string + userConnectionOptions.DemandPermission(); + } + else + { + connectionOptions.DemandPermission(); + } + } + catch (System.Security.SecurityException) + { + System.Diagnostics.Debug.Assert(false, "unexpected SecurityException for current codepath"); + throw; + } +#endif #endif Debug.Assert(reconnectSessionData == null || connectionOptions.ConnectRetryCount > 0, "Reconnect data supplied with CR turned off"); @@ -512,6 +531,9 @@ internal SqlInternalConnectionTds( _parserLock.Wait(canReleaseFromAnyThread: false); ThreadHasParserLockForClose = true; // In case of error, let ourselves know that we already own the parser lock +#if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); +#endif try { _timeout = TimeoutTimer.StartSecondsTimeout(connectionOptions.ConnectTimeout); @@ -524,7 +546,6 @@ internal SqlInternalConnectionTds( try { OpenLoginEnlist(_timeout, connectionOptions, credential, newPassword, newSecurePassword, redirectedUserInstance); - break; } catch (SqlException sqlex) @@ -544,6 +565,21 @@ internal SqlInternalConnectionTds( } } } + catch (System.OutOfMemoryException) + { + DoomThisConnection(); + throw; + } + catch (System.StackOverflowException) + { + DoomThisConnection(); + throw; + } + catch (System.Threading.ThreadAbortException) + { + DoomThisConnection(); + throw; + } finally { ThreadHasParserLockForClose = false; @@ -697,6 +733,7 @@ protected override bool ReadyToPrepareTransaction { get { + // TODO: probably need to use a different method, but that's a different bug bool result = FindLiveReader(null) == null; // can't prepare with a live data reader... return result; } @@ -719,6 +756,16 @@ public int ServerProcessId } } + /// + /// Get boolean that specifies whether an enlisted transaction can be unbound from + /// the connection when that transaction completes. + /// + /// + /// This override always returns false. + /// + /// + /// The SqlInternalConnectionTds.CheckEnlistedTransactionBinding method handles implicit unbinding for disposed transactions. + /// protected override bool UnbindOnTransactionCompletion { get @@ -738,7 +785,7 @@ protected override bool UnbindOnTransactionCompletion [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] // copied from Triaged.cs protected override void ChangeDatabaseInternal(string database) { - // Add brackets around database + // MDAC 73598 - add brackets around database database = SqlConnection.FixupDatabaseTransactionName(database); Task executeTask = _parser.TdsExecuteSQLBatch("use " + database, ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj, sync: true); Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes"); @@ -798,7 +845,7 @@ internal override void ValidateConnectionForExecute(SqlCommand command) { // if MARS is on, then a datareader associated with the command exists // or if MARS is off, then a datareader exists - throw ADP.OpenReaderExists(parser.MARSOn); + throw ADP.OpenReaderExists(parser.MARSOn); // MDAC 66411 } else if (!parser.MARSOn && parser._physicalStateObj.HasPendingData) { @@ -869,12 +916,8 @@ internal void CheckEnlistedTransactionBinding() } } - internal override bool IsConnectionAlive(bool throwOnException) - { - bool isAlive = false; - isAlive = _parser._physicalStateObj.IsConnectionAlive(throwOnException); - return isAlive; - } + internal override bool IsConnectionAlive(bool throwOnException) => + _parser._physicalStateObj.IsConnectionAlive(throwOnException); //////////////////////////////////////////////////////////////////////////////////////// // POOLING METHODS @@ -882,6 +925,10 @@ internal override bool IsConnectionAlive(bool throwOnException) protected override void Activate(Transaction transaction) { +#if NETFRAMEWORK + FailoverPermissionDemand(); // Demand for unspecified failover pooled connections +#endif + // When we're required to automatically enlist in transactions and // there is one we enlist in it. On the other hand, if there isn't a // transaction and we are currently enlisted in one, then we @@ -1023,8 +1070,7 @@ internal void ExecuteTransaction2005( string transactionName, System.Data.IsolationLevel iso, SqlInternalTransaction internalTransaction, - bool isDelegateControlRequest - ) + bool isDelegateControlRequest) { TdsEnums.TransactionManagerRequestType requestType = TdsEnums.TransactionManagerRequestType.Begin; TdsEnums.TransactionManagerIsolationLevel isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadCommitted; @@ -1145,7 +1191,7 @@ bool isDelegateControlRequest } } - // _parser may be nulled out during TdsExecuteTransactionManagerRequest. + // SQLBU #406778 - _parser may be nulled out during TdsExecuteTransactionManagerRequest. // Only use local variable after this call. _parser.TdsExecuteTransactionManagerRequest(null, requestType, transactionName, isoLevel, ConnectionOptions.ConnectTimeout, internalTransaction, stateObj, isDelegateControlRequest); @@ -1171,6 +1217,7 @@ bool isDelegateControlRequest internal override void DelegatedTransactionEnded() { + // TODO: I don't like the way that this works, but I don't want to rototill the entire pooler to avoid this call. base.DelegatedTransactionEnded(); } @@ -1207,6 +1254,7 @@ private void CompleteLogin(bool enlistOK) SqlClientEventSource.Log.TryTraceEvent(" {0}, Server never sent the requested federated authentication info", ObjectID); throw SQL.ParsingError(ParsingErrorState.FedAuthInfoNotReceived); } + if (!_sessionRecoveryAcknowledged) { _currentSessionData = null; @@ -1221,7 +1269,7 @@ private void CompleteLogin(bool enlistOK) _currentSessionData._initialCollation = _currentSessionData._collation; _currentSessionData._initialLanguage = _currentLanguage; } - bool isEncrypted = _parser.EncryptionOptions == EncryptionOptions.ON; + bool isEncrypted = (_parser.EncryptionOptions & EncryptionOptions.OPTIONS_MASK) == EncryptionOptions.ON; if (_recoverySessionData != null) { if (_recoverySessionData._encrypted != isEncrypted) @@ -1374,9 +1422,10 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, requestedFeatures |= TdsEnums.FeatureExtension.AzureSQLSupport; } - // The SQLDNSCaching feature is implicitly set + // The SQLDNSCaching and JSON features are implicitly set requestedFeatures |= TdsEnums.FeatureExtension.SQLDNSCaching; requestedFeatures |= TdsEnums.FeatureExtension.JsonSupport; + _parser.TdsLogin(login, requestedFeatures, _recoverySessionData, _fedAuthFeatureExtensionData, encrypt); } @@ -1515,23 +1564,29 @@ private void LoginNoFailover(ServerInfo serverInfo, Debug.Assert(object.ReferenceEquals(connectionOptions, this.ConnectionOptions), "ConnectionOptions argument and property must be the same"); // consider removing the argument int routingAttempts = 0; ServerInfo originalServerInfo = serverInfo; // serverInfo may end up pointing to new object due to routing, original object is used to set CurrentDatasource + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, host={1}", ObjectID, serverInfo.UserServerName); + int sleepInterval = 100; //milliseconds to sleep (back off) between attempts. ResolveExtendedServerName(serverInfo, !redirectedUserInstance, connectionOptions); long timeoutUnitInterval = 0; - if (connectionOptions.MultiSubnetFailover) + bool isParallel = connectionOptions.MultiSubnetFailover; + + if (isParallel) { + float failoverTimeoutStep = ADP.FailoverTimeoutStep; + // Determine unit interval if (timeout.IsInfinite) { - timeoutUnitInterval = checked((long)(ADP.FailoverTimeoutStep * (1000L * ADP.DefaultConnectionTimeout))); + timeoutUnitInterval = checked((long)(failoverTimeoutStep * (1000L * ADP.DefaultConnectionTimeout))); } else { - timeoutUnitInterval = checked((long)(ADP.FailoverTimeoutStep * timeout.MillisecondsRemaining)); + timeoutUnitInterval = checked((long)(failoverTimeoutStep * timeout.MillisecondsRemaining)); } } // Only three ways out of this loop: @@ -1545,14 +1600,18 @@ private void LoginNoFailover(ServerInfo serverInfo, // back into the parser for the error cases. int attemptNumber = 0; TimeoutTimer intervalTimer = null; + TimeoutTimer attemptOneLoginTimeout = timeout; + while (true) { - if (connectionOptions.MultiSubnetFailover) + if (isParallel) { - attemptNumber++; + int multiplier = ++attemptNumber; + // Set timeout for this attempt, but don't exceed original timer - long nextTimeoutInterval = checked(timeoutUnitInterval * attemptNumber); + long nextTimeoutInterval = checked(timeoutUnitInterval * multiplier); long milliseconds = timeout.MillisecondsRemaining; + if (nextTimeoutInterval > milliseconds) { nextTimeoutInterval = milliseconds; @@ -1570,10 +1629,15 @@ private void LoginNoFailover(ServerInfo serverInfo, try { + if (isParallel) + { + attemptOneLoginTimeout = intervalTimer; + } + AttemptOneLogin(serverInfo, newPassword, newSecurePassword, - connectionOptions.MultiSubnetFailover ? intervalTimer : timeout); + attemptOneLoginTimeout); if (connectionOptions.MultiSubnetFailover && ServerProvidedFailOverPartner != null) { @@ -1584,9 +1648,9 @@ private void LoginNoFailover(ServerInfo serverInfo, if (RoutingInfo != null) { SqlClientEventSource.Log.TryTraceEvent(" Routed to {0}", serverInfo.ExtendedServerName); - if (routingAttempts > _maxNumberOfRedirectRoute) + if (routingAttempts > MaxNumberOfRedirectRoute) { - throw SQL.ROR_RecursiveRoutingNotSupported(this, _maxNumberOfRedirectRoute); + throw SQL.ROR_RecursiveRoutingNotSupported(this, MaxNumberOfRedirectRoute); } if (timeout.IsExpired) @@ -1758,6 +1822,7 @@ TimeoutTimer timeout } // Initialize loop variables + bool failoverDemandDone = false; // have we demanded for partner information yet (as necessary)? int attemptNumber = 0; // Only three ways out of this loop: @@ -1792,6 +1857,14 @@ TimeoutTimer timeout ServerInfo currentServerInfo; if (useFailoverHost) { + if (!failoverDemandDone) + { +#if NETFRAMEWORK + FailoverPermissionDemand(); +#endif + failoverDemandDone = true; + } + // Primary server may give us a different failover partner than the connection string indicates. Update it if (ServerProvidedFailOverPartner != null && failoverServerInfo.ResolvedServerName != ServerProvidedFailOverPartner) { @@ -1817,18 +1890,19 @@ TimeoutTimer timeout intervalTimer, withFailover: true ); - int routingAttemps = 0; + + int routingAttempts = 0; while (RoutingInfo != null) { - if (routingAttemps > _maxNumberOfRedirectRoute) + if (routingAttempts > MaxNumberOfRedirectRoute) { - throw SQL.ROR_RecursiveRoutingNotSupported(this, _maxNumberOfRedirectRoute); + throw SQL.ROR_RecursiveRoutingNotSupported(this, MaxNumberOfRedirectRoute); } - routingAttemps++; + routingAttempts++; SqlClientEventSource.Log.TryTraceEvent(" Routed to {0}", RoutingInfo.ServerName); - if(_parser != null) + if (_parser != null) { _parser.Disconnect(); } @@ -1842,7 +1916,7 @@ TimeoutTimer timeout _originalClientConnectionId = _clientConnectionId; _routingDestination = currentServerInfo.UserServerName; - // restore properties that could be changed by the environemnt tokens + // restore properties that could be changed by the environment tokens _currentPacketSize = connectionOptions.PacketSize; _currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage; CurrentDatabase = _originalDatabase = connectionOptions.InitialCatalog; @@ -1856,6 +1930,7 @@ TimeoutTimer timeout intervalTimer, withFailover: true); } + break; // leave the while loop -- we've successfully connected } catch (SqlException sqlex) @@ -1966,12 +2041,11 @@ private void ResolveExtendedServerName(ServerInfo serverInfo, bool aliasLookup, } // Common code path for making one attempt to establish a connection and log in to server. - private void AttemptOneLogin( - ServerInfo serverInfo, - string newPassword, - SecureString newSecurePassword, - TimeoutTimer timeout, - bool withFailover = false) + private void AttemptOneLogin(ServerInfo serverInfo, + string newPassword, + SecureString newSecurePassword, + TimeoutTimer timeout, + bool withFailover = false) { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, timeout={1}[msec], server={2}", ObjectID, timeout.MillisecondsRemaining, serverInfo.ExtendedServerName); RoutingInfo = null; // forget routing information @@ -1998,7 +2072,15 @@ private void AttemptOneLogin( _timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PostLogin); } - +#if NETFRAMEWORK + internal void FailoverPermissionDemand() + { + if (PoolGroupProviderInfo != null) + { + PoolGroupProviderInfo.FailoverPermissionDemand(); + } + } +#endif //////////////////////////////////////////////////////////////////////////////////////// // PREPARED COMMAND METHODS @@ -2041,18 +2123,39 @@ internal bool GetSessionAndReconnectIfNeeded(SqlConnection parent, int timeout = try { - Task reconnectTask = parent.ValidateAndReconnect(() => +#if NETFRAMEWORK + RuntimeHelpers.PrepareConstrainedRegions(); +#endif + try { - ThreadHasParserLockForClose = false; - _parserLock.Release(); - releaseConnectionLock = false; - }, timeout); - if (reconnectTask != null) + Task reconnectTask = parent.ValidateAndReconnect(() => + { + ThreadHasParserLockForClose = false; + _parserLock.Release(); + releaseConnectionLock = false; + }, timeout); + if (reconnectTask != null) + { + AsyncHelper.WaitForCompletion(reconnectTask, timeout); + return true; + } + return false; + } + catch (System.OutOfMemoryException) { - AsyncHelper.WaitForCompletion(reconnectTask, timeout); - return true; + DoomThisConnection(); + throw; + } + catch (System.StackOverflowException) + { + DoomThisConnection(); + throw; + } + catch (System.Threading.ThreadAbortException) + { + DoomThisConnection(); + throw; } - return false; } finally { @@ -2070,7 +2173,7 @@ internal bool GetSessionAndReconnectIfNeeded(SqlConnection parent, int timeout = internal void BreakConnection() { - var connection = Connection; + SqlConnection connection = Connection; SqlClientEventSource.Log.TryTraceEvent(" {0}, Breaking connection.", ObjectID); DoomThisConnection(); // Mark connection as unusable, so it will be destroyed if (connection != null) @@ -2235,7 +2338,6 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) // We want to refresh the token, if taking the lock on the authentication context is successful. bool attemptRefreshTokenLocked = false; - if (_dbConnectionPool != null) { Debug.Assert(_dbConnectionPool.AuthenticationContexts != null); @@ -2291,9 +2393,9 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) // If the lock could not be obtained, it will return false, without attempting to fetch a new token. attemptRefreshTokenLocked = TryGetFedAuthTokenLocked(fedAuthInfo, dbConnectionPoolAuthenticationContext, out _fedAuthToken); - // If TryGetFedAuthTokenLocked returns true, it means lock was obtained and fedAuthToken should not be null. + // If TryGetFedAuthTokenLocked returns true, it means lock was obtained and _fedAuthToken should not be null. // If there was an exception in retrieving the new token, TryGetFedAuthTokenLocked should have thrown, so we won't be here. - Debug.Assert(!attemptRefreshTokenLocked || _fedAuthToken != null, "Either Lock should not have been obtained or fedAuthToken should not be null."); + Debug.Assert(!attemptRefreshTokenLocked || _fedAuthToken != null, "Either Lock should not have been obtained or _fedAuthToken should not be null."); Debug.Assert(!attemptRefreshTokenLocked || _newDbConnectionPoolAuthenticationContext != null, "Either Lock should not have been obtained or _newDbConnectionPoolAuthenticationContext should not be null."); // Indicate in EventSource Trace that we are successful with the update. @@ -2311,7 +2413,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) { // Get the Federated Authentication Token. _fedAuthToken = GetFedAuthToken(fedAuthInfo); - Debug.Assert(_fedAuthToken != null, "fedAuthToken should not be null."); + Debug.Assert(_fedAuthToken != null, "_fedAuthToken should not be null."); if (_dbConnectionPool != null) { @@ -2327,7 +2429,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) else if (!attemptRefreshTokenLocked) { Debug.Assert(dbConnectionPoolAuthenticationContext != null, "dbConnectionPoolAuthenticationContext should not be null."); - Debug.Assert(_fedAuthToken == null, "fedAuthToken should be null in this case."); + Debug.Assert(_fedAuthToken == null, "_fedAuthToken should be null in this case."); Debug.Assert(_newDbConnectionPoolAuthenticationContext == null, "_newDbConnectionPoolAuthenticationContext should be null."); _fedAuthToken = new SqlFedAuthToken(); @@ -2338,7 +2440,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) _fedAuthToken.expirationFileTime = dbConnectionPoolAuthenticationContext.ExpirationTime.ToFileTime(); } - Debug.Assert(_fedAuthToken != null && _fedAuthToken.accessToken != null, "fedAuthToken and fedAuthToken.accessToken cannot be null."); + Debug.Assert(_fedAuthToken != null && _fedAuthToken.accessToken != null, "_fedAuthToken and _fedAuthToken.accessToken cannot be null."); _parser.SendFedAuthToken(_fedAuthToken); } @@ -2423,7 +2525,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) // Username to use in error messages. string username = null; - var authProvider = SqlAuthenticationProvider.GetProvider(ConnectionOptions.Authentication); + SqlAuthenticationProvider authProvider = SqlAuthenticationProvider.GetProvider(ConnectionOptions.Authentication); if (authProvider == null && _accessTokenCallback == null) throw SQL.CannotFindAuthProvider(ConnectionOptions.Authentication.ToString()); @@ -2597,14 +2699,8 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) throw ADP.CreateSqlException(msalException, ConnectionOptions, this, username); } - SqlClientEventSource.Log.TryAdvancedTraceEvent( - " {0}, sleeping {1}[Milliseconds]", - ObjectID, - sleepInterval); - SqlClientEventSource.Log.TryAdvancedTraceEvent( - " {0}, remaining {1}[Milliseconds]", - ObjectID, - _timeout.MillisecondsRemaining); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, sleeping {1}[Milliseconds]", ObjectID, sleepInterval); + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, remaining {1}[Milliseconds]", ObjectID, _timeout.MillisecondsRemaining); Thread.Sleep(sleepInterval); sleepInterval *= 2; @@ -2645,7 +2741,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) internal void OnFeatureExtAck(int featureId, byte[] data) { - if (RoutingInfo != null && TdsEnums.FEATUREEXT_SQLDNSCACHING != featureId) + if (RoutingInfo != null && featureId != TdsEnums.FEATUREEXT_SQLDNSCACHING) { return; } @@ -2705,6 +2801,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data) case TdsEnums.FEATUREEXT_GLOBALTRANSACTIONS: { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for GlobalTransactions", ObjectID); + if (data.Length < 1) { SqlClientEventSource.Log.TryTraceEvent(" {0}, Unknown version number for GlobalTransactions", ObjectID); @@ -2718,9 +2815,11 @@ internal void OnFeatureExtAck(int featureId, byte[] data) } break; } + case TdsEnums.FEATUREEXT_FEDAUTH: { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for federated authentication", ObjectID); + if (!_federatedAuthenticationRequested) { SqlClientEventSource.Log.TryTraceEvent(" {0}, Did not request federated authentication", ObjectID); @@ -2743,6 +2842,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data) default: Debug.Fail("Unknown _fedAuthLibrary type"); + SqlClientEventSource.Log.TryTraceEvent(" {0}, Attempting to use unknown federated authentication library", ObjectID); throw SQL.ParsingErrorLibraryType(ParsingErrorState.FedAuthFeatureAckUnknownLibraryType, (int)_fedAuthFeatureExtensionData.libraryType); } @@ -2770,7 +2870,6 @@ internal void OnFeatureExtAck(int featureId, byte[] data) } #endif } - break; } case TdsEnums.FEATUREEXT_TCE: @@ -2802,6 +2901,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data) } break; } + case TdsEnums.FEATUREEXT_AZURESQLSUPPORT: { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for AzureSQLSupport", ObjectID); @@ -2817,10 +2917,10 @@ internal void OnFeatureExtAck(int featureId, byte[] data) if ((data[0] & 1) == 1 && SqlClientEventSource.Log.IsTraceEnabled()) { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, FailoverPartner enabled with Readonly intent for AzureSQL DB", ObjectID); - } break; } + case TdsEnums.FEATUREEXT_DATACLASSIFICATION: { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for DATACLASSIFICATION", ObjectID); @@ -2828,7 +2928,6 @@ internal void OnFeatureExtAck(int featureId, byte[] data) if (data.Length < 1) { SqlClientEventSource.Log.TryTraceEvent(" {0}, Unknown token for DATACLASSIFICATION", ObjectID); - throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); } byte supportedDataClassificationVersion = data[0]; @@ -2841,13 +2940,13 @@ internal void OnFeatureExtAck(int featureId, byte[] data) if (data.Length != 2) { SqlClientEventSource.Log.TryTraceEvent(" {0}, Unknown token for DATACLASSIFICATION", ObjectID); - throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); } byte enabled = data[1]; _parser.DataClassificationVersion = (enabled == 0) ? TdsEnums.DATA_CLASSIFICATION_NOT_ENABLED : supportedDataClassificationVersion; break; } + case TdsEnums.FEATUREEXT_UTF8SUPPORT: { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for UTF8 support", ObjectID); @@ -2855,7 +2954,6 @@ internal void OnFeatureExtAck(int featureId, byte[] data) if (data.Length < 1) { SqlClientEventSource.Log.TryTraceEvent(" {0}, Unknown value for UTF8 support", ObjectID); - throw SQL.ParsingError(); } break; @@ -2892,7 +2990,6 @@ internal void OnFeatureExtAck(int featureId, byte[] data) // get IPv4 + IPv6 + Port number // not put them in the DNS cache at this point but need to store them somewhere // generate pendingSQLDNSObject and turn on IsSQLDNSRetryEnabled flag - break; } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs index d3402a48bd..12a6378ddc 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlConnectionFactory.cs @@ -145,7 +145,7 @@ override protected DbConnectionInternal CreateConnection(DbConnectionOptions opt opt = new SqlConnectionString(opt, instanceName, false /* user instance=false */, null /* do not modify the Enlist value */); poolGroupProviderInfo = null; // null so we do not pass to constructor below... } - result = new SqlInternalConnectionTds(identity, opt, key.Credential, poolGroupProviderInfo, "", null, redirectedUserInstance, userOpt, recoverySessionData, pool, key.AccessToken, applyTransientFaultHandling: applyTransientFaultHandling, key.AccessTokenCallback); + result = new SqlInternalConnectionTds(identity, opt, key.Credential, poolGroupProviderInfo, "", null, redirectedUserInstance, userOpt, recoverySessionData, applyTransientFaultHandling, key.AccessToken, pool, key.AccessTokenCallback); } return result; } diff --git a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs index 8079231f61..5986736403 100644 --- a/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs +++ b/src/Microsoft.Data.SqlClient/netfx/src/Microsoft/Data/SqlClient/SqlInternalConnectionTds.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; -using System.Data; using System.Data.Common; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -15,27 +14,26 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Transactions; using Microsoft.Data.Common; using Microsoft.Data.ProviderBase; using Microsoft.Data.SqlClient.ConnectionPool; using Microsoft.Identity.Client; -using System.Transactions; - namespace Microsoft.Data.SqlClient { - internal class SessionStateRecord + internal sealed class SessionStateRecord { internal bool _recoverable; - internal UInt32 _version; - internal Int32 _dataLength; + internal uint _version; + internal int _dataLength; internal byte[] _data; } - internal class SessionData + internal sealed class SessionData { internal const int _maxNumberOfSessionStates = 256; - internal UInt32 _tdsVersion; + internal uint _tdsVersion; internal bool _encrypted; internal string _database; @@ -104,17 +102,17 @@ public void AssertUnrecoverableStateCountIsCorrect() } } - sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposable + internal sealed class SqlInternalConnectionTds : SqlInternalConnection, IDisposable { // https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/retry-after#simple-retry-for-errors-with-http-error-codes-500-600 internal const int MsalHttpRetryStatusCode = 429; - // Connection re-route limit - internal const int _maxNumberOfRedirectRoute = 10; + internal const int MaxNumberOfRedirectRoute = 10; // CONNECTION AND STATE VARIABLES private readonly SqlConnectionPoolGroupProviderInfo _poolGroupProviderInfo; // will only be null when called for ChangePassword, or creating SSE User Instance private TdsParser _parser; + private SqlLoginAck _loginAck; private SqlCredential _credential; private FederatedAuthenticationFeatureExtensionData _fedAuthFeatureExtensionData; @@ -128,7 +126,6 @@ sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposa // Federated Authentication // Response obtained from the server for FEDAUTHREQUIRED prelogin option. internal bool _fedAuthRequired; - internal bool _federatedAuthenticationRequested; internal bool _federatedAuthenticationAcknowledged; internal bool _federatedAuthenticationInfoRequested; // Keep this distinct from _federatedAuthenticationRequested, since some fedauth library types may not need more info @@ -137,7 +134,7 @@ sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposa // The Federated Authentication returned by TryGetFedAuthTokenLocked or GetFedAuthToken. SqlFedAuthToken _fedAuthToken = null; internal byte[] _accessTokenInBytes; - internal readonly Func> _accessTokenCallback; + internal readonly Func> _accessTokenCallback; private readonly ActiveDirectoryAuthenticationTimeoutRetryHelper _activeDirectoryAuthTimeoutRetryHelper; @@ -157,7 +154,7 @@ private int accessTokenExpirationBufferTime } /// - /// Get or set if SQLDNSCaching FeatureExtAck is supported by the server. + /// Get or set if SQLDNSCaching is supported by the server. /// internal bool IsSQLDNSCachingSupported { @@ -188,20 +185,20 @@ internal bool IsSQLDNSRetryEnabled } } - private bool DNSCachingBeforeRedirect = false; + private bool _dnsCachingBeforeRedirect = false; /// - /// Get or set if the control ring send redirect token and SQLDNSCaching FeatureExtAck with true + /// Get or set if the control ring send redirect token and feature ext ack with true for DNSCaching /// internal bool IsDNSCachingBeforeRedirectSupported { get { - return DNSCachingBeforeRedirect; + return _dnsCachingBeforeRedirect; } set { - DNSCachingBeforeRedirect = value; + _dnsCachingBeforeRedirect = value; } } @@ -253,9 +250,54 @@ internal bool IsDNSCachingBeforeRedirectSupported // If the context is expiring within the next 10 mins, then create a new context, irrespective of if another thread is trying to do the same. private static readonly TimeSpan _dbAuthenticationContextUnLockedRefreshTimeSpan = new TimeSpan(hours: 0, minutes: 10, seconds: 00); - private readonly TimeoutTimer _timeout; + // The errors in the transient error set are contained in + // https://azure.microsoft.com/en-us/documentation/articles/sql-database-develop-error-messages/#transient-faults-connection-loss-and-other-temporary-errors + private static readonly HashSet s_transientErrors = new HashSet + { + // SQL Error Code: 4060 + // Cannot open database "%.*ls" requested by the login. The login failed. + 4060, + + // SQL Error Code: 10928 + // Resource ID: %d. The %s limit for the database is %d and has been reached. + 10928, + + // SQL Error Code: 10929 + // Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d. + // However, the server is currently too busy to support requests greater than %d for this database. + 10929, + + // SQL Error Code: 40197 + // You will receive this error, when the service is down due to software or hardware upgrades, hardware failures, + // or any other failover problems. The error code (%d) embedded within the message of error 40197 provides + // additional information about the kind of failure or failover that occurred. Some examples of the error codes are + // embedded within the message of error 40197 are 40020, 40143, 40166, and 40540. + 40197, + 40020, + 40143, + 40166, - private static HashSet transientErrors = new HashSet(); + // The service has encountered an error processing your request. Please try again. + 40540, + + // The service is currently busy. Retry the request after 10 seconds. Incident ID: %ls. Code: %d. + 40501, + + // Database '%.*ls' on server '%.*ls' is not currently available. Please retry the connection later. + // If the problem persists, contact customer support, and provide them the session tracing ID of '%.*ls'. + 40613, + + // Can not connect to the SQL pool since it is paused. Please resume the SQL pool and try again. + 42108, + + // The SQL pool is warming up. Please try again. + 42109 + + // Do federation errors deserve to be here ? + // Note: Federation errors 10053 and 10054 might also deserve inclusion in your retry logic. + // 10053 + // 10054 + }; internal SessionData CurrentSessionData { @@ -294,28 +336,28 @@ internal SessionData CurrentSessionData // 2. _parserLock will also be taken during close (to prevent closing in the middle of a write) // 3. Whenever you have the _parserLock and are calling a method that would cause the connection to close if it failed (with the exception of any writing method), you MUST set ThreadHasParserLockForClose to true // * This is to prevent the connection deadlocking with itself (since you already have the _parserLock, and Closing the connection will attempt to re-take that lock) - // * It is safe to set ThreadHasParserLockForClose to true when writing as well, but it is unneccesary + // * It is safe to set ThreadHasParserLockForClose to true when writing as well, but it is unnecessary // * If you have a method that takes _parserLock, it is a good idea check ThreadHasParserLockForClose first (if you don't expect _parserLock to be taken by something higher on the stack, then you should at least assert that it is false) // 4. ThreadHasParserLockForClose is thread-specific - this means that you must set it to false before returning a Task, and set it back to true in the continuation // 5. ThreadHasParserLockForClose should only be modified if you currently own the _parserLock // 6. Reading ThreadHasParserLockForClose is thread-safe internal class SyncAsyncLock { - SemaphoreSlim semaphore = new SemaphoreSlim(1); + private SemaphoreSlim _semaphore = new SemaphoreSlim(1); internal void Wait(bool canReleaseFromAnyThread) { - Monitor.Enter(semaphore); // semaphore is used as lock object, no relation to SemaphoreSlim.Wait/Release methods - if (canReleaseFromAnyThread || semaphore.CurrentCount == 0) + Monitor.Enter(_semaphore); // semaphore is used as lock object, no relation to SemaphoreSlim.Wait/Release methods + if (canReleaseFromAnyThread || _semaphore.CurrentCount == 0) { - semaphore.Wait(); + _semaphore.Wait(); if (canReleaseFromAnyThread) { - Monitor.Exit(semaphore); + Monitor.Exit(_semaphore); } else { - semaphore.Release(); + _semaphore.Release(); } } } @@ -326,21 +368,21 @@ internal void Wait(bool canReleaseFromAnyThread, int timeout, ref bool lockTaken bool hasMonitor = false; try { - Monitor.TryEnter(semaphore, timeout, ref hasMonitor); // semaphore is used as lock object, no relation to SemaphoreSlim.Wait/Release methods + Monitor.TryEnter(_semaphore, timeout, ref hasMonitor); // semaphore is used as lock object, no relation to SemaphoreSlim.Wait/Release methods if (hasMonitor) { - if ((canReleaseFromAnyThread) || (semaphore.CurrentCount == 0)) + if ((canReleaseFromAnyThread) || (_semaphore.CurrentCount == 0)) { - if (semaphore.Wait(timeout)) + if (_semaphore.Wait(timeout)) { if (canReleaseFromAnyThread) { - Monitor.Exit(semaphore); + Monitor.Exit(_semaphore); hasMonitor = false; } else { - semaphore.Release(); + _semaphore.Release(); } lockTaken = true; } @@ -355,20 +397,20 @@ internal void Wait(bool canReleaseFromAnyThread, int timeout, ref bool lockTaken { if ((!lockTaken) && (hasMonitor)) { - Monitor.Exit(semaphore); + Monitor.Exit(_semaphore); } } } internal void Release() { - if (semaphore.CurrentCount == 0) + if (_semaphore.CurrentCount == 0) { // semaphore methods were used for locking - semaphore.Release(); + _semaphore.Release(); } else { - Monitor.Exit(semaphore); + Monitor.Exit(_semaphore); } } @@ -377,14 +419,14 @@ internal bool CanBeReleasedFromAnyThread { get { - return semaphore.CurrentCount == 0; + return _semaphore.CurrentCount == 0; } } - // Necessary but not sufficient condition for thread to have lock (since sempahore may be obtained by any thread) + // Necessary but not sufficient condition for thread to have lock (since semaphore may be obtained by any thread) internal bool ThreadMayHaveLock() { - return Monitor.IsEntered(semaphore) || semaphore.CurrentCount == 0; + return Monitor.IsEntered(_semaphore) || _semaphore.CurrentCount == 0; } } @@ -400,20 +442,14 @@ internal SqlConnectionTimeoutErrorInternal TimeoutErrorInternal } // OTHER STATE VARIABLES AND REFERENCES - internal Guid _clientConnectionId = Guid.Empty; // Routing information (ROR) - RoutingInfo _routingInfo = null; private Guid _originalClientConnectionId = Guid.Empty; private string _routingDestination = null; + private readonly TimeoutTimer _timeout; - static SqlInternalConnectionTds() - { - populateTransientErrors(); - } - - // although the new password is generally not used it must be passed to the c'tor + // although the new password is generally not used it must be passed to the ctor // the new Login7 packet will always write out the new password (or a length of zero and no bytes if not present) // internal SqlInternalConnectionTds( @@ -426,20 +462,20 @@ internal SqlInternalConnectionTds( bool redirectedUserInstance, SqlConnectionString userConnectionOptions = null, // NOTE: userConnectionOptions may be different to connectionOptions if the connection string has been expanded (see SqlConnectionString.Expand) SessionData reconnectSessionData = null, - DbConnectionPool pool = null, - string accessToken = null, bool applyTransientFaultHandling = false, + string accessToken = null, + DbConnectionPool pool = null, Func> accessTokenCallback = null) : base(connectionOptions) { - #if DEBUG if (reconnectSessionData != null) { reconnectSessionData._debugReconnectDataApplied = true; } try - { // use this to help validate this object is only created after the following permission has been previously demanded in the current codepath + { + // use this to help validate this object is only created after the following permission has been previously demanded in the current codepath if (userConnectionOptions != null) { // As mentioned above, userConnectionOptions may be different to connectionOptions, so we need to demand on the correct connection string @@ -509,12 +545,13 @@ internal SqlInternalConnectionTds( _parserLock.Wait(canReleaseFromAnyThread: false); ThreadHasParserLockForClose = true; // In case of error, let ourselves know that we already own the parser lock + RuntimeHelpers.PrepareConstrainedRegions(); try { _timeout = TimeoutTimer.StartSecondsTimeout(connectionOptions.ConnectTimeout); - // If transient fault handling is enabled then we can retry the login upto the ConnectRetryCount. + // If transient fault handling is enabled then we can retry the login up to the ConnectRetryCount. int connectionEstablishCount = applyTransientFaultHandling ? connectionOptions.ConnectRetryCount + 1 : 1; int transientRetryIntervalInMilliSeconds = connectionOptions.ConnectRetryInterval * 1000; // Max value of transientRetryInterval is 60*1000 ms. The max value allowed for ConnectRetryInterval is 60 for (int i = 0; i < connectionEstablishCount; i++) @@ -564,49 +601,6 @@ internal SqlInternalConnectionTds( SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, constructed new TDS internal connection", ObjectID); } - // The errors in the transient error set are contained in - // https://azure.microsoft.com/en-us/documentation/articles/sql-database-develop-error-messages/#transient-faults-connection-loss-and-other-temporary-errors - private static void populateTransientErrors() - { - // SQL Error Code: 4060 - // Cannot open database "%.*ls" requested by the login. The login failed. - transientErrors.Add(4060); - // SQL Error Code: 10928 - // Resource ID: %d. The %s limit for the database is %d and has been reached. - transientErrors.Add(10928); - // SQL Error Code: 10929 - // Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d. - // However, the server is currently too busy to support requests greater than %d for this database. - transientErrors.Add(10929); - // SQL Error Code: 40197 - // You will receive this error, when the service is down due to software or hardware upgrades, hardware failures, - // or any other failover problems. The error code (%d) embedded within the message of error 40197 provides - // additional information about the kind of failure or failover that occurred. Some examples of the error codes are - // embedded within the message of error 40197 are 40020, 40143, 40166, and 40540. - transientErrors.Add(40197); - transientErrors.Add(40020); - transientErrors.Add(40143); - transientErrors.Add(40166); - // The service has encountered an error processing your request. Please try again. - transientErrors.Add(40540); - // The service is currently busy. Retry the request after 10 seconds. Incident ID: %ls. Code: %d. - transientErrors.Add(40501); - // Database '%.*ls' on server '%.*ls' is not currently available. Please retry the connection later. - // If the problem persists, contact customer support, and provide them the session tracing ID of '%.*ls'. - transientErrors.Add(40613); - - // Can not connect to the SQL pool since it is paused. Please resume the SQL pool and try again. - transientErrors.Add(42108); - - // The SQL pool is warming up. Please try again. - transientErrors.Add(42109); - // Do federation errors deserve to be here ? - // Note: Federation errors 10053 and 10054 might also deserve inclusion in your retry logic. - //transientErrors.Add(10053); - //transientErrors.Add(10054); - } - - // Returns true if the Sql error is a transient. private bool IsTransientError(SqlException exc) { @@ -616,7 +610,7 @@ private bool IsTransientError(SqlException exc) } foreach (SqlError error in exc.Errors) { - if (transientErrors.Contains(error.Number)) + if (s_transientErrors.Contains(error.Number)) { // When server timeouts, connection is doomed. Reset here to allow reconnect. UnDoomThisConnection(); @@ -650,15 +644,9 @@ internal string RoutingDestination } } - internal RoutingInfo RoutingInfo - { - get - { - return _routingInfo; - } - } + internal RoutingInfo RoutingInfo { get; private set; } = null; - override internal SqlInternalTransaction CurrentTransaction + internal override SqlInternalTransaction CurrentTransaction { get { @@ -666,7 +654,7 @@ override internal SqlInternalTransaction CurrentTransaction } } - override internal SqlInternalTransaction AvailableInternalTransaction + internal override SqlInternalTransaction AvailableInternalTransaction { get { @@ -674,8 +662,7 @@ override internal SqlInternalTransaction AvailableInternalTransaction } } - - override internal SqlInternalTransaction PendingTransaction + internal override SqlInternalTransaction PendingTransaction { get { @@ -699,7 +686,7 @@ internal string InstanceName } } - override internal bool IsLockedForBulkCopy + internal override bool IsLockedForBulkCopy { get { @@ -707,7 +694,7 @@ override internal bool IsLockedForBulkCopy } } - override protected internal bool IsNonPoolableTransactionRoot + internal protected override bool IsNonPoolableTransactionRoot { get { @@ -715,7 +702,7 @@ override protected internal bool IsNonPoolableTransactionRoot } } - override internal bool Is2008OrNewer + internal override bool Is2008OrNewer { get { @@ -755,7 +742,7 @@ internal SqlConnectionPoolGroupProviderInfo PoolGroupProviderInfo } } - override protected bool ReadyToPrepareTransaction + protected override bool ReadyToPrepareTransaction { get { @@ -765,7 +752,7 @@ override protected bool ReadyToPrepareTransaction } } - override public string ServerVersion + public override string ServerVersion { get { @@ -773,6 +760,7 @@ override public string ServerVersion (short)_loginAck.minorVersion, _loginAck.buildNum)); } } + public int ServerProcessId { get @@ -808,16 +796,16 @@ protected override bool UnbindOnTransactionCompletion // GENERAL METHODS //////////////////////////////////////////////////////////////////////////////////////// [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] // copied from Triaged.cs - override protected void ChangeDatabaseInternal(string database) + protected override void ChangeDatabaseInternal(string database) { // MDAC 73598 - add brackets around database database = SqlConnection.FixupDatabaseTransactionName(database); - System.Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch("use " + database, ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj, sync: true); + Task executeTask = _parser.TdsExecuteSQLBatch("use " + database, ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj, sync: true); Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes"); _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj); } - override public void Dispose() + public override void Dispose() { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0} disposing", ObjectID); try @@ -831,7 +819,7 @@ override public void Dispose() } } finally - { // UNDONE: MDAC 77928 + { // close will always close, even if exception is thrown // remember to null out any object references _loginAck = null; @@ -840,7 +828,7 @@ override public void Dispose() base.Dispose(); } - override internal void ValidateConnectionForExecute(SqlCommand command) + internal override void ValidateConnectionForExecute(SqlCommand command) { TdsParser parser = _parser; if ((parser == null) || (parser.State == TdsParserState.Broken) || (parser.State == TdsParserState.Closed)) @@ -901,7 +889,7 @@ override internal void ValidateConnectionForExecute(SqlCommand command) /// /// When using Implicit transaction unbinding, /// verify that the enlisted transaction is active. - /// If it is not active, and the transaction object has been diposed, unbind from the transaction. + /// If it is not active, and the transaction object has been disposed, unbind from the transaction. /// If it is not active and not disposed, throw an exception. /// /// @@ -948,7 +936,7 @@ internal override bool IsConnectionAlive(bool throwOnException) => // POOLING METHODS //////////////////////////////////////////////////////////////////////////////////////// - override protected void Activate(Transaction transaction) + protected override void Activate(Transaction transaction) { FailoverPermissionDemand(); // Demand for unspecified failover pooled connections @@ -973,7 +961,7 @@ override protected void Activate(Transaction transaction) } } - override protected void InternalDeactivate() + protected override void InternalDeactivate() { // When we're deactivated, the user must have called End on all // the async commands, or we don't know that we're in a state that @@ -994,7 +982,6 @@ override protected void InternalDeactivate() Debug.Assert(_parser != null || IsConnectionDoomed, "Deactivating a disposed connection?"); if (_parser != null) { - _parser.Deactivate(IsConnectionDoomed); if (!IsConnectionDoomed) @@ -1010,7 +997,7 @@ private void ResetConnection() { // For implicit pooled connections, if connection reset behavior is specified, // reset the database and language properties back to default. It is important - // to do this on activate so that the hashtable is correct before SqlConnection + // to do this on activate so that the dictionary is correct before SqlConnection // obtains a clone. Debug.Assert(!HasLocalTransactionFromAPI, "Upon ResetConnection SqlInternalConnectionTds has a currently ongoing local transaction."); @@ -1024,7 +1011,7 @@ private void ResetConnection() // https://github.com/dotnet/SqlClient/issues/2970 _parser.PrepareResetConnection(EnlistedTransaction is not null && Pool is not null); - // Reset hashtable values, since calling reset will not send us env_changes. + // Reset dictionary values, since calling reset will not send us env_changes. CurrentDatabase = _originalDatabase; _currentLanguage = _originalLanguage; } @@ -1046,7 +1033,7 @@ internal void IncrementAsyncCount() // LOCAL TRANSACTION METHODS //////////////////////////////////////////////////////////////////////////////////////// - override internal void DisconnectTransaction(SqlInternalTransaction internalTransaction) + internal override void DisconnectTransaction(SqlInternalTransaction internalTransaction) { TdsParser parser = Parser; @@ -1061,7 +1048,7 @@ internal void ExecuteTransaction(TransactionRequest transactionRequest, string n ExecuteTransaction(transactionRequest, name, iso, null, false); } - override internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, System.Data.IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest) + internal override void ExecuteTransaction(TransactionRequest transactionRequest, string name, System.Data.IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest) { if (IsConnectionDoomed) { // doomed means we can't do anything else... @@ -1182,19 +1169,17 @@ internal void ExecuteTransaction2005( } } - - // SQLBUDT #20010853 - Promote, Commit and Rollback requests for // delegated transactions often happen while there is an open result // set, so we need to handle them by using a different MARS session, // otherwise we'll write on the physical state objects while someone // else is using it. When we don't have MARS enabled, we need to - // lock the physical state object to syncronize it's use at least + // lock the physical state object to synchronize it's use at least // until we increment the open results count. Once it's been // incremented the delegated transaction requests will fail, so they // won't stomp on anything. // - // We need to keep this lock through the duration of the TM reqeuest + // We need to keep this lock through the duration of the TM request // so that we won't hijack a different request's data stream and a // different request won't hijack ours, so we have a lock here on // an object that the ExecTMReq will also lock, but since we're on @@ -1217,7 +1202,7 @@ internal void ExecuteTransaction2005( } } - // SQLBU #406778 - _parser may be nulled out during TdsExecuteTrannsactionManagerRequest. + // SQLBU #406778 - _parser may be nulled out during TdsExecuteTransactionManagerRequest. // Only use local variable after this call. _parser.TdsExecuteTransactionManagerRequest(null, requestType, transactionName, isoLevel, ConnectionOptions.ConnectTimeout, internalTransaction, stateObj, isDelegateControlRequest); @@ -1241,20 +1226,20 @@ internal void ExecuteTransaction2005( // DISTRIBUTED TRANSACTION METHODS //////////////////////////////////////////////////////////////////////////////////////// - override internal void DelegatedTransactionEnded() + internal override void DelegatedTransactionEnded() { // TODO: I don't like the way that this works, but I don't want to rototill the entire pooler to avoid this call. base.DelegatedTransactionEnded(); } - override protected byte[] GetDTCAddress() + protected override byte[] GetDTCAddress() { byte[] dtcAddress = _parser.GetDTCAddress(ConnectionOptions.ConnectTimeout, _parser.GetSession(this)); Debug.Assert(dtcAddress != null, "null dtcAddress?"); return dtcAddress; } - override protected void PropagateTransactionCookie(byte[] cookie) + protected override void PropagateTransactionCookie(byte[] cookie) { _parser.PropagateDistributedTransaction(cookie, ConnectionOptions.ConnectTimeout, _parser._physicalStateObj); } @@ -1267,7 +1252,7 @@ private void CompleteLogin(bool enlistOK) { _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj); - if (_routingInfo == null) + if (RoutingInfo == null) { // ROR should not affect state of connection recovery if (_federatedAuthenticationRequested && !_federatedAuthenticationAcknowledged) @@ -1319,12 +1304,13 @@ private void CompleteLogin(bool enlistOK) // for non-pooled connections, enlist in a distributed transaction // if present - and user specified to enlist - if (enlistOK && ConnectionOptions.Enlist && _routingInfo == null) + if (enlistOK && ConnectionOptions.Enlist && RoutingInfo == null) { _parser._physicalStateObj.SniContext = SniContext.Snix_AutoEnlist; Transaction tx = ADP.GetCurrentTransaction(); Enlist(tx); } + _parser._physicalStateObj.SniContext = SniContext.Snix_Login; } @@ -1369,9 +1355,9 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, login.language = _currentLanguage; if (!login.userInstance) - { // Do not send attachdbfilename or database to SSE primary instance + { + // Do not send attachdbfilename or database to SSE primary instance login.database = CurrentDatabase; - ; login.attachDBFilename = ConnectionOptions.AttachDBFilename; } @@ -1404,8 +1390,8 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, // in Login7, indicating the intent to use Active Directory Authentication for SQL Server. if (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryPassword || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive - || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow + || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault @@ -1438,10 +1424,8 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, _federatedAuthenticationRequested = true; } - // The TCE, DATACLASSIFICATION and GLOBALTRANSACTIONS, UTF8 support feature are implicitly requested - requestedFeatures |= TdsEnums.FeatureExtension.Tce | TdsEnums.FeatureExtension.DataClassification | TdsEnums.FeatureExtension.GlobalTransactions; - - requestedFeatures |= TdsEnums.FeatureExtension.UTF8Support; + // The GLOBALTRANSACTIONS, DATACLASSIFICATION, TCE, and UTF8 support features are implicitly requested + requestedFeatures |= TdsEnums.FeatureExtension.GlobalTransactions | TdsEnums.FeatureExtension.DataClassification | TdsEnums.FeatureExtension.Tce | TdsEnums.FeatureExtension.UTF8Support; // The AzureSQLSupport feature is implicitly set for ReadOnly login if (ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly) @@ -1449,9 +1433,8 @@ private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, requestedFeatures |= TdsEnums.FeatureExtension.AzureSQLSupport; } - // The SQLDNSCaching feature is implicitly set + // The SQLDNSCaching and JSON features are implicitly set requestedFeatures |= TdsEnums.FeatureExtension.SQLDNSCaching; - requestedFeatures |= TdsEnums.FeatureExtension.JsonSupport; _parser.TdsLogin(login, requestedFeatures, _recoverySessionData, _fedAuthFeatureExtensionData, encrypt); @@ -1467,14 +1450,16 @@ private void LoginFailure() // no-op, so no issues there. if (_parser != null) { - _parser.Disconnect(); } - // TODO: Need a performance counter for Failed Connections } - private void OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectionOptions, SqlCredential credential, - string newPassword, SecureString newSecurePassword, bool redirectedUserInstance) + private void OpenLoginEnlist(TimeoutTimer timeout, + SqlConnectionString connectionOptions, + SqlCredential credential, + string newPassword, + SecureString newSecurePassword, + bool redirectedUserInstance) { bool useFailoverPartner; // should we use primary or secondary first ServerInfo dataSource = new ServerInfo(connectionOptions); @@ -1517,7 +1502,8 @@ private void OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectio else { _timeoutErrorInternal.SetFailoverScenario(false); // not a failover scenario - LoginNoFailover(dataSource, + LoginNoFailover( + dataSource, newPassword, newSecurePassword, redirectedUserInstance, @@ -1546,7 +1532,6 @@ private void OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectio } catch (Exception e) { - // UNDONE - should not be catching all exceptions!!! if (ADP.IsCatchableExceptionType(e)) { LoginFailure(); @@ -1564,11 +1549,10 @@ private void OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectio // to connect. private bool IsDoNotRetryConnectError(SqlException exc) { - return (TdsEnums.LOGON_FAILED == exc.Number) // actual logon failed, i.e. bad password || (TdsEnums.PASSWORD_EXPIRED == exc.Number) // actual logon failed, i.e. password isExpired - || (TdsEnums.IMPERSONATION_FAILED == exc.Number) // Insuficient privelege for named pipe, among others - || exc._doNotReconnect; // Exception explicitly supressed reconnection attempts + || (TdsEnums.IMPERSONATION_FAILED == exc.Number) // Insufficient privilege for named pipe, among others + || exc._doNotReconnect; // Exception explicitly suppressed reconnection attempts } // Attempt to login to a host that does not have a failover partner @@ -1580,10 +1564,14 @@ private bool IsDoNotRetryConnectError(SqlException exc) // DEVNOTE: The logic in this method is paralleled by the logic in LoginWithFailover. // Changes to either one should be examined to see if they need to be reflected in the other // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureString newSecurePassword, bool redirectedUserInstance, - SqlConnectionString connectionOptions, SqlCredential credential, TimeoutTimer timeout) + private void LoginNoFailover(ServerInfo serverInfo, + string newPassword, + SecureString newSecurePassword, + bool redirectedUserInstance, + SqlConnectionString connectionOptions, + SqlCredential credential, + TimeoutTimer timeout) { - Debug.Assert(object.ReferenceEquals(connectionOptions, this.ConnectionOptions), "ConnectionOptions argument and property must be the same"); // consider removing the argument int routingAttempts = 0; ServerInfo originalServerInfo = serverInfo; // serverInfo may end up pointing to new object due to routing, original object is used to set CurrentDatasource @@ -1594,16 +1582,16 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt ResolveExtendedServerName(serverInfo, !redirectedUserInstance, connectionOptions); - Boolean disableTnir = ShouldDisableTnir(connectionOptions); + bool disableTnir = ShouldDisableTnir(connectionOptions); long timeoutUnitInterval = 0; - Boolean isParallel = connectionOptions.MultiSubnetFailover || (connectionOptions.TransparentNetworkIPResolution && !disableTnir); - + bool isParallel = connectionOptions.MultiSubnetFailover || (connectionOptions.TransparentNetworkIPResolution && !disableTnir); if (isParallel) { float failoverTimeoutStep = connectionOptions.MultiSubnetFailover ? ADP.FailoverTimeoutStep : ADP.FailoverTimeoutStepForTnir; + // Determine unit interval if (timeout.IsInfinite) { @@ -1625,11 +1613,10 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt // back into the parser for the error cases. int attemptNumber = 0; TimeoutTimer intervalTimer = null; - TimeoutTimer attemptOneLoginTimeout = timeout; + while (true) { - Boolean isFirstTransparentAttempt = connectionOptions.TransparentNetworkIPResolution && !disableTnir && attemptNumber == 1; if (isParallel) @@ -1665,11 +1652,11 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt _parser.Disconnect(); _parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous); - Debug.Assert(SniContext.Undefined == Parser._physicalStateObj.SniContext, string.Format((IFormatProvider)null, "SniContext should be Undefined; actual Value: {0}", Parser._physicalStateObj.SniContext)); + Debug.Assert(SniContext.Undefined == Parser._physicalStateObj.SniContext, $"SniContext should be Undefined; actual Value: {Parser._physicalStateObj.SniContext}"); try { - // UNDONE: ITEM12001110 (DB Mirroring Reconnect) Old behavior of not truly honoring timeout presevered + // UNDONE: ITEM12001110 (DB Mirroring Reconnect) Old behavior of not truly honoring timeout preserved // for non-failover, non-MSF scenarios to avoid breaking changes as part of a QFE. Consider fixing timeout // handling in next full release and removing ignoreSniOpenTimeout parameter. @@ -1687,17 +1674,16 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt if (connectionOptions.MultiSubnetFailover && ServerProvidedFailOverPartner != null) { - // connection succeeded: trigger exception if server sends failover partner and MultiSubnetFailover is used. + // connection succeeded: trigger exception if server sends failover partner and MultiSubnetFailover is used throw SQL.MultiSubnetFailoverWithFailoverPartner(serverProvidedFailoverPartner: true, internalConnection: this); } - if (_routingInfo != null) + if (RoutingInfo != null) { SqlClientEventSource.Log.TryTraceEvent(" Routed to {0}", serverInfo.ExtendedServerName); - - if (routingAttempts > _maxNumberOfRedirectRoute) + if (routingAttempts > MaxNumberOfRedirectRoute) { - throw SQL.ROR_RecursiveRoutingNotSupported(this, _maxNumberOfRedirectRoute); + throw SQL.ROR_RecursiveRoutingNotSupported(this, MaxNumberOfRedirectRoute); } if (timeout.IsExpired) @@ -1705,7 +1691,7 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt throw SQL.ROR_TimeoutAfterRoutingInfo(this); } - serverInfo = new ServerInfo(ConnectionOptions, _routingInfo, serverInfo.ResolvedServerName, serverInfo.ServerSPN); + serverInfo = new ServerInfo(ConnectionOptions, RoutingInfo, serverInfo.ResolvedServerName, serverInfo.ServerSPN); _timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.RoutingDestination); _originalClientConnectionId = _clientConnectionId; _routingDestination = serverInfo.UserServerName; @@ -1737,8 +1723,9 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt || TdsParserState.Closed != _parser.State || IsDoNotRetryConnectError(sqlex) || timeout.IsExpired) - { // no more time to try again - throw; // Caller will call LoginFailure() + { + // no more time to try again + throw; // Caller will call LoginFailure() } // Check sleep interval to make sure we won't exceed the timeout @@ -1747,8 +1734,6 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt { throw; } - - // TODO: Stash parser away somewhere so we can examine it's state during debugging } // We only get here when we failed to connect, but are going to re-try @@ -1783,7 +1768,6 @@ private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureSt // Sleep for a bit to prevent clogging the network with requests, // then update sleep interval for next iteration (max 1 second interval) SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, sleeping {1}[milisec]", ObjectID, sleepInterval); - Thread.Sleep(sleepInterval); sleepInterval = (sleepInterval < 500) ? sleepInterval * 2 : 1000; } @@ -1868,7 +1852,6 @@ private void LoginWithFailover( TimeoutTimer timeout ) { - Debug.Assert(!connectionOptions.MultiSubnetFailover, "MultiSubnetFailover should not be set if failover partner is used"); SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, useFailover={1}[bool], primary={2}, failover={3}", ObjectID, useFailoverHost, primaryServerInfo.UserServerName, failoverHost); @@ -1880,8 +1863,7 @@ TimeoutTimer timeout ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, connectionOptions); if (ServerProvidedFailOverPartner == null) - {// No point in resolving the failover partner when we're going to override it below - // Don't resolve aliases if failover == primary // UNDONE: WHY? Previous code in TdsParser.Connect did this, but the reason is not clear + { ResolveExtendedServerName(failoverServerInfo, !redirectedUserInstance && failoverHost != primaryServerInfo.UserServerName, connectionOptions); } @@ -1926,7 +1908,7 @@ TimeoutTimer timeout } _parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous); - Debug.Assert(SniContext.Undefined == Parser._physicalStateObj.SniContext, string.Format((IFormatProvider)null, "SniContext should be Undefined; actual Value: {0}", Parser._physicalStateObj.SniContext)); + Debug.Assert(SniContext.Undefined == Parser._physicalStateObj.SniContext, $"SniContext should be Undefined; actual Value: {Parser._physicalStateObj.SniContext}"); ServerInfo currentServerInfo; if (useFailoverHost) @@ -1964,33 +1946,34 @@ TimeoutTimer timeout ); int routingAttempts = 0; - while (_routingInfo != null) + while (RoutingInfo != null) { - if (routingAttempts > _maxNumberOfRedirectRoute) + if (routingAttempts > MaxNumberOfRedirectRoute) { - throw SQL.ROR_RecursiveRoutingNotSupported(this, _maxNumberOfRedirectRoute); + throw SQL.ROR_RecursiveRoutingNotSupported(this, MaxNumberOfRedirectRoute); } routingAttempts++; - SqlClientEventSource.Log.TryTraceEvent(" Routed to {0}", _routingInfo.ServerName); + SqlClientEventSource.Log.TryTraceEvent(" Routed to {0}", RoutingInfo.ServerName); if (_parser != null) { _parser.Disconnect(); } - _parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous); + _parser = new TdsParser(ConnectionOptions.MARS, connectionOptions.Asynchronous); + Debug.Assert(SniContext.Undefined == Parser._physicalStateObj.SniContext, $"SniContext should be Undefined; actual Value: {Parser._physicalStateObj.SniContext}"); - currentServerInfo = new ServerInfo(ConnectionOptions, _routingInfo, currentServerInfo.ResolvedServerName, currentServerInfo.ServerSPN); + currentServerInfo = new ServerInfo(ConnectionOptions, RoutingInfo, currentServerInfo.ResolvedServerName, currentServerInfo.ServerSPN); _timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.RoutingDestination); _originalClientConnectionId = _clientConnectionId; _routingDestination = currentServerInfo.UserServerName; // restore properties that could be changed by the environment tokens - _currentPacketSize = ConnectionOptions.PacketSize; + _currentPacketSize = connectionOptions.PacketSize; _currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage; - CurrentDatabase = _originalDatabase = ConnectionOptions.InitialCatalog; + CurrentDatabase = _originalDatabase = connectionOptions.InitialCatalog; _currentFailoverPartner = null; _instanceName = string.Empty; @@ -1999,8 +1982,7 @@ TimeoutTimer timeout newPassword, newSecurePassword, intervalTimer, - withFailover: true - ); + withFailover: true); } break; // leave the while loop -- we've successfully connected @@ -2032,8 +2014,6 @@ TimeoutTimer timeout throw; } } - - // TODO: Stash parser away somewhere so we can examine it's state during debugging } // We only get here when we failed to connect, but are going to re-try @@ -2115,11 +2095,16 @@ private void ResolveExtendedServerName(ServerInfo serverInfo, bool aliasLookup, } // Common code path for making one attempt to establish a connection and log in to server. - private void AttemptOneLogin(ServerInfo serverInfo, string newPassword, SecureString newSecurePassword, TimeoutTimer timeout, bool withFailover = false, bool isFirstTransparentAttempt = true, bool disableTnir = false) + private void AttemptOneLogin(ServerInfo serverInfo, + string newPassword, + SecureString newSecurePassword, + TimeoutTimer timeout, + bool withFailover = false, + bool isFirstTransparentAttempt = true, + bool disableTnir = false) { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, timeout={1}[msec], server={2}", ObjectID, timeout.MillisecondsRemaining, serverInfo.ExtendedServerName); - - _routingInfo = null; // forget routing information + RoutingInfo = null; // forget routing information _parser._physicalStateObj.SniContext = SniContext.Snix_Connect; @@ -2145,7 +2130,6 @@ private void AttemptOneLogin(ServerInfo serverInfo, string newPassword, SecureSt _timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PostLogin); } - internal void FailoverPermissionDemand() { if (PoolGroupProviderInfo != null) @@ -2179,11 +2163,10 @@ protected override void ReleaseAdditionalLocksForClose(bool lockToken) } } - // called by SqlConnection.RepairConnection which is a relatevly expensive way of repair inner connection + // called by SqlConnection.RepairConnection which is a relatively expensive way of repair inner connection // prior to execution of request, used from EnlistTransaction, EnlistDistributedTransaction and ChangeDatabase internal bool GetSessionAndReconnectIfNeeded(SqlConnection parent, int timeout = 0) { - Debug.Assert(!ThreadHasParserLockForClose, "Cannot call this method if caller has parser lock"); if (ThreadHasParserLockForClose) { @@ -2247,7 +2230,6 @@ internal void BreakConnection() SqlConnection connection = Connection; SqlClientEventSource.Log.TryTraceEvent(" {0}, Breaking connection.", ObjectID); DoomThisConnection(); // Mark connection as unusable, so it will be destroyed - if (connection != null) { connection.Close(); @@ -2258,7 +2240,7 @@ internal bool IgnoreEnvChange { // true if we are only draining environment change tokens, used by TdsParser get { - return _routingInfo != null; // connection was routed, ignore rest of env change + return RoutingInfo != null; // connection was routed, ignore rest of env change } } @@ -2268,7 +2250,7 @@ internal void OnEnvChange(SqlEnvChange rec) switch (rec._type) { case TdsEnums.ENV_DATABASE: - // If connection is not open and recovery is not in progresss, store the server value as the original. + // If connection is not open and recovery is not in progress, store the server value as the original. if (!_fConnectionOpen && _recoverySessionData == null) { _originalDatabase = rec._newValue; @@ -2278,13 +2260,13 @@ internal void OnEnvChange(SqlEnvChange rec) break; case TdsEnums.ENV_LANG: - // If connection is not open and recovery is not in progresss, store the server value as the original. + // If connection is not open and recovery is not in progress, store the server value as the original. if (!_fConnectionOpen && _recoverySessionData == null) { _originalLanguage = rec._newValue; } - _currentLanguage = rec._newValue; // TODO: finish this. + _currentLanguage = rec._newValue; break; case TdsEnums.ENV_PACKETSIZE: @@ -2353,7 +2335,7 @@ internal void OnEnvChange(SqlEnvChange rec) { throw SQL.ROR_InvalidRoutingInfo(this); } - _routingInfo = rec._newRoutingInfo; + RoutingInfo = rec._newRoutingInfo; break; default: @@ -2365,7 +2347,6 @@ internal void OnEnvChange(SqlEnvChange rec) internal void OnLoginAck(SqlLoginAck rec) { _loginAck = rec; - // UNDONE: throw an error if this is not 7.0 or 7.1[5]. if (_recoverySessionData != null) { if (_recoverySessionData._tdsVersion != rec.tdsVersion) @@ -2389,11 +2370,11 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) || _credential != null || _accessTokenCallback != null || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryInteractive + || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryMSI || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDefault || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryWorkloadIdentity - || ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired), "Credentials aren't provided for calling MSAL"); Debug.Assert(fedAuthInfo != null, "info should not be null."); @@ -2467,7 +2448,7 @@ internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) Debug.Assert(!attemptRefreshTokenLocked || _fedAuthToken != null, "Either Lock should not have been obtained or _fedAuthToken should not be null."); Debug.Assert(!attemptRefreshTokenLocked || _newDbConnectionPoolAuthenticationContext != null, "Either Lock should not have been obtained or _newDbConnectionPoolAuthenticationContext should not be null."); - // Indicate in Bid Trace that we are successful with the update. + // Indicate in EventSource Trace that we are successful with the update. if (attemptRefreshTokenLocked) { SqlClientEventSource.Log.TryTraceEvent(" {0}, The attempt to get a new access token succeeded under the locked mode.", ObjectID); @@ -2578,7 +2559,6 @@ internal bool TryGetFedAuthTokenLocked(SqlFedAuthInfo fedAuthInfo, DbConnectionP /// SqlFedAuthToken internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) { - Debug.Assert(fedAuthInfo != null, "fedAuthInfo should not be null."); // No:of milliseconds to sleep for the inital back off. @@ -2749,9 +2729,7 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) // Deal with normal MsalExceptions. catch (MsalException msalException) { - if (MsalError.UnknownError != msalException.ErrorCode - || _timeout.IsExpired - || _timeout.MillisecondsRemaining <= sleepInterval) + if (MsalError.UnknownError != msalException.ErrorCode || _timeout.IsExpired || _timeout.MillisecondsRemaining <= sleepInterval) { SqlClientEventSource.Log.TryTraceEvent(" {0}", msalException.ErrorCode); @@ -2767,7 +2745,21 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) // All other exceptions from MSAL/Azure Identity APIs catch (Exception e) { - throw SqlException.CreateException(new() { new(0, (byte)0x00, (byte)TdsEnums.FATAL_ERROR_CLASS, ConnectionOptions.DataSource, e.Message, ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, 0) }, "", this, e); + throw SqlException.CreateException( + new() + { + new( + 0, + (byte)0x00, + (byte)TdsEnums.FATAL_ERROR_CLASS, + ConnectionOptions.DataSource, + e.Message, + ActiveDirectoryAuthentication.MSALGetAccessTokenFunctionName, + 0) + }, + "", + this, + e); } } @@ -2786,12 +2778,9 @@ internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) internal void OnFeatureExtAck(int featureId, byte[] data) { - if (_routingInfo != null) + if (RoutingInfo != null && featureId != TdsEnums.FEATUREEXT_SQLDNSCACHING) { - if (TdsEnums.FEATUREEXT_SQLDNSCACHING != featureId) - { - return; - } + return; } switch (featureId) @@ -2845,6 +2834,25 @@ internal void OnFeatureExtAck(int featureId, byte[] data) } break; } + + case TdsEnums.FEATUREEXT_GLOBALTRANSACTIONS: + { + SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for GlobalTransactions", ObjectID); + + if (data.Length < 1) + { + SqlClientEventSource.Log.TryTraceEvent(" {0}, Unknown version number for GlobalTransactions", ObjectID); + throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); + } + + IsGlobalTransaction = true; + if (1 == data[0]) + { + IsGlobalTransactionsEnabledForServer = true; + } + break; + } + case TdsEnums.FEATUREEXT_FEDAUTH: { SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for federated authentication", ObjectID); @@ -2899,7 +2907,6 @@ internal void OnFeatureExtAck(int featureId, byte[] data) } #endif } - break; } case TdsEnums.FEATUREEXT_TCE: @@ -2926,27 +2933,9 @@ internal void OnFeatureExtAck(int featureId, byte[] data) if (data.Length > 1) { + // Extract the type of enclave being used by the server. _parser.EnclaveType = Encoding.Unicode.GetString(data, 2, (data.Length - 2)); } - - break; - } - - case TdsEnums.FEATUREEXT_GLOBALTRANSACTIONS: - { - SqlClientEventSource.Log.TryAdvancedTraceEvent(" {0}, Received feature extension acknowledgement for GlobalTransactions", ObjectID); - - if (data.Length < 1) - { - SqlClientEventSource.Log.TryTraceEvent(" {0}, Unknown version number for GlobalTransactions", ObjectID); - throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); - } - - IsGlobalTransaction = true; - if (1 == data[0]) - { - IsGlobalTransactionsEnabledForServer = true; - } break; } @@ -2976,7 +2965,6 @@ internal void OnFeatureExtAck(int featureId, byte[] data) if (data.Length < 1) { SqlClientEventSource.Log.TryTraceEvent(" {0}, Unknown token for DATACLASSIFICATION", ObjectID); - throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream); } byte supportedDataClassificationVersion = data[0]; @@ -3023,7 +3011,7 @@ internal void OnFeatureExtAck(int featureId, byte[] data) IsSQLDNSCachingSupported = true; _cleanSQLDNSCaching = false; - if (_routingInfo != null) + if (RoutingInfo != null) { IsDNSCachingBeforeRedirectSupported = true; } @@ -3035,12 +3023,10 @@ internal void OnFeatureExtAck(int featureId, byte[] data) _cleanSQLDNSCaching = true; } - // need to add more steps for phrase 2 + // need to add more steps for phase 2 // get IPv4 + IPv6 + Port number // not put them in the DNS cache at this point but need to store them somewhere - // generate pendingSQLDNSObject and turn on IsSQLDNSRetryEnabled flag - break; } @@ -3121,14 +3107,14 @@ internal string UserServerName { get { - return m_userServerName; + return _userServerName; } private set { - m_userServerName = value; + _userServerName = value; } } - private string m_userServerName; + private string _userServerName; internal readonly string PreRoutingServerName; diff --git a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs index 83b98a2b66..873d87c92e 100644 --- a/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs +++ b/src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/TdsParserHelperClasses.cs @@ -30,8 +30,8 @@ internal enum EncryptionOptions NOT_SUP, REQ, LOGIN, -#if NETFRAMEWORK OPTIONS_MASK = 0x3f, +#if NETFRAMEWORK CTAIP = 0x40, CLIENT_CERT = 0x80, #endif