1 //------------------------------------------------------------------------------
2 // <copyright file="SqlInternalConnectionTds.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 // <owner current="true" primary="true">[....]</owner>
6 // <owner current="true" primary="false">[....]</owner>
7 //------------------------------------------------------------------------------
9 namespace System.Data.SqlClient
12 using System.Collections.Generic;
14 using System.Data.Common;
15 using System.Data.ProviderBase;
16 using System.Diagnostics;
17 using System.Globalization;
18 using System.Reflection;
19 using System.Runtime.CompilerServices;
20 using System.Runtime.ConstrainedExecution;
21 using System.Runtime.InteropServices;
22 using System.Security;
23 using System.Security.Permissions;
25 using System.Threading;
26 using SysTx = System.Transactions;
27 using System.Diagnostics.CodeAnalysis;
28 using System.Threading.Tasks;
31 internal class SessionStateRecord {
32 internal bool _recoverable;
33 internal UInt32 _version;
34 internal Int32 _dataLength;
35 internal byte[] _data;
38 internal class SessionData {
39 internal const int _maxNumberOfSessionStates = 256;
40 internal UInt32 _tdsVersion;
41 internal bool _encrypted;
43 internal string _database;
44 internal SqlCollation _collation;
45 internal string _language;
47 internal string _initialDatabase;
48 internal SqlCollation _initialCollation;
49 internal string _initialLanguage;
51 internal byte _unrecoverableStatesCount = 0;
52 internal Dictionary<string, Tuple<string, string>> _resolvedAliases;
55 internal bool _debugReconnectDataApplied;
58 internal SessionStateRecord[] _delta = new SessionStateRecord[_maxNumberOfSessionStates];
59 internal bool _deltaDirty = false;
60 internal byte[][] _initialState = new byte[_maxNumberOfSessionStates][];
62 public SessionData(SessionData recoveryData) {
63 _initialDatabase = recoveryData._initialDatabase;
64 _initialCollation = recoveryData._initialCollation;
65 _initialLanguage = recoveryData._initialLanguage;
66 _resolvedAliases = recoveryData._resolvedAliases;
68 for (int i = 0; i < _maxNumberOfSessionStates; i++) {
69 if (recoveryData._initialState[i] != null) {
70 _initialState[i] = (byte[])recoveryData._initialState[i].Clone();
75 public SessionData() {
76 _resolvedAliases = new Dictionary<string, Tuple<string, string>>(2);
84 _delta = new SessionStateRecord[_maxNumberOfSessionStates];
87 _unrecoverableStatesCount = 0;
90 [Conditional("DEBUG")]
91 public void AssertUnrecoverableStateCountIsCorrect() {
92 byte unrecoverableCount = 0;
93 foreach (var state in _delta) {
94 if (state != null && !state._recoverable)
97 Debug.Assert(unrecoverableCount == _unrecoverableStatesCount, "Unrecoverable count does not match");
101 sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposable {
102 // CONNECTION AND STATE VARIABLES
103 private readonly SqlConnectionPoolGroupProviderInfo _poolGroupProviderInfo; // will only be null when called for ChangePassword, or creating SSE User Instance
104 private TdsParser _parser;
105 private SqlLoginAck _loginAck;
106 private SqlCredential _credential;
107 private FederatedAuthenticationFeatureExtensionData? _fedAuthFeatureExtensionData;
109 // Connection Resiliency
110 private bool _sessionRecoveryRequested;
111 internal bool _sessionRecoveryAcknowledged;
112 internal SessionData _currentSessionData; // internal for use from TdsParser only, otehr should use CurrentSessionData property that will fix database and language
113 private SessionData _recoverySessionData;
115 // Federated Authentication
116 // Response obtained from the server for FEDAUTHREQUIRED prelogin option.
117 internal bool _fedAuthRequired;
119 internal bool _federatedAuthenticationRequested;
120 internal bool _federatedAuthenticationAcknowledged;
121 internal bool _federatedAuthenticationInfoRequested; // Keep this distinct from _federatedAuthenticationRequested, since some fedauth library types may not need more info
122 internal bool _federatedAuthenticationInfoReceived;
125 internal byte _tceVersionSupported;
127 internal byte[] _accessTokenInBytes;
129 // The pool that this connection is associated with, if at all it is.
130 private DbConnectionPool _dbConnectionPool;
132 // This is used to preserve the authentication context object if we decide to cache it for subsequent connections in the same pool.
133 // This will finally end up in _dbConnectionPool.AuthenticationContexts, but only after 1 successful login to SQL Server using this context.
134 // This variable is to persist the context after we have generated it, but before we have successfully completed the login with this new context.
135 // If this connection attempt ended up re-using the existing context and not create a new one, this will be null (since the context is not new).
136 private DbConnectionPoolAuthenticationContext _newDbConnectionPoolAuthenticationContext;
138 // The key of the authentication context, built from information found in the FedAuthInfoToken.
139 private DbConnectionPoolAuthenticationContextKey _dbConnectionPoolAuthenticationContextKey;
142 // This is a test hook to enable testing of the retry paths for ADAL get access token.
143 // Sample code to enable:
145 // Type type = typeof(SqlConnection).Assembly.GetType("System.Data.SqlClient.SqlInternalConnectionTds");
146 // System.Reflection.FieldInfo field = type.GetField("_forceAdalRetry", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
147 // if (field != null) {
148 // field.SetValue(null, true);
151 internal static bool _forceAdalRetry = false;
153 // This is a test hook to simulate a token expiring within the next 45 minutes.
154 private static bool _forceExpiryLocked = false;
156 // This is a test hook to simulate a token expiring within the next 10 minutes.
157 private static bool _forceExpiryUnLocked = false;
160 // The timespan defining the amount of time the authentication context needs to be valid for at-least, to re-use the cached context,
161 // without making an attempt to refresh it. IF the context is expiring within the next 45 mins, then try to take a lock and refresh
162 // the context, if the lock is acquired.
163 private static readonly TimeSpan _dbAuthenticationContextLockedRefreshTimeSpan = new TimeSpan(hours: 0, minutes: 45, seconds: 00);
165 // The timespan defining the minimum amount of time the authentication context needs to be valid for re-using the cached context.
166 // 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.
167 private static readonly TimeSpan _dbAuthenticationContextUnLockedRefreshTimeSpan = new TimeSpan(hours: 0, minutes: 10, seconds: 00);
169 private readonly TimeoutTimer _timeout;
171 private static HashSet<int> transientErrors = new HashSet<int>();
173 internal SessionData CurrentSessionData {
175 if (_currentSessionData != null) {
176 _currentSessionData._database = CurrentDatabase;
177 _currentSessionData._language = _currentLanguage;
179 return _currentSessionData;
184 private bool _fConnectionOpen = false;
186 // FOR CONNECTION RESET MANAGEMENT
187 private bool _fResetConnection;
188 private string _originalDatabase;
189 private string _currentFailoverPartner; // only set by ENV change from server
190 private string _originalLanguage;
191 private string _currentLanguage;
192 private int _currentPacketSize;
193 private int _asyncCommandCount; // number of async Begins minus number of async Ends.
196 private string _instanceName = String.Empty;
199 private DbConnectionPoolIdentity _identity; // Used to lookup info for notification matching Start().
201 // FOR SYNCHRONIZATION IN TdsParser
202 // How to use these locks:
203 // 1. Whenever writing to the connection (with the exception of Cancellation) the _parserLock MUST be taken
204 // 2. _parserLock will also be taken during close (to prevent closing in the middle of a write)
205 // 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
206 // * 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)
207 // * It is safe to set ThreadHasParserLockForClose to true when writing as well, but it is unneccesary
208 // * 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)
209 // 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
210 // 5. ThreadHasParserLockForClose should only be modified if you currently own the _parserLock
211 // 6. Reading ThreadHasParserLockForClose is thread-safe
212 internal class SyncAsyncLock
214 SemaphoreSlim semaphore = new SemaphoreSlim(1);
216 internal void Wait(bool canReleaseFromAnyThread)
218 Monitor.Enter(semaphore); // semaphore is used as lock object, no relation to SemaphoreSlim.Wait/Release methods
219 if (canReleaseFromAnyThread || semaphore.CurrentCount==0) {
221 if (canReleaseFromAnyThread) {
222 Monitor.Exit(semaphore);
230 internal void Wait(bool canReleaseFromAnyThread, int timeout, ref bool lockTaken) {
232 bool hasMonitor = false;
234 Monitor.TryEnter(semaphore, timeout, ref hasMonitor); // semaphore is used as lock object, no relation to SemaphoreSlim.Wait/Release methods
236 if ((canReleaseFromAnyThread) || (semaphore.CurrentCount == 0)) {
237 if (semaphore.Wait(timeout)) {
238 if (canReleaseFromAnyThread) {
239 Monitor.Exit(semaphore);
255 if ((!lockTaken) && (hasMonitor)) {
256 Monitor.Exit(semaphore);
261 internal void Release()
263 if (semaphore.CurrentCount==0) { // semaphore methods were used for locking
267 Monitor.Exit(semaphore);
272 internal bool CanBeReleasedFromAnyThread {
274 return semaphore.CurrentCount==0;
278 // Necessary but not sufficient condition for thread to have lock (since sempahore may be obtained by any thread)
279 internal bool ThreadMayHaveLock() {
280 return Monitor.IsEntered(semaphore) || semaphore.CurrentCount == 0;
285 internal SyncAsyncLock _parserLock = new SyncAsyncLock();
286 private int _threadIdOwningParserLock = -1;
288 private SqlConnectionTimeoutErrorInternal timeoutErrorInternal;
290 internal SqlConnectionTimeoutErrorInternal TimeoutErrorInternal
292 get { return timeoutErrorInternal; }
295 // OTHER STATE VARIABLES AND REFERENCES
297 internal Guid _clientConnectionId = Guid.Empty;
299 // Routing information (ROR)
300 RoutingInfo _routingInfo = null;
301 private Guid _originalClientConnectionId = Guid.Empty;
302 private string _routingDestination = null;
304 static SqlInternalConnectionTds()
306 populateTransientErrors();
309 // although the new password is generally not used it must be passed to the c'tor
310 // the new Login7 packet will always write out the new password (or a length of zero and no bytes if not present)
312 internal SqlInternalConnectionTds(
313 DbConnectionPoolIdentity identity,
314 SqlConnectionString connectionOptions,
315 SqlCredential credential,
318 SecureString newSecurePassword,
319 bool redirectedUserInstance,
320 SqlConnectionString userConnectionOptions = null, // NOTE: userConnectionOptions may be different to connectionOptions if the connection string has been expanded (see SqlConnectionString.Expand)
321 SessionData reconnectSessionData = null,
322 DbConnectionPool pool = null,
323 string accessToken = null,
324 bool applyTransientFaultHandling = false
325 ) : base(connectionOptions) {
328 if (reconnectSessionData != null) {
329 reconnectSessionData._debugReconnectDataApplied = true;
331 try { // use this to help validate this object is only created after the following permission has been previously demanded in the current codepath
332 if (userConnectionOptions != null) {
333 // As mentioned above, userConnectionOptions may be different to connectionOptions, so we need to demand on the correct connection string
334 userConnectionOptions.DemandPermission();
337 connectionOptions.DemandPermission();
340 catch(System.Security.SecurityException) {
341 System.Diagnostics.Debug.Assert(false, "unexpected SecurityException for current codepath");
345 Debug.Assert(reconnectSessionData == null || connectionOptions.ConnectRetryCount > 0, "Reconnect data supplied with CR turned off");
347 _dbConnectionPool = pool;
349 if (connectionOptions.ConnectRetryCount > 0) {
350 _recoverySessionData = reconnectSessionData;
351 if (reconnectSessionData == null) {
352 _currentSessionData = new SessionData();
355 _currentSessionData = new SessionData(_recoverySessionData);
356 _originalDatabase = _recoverySessionData._initialDatabase;
357 _originalLanguage = _recoverySessionData._initialLanguage;
361 if (connectionOptions.UserInstance && InOutOfProcHelper.InProc) {
362 throw SQL.UserInstanceNotAvailableInProc();
365 if (accessToken != null) {
366 _accessTokenInBytes = System.Text.Encoding.Unicode.GetBytes(accessToken);
369 _identity = identity;
370 Debug.Assert(newSecurePassword != null || newPassword != null, "cannot have both new secure change password and string based change password to be null");
371 Debug.Assert(credential == null || (String.IsNullOrEmpty(connectionOptions.UserID) && String.IsNullOrEmpty(connectionOptions.Password)), "cannot mix the new secure password system and the connection string based password");
373 Debug.Assert(credential == null || !connectionOptions.IntegratedSecurity, "Cannot use SqlCredential and Integrated Security");
374 Debug.Assert(credential == null || !connectionOptions.ContextConnection, "Cannot use SqlCredential with context connection");
376 _poolGroupProviderInfo = (SqlConnectionPoolGroupProviderInfo)providerInfo;
377 _fResetConnection = connectionOptions.ConnectionReset;
378 if (_fResetConnection && _recoverySessionData == null) {
379 _originalDatabase = connectionOptions.InitialCatalog;
380 _originalLanguage = connectionOptions.CurrentLanguage;
383 timeoutErrorInternal = new SqlConnectionTimeoutErrorInternal();
384 _credential = credential;
386 _parserLock.Wait(canReleaseFromAnyThread:false);
387 ThreadHasParserLockForClose = true; // In case of error, let ourselves know that we already own the parser lock
388 RuntimeHelpers.PrepareConstrainedRegions();
391 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
393 RuntimeHelpers.PrepareConstrainedRegions();
395 tdsReliabilitySection.Start();
399 _timeout = TimeoutTimer.StartSecondsTimeout(connectionOptions.ConnectTimeout);
401 // If transient fault handling is enabled then we can retry the login upto the ConnectRetryCount.
402 int connectionEstablishCount = applyTransientFaultHandling ? connectionOptions.ConnectRetryCount + 1 : 1;
403 int transientRetryIntervalInMilliSeconds = connectionOptions.ConnectRetryInterval * 1000; // Max value of transientRetryInterval is 60*1000 ms. The max value allowed for ConnectRetryInterval is 60
404 for (int i = 0; i < connectionEstablishCount; i++)
408 OpenLoginEnlist(_timeout, connectionOptions, credential, newPassword, newSecurePassword, redirectedUserInstance);
411 catch (SqlException sqlex)
413 if (i + 1 == connectionEstablishCount
414 || !applyTransientFaultHandling
415 || _timeout.IsExpired
416 || _timeout.MillisecondsRemaining < transientRetryIntervalInMilliSeconds
417 || !IsTransientError(sqlex))
423 Thread.Sleep(transientRetryIntervalInMilliSeconds);
430 tdsReliabilitySection.Stop();
434 catch (System.OutOfMemoryException) {
435 DoomThisConnection();
438 catch (System.StackOverflowException) {
439 DoomThisConnection();
442 catch (System.Threading.ThreadAbortException) {
443 DoomThisConnection();
447 ThreadHasParserLockForClose = false;
448 _parserLock.Release();
450 if (Bid.AdvancedOn) {
451 Bid.Trace("<sc.SqlInternalConnectionTds.ctor|ADV> %d#, constructed new TDS internal connection\n", ObjectID);
455 // The erros in the transient error set are contained in
456 // https://azure.microsoft.com/en-us/documentation/articles/sql-database-develop-error-messages/#transient-faults-connection-loss-and-other-temporary-errors
457 private static void populateTransientErrors()
459 // SQL Error Code: 4060
460 // Cannot open database "%.*ls" requested by the login. The login failed.
461 transientErrors.Add(4060);
462 // SQL Error Code: 10928
463 // Resource ID: %d. The %s limit for the database is %d and has been reached.
464 transientErrors.Add(10928);
465 // SQL Error Code: 10929
466 // Resource ID: %d. The %s minimum guarantee is %d, maximum limit is %d and the current usage for the database is %d.
467 // However, the server is currently too busy to support requests greater than %d for this database.
468 transientErrors.Add(10929);
469 // SQL Error Code: 40197
470 // You will receive this error, when the service is down due to software or hardware upgrades, hardware failures,
471 // or any other failover problems. The error code (%d) embedded within the message of error 40197 provides
472 // additional information about the kind of failure or failover that occurred. Some examples of the error codes are
473 // embedded within the message of error 40197 are 40020, 40143, 40166, and 40540.
474 transientErrors.Add(40197);
475 transientErrors.Add(40020);
476 transientErrors.Add(40143);
477 transientErrors.Add(40166);
478 // The service has encountered an error processing your request. Please try again.
479 transientErrors.Add(40540);
480 // The service is currently busy. Retry the request after 10 seconds. Incident ID: %ls. Code: %d.
481 transientErrors.Add(40501);
482 // Database '%.*ls' on server '%.*ls' is not currently available. Please retry the connection later.
483 // If the problem persists, contact customer support, and provide them the session tracing ID of '%.*ls'.
484 transientErrors.Add(40613);
485 // Do federation errors deserve to be here ?
486 // Note: Federation errors 10053 and 10054 might also deserve inclusion in your retry logic.
487 //transientErrors.Add(10053);
488 //transientErrors.Add(10054);
492 // Returns true if the Sql error is a transient.
493 private bool IsTransientError(SqlException exc)
499 foreach (SqlError error in exc.Errors)
501 if (transientErrors.Contains(error.Number))
509 internal Guid ClientConnectionId {
511 return _clientConnectionId;
515 internal Guid OriginalClientConnectionId {
517 return _originalClientConnectionId;
521 internal string RoutingDestination {
523 return _routingDestination;
527 override internal SqlInternalTransaction CurrentTransaction {
529 return _parser.CurrentTransaction;
533 override internal SqlInternalTransaction AvailableInternalTransaction {
535 return _parser._fResetConnection ? null : CurrentTransaction;
540 override internal SqlInternalTransaction PendingTransaction {
542 return _parser.PendingTransaction;
546 internal DbConnectionPoolIdentity Identity {
552 internal string InstanceName {
554 return _instanceName;
558 override internal bool IsLockedForBulkCopy {
560 return (!Parser.MARSOn && Parser._physicalStateObj.BcpLock);
564 override protected internal bool IsNonPoolableTransactionRoot {
566 return IsTransactionRoot && (!IsKatmaiOrNewer || null == Pool);
570 override internal bool IsShiloh {
572 return _loginAck.isVersion8;
576 override internal bool IsYukonOrNewer {
578 return _parser.IsYukonOrNewer;
582 override internal bool IsKatmaiOrNewer {
584 return _parser.IsKatmaiOrNewer;
588 internal int PacketSize {
590 return _currentPacketSize;
594 internal TdsParser Parser {
600 internal string ServerProvidedFailOverPartner {
602 return _currentFailoverPartner;
606 internal SqlConnectionPoolGroupProviderInfo PoolGroupProviderInfo {
608 return _poolGroupProviderInfo;
612 override protected bool ReadyToPrepareTransaction {
615 bool result = (null == FindLiveReader(null)); // can't prepare with a live data reader...
620 override public string ServerVersion {
622 return(String.Format((IFormatProvider)null, "{0:00}.{1:00}.{2:0000}", _loginAck.majorVersion,
623 (short) _loginAck.minorVersion, _loginAck.buildNum));
628 /// Get boolean that specifies whether an enlisted transaction can be unbound from
629 /// the connection when that transaction completes.
632 /// This override always returns false.
635 /// The SqlInternalConnectionTds.CheckEnlistedTransactionBinding method handles implicit unbinding for disposed transactions.
637 protected override bool UnbindOnTransactionCompletion
646 ////////////////////////////////////////////////////////////////////////////////////////
648 ////////////////////////////////////////////////////////////////////////////////////////
649 [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] // copied from Triaged.cs
650 override protected void ChangeDatabaseInternal(string database) {
651 // MDAC 73598 - add brackets around database
652 database = SqlConnection.FixupDatabaseTransactionName(database);
653 Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch("use " + database, ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj, sync: true);
654 Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");
655 _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
658 override public void Dispose() {
659 if (Bid.AdvancedOn) {
660 Bid.Trace("<sc.SqlInternalConnectionTds.Dispose|ADV> %d# disposing\n", base.ObjectID);
663 TdsParser parser = Interlocked.Exchange(ref _parser, null); // guard against multiple concurrent dispose calls -- Delegated Transactions might cause this.
665 Debug.Assert(parser != null && _fConnectionOpen || parser == null && !_fConnectionOpen, "Unexpected state on dispose");
666 if (null != parser) {
670 finally { // UNDONE: MDAC 77928
671 // close will always close, even if exception is thrown
672 // remember to null out any object references
674 _fConnectionOpen = false; // mark internal connection as closed
679 override internal void ValidateConnectionForExecute(SqlCommand command) {
680 TdsParser parser = _parser;
681 if ((parser == null) || (parser.State == TdsParserState.Broken) || (parser.State == TdsParserState.Closed)) {
682 throw ADP.ClosedConnectionError();
685 SqlDataReader reader = null;
687 if (null != command) { // command can't have datareader already associated with it
688 reader = FindLiveReader(command);
691 else { // single execution/datareader per connection
692 if (_asyncCommandCount > 0) {
693 throw SQL.MARSUnspportedOnConnection();
696 reader = FindLiveReader(null);
698 if (null != reader) {
699 // if MARS is on, then a datareader associated with the command exists
700 // or if MARS is off, then a datareader exists
701 throw ADP.OpenReaderExists(); // MDAC 66411
703 else if (!parser.MARSOn && parser._physicalStateObj._pendingData) {
704 parser.DrainData(parser._physicalStateObj);
706 Debug.Assert(!parser._physicalStateObj._pendingData, "Should not have a busy physicalStateObject at this point!");
708 parser.RollbackOrphanedAPITransactions();
713 /// Validate the enlisted transaction state, taking into consideration the ambient transaction and transaction unbinding mode.
714 /// If there is no enlisted transaction, this method is a nop.
718 /// This method must be called while holding a lock on the SqlInternalConnection instance,
719 /// to ensure we don't accidentally execute after the transaction has completed on a different thread,
720 /// causing us to unwittingly execute in auto-commit mode.
724 /// When using Explicit transaction unbinding,
725 /// verify that the enlisted transaction is active and equal to the current ambient transaction.
729 /// When using Implicit transaction unbinding,
730 /// verify that the enlisted transaction is active.
731 /// If it is not active, and the transaction object has been diposed, unbind from the transaction.
732 /// If it is not active and not disposed, throw an exception.
735 internal void CheckEnlistedTransactionBinding()
737 // If we are enlisted in a transaction, check that transaction is active.
738 // When using explicit transaction unbinding, also verify that the enlisted transaction is the current transaction.
739 SysTx.Transaction enlistedTransaction = EnlistedTransaction;
741 if (enlistedTransaction != null)
743 bool requireExplicitTransactionUnbind = ConnectionOptions.TransactionBinding == SqlConnectionString.TransactionBindingEnum.ExplicitUnbind;
745 if (requireExplicitTransactionUnbind)
747 SysTx.Transaction currentTransaction = SysTx.Transaction.Current;
749 if (SysTx.TransactionStatus.Active != enlistedTransaction.TransactionInformation.Status || !enlistedTransaction.Equals(currentTransaction))
751 throw ADP.TransactionConnectionMismatch();
754 else // implicit transaction unbind
756 if (SysTx.TransactionStatus.Active != enlistedTransaction.TransactionInformation.Status)
758 if (EnlistedTransactionDisposed)
760 DetachTransaction(enlistedTransaction, true);
764 throw ADP.TransactionCompletedButNotDisposed();
771 internal override bool IsConnectionAlive(bool throwOnException)
773 bool isAlive = false;
775 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
777 RuntimeHelpers.PrepareConstrainedRegions();
780 tdsReliabilitySection.Start();
783 isAlive = _parser._physicalStateObj.IsConnectionAlive(throwOnException);
789 tdsReliabilitySection.Stop();
795 ////////////////////////////////////////////////////////////////////////////////////////
797 ////////////////////////////////////////////////////////////////////////////////////////
799 override protected void Activate(SysTx.Transaction transaction) {
800 FailoverPermissionDemand(); // Demand for unspecified failover pooled connections
802 // When we're required to automatically enlist in transactions and
803 // there is one we enlist in it. On the other hand, if there isn't a
804 // transaction and we are currently enlisted in one, then we
807 // Regardless of whether we're required to automatically enlist,
808 // when there is not a current transaction, we cannot leave the
809 // connection enlisted in a transaction.
810 if (null != transaction){
811 if (ConnectionOptions.Enlist) {
820 override protected void InternalDeactivate() {
821 // When we're deactivated, the user must have called End on all
822 // the async commands, or we don't know that we're in a state that
823 // we can recover from. We doom the connection in this case, to
824 // prevent odd cases when we go to the wire.
825 if (0 != _asyncCommandCount) {
826 DoomThisConnection();
829 // If we're deactivating with a delegated transaction, we
830 // should not be cleaning up the parser just yet, that will
831 // cause our transaction to be rolled back and the connection
832 // to be reset. We'll get called again once the delegated
833 // transaction is completed and we can do it all then.
834 if (!IsNonPoolableTransactionRoot) {
835 Debug.Assert(null != _parser || IsConnectionDoomed, "Deactivating a disposed connection?");
836 if (_parser != null) {
838 _parser.Deactivate(IsConnectionDoomed);
840 if (!IsConnectionDoomed) {
847 [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] // copied from Triaged.cs
848 private void ResetConnection() {
849 // For implicit pooled connections, if connection reset behavior is specified,
850 // reset the database and language properties back to default. It is important
851 // to do this on activate so that the hashtable is correct before SqlConnection
854 Debug.Assert(!HasLocalTransactionFromAPI, "Upon ResetConnection SqlInternalConnectionTds has a currently ongoing local transaction.");
855 Debug.Assert(!_parser._physicalStateObj._pendingData, "Upon ResetConnection SqlInternalConnectionTds has pending data.");
857 if (_fResetConnection) {
858 // Ensure we are either going against shiloh, or we are not enlisted in a
859 // distributed transaction - otherwise don't reset!
861 // Prepare the parser for the connection reset - the next time a trip
862 // to the server is made.
863 _parser.PrepareResetConnection(IsTransactionRoot && !IsNonPoolableTransactionRoot);
865 else if (!IsEnlistedInTransaction) {
866 // If not Shiloh, we are going against Sphinx. On Sphinx, we
867 // may only reset if not enlisted in a distributed transaction.
870 Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch("sp_reset_connection", 30, null, _parser._physicalStateObj, sync: true);
871 Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");
872 _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
874 catch (Exception e) {
876 if (!ADP.IsCatchableExceptionType(e)) {
880 DoomThisConnection();
881 ADP.TraceExceptionWithoutRethrow(e);
885 // Reset hashtable values, since calling reset will not send us env_changes.
886 CurrentDatabase = _originalDatabase;
887 _currentLanguage = _originalLanguage;
891 internal void DecrementAsyncCount() {
892 Interlocked.Decrement(ref _asyncCommandCount);
895 internal void IncrementAsyncCount() {
896 Interlocked.Increment(ref _asyncCommandCount);
900 ////////////////////////////////////////////////////////////////////////////////////////
901 // LOCAL TRANSACTION METHODS
902 ////////////////////////////////////////////////////////////////////////////////////////
904 override internal void DisconnectTransaction(SqlInternalTransaction internalTransaction) {
905 TdsParser parser = Parser;
907 if (null != parser) {
908 parser.DisconnectTransaction(internalTransaction);
912 internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, IsolationLevel iso) {
913 ExecuteTransaction(transactionRequest, name, iso, null, false);
916 override internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest) {
917 if (IsConnectionDoomed) { // doomed means we can't do anything else...
918 if (transactionRequest == TransactionRequest.Rollback
919 || transactionRequest == TransactionRequest.IfRollback) {
922 throw SQL.ConnectionDoomed();
925 if (transactionRequest == TransactionRequest.Commit
926 || transactionRequest == TransactionRequest.Rollback
927 || transactionRequest == TransactionRequest.IfRollback) {
928 if (!Parser.MARSOn && Parser._physicalStateObj.BcpLock) {
929 throw SQL.ConnectionLockedForBcpEvent();
933 string transactionName = (null == name) ? String.Empty : name;
935 if (!_parser.IsYukonOrNewer) {
936 ExecuteTransactionPreYukon(transactionRequest, transactionName, iso, internalTransaction);
939 ExecuteTransactionYukon(transactionRequest, transactionName, iso, internalTransaction, isDelegateControlRequest);
943 // This function will not handle idle connection resiliency, as older servers will not support it
944 internal void ExecuteTransactionPreYukon(
945 TransactionRequest transactionRequest,
946 string transactionName,
948 SqlInternalTransaction internalTransaction) {
949 StringBuilder sqlBatch = new StringBuilder();
952 case IsolationLevel.Unspecified:
954 case IsolationLevel.ReadCommitted:
955 sqlBatch.Append(TdsEnums.TRANS_READ_COMMITTED);
956 sqlBatch.Append(";");
958 case IsolationLevel.ReadUncommitted:
959 sqlBatch.Append(TdsEnums.TRANS_READ_UNCOMMITTED);
960 sqlBatch.Append(";");
962 case IsolationLevel.RepeatableRead:
963 sqlBatch.Append(TdsEnums.TRANS_REPEATABLE_READ);
964 sqlBatch.Append(";");
966 case IsolationLevel.Serializable:
967 sqlBatch.Append(TdsEnums.TRANS_SERIALIZABLE);
968 sqlBatch.Append(";");
970 case IsolationLevel.Snapshot:
971 throw SQL.SnapshotNotSupported(IsolationLevel.Snapshot);
973 case IsolationLevel.Chaos:
974 throw SQL.NotSupportedIsolationLevel(iso);
977 throw ADP.InvalidIsolationLevel(iso);
980 if (!ADP.IsEmpty(transactionName)) {
981 transactionName = " " + SqlConnection.FixupDatabaseTransactionName(transactionName);
984 switch (transactionRequest) {
985 case TransactionRequest.Begin:
986 sqlBatch.Append(TdsEnums.TRANS_BEGIN);
987 sqlBatch.Append(transactionName);
989 case TransactionRequest.Promote:
990 Debug.Assert(false, "Promote called with transaction name or on pre-Yukon!");
992 case TransactionRequest.Commit:
993 sqlBatch.Append(TdsEnums.TRANS_COMMIT);
994 sqlBatch.Append(transactionName);
996 case TransactionRequest.Rollback:
997 sqlBatch.Append(TdsEnums.TRANS_ROLLBACK);
998 sqlBatch.Append(transactionName);
1000 case TransactionRequest.IfRollback:
1001 sqlBatch.Append(TdsEnums.TRANS_IF_ROLLBACK);
1002 sqlBatch.Append(transactionName);
1004 case TransactionRequest.Save:
1005 sqlBatch.Append(TdsEnums.TRANS_SAVE);
1006 sqlBatch.Append(transactionName);
1009 Debug.Assert(false, "Unknown transaction type");
1013 Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch(sqlBatch.ToString(), ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj, sync: true);
1014 Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");
1015 _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
1017 // Prior to Yukon, we didn't have any transaction tokens to manage,
1018 // or any feedback to know when one was created, so we just presume
1019 // that successful execution of the request caused the transaction
1020 // to be created, and we set that on the parser.
1021 if (TransactionRequest.Begin == transactionRequest) {
1022 Debug.Assert(null != internalTransaction, "Begin Transaction request without internal transaction");
1023 _parser.CurrentTransaction = internalTransaction;
1028 internal void ExecuteTransactionYukon(
1029 TransactionRequest transactionRequest,
1030 string transactionName,
1032 SqlInternalTransaction internalTransaction,
1033 bool isDelegateControlRequest) {
1034 TdsEnums.TransactionManagerRequestType requestType = TdsEnums.TransactionManagerRequestType.Begin;
1035 TdsEnums.TransactionManagerIsolationLevel isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadCommitted;
1038 case IsolationLevel.Unspecified:
1039 isoLevel = TdsEnums.TransactionManagerIsolationLevel.Unspecified;
1041 case IsolationLevel.ReadCommitted:
1042 isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadCommitted;
1044 case IsolationLevel.ReadUncommitted:
1045 isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadUncommitted;
1047 case IsolationLevel.RepeatableRead:
1048 isoLevel = TdsEnums.TransactionManagerIsolationLevel.RepeatableRead;
1050 case IsolationLevel.Serializable:
1051 isoLevel = TdsEnums.TransactionManagerIsolationLevel.Serializable;
1053 case IsolationLevel.Snapshot:
1054 isoLevel = TdsEnums.TransactionManagerIsolationLevel.Snapshot;
1056 case IsolationLevel.Chaos:
1057 throw SQL.NotSupportedIsolationLevel(iso);
1059 throw ADP.InvalidIsolationLevel(iso);
1062 TdsParserStateObject stateObj = _parser._physicalStateObj;
1063 TdsParser parser = _parser;
1064 bool mustPutSession = false;
1065 bool releaseConnectionLock = false;
1067 Debug.Assert(!ThreadHasParserLockForClose || _parserLock.ThreadMayHaveLock(), "Thread claims to have parser lock, but lock is not taken");
1068 if (!ThreadHasParserLockForClose) {
1069 _parserLock.Wait(canReleaseFromAnyThread:false);
1070 ThreadHasParserLockForClose = true; // In case of error, let the connection know that we already own the parser lock
1071 releaseConnectionLock = true;
1074 switch (transactionRequest) {
1075 case TransactionRequest.Begin:
1076 requestType = TdsEnums.TransactionManagerRequestType.Begin;
1078 case TransactionRequest.Promote:
1079 requestType = TdsEnums.TransactionManagerRequestType.Promote;
1081 case TransactionRequest.Commit:
1082 requestType = TdsEnums.TransactionManagerRequestType.Commit;
1084 case TransactionRequest.IfRollback:
1085 // Map IfRollback to Rollback since with Yukon and beyond we should never need
1086 // the if since the server will inform us when transactions have completed
1087 // as a result of an error on the server.
1088 case TransactionRequest.Rollback:
1089 requestType = TdsEnums.TransactionManagerRequestType.Rollback;
1091 case TransactionRequest.Save:
1092 requestType = TdsEnums.TransactionManagerRequestType.Save;
1095 Debug.Assert(false, "Unknown transaction type");
1099 // only restore if connection lock has been taken within the function
1100 if (internalTransaction != null && internalTransaction.RestoreBrokenConnection && releaseConnectionLock) {
1101 Task reconnectTask = internalTransaction.Parent.Connection.ValidateAndReconnect(() => {
1102 ThreadHasParserLockForClose = false;
1103 _parserLock.Release();
1104 releaseConnectionLock = false;
1106 if (reconnectTask != null) {
1107 AsyncHelper.WaitForCompletion(reconnectTask, 0); // there is no specific timeout for BeginTransaction, uses ConnectTimeout
1108 internalTransaction.ConnectionHasBeenRestored = true;
1115 // SQLBUDT #20010853 - Promote, Commit and Rollback requests for
1116 // delegated transactions often happen while there is an open result
1117 // set, so we need to handle them by using a different MARS session,
1118 // otherwise we'll write on the physical state objects while someone
1119 // else is using it. When we don't have MARS enabled, we need to
1120 // lock the physical state object to syncronize it's use at least
1121 // until we increment the open results count. Once it's been
1122 // incremented the delegated transaction requests will fail, so they
1123 // won't stomp on anything.
1125 // We need to keep this lock through the duration of the TM reqeuest
1126 // so that we won't hijack a different request's data stream and a
1127 // different request won't hijack ours, so we have a lock here on
1128 // an object that the ExecTMReq will also lock, but since we're on
1129 // the same thread, the lock is a no-op.
1131 if (null != internalTransaction && internalTransaction.IsDelegated) {
1132 if (_parser.MARSOn) {
1133 stateObj = _parser.GetSession(this);
1134 mustPutSession = true;
1136 else if (internalTransaction.OpenResultsCount != 0) {
1137 throw SQL.CannotCompleteDelegatedTransactionWithOpenResults(this);
1141 // SQLBU #406778 - _parser may be nulled out during TdsExecuteTrannsactionManagerRequest.
1142 // Only use local variable after this call.
1143 _parser.TdsExecuteTransactionManagerRequest(null, requestType, transactionName, isoLevel,
1144 ConnectionOptions.ConnectTimeout, internalTransaction, stateObj, isDelegateControlRequest);
1147 if (mustPutSession) {
1148 parser.PutSession(stateObj);
1151 if (releaseConnectionLock) {
1152 ThreadHasParserLockForClose = false;
1153 _parserLock.Release();
1158 ////////////////////////////////////////////////////////////////////////////////////////
1159 // DISTRIBUTED TRANSACTION METHODS
1160 ////////////////////////////////////////////////////////////////////////////////////////
1162 override internal void DelegatedTransactionEnded() {
1164 base.DelegatedTransactionEnded();
1167 override protected byte[] GetDTCAddress() {
1168 byte[] dtcAddress = _parser.GetDTCAddress(ConnectionOptions.ConnectTimeout, _parser.GetSession(this));
1169 Debug.Assert(null != dtcAddress, "null dtcAddress?");
1173 override protected void PropagateTransactionCookie(byte[] cookie) {
1174 _parser.PropagateDistributedTransaction(cookie, ConnectionOptions.ConnectTimeout, _parser._physicalStateObj);
1177 ////////////////////////////////////////////////////////////////////////////////////////
1178 // LOGIN-RELATED METHODS
1179 ////////////////////////////////////////////////////////////////////////////////////////
1181 private void CompleteLogin(bool enlistOK) {
1182 _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
1184 if (_routingInfo == null) { // ROR should not affect state of connection recovery
1185 if (_federatedAuthenticationRequested && !_federatedAuthenticationAcknowledged) {
1186 Bid.Trace("<sc.SqlInternalConnectionTds.CompleteLogin|ERR> %d#, Server did not acknowledge the federated authentication request\n", ObjectID);
1187 throw SQL.ParsingError(ParsingErrorState.FedAuthNotAcknowledged);
1189 if (_federatedAuthenticationInfoRequested && !_federatedAuthenticationInfoReceived) {
1190 Bid.Trace("<sc.SqlInternalConnectionTds.CompleteLogin|ERR> %d#, Server never sent the requested federated authentication info\n", ObjectID);
1191 throw SQL.ParsingError(ParsingErrorState.FedAuthInfoNotReceived);
1194 if (!_sessionRecoveryAcknowledged) {
1195 _currentSessionData = null;
1196 if (_recoverySessionData != null) {
1197 throw SQL.CR_NoCRAckAtReconnection(this);
1200 if (_currentSessionData != null && _recoverySessionData==null) {
1201 _currentSessionData._initialDatabase = CurrentDatabase;
1202 _currentSessionData._initialCollation = _currentSessionData._collation;
1203 _currentSessionData._initialLanguage = _currentLanguage;
1205 bool isEncrypted = _parser.EncryptionOptions == EncryptionOptions.ON;
1206 if (_recoverySessionData != null) {
1207 if (_recoverySessionData._encrypted != isEncrypted) {
1208 throw SQL.CR_EncryptionChanged(this);
1211 if (_currentSessionData != null) {
1212 _currentSessionData._encrypted = isEncrypted;
1214 _recoverySessionData = null;
1217 Debug.Assert(SniContext.Snix_Login == Parser._physicalStateObj.SniContext, String.Format((IFormatProvider)null, "SniContext should be Snix_Login; actual Value: {0}", Parser._physicalStateObj.SniContext));
1218 _parser._physicalStateObj.SniContext = SniContext.Snix_EnableMars;
1219 _parser.EnableMars();
1221 _fConnectionOpen = true; // mark connection as open
1223 if (Bid.AdvancedOn) {
1224 Bid.Trace("<sc.SqlInternalConnectionTds.CompleteLogin|ADV> Post-Login Phase: Server connection obtained.\n");
1227 // for non-pooled connections, enlist in a distributed transaction
1228 // if present - and user specified to enlist
1229 if(enlistOK && ConnectionOptions.Enlist) {
1230 _parser._physicalStateObj.SniContext = SniContext.Snix_AutoEnlist;
1231 SysTx.Transaction tx = ADP.GetCurrentTransaction();
1234 _parser._physicalStateObj.SniContext=SniContext.Snix_Login;
1237 private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, SecureString newSecurePassword) {
1238 // create a new login record
1239 SqlLogin login = new SqlLogin();
1241 // gather all the settings the user set in the connection string or
1242 // properties and do the login
1243 CurrentDatabase = server.ResolvedDatabaseName;
1244 _currentPacketSize = ConnectionOptions.PacketSize;
1245 _currentLanguage = ConnectionOptions.CurrentLanguage;
1247 int timeoutInSeconds = 0;
1249 // If a timeout tick value is specified, compute the timeout based
1250 // upon the amount of time left in seconds.
1251 if (!timeout.IsInfinite)
1253 long t = timeout.MillisecondsRemaining/1000;
1254 if ((long)Int32.MaxValue > t)
1256 timeoutInSeconds = (int)t;
1260 login.authentication = ConnectionOptions.Authentication;
1261 login.timeout = timeoutInSeconds;
1262 login.userInstance = ConnectionOptions.UserInstance;
1263 login.hostName = ConnectionOptions.ObtainWorkstationId();
1264 login.userName = ConnectionOptions.UserID;
1265 login.password = ConnectionOptions.Password;
1266 login.applicationName = ConnectionOptions.ApplicationName;
1268 login.language = _currentLanguage;
1269 if (!login.userInstance) { // Do not send attachdbfilename or database to SSE primary instance
1270 login.database = CurrentDatabase;;
1271 login.attachDBFilename = ConnectionOptions.AttachDBFilename;
1274 // VSTS#795621 - Ensure ServerName is Sent During TdsLogin To Enable Sql Azure Connectivity.
1275 // Using server.UserServerName (versus ConnectionOptions.DataSource) since TdsLogin requires
1276 // serverName to always be non-null.
1277 login.serverName = server.UserServerName;
1279 login.useReplication = ConnectionOptions.Replication;
1280 login.useSSPI = ConnectionOptions.IntegratedSecurity
1281 || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && !_fedAuthRequired);
1282 login.packetSize = _currentPacketSize;
1283 login.newPassword = newPassword;
1284 login.readOnlyIntent = ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly;
1285 login.credential = _credential;
1286 if (newSecurePassword != null) {
1287 login.newSecurePassword = newSecurePassword;
1290 TdsEnums.FeatureExtension requestedFeatures = TdsEnums.FeatureExtension.None;
1291 if (ConnectionOptions.ConnectRetryCount>0) {
1292 requestedFeatures |= TdsEnums.FeatureExtension.SessionRecovery;
1293 _sessionRecoveryRequested = true;
1296 // If the workflow being used is Active Directory Password or Active Directory Integrated and server's prelogin response
1297 // for FEDAUTHREQUIRED option indicates Federated Authentication is required, we have to insert FedAuth Feature Extension
1298 // in Login7, indicating the intent to use Active Directory Authentication Library for SQL Server.
1299 if (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryPassword
1300 || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired)) {
1301 requestedFeatures |= TdsEnums.FeatureExtension.FedAuth;
1302 _federatedAuthenticationInfoRequested = true;
1303 _fedAuthFeatureExtensionData =
1304 new FederatedAuthenticationFeatureExtensionData {
1305 libraryType = TdsEnums.FedAuthLibrary.ADAL,
1306 authentication = ConnectionOptions.Authentication,
1307 fedAuthRequiredPreLoginResponse = _fedAuthRequired
1310 if (_accessTokenInBytes != null) {
1311 requestedFeatures |= TdsEnums.FeatureExtension.FedAuth;
1312 _fedAuthFeatureExtensionData = new FederatedAuthenticationFeatureExtensionData {
1313 libraryType = TdsEnums.FedAuthLibrary.SecurityToken,
1314 fedAuthRequiredPreLoginResponse = _fedAuthRequired,
1315 accessToken = _accessTokenInBytes
1317 // No need any further info from the server for token based authentication. So set _federatedAuthenticationRequested to true
1318 _federatedAuthenticationRequested = true;
1321 // The TCE and GLOBALTRANSACTIONS feature are implicitly requested
1322 requestedFeatures |= TdsEnums.FeatureExtension.Tce | TdsEnums.FeatureExtension.GlobalTransactions;
1323 _parser.TdsLogin(login, requestedFeatures, _recoverySessionData, _fedAuthFeatureExtensionData);
1326 private void LoginFailure() {
1327 Bid.Trace("<sc.SqlInternalConnectionTds.LoginFailure|RES|CPOOL> %d#\n", ObjectID);
1329 // If the parser was allocated and we failed, then we must have failed on
1330 // either the Connect or Login, either way we should call Disconnect.
1331 // Disconnect can be called if the connection is already closed - becomes
1332 // no-op, so no issues there.
1333 if (_parser != null) {
1335 _parser.Disconnect();
1340 private void OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectionOptions, SqlCredential credential,
1341 string newPassword, SecureString newSecurePassword, bool redirectedUserInstance) {
1342 bool useFailoverPartner; // should we use primary or secondary first
1343 ServerInfo dataSource = new ServerInfo(connectionOptions);
1344 string failoverPartner;
1346 if (null != PoolGroupProviderInfo) {
1347 useFailoverPartner = PoolGroupProviderInfo.UseFailoverPartner;
1348 failoverPartner = PoolGroupProviderInfo.FailoverPartner;
1351 // Only ChangePassword or SSE User Instance comes through this code path.
1352 useFailoverPartner = false;
1353 failoverPartner = ConnectionOptions.FailoverPartner;
1356 timeoutErrorInternal.SetInternalSourceType(useFailoverPartner ? SqlConnectionInternalSourceType.Failover : SqlConnectionInternalSourceType.Principle);
1358 bool hasFailoverPartner = !ADP.IsEmpty(failoverPartner);
1360 // Open the connection and Login
1362 timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.PreLoginBegin);
1363 if (hasFailoverPartner) {
1364 timeoutErrorInternal.SetFailoverScenario(true); // this is a failover scenario
1371 redirectedUserInstance,
1377 timeoutErrorInternal.SetFailoverScenario(false); // not a failover scenario
1378 LoginNoFailover(dataSource, newPassword, newSecurePassword, redirectedUserInstance,
1379 connectionOptions, credential, timeout);
1381 timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PostLogin);
1383 catch (Exception e) {
1385 if (ADP.IsCatchableExceptionType(e)) {
1390 timeoutErrorInternal.SetAllCompleteMarker();
1393 _parser._physicalStateObj.InvalidateDebugOnlyCopyOfSniContext();
1397 // Is the given Sql error one that should prevent retrying
1399 private bool IsDoNotRetryConnectError(SqlException exc) {
1401 return (TdsEnums.LOGON_FAILED == exc.Number) // actual logon failed, i.e. bad password
1402 || (TdsEnums.PASSWORD_EXPIRED == exc.Number) // actual logon failed, i.e. password isExpired
1403 || (TdsEnums.IMPERSONATION_FAILED == exc.Number) // Insuficient privelege for named pipe, among others
1404 || exc._doNotReconnect; // Exception explicitly supressed reconnection attempts
1407 // Attempt to login to a host that does not have a failover partner
1409 // Will repeatedly attempt to connect, but back off between each attempt so as not to clog the network.
1410 // Back off period increases for first few failures: 100ms, 200ms, 400ms, 800ms, then 1000ms for subsequent attempts
1412 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1413 // DEVNOTE: The logic in this method is paralleled by the logic in LoginWithFailover.
1414 // Changes to either one should be examined to see if they need to be reflected in the other
1415 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1416 private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureString newSecurePassword, bool redirectedUserInstance,
1417 SqlConnectionString connectionOptions, SqlCredential credential, TimeoutTimer timeout) {
1419 Debug.Assert(object.ReferenceEquals(connectionOptions, this.ConnectionOptions), "ConnectionOptions argument and property must be the same"); // consider removing the argument
1420 int routingAttempts = 0;
1421 ServerInfo originalServerInfo = serverInfo; // serverInfo may end up pointing to new object due to routing, original object is used to set CurrentDatasource
1423 if (Bid.AdvancedOn) {
1424 Bid.Trace("<sc.SqlInternalConnectionTds.LoginNoFailover|ADV> %d#, host=%ls\n", ObjectID, serverInfo.UserServerName);
1426 int sleepInterval = 100; //milliseconds to sleep (back off) between attempts.
1428 ResolveExtendedServerName(serverInfo, !redirectedUserInstance, connectionOptions);
1430 long timeoutUnitInterval = 0;
1431 Boolean isParallel = connectionOptions.MultiSubnetFailover || connectionOptions.TransparentNetworkIPResolution;
1434 // Determine unit interval
1435 if (timeout.IsInfinite) {
1436 timeoutUnitInterval = checked((long)(ADP.FailoverTimeoutStep * (1000L * ADP.DefaultConnectionTimeout)));
1439 timeoutUnitInterval = checked((long)(ADP.FailoverTimeoutStep * timeout.MillisecondsRemaining));
1442 // Only three ways out of this loop:
1443 // 1) Successfully connected
1444 // 2) Parser threw exception while main timer was expired
1445 // 3) Parser threw logon failure-related exception
1446 // 4) Parser threw exception in post-initial connect code,
1447 // such as pre-login handshake or during actual logon. (parser state != Closed)
1449 // Of these methods, only #1 exits normally. This preserves the call stack on the exception
1450 // back into the parser for the error cases.
1451 int attemptNumber = 0;
1452 TimeoutTimer intervalTimer = null;
1453 TimeoutTimer firstTransparentAttemptTimeout = TimeoutTimer.StartMillisecondsTimeout(ADP.FirstTransparentAttemptTimeout);
1454 TimeoutTimer attemptOneLoginTimeout = timeout;
1459 // Set timeout for this attempt, but don't exceed original timer
1460 long nextTimeoutInterval = checked(timeoutUnitInterval * attemptNumber);
1461 long milliseconds = timeout.MillisecondsRemaining;
1462 if (nextTimeoutInterval > milliseconds) {
1463 nextTimeoutInterval = milliseconds;
1465 intervalTimer = TimeoutTimer.StartMillisecondsTimeout(nextTimeoutInterval);
1468 // Re-allocate parser each time to make sure state is known
1469 // RFC 50002652 - if parser was created by previous attempt, dispose it to properly close the socket, if created
1470 if (_parser != null)
1471 _parser.Disconnect();
1473 _parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous);
1474 Debug.Assert(SniContext.Undefined== Parser._physicalStateObj.SniContext, String.Format((IFormatProvider)null, "SniContext should be Undefined; actual Value: {0}", Parser._physicalStateObj.SniContext));
1480 Boolean isFirstTransparentAttempt = connectionOptions.TransparentNetworkIPResolution && attemptNumber == 1;
1482 if(isFirstTransparentAttempt) {
1483 attemptOneLoginTimeout = firstTransparentAttemptTimeout;
1487 attemptOneLoginTimeout = intervalTimer;
1491 AttemptOneLogin( serverInfo,
1494 !isParallel, // ignore timeout for SniOpen call unless MSF , and TNIR
1495 attemptOneLoginTimeout,
1496 isFirstTransparentAttempt:isFirstTransparentAttempt);
1498 if (connectionOptions.MultiSubnetFailover && null != ServerProvidedFailOverPartner) {
1499 // connection succeeded: trigger exception if server sends failover partner and MultiSubnetFailover is used.
1500 throw SQL.MultiSubnetFailoverWithFailoverPartner(serverProvidedFailoverPartner: true, internalConnection: this);
1503 if (_routingInfo != null) {
1504 Bid.Trace("<sc.SqlInternalConnectionTds.LoginNoFailover> Routed to %ls", serverInfo.ExtendedServerName);
1506 if (routingAttempts > 0) {
1507 throw SQL.ROR_RecursiveRoutingNotSupported(this);
1510 if (timeout.IsExpired) {
1511 throw SQL.ROR_TimeoutAfterRoutingInfo(this);
1514 serverInfo = new ServerInfo(ConnectionOptions, _routingInfo, serverInfo.ResolvedServerName);
1515 timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.RoutingDestination);
1516 _originalClientConnectionId = _clientConnectionId;
1517 _routingDestination = serverInfo.UserServerName;
1519 // restore properties that could be changed by the environment tokens
1520 _currentPacketSize = ConnectionOptions.PacketSize;
1521 _currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage;
1522 CurrentDatabase = _originalDatabase = ConnectionOptions.InitialCatalog;
1523 _currentFailoverPartner = null;
1524 _instanceName = String.Empty;
1528 continue; // repeat the loop, but skip code reserved for failed connections (after the catch)
1531 break; // leave the while loop -- we've successfully connected
1534 catch (SqlException sqlex) {
1536 || TdsParserState.Closed != _parser.State
1537 || IsDoNotRetryConnectError(sqlex)
1538 || timeout.IsExpired) { // no more time to try again
1539 throw; // Caller will call LoginFailure()
1542 // Check sleep interval to make sure we won't exceed the timeout
1543 // Do this in the catch block so we can re-throw the current exception
1544 if (timeout.MillisecondsRemaining <= sleepInterval) {
1551 // We only get here when we failed to connect, but are going to re-try
1553 // Switch to failover logic if the server provided a partner
1554 if (null != ServerProvidedFailOverPartner) {
1555 if (connectionOptions.MultiSubnetFailover) {
1556 // connection failed: do not allow failover to server-provided failover partner if MultiSubnetFailover is set
1557 throw SQL.MultiSubnetFailoverWithFailoverPartner(serverProvidedFailoverPartner: true, internalConnection: this);
1559 Debug.Assert(ConnectionOptions.ApplicationIntent != ApplicationIntent.ReadOnly, "FAILOVER+AppIntent=RO: Should already fail (at LOGSHIPNODE in OnEnvChange)");
1561 timeoutErrorInternal.ResetAndRestartPhase();
1562 timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.PreLoginBegin);
1563 timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Failover);
1564 timeoutErrorInternal.SetFailoverScenario(true); // this is a failover scenario
1566 true, // start by using failover partner, since we already failed to connect to the primary
1568 ServerProvidedFailOverPartner,
1571 redirectedUserInstance,
1575 return; // LoginWithFailover successfully connected and handled entire connection setup
1578 // Sleep for a bit to prevent clogging the network with requests,
1579 // then update sleep interval for next iteration (max 1 second interval)
1580 if (Bid.AdvancedOn) {
1581 Bid.Trace("<sc.SqlInternalConnectionTds.LoginNoFailover|ADV> %d#, sleeping %d{milisec}\n", ObjectID, sleepInterval);
1583 Thread.Sleep(sleepInterval);
1584 sleepInterval = (sleepInterval < 500) ? sleepInterval * 2 : 1000;
1587 if (null != PoolGroupProviderInfo) {
1588 // We must wait for CompleteLogin to finish for to have the
1589 // env change from the server to know its designated failover
1590 // partner; save this information in _currentFailoverPartner.
1591 PoolGroupProviderInfo.FailoverCheck(this, false, connectionOptions, ServerProvidedFailOverPartner);
1593 CurrentDataSource = originalServerInfo.UserServerName;
1596 // Attempt to login to a host that has a failover partner
1598 // Connection & timeout sequence is
1599 // First target, timeout = interval * 1
1600 // second target, timeout = interval * 1
1602 // First target, timeout = interval * 2
1603 // Second target, timeout = interval * 2
1605 // First Target, timeout = interval * 3
1608 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1609 // DEVNOTE: The logic in this method is paralleled by the logic in LoginNoFailover.
1610 // Changes to either one should be examined to see if they need to be reflected in the other
1611 // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
1612 private void LoginWithFailover(
1613 bool useFailoverHost,
1614 ServerInfo primaryServerInfo,
1615 string failoverHost,
1617 SecureString newSecurePassword,
1618 bool redirectedUserInstance,
1619 SqlConnectionString connectionOptions,
1620 SqlCredential credential,
1621 TimeoutTimer timeout
1624 Debug.Assert(!connectionOptions.MultiSubnetFailover, "MultiSubnetFailover should not be set if failover partner is used");
1626 if (Bid.AdvancedOn) {
1627 Bid.Trace("<sc.SqlInternalConnectionTds.LoginWithFailover|ADV> %d#, useFailover=%d{bool}, primary=", ObjectID, useFailoverHost);
1628 Bid.PutStr(primaryServerInfo.UserServerName);
1629 Bid.PutStr(", failover=");
1630 Bid.PutStr(failoverHost);
1633 int sleepInterval = 100; //milliseconds to sleep (back off) between attempts.
1634 long timeoutUnitInterval;
1636 string protocol = ConnectionOptions.NetworkLibrary;
1637 ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost);
1639 ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, connectionOptions);
1640 if (null == ServerProvidedFailOverPartner) {// No point in resolving the failover partner when we're going to override it below
1641 // Don't resolve aliases if failover == primary //
1642 ResolveExtendedServerName(failoverServerInfo, !redirectedUserInstance && failoverHost != primaryServerInfo.UserServerName, connectionOptions);
1645 // Determine unit interval
1646 if (timeout.IsInfinite) {
1647 timeoutUnitInterval = checked((long) ADP.FailoverTimeoutStep * ADP.TimerFromSeconds(ADP.DefaultConnectionTimeout));
1650 timeoutUnitInterval = checked((long) (ADP.FailoverTimeoutStep * timeout.MillisecondsRemaining));
1653 // Initialize loop variables
1654 bool failoverDemandDone = false; // have we demanded for partner information yet (as necessary)?
1655 int attemptNumber = 0;
1657 // Only three ways out of this loop:
1658 // 1) Successfully connected
1659 // 2) Parser threw exception while main timer was expired
1660 // 3) Parser threw logon failure-related exception (LOGON_FAILED, PASSWORD_EXPIRED, etc)
1662 // Of these methods, only #1 exits normally. This preserves the call stack on the exception
1663 // back into the parser for the error cases.
1665 // Set timeout for this attempt, but don't exceed original timer
1666 long nextTimeoutInterval = checked(timeoutUnitInterval * ((attemptNumber / 2) + 1));
1667 long milliseconds = timeout.MillisecondsRemaining;
1668 if (nextTimeoutInterval > milliseconds) {
1669 nextTimeoutInterval = milliseconds;
1672 TimeoutTimer intervalTimer = TimeoutTimer.StartMillisecondsTimeout(nextTimeoutInterval);
1674 // Re-allocate parser each time to make sure state is known
1675 // RFC 50002652 - if parser was created by previous attempt, dispose it to properly close the socket, if created
1676 if (_parser != null)
1677 _parser.Disconnect();
1679 _parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous);
1680 Debug.Assert(SniContext.Undefined== Parser._physicalStateObj.SniContext, String.Format((IFormatProvider)null, "SniContext should be Undefined; actual Value: {0}", Parser._physicalStateObj.SniContext));
1682 ServerInfo currentServerInfo;
1683 if (useFailoverHost) {
1684 if (!failoverDemandDone) {
1685 FailoverPermissionDemand();
1686 failoverDemandDone = true;
1689 // Primary server may give us a different failover partner than the connection string indicates. Update it
1690 if (null != ServerProvidedFailOverPartner && failoverServerInfo.ResolvedServerName != ServerProvidedFailOverPartner) {
1691 if (Bid.AdvancedOn) {
1692 Bid.Trace("<sc.SqlInternalConnectionTds.LoginWithFailover|ADV> %d#, new failover partner=%ls\n", ObjectID, ServerProvidedFailOverPartner);
1694 failoverServerInfo.SetDerivedNames(protocol, ServerProvidedFailOverPartner);
1696 currentServerInfo = failoverServerInfo;
1697 timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Failover);
1700 currentServerInfo = primaryServerInfo;
1701 timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Principle);
1705 // Attempt login. Use timerInterval for attempt timeout unless infinite timeout was requested.
1710 false, // Use timeout in SniOpen
1715 if (_routingInfo != null) {
1716 // We are in login with failover scenation and server sent routing information
1717 // If it is read-only routing - we did not supply AppIntent=RO (it should be checked before)
1718 // If it is something else, not known yet (future server) - this client is not designed to support this.
1719 // In any case, server should not have sent the routing info.
1720 Bid.Trace("<sc.SqlInternalConnectionTds.LoginWithFailover> Routed to %ls", _routingInfo.ServerName);
1721 throw SQL.ROR_UnexpectedRoutingInfo(this);
1724 break; // leave the while loop -- we've successfully connected
1726 catch (SqlException sqlex) {
1727 if (IsDoNotRetryConnectError(sqlex)
1728 || timeout.IsExpired)
1729 { // no more time to try again
1730 throw; // Caller will call LoginFailure()
1733 if (IsConnectionDoomed) {
1737 if (1 == attemptNumber % 2) {
1738 // Check sleep interval to make sure we won't exceed the original timeout
1739 // Do this in the catch block so we can re-throw the current exception
1740 if (timeout.MillisecondsRemaining <= sleepInterval) {
1748 // We only get here when we failed to connect, but are going to re-try
1750 // After trying to connect to both servers fails, sleep for a bit to prevent clogging
1751 // the network with requests, then update sleep interval for next iteration (max 1 second interval)
1752 if (1 == attemptNumber % 2) {
1753 if (Bid.AdvancedOn) {
1754 Bid.Trace("<sc.SqlInternalConnectionTds.LoginWithFailover|ADV> %d#, sleeping %d{milisec}\n", ObjectID, sleepInterval);
1756 Thread.Sleep(sleepInterval);
1757 sleepInterval = (sleepInterval < 500) ? sleepInterval * 2 : 1000;
1760 // Update attempt number and target host
1762 useFailoverHost = !useFailoverHost;
1765 // If we get here, connection/login succeeded! Just a few more checks & record-keeping
1767 // if connected to failover host, but said host doesn't have DbMirroring set up, throw an error
1768 if (useFailoverHost && null == ServerProvidedFailOverPartner) {
1769 throw SQL.InvalidPartnerConfiguration(failoverHost, CurrentDatabase);
1772 if (null != PoolGroupProviderInfo) {
1773 // We must wait for CompleteLogin to finish for to have the
1774 // env change from the server to know its designated failover
1775 // partner; save this information in _currentFailoverPartner.
1776 PoolGroupProviderInfo.FailoverCheck(this, useFailoverHost, connectionOptions, ServerProvidedFailOverPartner);
1778 CurrentDataSource = (useFailoverHost ? failoverHost : primaryServerInfo.UserServerName);
1781 private void ResolveExtendedServerName(ServerInfo serverInfo, bool aliasLookup, SqlConnectionString options) {
1782 if (serverInfo.ExtendedServerName == null) {
1783 string host = serverInfo.UserServerName;
1784 string protocol = serverInfo.UserProtocol;
1786 if (aliasLookup) { // We skip this for UserInstances...
1787 // Perform registry lookup to see if host is an alias. It will appropriately set host and protocol, if an Alias.
1788 // Check if it was already resolved, during CR reconnection _currentSessionData values will be copied from
1789 // _reconnectSessonData of the previous connection
1790 if (_currentSessionData != null && !string.IsNullOrEmpty(host)) {
1791 Tuple<string, string> hostPortPair;
1792 if (_currentSessionData._resolvedAliases.TryGetValue(host, out hostPortPair)) {
1793 host = hostPortPair.Item1;
1794 protocol = hostPortPair.Item2;
1797 TdsParserStaticMethods.AliasRegistryLookup(ref host, ref protocol);
1798 _currentSessionData._resolvedAliases.Add(serverInfo.UserServerName, new Tuple<string, string>(host, protocol));
1802 TdsParserStaticMethods.AliasRegistryLookup(ref host, ref protocol);
1806 if (options.EnforceLocalHost) {
1807 // verify LocalHost for |DataDirectory| usage
1808 SqlConnectionString.VerifyLocalHostAndFixup(ref host, true, true /*fix-up to "."*/);
1812 serverInfo.SetDerivedNames(protocol, host);
1816 // Common code path for making one attempt to establish a connection and log in to server.
1817 private void AttemptOneLogin(ServerInfo serverInfo, string newPassword, SecureString newSecurePassword, bool ignoreSniOpenTimeout, TimeoutTimer timeout, bool withFailover = false, bool isFirstTransparentAttempt = true) {
1818 if (Bid.AdvancedOn) {
1819 Bid.Trace("<sc.SqlInternalConnectionTds.AttemptOneLogin|ADV> %d#, timout=%I64d{msec}, server=", ObjectID, timeout.MillisecondsRemaining);
1820 Bid.PutStr(serverInfo.ExtendedServerName);
1824 _routingInfo = null; // forget routing information
1826 _parser._physicalStateObj.SniContext = SniContext.Snix_Connect;
1828 _parser.Connect(serverInfo,
1830 ignoreSniOpenTimeout,
1831 timeout.LegacyTimerExpire,
1832 ConnectionOptions.Encrypt,
1833 ConnectionOptions.TrustServerCertificate,
1834 ConnectionOptions.IntegratedSecurity,
1836 isFirstTransparentAttempt,
1837 ConnectionOptions.Authentication);
1839 timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake);
1840 timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.LoginBegin);
1842 _parser._physicalStateObj.SniContext = SniContext.Snix_Login;
1843 this.Login(serverInfo, timeout, newPassword, newSecurePassword);
1845 timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth);
1846 timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.PostLogin);
1848 CompleteLogin(!ConnectionOptions.Pooling);
1850 timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PostLogin);
1854 internal void FailoverPermissionDemand() {
1855 if (null != PoolGroupProviderInfo) {
1856 PoolGroupProviderInfo.FailoverPermissionDemand();
1860 ////////////////////////////////////////////////////////////////////////////////////////
1861 // PREPARED COMMAND METHODS
1862 ////////////////////////////////////////////////////////////////////////////////////////
1864 protected override object ObtainAdditionalLocksForClose() {
1865 bool obtainParserLock = !ThreadHasParserLockForClose;
1866 Debug.Assert(obtainParserLock || _parserLock.ThreadMayHaveLock(), "Thread claims to have lock, but lock is not taken");
1867 if (obtainParserLock) {
1868 _parserLock.Wait(canReleaseFromAnyThread: false);
1869 ThreadHasParserLockForClose = true;
1871 return obtainParserLock;
1874 protected override void ReleaseAdditionalLocksForClose(object lockToken) {
1875 Debug.Assert(lockToken is bool, "Lock token should be boolean");
1876 if ((bool)lockToken) {
1877 ThreadHasParserLockForClose = false;
1878 _parserLock.Release();
1882 // called by SqlConnection.RepairConnection which is a relatevly expensive way of repair inner connection
1883 // prior to execution of request, used from EnlistTransaction, EnlistDistributedTransaction and ChangeDatabase
1884 internal bool GetSessionAndReconnectIfNeeded(SqlConnection parent, int timeout = 0) {
1886 Debug.Assert(!ThreadHasParserLockForClose, "Cannot call this method if caller has parser lock");
1887 if (ThreadHasParserLockForClose) {
1888 return false; // we cannot restore if we cannot release lock
1891 _parserLock.Wait(canReleaseFromAnyThread: false);
1892 ThreadHasParserLockForClose = true; // In case of error, let the connection know that we already own the parser lock
1893 bool releaseConnectionLock = true;
1896 RuntimeHelpers.PrepareConstrainedRegions();
1899 TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
1900 RuntimeHelpers.PrepareConstrainedRegions();
1902 tdsReliabilitySection.Start();
1904 Task reconnectTask = parent.ValidateAndReconnect(() => {
1905 ThreadHasParserLockForClose = false;
1906 _parserLock.Release();
1907 releaseConnectionLock = false;
1909 if (reconnectTask != null) {
1910 AsyncHelper.WaitForCompletion(reconnectTask, timeout);
1917 tdsReliabilitySection.Stop();
1921 catch (System.OutOfMemoryException) {
1922 DoomThisConnection();
1925 catch (System.StackOverflowException) {
1926 DoomThisConnection();
1929 catch (System.Threading.ThreadAbortException) {
1930 DoomThisConnection();
1935 if (releaseConnectionLock) {
1936 ThreadHasParserLockForClose = false;
1937 _parserLock.Release();
1942 ////////////////////////////////////////////////////////////////////////////////////////
1944 ////////////////////////////////////////////////////////////////////////////////////////
1946 internal void BreakConnection() {
1947 var connection = Connection;
1948 Bid.Trace("<sc.SqlInternalConnectionTds.BreakConnection|RES|CPOOL> %d#, Breaking connection.\n", ObjectID);
1949 DoomThisConnection(); // Mark connection as unusable, so it will be destroyed
1950 if (null != connection) {
1955 internal bool IgnoreEnvChange { // true if we are only draining environment change tokens, used by TdsParser
1957 return _routingInfo != null; // connection was routed, ignore rest of env change
1961 internal void OnEnvChange(SqlEnvChange rec) {
1962 Debug.Assert(!IgnoreEnvChange,"This function should not be called if IgnoreEnvChange is set!");
1964 case TdsEnums.ENV_DATABASE:
1965 // If connection is not open and recovery is not in progresss, store the server value as the original.
1966 if (!_fConnectionOpen && _recoverySessionData == null) {
1967 _originalDatabase = rec.newValue;
1970 CurrentDatabase = rec.newValue;
1973 case TdsEnums.ENV_LANG:
1974 // If connection is not open and recovery is not in progresss, store the server value as the original.
1975 if (!_fConnectionOpen && _recoverySessionData == null) {
1976 _originalLanguage = rec.newValue;
1979 _currentLanguage = rec.newValue; // TODO: finish this.
1982 case TdsEnums.ENV_PACKETSIZE:
1983 _currentPacketSize = Int32.Parse(rec.newValue, CultureInfo.InvariantCulture);
1986 case TdsEnums.ENV_COLLATION:
1987 if (_currentSessionData != null) {
1988 _currentSessionData._collation = rec.newCollation;
1992 case TdsEnums.ENV_CHARSET:
1993 case TdsEnums.ENV_LOCALEID:
1994 case TdsEnums.ENV_COMPFLAGS:
1995 case TdsEnums.ENV_BEGINTRAN:
1996 case TdsEnums.ENV_COMMITTRAN:
1997 case TdsEnums.ENV_ROLLBACKTRAN:
1998 case TdsEnums.ENV_ENLISTDTC:
1999 case TdsEnums.ENV_DEFECTDTC:
2000 // only used on parser
2003 case TdsEnums.ENV_LOGSHIPNODE:
2004 if (ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly) {
2005 throw SQL.ROR_FailoverNotSupportedServer(this);
2007 _currentFailoverPartner = rec.newValue;
2010 case TdsEnums.ENV_PROMOTETRANSACTION:
2011 PromotedDTCToken = rec.newBinValue;
2014 case TdsEnums.ENV_TRANSACTIONENDED:
2017 case TdsEnums.ENV_TRANSACTIONMANAGERADDRESS:
2018 // For now we skip these Yukon only env change notifications
2021 case TdsEnums.ENV_SPRESETCONNECTIONACK:
2022 // connection is being reset
2023 if (_currentSessionData != null) {
2024 _currentSessionData.Reset();
2028 case TdsEnums.ENV_USERINSTANCE:
2029 _instanceName = rec.newValue;
2032 case TdsEnums.ENV_ROUTING:
2033 if (Bid.AdvancedOn) {
2034 Bid.Trace("<sc.SqlInternalConnectionTds.OnEnvChange> %d#, Received routing info\n", ObjectID);
2036 if (string.IsNullOrEmpty(rec.newRoutingInfo.ServerName) || rec.newRoutingInfo.Protocol != 0 || rec.newRoutingInfo.Port == 0) {
2037 throw SQL.ROR_InvalidRoutingInfo(this);
2039 _routingInfo = rec.newRoutingInfo;
2043 Debug.Assert(false, "Missed token in EnvChange!");
2048 internal void OnLoginAck(SqlLoginAck rec) {
2051 if (_recoverySessionData != null) {
2052 if (_recoverySessionData._tdsVersion != rec.tdsVersion) {
2053 throw SQL.CR_TDSVersionNotPreserved(this);
2056 if (_currentSessionData != null) {
2057 _currentSessionData._tdsVersion = rec.tdsVersion;
2062 /// Generates (if appropriate) and sends a Federated Authentication Access token to the server, using the Federated Authentication Info.
2064 /// <param name="fedAuthInfo">Federated Authentication Info.</param>
2065 internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) {
2066 Debug.Assert((ConnectionOptions.HasUserIdKeyword && ConnectionOptions.HasPasswordKeyword)
2067 || _credential != null
2068 || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired),
2069 "Credentials aren't provided for calling ADAL");
2070 Debug.Assert(fedAuthInfo != null, "info should not be null.");
2071 Debug.Assert(_dbConnectionPoolAuthenticationContextKey == null, "_dbConnectionPoolAuthenticationContextKey should be null.");
2073 Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, Generating federated authentication token\n", ObjectID);
2075 DbConnectionPoolAuthenticationContext dbConnectionPoolAuthenticationContext = null;
2077 // We want to refresh the token without taking a lock on the context, allowed when the access token is expiring within the next 10 mins.
2078 bool attemptRefreshTokenUnLocked = false;
2080 // We want to refresh the token, if taking the lock on the authentication context is successful.
2081 bool attemptRefreshTokenLocked = false;
2083 // The Federated Authentication returned by TryGetFedAuthTokenLocked or GetFedAuthToken.
2084 SqlFedAuthToken fedAuthToken = null;
2086 if (_dbConnectionPool != null) {
2087 Debug.Assert(_dbConnectionPool.AuthenticationContexts != null);
2089 // Construct the dbAuthenticationContextKey with information from FedAuthInfo and store for later use, when inserting in to the token cache.
2090 _dbConnectionPoolAuthenticationContextKey = new DbConnectionPoolAuthenticationContextKey(fedAuthInfo.stsurl, fedAuthInfo.spn);
2092 // Try to retrieve the authentication context from the pool, if one does exist for this key.
2093 if (_dbConnectionPool.AuthenticationContexts.TryGetValue(_dbConnectionPoolAuthenticationContextKey, out dbConnectionPoolAuthenticationContext)) {
2094 Debug.Assert(dbConnectionPoolAuthenticationContext != null, "dbConnectionPoolAuthenticationContext should not be null.");
2096 // The timespan between UTCNow and the token expiry.
2097 TimeSpan contextValidity = dbConnectionPoolAuthenticationContext.ExpirationTime.Subtract(DateTime.UtcNow);
2099 // If the authentication context is expiring within next 10 minutes, lets just re-create a token for this connection attempt.
2100 // And on successful login, try to update the cache with the new token.
2101 if (contextValidity <= _dbAuthenticationContextUnLockedRefreshTimeSpan) {
2102 Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, The expiration time is less than 10 mins, so trying to get new access token regardless of if an other thread is also trying to update it.The expiration time is %s. Current Time is %s.\n", ObjectID, dbConnectionPoolAuthenticationContext.ExpirationTime.ToLongTimeString(), DateTime.UtcNow.ToLongTimeString());
2104 attemptRefreshTokenUnLocked = true;
2108 // Checking if any failpoints are enabled.
2109 else if (_forceExpiryUnLocked) {
2110 attemptRefreshTokenUnLocked = true;
2112 else if (_forceExpiryLocked) {
2113 attemptRefreshTokenLocked = TryGetFedAuthTokenLocked(fedAuthInfo, dbConnectionPoolAuthenticationContext, out fedAuthToken);
2117 // If the token is expiring within the next 45 mins, try to fetch a new token, if there is no thread already doing it.
2118 // If a thread is already doing the refresh, just use the existing token in the cache and proceed.
2119 else if (contextValidity <= _dbAuthenticationContextLockedRefreshTimeSpan) {
2120 if (Bid.AdvancedOn) {
2121 Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, The authentication context needs a refresh.The expiration time is %s. Current Time is %s.\n", ObjectID, dbConnectionPoolAuthenticationContext.ExpirationTime.ToLongTimeString(), DateTime.UtcNow.ToLongTimeString());
2124 // Call the function which tries to acquire a lock over the authentication context before trying to update.
2125 // If the lock could not be obtained, it will return false, without attempting to fetch a new token.
2126 attemptRefreshTokenLocked = TryGetFedAuthTokenLocked(fedAuthInfo, dbConnectionPoolAuthenticationContext, out fedAuthToken);
2128 // If TryGetFedAuthTokenLocked returns true, it means lock was obtained and fedAuthToken should not be null.
2129 // If there was an exception in retrieving the new token, TryGetFedAuthTokenLocked should have thrown, so we won't be here.
2130 Debug.Assert(!attemptRefreshTokenLocked || fedAuthToken != null, "Either Lock should not have been obtained or fedAuthToken should not be null.");
2131 Debug.Assert(!attemptRefreshTokenLocked || _newDbConnectionPoolAuthenticationContext != null, "Either Lock should not have been obtained or _newDbConnectionPoolAuthenticationContext should not be null.");
2133 // Indicate in Bid Trace that we are successful with the update.
2134 if (attemptRefreshTokenLocked) {
2135 Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, The attempt to get a new access token succeeded under the locked mode.");
2139 else if (Bid.AdvancedOn) {
2140 Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, Found an authentication context in the cache that does not need a refresh at this time. Re-using the cached token.\n", ObjectID);
2145 // dbConnectionPoolAuthenticationContext will be null if either this is the first connection attempt in the pool or pooling is disabled.
2146 if (dbConnectionPoolAuthenticationContext == null || attemptRefreshTokenUnLocked) {
2147 // Get the Federated Authentication Token.
2148 fedAuthToken = GetFedAuthToken(fedAuthInfo);
2149 Debug.Assert(fedAuthToken != null, "fedAuthToken should not be null.");
2151 if (_dbConnectionPool != null) {
2152 // GetFedAuthToken should have updated _newDbConnectionPoolAuthenticationContext.
2153 Debug.Assert(_newDbConnectionPoolAuthenticationContext != null, "_newDbConnectionPoolAuthenticationContext should not be null.");
2156 else if (!attemptRefreshTokenLocked) {
2157 Debug.Assert(dbConnectionPoolAuthenticationContext != null, "dbConnectionPoolAuthenticationContext should not be null.");
2158 Debug.Assert(fedAuthToken == null, "fedAuthToken should be null in this case.");
2159 Debug.Assert(_newDbConnectionPoolAuthenticationContext == null, "_newDbConnectionPoolAuthenticationContext should be null.");
2161 fedAuthToken = new SqlFedAuthToken();
2163 // If the code flow is here, then we are re-using the context from the cache for this connection attempt and not
2164 // generating a new access token on this thread.
2165 fedAuthToken.accessToken = dbConnectionPoolAuthenticationContext.AccessToken;
2168 Debug.Assert(fedAuthToken != null && fedAuthToken.accessToken != null, "fedAuthToken and fedAuthToken.accessToken cannot be null.");
2169 _parser.SendFedAuthToken(fedAuthToken);
2173 /// Tries to acquire a lock on the authentication context. If successful in acquiring the lock, gets a new token and assigns it in the out parameter. Else returns false.
2175 /// <param name="fedAuthInfo">Federated Authentication Info</param>
2176 /// <param name="dbConnectionPoolAuthenticationContext">Authentication Context cached in the connection pool.</param>
2177 /// <param name="fedAuthToken">Out parameter, carrying the token if we acquired a lock and got the token.</param>
2178 /// <returns></returns>
2179 internal bool TryGetFedAuthTokenLocked(SqlFedAuthInfo fedAuthInfo, DbConnectionPoolAuthenticationContext dbConnectionPoolAuthenticationContext, out SqlFedAuthToken fedAuthToken) {
2181 Debug.Assert(fedAuthInfo != null, "fedAuthInfo should not be null.");
2182 Debug.Assert(dbConnectionPoolAuthenticationContext != null, "dbConnectionPoolAuthenticationContext should not be null.");
2184 fedAuthToken = null;
2186 // Variable which indicates if we did indeed manage to acquire the lock on the authentication context, to try update it.
2187 bool authenticationContextLocked = false;
2189 // Prepare CER to ensure the lock on authentication context is released.
2190 RuntimeHelpers.PrepareConstrainedRegions();
2192 // Try to obtain a lock on the context. If acquired, this thread got the opportunity to update.
2193 // Else some other thread is already updating it, so just proceed forward with the existing token in the cache.
2194 if (dbConnectionPoolAuthenticationContext.LockToUpdate()) {
2195 Bid.Trace("<sc.SqlInternalConnectionTds.TryGetFedAuthTokenLocked> %d#, Acquired the lock to update the authentication context.The expiration time is %s. Current Time is %s.\n", ObjectID, dbConnectionPoolAuthenticationContext.ExpirationTime.ToLongTimeString(), DateTime.UtcNow.ToLongTimeString());
2197 authenticationContextLocked = true;
2200 Bid.Trace("<sc.SqlInternalConnectionTds.TryGetFedAuthTokenLocked> %d#, Refreshing the context is already in progress by another thread.\n", ObjectID);
2203 if (authenticationContextLocked) {
2204 // Get the Federated Authentication Token.
2205 fedAuthToken = GetFedAuthToken(fedAuthInfo);
2206 Debug.Assert(fedAuthToken != null, "fedAuthToken should not be null.");
2210 if (authenticationContextLocked) {
2211 // Release the lock we took on the authentication context, even if we have not yet updated the cache with the new context. Login process can fail at several places after this step and so there is no guarantee that the new context will make it to the cache. So we shouldn't miss resetting the flag. With the reset, at-least another thread may have a chance to update it.
2212 dbConnectionPoolAuthenticationContext.ReleaseLockToUpdate();
2216 return authenticationContextLocked;
2220 /// Get the Federated Authentication Token.
2222 /// <param name="fedAuthInfo">Information obtained from server as Federated Authentication Info.</param>
2223 /// <returns>SqlFedAuthToken</returns>
2224 internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) {
2226 Debug.Assert(fedAuthInfo != null, "fedAuthInfo should not be null.");
2228 // No:of milliseconds to sleep for the inital back off.
2229 int sleepInterval = 100;
2231 // No:of attempts, for tracing purposes, if we underwent retries.
2232 int numberOfAttempts = 0;
2234 // Object that will be returned to the caller, containing all required data about the token.
2235 SqlFedAuthToken fedAuthToken = new SqlFedAuthToken();
2237 // Username to use in error messages.
2238 string username = null;
2243 if (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated) {
2244 username = TdsEnums.NTAUTHORITYANONYMOUSLOGON;
2245 fedAuthToken.accessToken = ADALNativeWrapper.ADALGetAccessTokenForWindowsIntegrated(fedAuthInfo.stsurl,
2247 _clientConnectionId, ActiveDirectoryAuthentication.AdoClientId,
2248 ref fedAuthToken.expirationFileTime);
2250 else if (_credential != null) {
2251 username = _credential.UserId;
2252 fedAuthToken.accessToken = ADALNativeWrapper.ADALGetAccessToken(_credential.UserId,
2253 _credential.Password,
2256 _clientConnectionId,
2257 ActiveDirectoryAuthentication.AdoClientId,
2258 ref fedAuthToken.expirationFileTime);
2261 username = ConnectionOptions.UserID;
2262 fedAuthToken.accessToken = ADALNativeWrapper.ADALGetAccessToken(ConnectionOptions.UserID,
2263 ConnectionOptions.Password,
2266 _clientConnectionId,
2267 ActiveDirectoryAuthentication.AdoClientId,
2268 ref fedAuthToken.expirationFileTime);
2271 Debug.Assert(fedAuthToken.accessToken != null, "AccessToken should not be null.");
2273 if (_forceAdalRetry) {
2274 // 3399614468 is 0xCAA20004L just for testing.
2275 throw new AdalException("Force retry in GetFedAuthToken", ActiveDirectoryAuthentication.GetAccessTokenTansisentError, 3399614468, 6);
2278 // Break out of the retry loop in successful case.
2281 catch (AdalException adalException) {
2283 uint errorCategory = adalException.GetCategory();
2285 if (ActiveDirectoryAuthentication.GetAccessTokenTansisentError != errorCategory
2286 || _timeout.IsExpired
2287 || _timeout.MillisecondsRemaining <= sleepInterval) {
2289 string errorStatus = adalException.GetStatus().ToString("X");
2291 Bid.Trace("<sc.SqlInternalConnectionTds.GetFedAuthToken.ADALException category:> %d# <error:> %s#\n", (int)errorCategory, errorStatus);
2294 SqlErrorCollection sqlErs = new SqlErrorCollection();
2295 sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, Res.GetString(Res.SQL_ADALFailure, username, ConnectionOptions.Authentication.ToString("G")), ActiveDirectoryAuthentication.AdalGetAccessTokenFunctionName, 0));
2298 string errorMessage1 = Res.GetString(Res.SQL_ADALInnerException, errorStatus, adalException.GetState());
2299 sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, errorMessage1, ActiveDirectoryAuthentication.AdalGetAccessTokenFunctionName, 0));
2302 if (!string.IsNullOrEmpty(adalException.Message)) {
2303 sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, adalException.Message, ActiveDirectoryAuthentication.AdalGetAccessTokenFunctionName, 0));
2305 SqlException exc = SqlException.CreateException(sqlErs, "", this);
2309 Bid.Trace("<sc.SqlInternalConnectionTds.GetFedAuthToken|ADV> %d#, sleeping %d{Milliseconds}\n", ObjectID, sleepInterval);
2310 Bid.Trace("<sc.SqlInternalConnectionTds.GetFedAuthToken|ADV> %d#, remaining %d{Milliseconds}\n", ObjectID, _timeout.MillisecondsRemaining);
2312 Thread.Sleep(sleepInterval);
2317 Debug.Assert(fedAuthToken != null, "fedAuthToken should not be null.");
2318 Debug.Assert(fedAuthToken.accessToken != null && fedAuthToken.accessToken.Length > 0, "fedAuthToken.accessToken should not be null or empty.");
2320 // Store the newly generated token in _newDbConnectionPoolAuthenticationContext, only if using pooling.
2321 if (_dbConnectionPool != null) {
2322 DateTime expirationTime = DateTime.FromFileTimeUtc(fedAuthToken.expirationFileTime);
2323 _newDbConnectionPoolAuthenticationContext = new DbConnectionPoolAuthenticationContext(fedAuthToken.accessToken, expirationTime);
2326 Bid.Trace("<sc.SqlInternalConnectionTds.GetFedAuthToken> %d#, Finished generating federated authentication token.\n", ObjectID);
2328 return fedAuthToken;
2331 internal void OnFeatureExtAck(int featureId, byte[] data) {
2332 if (_routingInfo != null) {
2335 switch (featureId) {
2336 case TdsEnums.FEATUREEXT_SRECOVERY: {
2337 // Session recovery not requested
2338 if (!_sessionRecoveryRequested) {
2339 throw SQL.ParsingErrorFeatureId(ParsingErrorState.UnrequestedFeatureAckReceived, featureId);
2341 _sessionRecoveryAcknowledged = true;
2344 foreach (var s in _currentSessionData._delta) {
2345 Debug.Assert(s==null, "Delta should be null at this point");
2348 Debug.Assert(_currentSessionData._unrecoverableStatesCount == 0, "Unrecoverable states count should be 0");
2351 while (i < data.Length) {
2352 byte stateId = data[i]; i++;
2354 byte bLen = data[i]; i++;
2356 len = BitConverter.ToInt32(data, i); i += 4;
2361 byte[] stateData = new byte[len];
2362 Buffer.BlockCopy(data, i, stateData, 0, len); i += len;
2363 if (_recoverySessionData == null) {
2364 _currentSessionData._initialState[stateId] = stateData;
2367 _currentSessionData._delta[stateId] = new SessionStateRecord { _data = stateData, _dataLength = len, _recoverable = true, _version = 0 };
2368 _currentSessionData._deltaDirty = true;
2373 case TdsEnums.FEATUREEXT_FEDAUTH: {
2374 if (Bid.AdvancedOn) {
2375 Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck> %d#, Received feature extension acknowledgement for federated authentication\n", ObjectID);
2377 if (!_federatedAuthenticationRequested) {
2378 Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Did not request federated authentication\n", ObjectID);
2379 throw SQL.ParsingErrorFeatureId(ParsingErrorState.UnrequestedFeatureAckReceived, featureId);
2382 Debug.Assert(_fedAuthFeatureExtensionData != null, "_fedAuthFeatureExtensionData must not be null when _federatedAuthenticatonRequested == true");
2384 switch (_fedAuthFeatureExtensionData.Value.libraryType) {
2385 case TdsEnums.FedAuthLibrary.ADAL:
2386 case TdsEnums.FedAuthLibrary.SecurityToken:
2387 // The server shouldn't have sent any additional data with the ack (like a nonce)
2388 if (data.Length != 0) {
2389 Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Federated authentication feature extension ack for ADAL and Security Token includes extra data\n", ObjectID);
2390 throw SQL.ParsingError(ParsingErrorState.FedAuthFeatureAckContainsExtraData);
2395 Debug.Assert(false, "Unknown _fedAuthLibrary type");
2396 Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Attempting to use unknown federated authentication library\n", ObjectID);
2397 throw SQL.ParsingErrorLibraryType(ParsingErrorState.FedAuthFeatureAckUnknownLibraryType, (int)_fedAuthFeatureExtensionData.Value.libraryType);
2399 _federatedAuthenticationAcknowledged = true;
2401 // If a new authentication context was used as part of this login attempt, try to update the new context in the cache, i.e.dbConnectionPool.AuthenticationContexts.
2402 // ChooseAuthenticationContextToUpdate will take care that only the context which has more validity will remain in the cache, based on the Update logic.
2403 if (_newDbConnectionPoolAuthenticationContext != null)
2405 Debug.Assert(_dbConnectionPool != null, "_dbConnectionPool should not be null when _newDbConnectionPoolAuthenticationContext != null.");
2407 DbConnectionPoolAuthenticationContext newAuthenticationContextInCacheAfterAddOrUpdate = _dbConnectionPool.AuthenticationContexts.AddOrUpdate(_dbConnectionPoolAuthenticationContextKey, _newDbConnectionPoolAuthenticationContext,
2408 (key, oldValue) => DbConnectionPoolAuthenticationContext.ChooseAuthenticationContextToUpdate(oldValue, _newDbConnectionPoolAuthenticationContext));
2410 Debug.Assert(newAuthenticationContextInCacheAfterAddOrUpdate != null, "newAuthenticationContextInCacheAfterAddOrUpdate should not be null.");
2412 // For debug purposes, assert and trace if we ended up updating the cache with the new one or some other thread's context won the expiration ----.
2413 if (newAuthenticationContextInCacheAfterAddOrUpdate == _newDbConnectionPoolAuthenticationContext) {
2414 Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Updated the new dbAuthenticationContext in the _dbConnectionPool.AuthenticationContexts. \n", ObjectID);
2417 Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, AddOrUpdate attempted on _dbConnectionPool.AuthenticationContexts, but it did not update the new value. \n", ObjectID);
2424 case TdsEnums.FEATUREEXT_TCE: {
2425 if (Bid.AdvancedOn) {
2426 Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck> %d#, Received feature extension acknowledgement for TCE\n", ObjectID);
2429 if (data.Length < 1) {
2430 Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Unknown version number for TCE\n", ObjectID);
2431 throw SQL.ParsingError(ParsingErrorState.TceUnknownVersion);
2434 byte supportedTceVersion = data[0];
2435 if (0 == supportedTceVersion || supportedTceVersion > TdsEnums.MAX_SUPPORTED_TCE_VERSION) {
2436 Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Invalid version number for TCE\n", ObjectID);
2437 throw SQL.ParsingErrorValue(ParsingErrorState.TceInvalidVersion, supportedTceVersion);
2440 _tceVersionSupported = supportedTceVersion;
2441 Debug.Assert (_tceVersionSupported == TdsEnums.MAX_SUPPORTED_TCE_VERSION, "Client support TCE version 1");
2442 _parser.IsColumnEncryptionSupported = true;
2446 case TdsEnums.FEATUREEXT_GLOBALTRANSACTIONS: {
2447 if (Bid.AdvancedOn) {
2448 Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck> %d#, Received feature extension acknowledgement for GlobalTransactions\n", ObjectID);
2451 if (data.Length < 1) {
2452 Bid.Trace("<sc.SqlInternalConnectionTds.OnFeatureExtAck|ERR> %d#, Unknown version number for GlobalTransactions\n", ObjectID);
2453 throw SQL.ParsingError(ParsingErrorState.CorruptedTdsStream);
2456 IsGlobalTransaction = true;
2458 IsGlobalTransactionsEnabledForServer = true;
2464 // Unknown feature ack
2465 throw SQL.ParsingErrorFeatureId(ParsingErrorState.UnknownFeatureAck, featureId);
2470 ////////////////////////////////////////////////////////////////////////////////////////
2471 // Helper methods for Locks
2472 ////////////////////////////////////////////////////////////////////////////////////////
2474 // Indicates if the current thread claims to hold the parser lock
2475 internal bool ThreadHasParserLockForClose {
2477 return _threadIdOwningParserLock == Thread.CurrentThread.ManagedThreadId;
2480 Debug.Assert(_parserLock.ThreadMayHaveLock(), "Should not modify ThreadHasParserLockForClose without taking the lock first");
2481 Debug.Assert(_threadIdOwningParserLock == -1 || _threadIdOwningParserLock == Thread.CurrentThread.ManagedThreadId, "Another thread already claims to own the parser lock");
2484 // If setting to true, then the thread owning the lock is the current thread
2485 _threadIdOwningParserLock = Thread.CurrentThread.ManagedThreadId;
2487 else if (_threadIdOwningParserLock == Thread.CurrentThread.ManagedThreadId) {
2488 // If setting to false and currently owns the lock, then no-one owns the lock
2489 _threadIdOwningParserLock = -1;
2491 // else This thread didn't own the parser lock and doesn't claim to own it, so do nothing
2495 internal override bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource<DbConnectionInternal> retry, DbConnectionOptions userOptions) {
2496 return base.TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions);
2500 internal sealed class ServerInfo {
2501 internal string ExtendedServerName { get; private set; } // the resolved servername with protocol
2502 internal string ResolvedServerName { get; private set; } // the resolved servername only
2503 internal string ResolvedDatabaseName { get; private set; } // name of target database after resolution
2504 internal string UserProtocol { get; private set; } // the user specified protocol
2506 // The original user-supplied server name from the connection string.
2507 // If connection string has no Data Source, the value is set to string.Empty.
2508 // In case of routing, will be changed to routing destination
2509 internal string UserServerName
2513 return m_userServerName;
2517 m_userServerName = value;
2519 } private string m_userServerName;
2521 internal readonly string PreRoutingServerName;
2523 // Initialize server info from connection options,
2524 internal ServerInfo(SqlConnectionString userOptions) : this(userOptions, userOptions.DataSource) {}
2526 // Initialize server info from connection options, but override DataSource with given server name
2527 internal ServerInfo (SqlConnectionString userOptions, string serverName) {
2530 Debug.Assert(null != userOptions);
2535 Debug.Assert(serverName != null, "server name should never be null");
2536 UserServerName = (serverName ?? string.Empty); // ensure user server name is not null
2538 UserProtocol = userOptions.NetworkLibrary;
2539 ResolvedDatabaseName = userOptions.InitialCatalog;
2540 PreRoutingServerName = null;
2544 // Initialize server info from connection options, but override DataSource with given server name
2545 internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string preRoutingServerName) {
2548 Debug.Assert(null != userOptions && null!=routing);
2552 Debug.Assert(routing.ServerName != null, "server name should never be null");
2553 if (routing == null || routing.ServerName == null) {
2554 UserServerName = string.Empty; // ensure user server name is not null
2557 UserServerName = string.Format(CultureInfo.InvariantCulture, "{0},{1}", routing.ServerName, routing.Port);
2559 PreRoutingServerName = preRoutingServerName;
2560 UserProtocol = TdsEnums.TCP;
2561 SetDerivedNames(UserProtocol, UserServerName);
2562 ResolvedDatabaseName = userOptions.InitialCatalog;
2565 internal void SetDerivedNames(string protocol, string serverName) {
2566 // The following concatenates the specified netlib network protocol to the host string, if netlib is not null
2567 // and the flag is on. This allows the user to specify the network protocol for the connection - but only
2568 // when using the Dbnetlib dll. If the protocol is not specified, the netlib will
2569 // try all protocols in the order listed in the Client Network Utility. Connect will
2570 // then fail if all protocols fail.
2571 if (!ADP.IsEmpty(protocol)) {
2572 ExtendedServerName = protocol + ":" + serverName;
2575 ExtendedServerName = serverName;
2577 ResolvedServerName = serverName;