| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009 |
- //------------------------------------------------------------------------------
- // <copyright file="SqlInternalConnectionTds.cs" company="Microsoft">
- // Copyright (c) Microsoft Corporation. All rights reserved.
- // </copyright>
- // <owner current="true" primary="true">[....]</owner>
- // <owner current="true" primary="false">[....]</owner>
- //------------------------------------------------------------------------------
- namespace System.Data.SqlClient
- {
- using System;
- using System.Collections.Generic;
- using System.Data;
- using System.Data.Common;
- using System.Data.ProviderBase;
- using System.Diagnostics;
- using System.Globalization;
- using System.Reflection;
- using System.Runtime.CompilerServices;
- using System.Runtime.ConstrainedExecution;
- using System.Runtime.InteropServices;
- using System.Security;
- using System.Security.Permissions;
- using System.Text;
- using System.Threading;
- using SysTx = System.Transactions;
- using System.Diagnostics.CodeAnalysis;
- using System.Threading.Tasks;
-
- internal class SessionStateRecord {
- internal bool _recoverable;
- internal UInt32 _version;
- internal Int32 _dataLength;
- internal byte[] _data;
- }
- internal class SessionData {
- internal const int _maxNumberOfSessionStates = 256;
- internal UInt32 _tdsVersion;
- internal bool _encrypted;
- internal string _database;
- internal SqlCollation _collation;
- internal string _language;
- internal string _initialDatabase;
- internal SqlCollation _initialCollation;
- internal string _initialLanguage;
-
- internal byte _unrecoverableStatesCount = 0;
- internal Dictionary<string, Tuple<string, string>> _resolvedAliases;
- #if DEBUG
- internal bool _debugReconnectDataApplied;
- #endif
- internal SessionStateRecord[] _delta = new SessionStateRecord[_maxNumberOfSessionStates];
- internal bool _deltaDirty = false;
- internal byte[][] _initialState = new byte[_maxNumberOfSessionStates][];
- public SessionData(SessionData recoveryData) {
- _initialDatabase = recoveryData._initialDatabase;
- _initialCollation = recoveryData._initialCollation;
- _initialLanguage = recoveryData._initialLanguage;
- _resolvedAliases = recoveryData._resolvedAliases;
- for (int i = 0; i < _maxNumberOfSessionStates; i++) {
- if (recoveryData._initialState[i] != null) {
- _initialState[i] = (byte[])recoveryData._initialState[i].Clone();
- }
- }
- }
- public SessionData() {
- _resolvedAliases = new Dictionary<string, Tuple<string, string>>(2);
- }
- public void Reset() {
- _database = null;
- _collation = null;
- _language = null;
- if (_deltaDirty) {
- _delta = new SessionStateRecord[_maxNumberOfSessionStates];
- _deltaDirty = false;
- }
- _unrecoverableStatesCount = 0;
- }
- [Conditional("DEBUG")]
- public void AssertUnrecoverableStateCountIsCorrect() {
- byte unrecoverableCount = 0;
- foreach (var state in _delta) {
- if (state != null && !state._recoverable)
- unrecoverableCount++;
- }
- Debug.Assert(unrecoverableCount == _unrecoverableStatesCount, "Unrecoverable count does not match");
- }
- }
- sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposable {
- // CONNECTION AND STATE VARIABLES
- private readonly SqlConnectionPoolGroupProviderInfo _poolGroupProviderInfo; // will only be null when called for ChangePassword, or creating SSE User Instance
- private TdsParser _parser;
- private SqlLoginAck _loginAck;
- private SqlCredential _credential;
- // Connection Resiliency
- private bool _sessionRecoveryRequested;
- internal bool _sessionRecoveryAcknowledged;
- internal SessionData _currentSessionData; // internal for use from TdsParser only, otehr should use CurrentSessionData property that will fix database and language
- private SessionData _recoverySessionData;
- internal SessionData CurrentSessionData {
- get {
- if (_currentSessionData != null) {
- _currentSessionData._database = CurrentDatabase;
- _currentSessionData._language = _currentLanguage;
- }
- return _currentSessionData;
- }
- }
- // FOR POOLING
- private bool _fConnectionOpen = false;
- // FOR CONNECTION RESET MANAGEMENT
- private bool _fResetConnection;
- private string _originalDatabase;
- private string _currentFailoverPartner; // only set by ENV change from server
- private string _originalLanguage;
- private string _currentLanguage;
- private int _currentPacketSize;
- private int _asyncCommandCount; // number of async Begins minus number of async Ends.
- // FOR SSE
- private string _instanceName = String.Empty;
- // FOR NOTIFICATIONS
- private DbConnectionPoolIdentity _identity; // Used to lookup info for notification matching Start().
- // FOR SYNCHRONIZATION IN TdsParser
- // How to use these locks:
- // 1. Whenever writing to the connection (with the exception of Cancellation) the _parserLock MUST be taken
- // 2. _parserLock will also be taken during close (to prevent closing in the middle of a write)
- // 3. Whenever you have the _parserLock and are calling a method that would cause the connection to close if it failed (with the exception of any writing method), you MUST set ThreadHasParserLockForClose to true
- // * This is to prevent the connection deadlocking with itself (since you already have the _parserLock, and Closing the connection will attempt to re-take that lock)
- // * It is safe to set ThreadHasParserLockForClose to true when writing as well, but it is unneccesary
- // * If you have a method that takes _parserLock, it is a good idea check ThreadHasParserLockForClose first (if you don't expect _parserLock to be taken by something higher on the stack, then you should at least assert that it is false)
- // 4. ThreadHasParserLockForClose is thread-specific - this means that you must set it to false before returning a Task, and set it back to true in the continuation
- // 5. ThreadHasParserLockForClose should only be modified if you currently own the _parserLock
- // 6. Reading ThreadHasParserLockForClose is thread-safe
- internal class SyncAsyncLock
- {
- SemaphoreSlim semaphore = new SemaphoreSlim(1);
- internal void Wait(bool canReleaseFromAnyThread)
- {
- Monitor.Enter(semaphore); // semaphore is used as lock object, no relation to SemaphoreSlim.Wait/Release methods
- if (canReleaseFromAnyThread || semaphore.CurrentCount==0) {
- semaphore.Wait();
- if (canReleaseFromAnyThread) {
- Monitor.Exit(semaphore);
- }
- else {
- semaphore.Release();
- }
- }
- }
-
- internal void Wait(bool canReleaseFromAnyThread, int timeout, ref bool lockTaken) {
- lockTaken = false;
- bool hasMonitor = false;
- try {
- Monitor.TryEnter(semaphore, timeout, ref hasMonitor); // semaphore is used as lock object, no relation to SemaphoreSlim.Wait/Release methods
- if (hasMonitor) {
- if ((canReleaseFromAnyThread) || (semaphore.CurrentCount == 0)) {
- if (semaphore.Wait(timeout)) {
- if (canReleaseFromAnyThread) {
- Monitor.Exit(semaphore);
- hasMonitor = false;
- }
- else {
- semaphore.Release();
- }
- lockTaken = true;
- }
- }
- else {
- lockTaken = true;
- }
- }
- }
- finally
- {
- if ((!lockTaken) && (hasMonitor)) {
- Monitor.Exit(semaphore);
- }
- }
- }
- internal void Release()
- {
- if (semaphore.CurrentCount==0) { // semaphore methods were used for locking
- semaphore.Release();
- }
- else {
- Monitor.Exit(semaphore);
- }
- }
- internal bool CanBeReleasedFromAnyThread {
- get {
- return semaphore.CurrentCount==0;
- }
- }
- // Necessary but not sufficient condition for thread to have lock (since sempahore may be obtained by any thread)
- internal bool ThreadMayHaveLock() {
- return Monitor.IsEntered(semaphore) || semaphore.CurrentCount == 0;
- }
- }
- internal SyncAsyncLock _parserLock = new SyncAsyncLock();
- private int _threadIdOwningParserLock = -1;
- private SqlConnectionTimeoutErrorInternal timeoutErrorInternal;
- internal SqlConnectionTimeoutErrorInternal TimeoutErrorInternal
- {
- get { return timeoutErrorInternal; }
- }
-
- // OTHER STATE VARIABLES AND REFERENCES
- internal Guid _clientConnectionId = Guid.Empty;
- // Routing information (ROR)
- RoutingInfo _routingInfo = null;
- private Guid _originalClientConnectionId = Guid.Empty;
- private string _routingDestination = null;
- // although the new password is generally not used it must be passed to the c'tor
- // the new Login7 packet will always write out the new password (or a length of zero and no bytes if not present)
- //
- internal SqlInternalConnectionTds(
- DbConnectionPoolIdentity identity,
- SqlConnectionString connectionOptions,
- SqlCredential credential,
- object providerInfo,
- string newPassword,
- SecureString newSecurePassword,
- bool redirectedUserInstance,
- SqlConnectionString userConnectionOptions = null, // NOTE: userConnectionOptions may be different to connectionOptions if the connection string has been expanded (see SqlConnectionString.Expand)
- SessionData reconnectSessionData = null) : base(connectionOptions) {
- #if DEBUG
- if (reconnectSessionData != null) {
- reconnectSessionData._debugReconnectDataApplied = true;
- }
- try { // use this to help validate this object is only created after the following permission has been previously demanded in the current codepath
- if (userConnectionOptions != null) {
- // As mentioned above, userConnectionOptions may be different to connectionOptions, so we need to demand on the correct connection string
- userConnectionOptions.DemandPermission();
- }
- else {
- connectionOptions.DemandPermission();
- }
- }
- catch(System.Security.SecurityException) {
- System.Diagnostics.Debug.Assert(false, "unexpected SecurityException for current codepath");
- throw;
- }
- #endif
- Debug.Assert(reconnectSessionData == null || connectionOptions.ConnectRetryCount > 0, "Reconnect data supplied with CR turned off");
-
- if (connectionOptions.ConnectRetryCount > 0) {
- _recoverySessionData = reconnectSessionData;
- if (reconnectSessionData == null) {
- _currentSessionData = new SessionData();
- }
- else {
- _currentSessionData = new SessionData(_recoverySessionData);
- _originalDatabase = _recoverySessionData._initialDatabase;
- _originalLanguage = _recoverySessionData._initialLanguage;
- }
- }
- if (connectionOptions.UserInstance && InOutOfProcHelper.InProc) {
- throw SQL.UserInstanceNotAvailableInProc();
- }
- _identity = identity;
- Debug.Assert(newSecurePassword != null || newPassword != null, "cannot have both new secure change password and string based change password to be null");
- 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");
- Debug.Assert(credential == null || !connectionOptions.IntegratedSecurity, "Cannot use SqlCredential and Integrated Security");
- Debug.Assert(credential == null || !connectionOptions.ContextConnection, "Cannot use SqlCredential with context connection");
- _poolGroupProviderInfo = (SqlConnectionPoolGroupProviderInfo)providerInfo;
- _fResetConnection = connectionOptions.ConnectionReset;
- if (_fResetConnection && _recoverySessionData == null) {
- _originalDatabase = connectionOptions.InitialCatalog;
- _originalLanguage = connectionOptions.CurrentLanguage;
- }
- timeoutErrorInternal = new SqlConnectionTimeoutErrorInternal();
- _credential = credential;
- _parserLock.Wait(canReleaseFromAnyThread:false);
- ThreadHasParserLockForClose = true; // In case of error, let ourselves know that we already own the parser lock
- RuntimeHelpers.PrepareConstrainedRegions();
- try {
- #if DEBUG
- TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
- RuntimeHelpers.PrepareConstrainedRegions();
- try {
- tdsReliabilitySection.Start();
- #else
- {
- #endif //DEBUG
- var timeout = TimeoutTimer.StartSecondsTimeout(connectionOptions.ConnectTimeout);
- OpenLoginEnlist(timeout, connectionOptions, credential, newPassword, newSecurePassword, redirectedUserInstance);
- }
- #if DEBUG
- finally {
- tdsReliabilitySection.Stop();
- }
- #endif //DEBUG
- }
- catch (System.OutOfMemoryException) {
- DoomThisConnection();
- throw;
- }
- catch (System.StackOverflowException) {
- DoomThisConnection();
- throw;
- }
- catch (System.Threading.ThreadAbortException) {
- DoomThisConnection();
- throw;
- }
- finally {
- ThreadHasParserLockForClose = false;
- _parserLock.Release();
- }
- if (Bid.AdvancedOn) {
- Bid.Trace("<sc.SqlInternalConnectionTds.ctor|ADV> %d#, constructed new TDS internal connection\n", ObjectID);
- }
- }
-
- internal Guid ClientConnectionId {
- get {
- return _clientConnectionId;
- }
- }
-
- internal Guid OriginalClientConnectionId {
- get {
- return _originalClientConnectionId;
- }
- }
-
- internal string RoutingDestination {
- get {
- return _routingDestination;
- }
- }
- override internal SqlInternalTransaction CurrentTransaction {
- get {
- return _parser.CurrentTransaction;
- }
- }
- override internal SqlInternalTransaction AvailableInternalTransaction {
- get {
- return _parser._fResetConnection ? null : CurrentTransaction;
- }
- }
- override internal SqlInternalTransaction PendingTransaction {
- get {
- return _parser.PendingTransaction;
- }
- }
-
- internal DbConnectionPoolIdentity Identity {
- get {
- return _identity;
- }
- }
- internal string InstanceName {
- get {
- return _instanceName;
- }
- }
- override internal bool IsLockedForBulkCopy {
- get {
- return (!Parser.MARSOn && Parser._physicalStateObj.BcpLock);
- }
- }
- override protected internal bool IsNonPoolableTransactionRoot {
- get {
- return IsTransactionRoot && (!IsKatmaiOrNewer || null == Pool);
- }
- }
- override internal bool IsShiloh {
- get {
- return _loginAck.isVersion8;
- }
- }
- override internal bool IsYukonOrNewer {
- get {
- return _parser.IsYukonOrNewer;
- }
- }
- override internal bool IsKatmaiOrNewer {
- get {
- return _parser.IsKatmaiOrNewer;
- }
- }
- internal int PacketSize {
- get {
- return _currentPacketSize;
- }
- }
- internal TdsParser Parser {
- get {
- return _parser;
- }
- }
- internal string ServerProvidedFailOverPartner {
- get {
- return _currentFailoverPartner;
- }
- }
- internal SqlConnectionPoolGroupProviderInfo PoolGroupProviderInfo {
- get {
- return _poolGroupProviderInfo;
- }
- }
-
- override protected bool ReadyToPrepareTransaction {
- get {
- //
- bool result = (null == FindLiveReader(null)); // can't prepare with a live data reader...
- return result;
- }
- }
- override public string ServerVersion {
- get {
- return(String.Format((IFormatProvider)null, "{0:00}.{1:00}.{2:0000}", _loginAck.majorVersion,
- (short) _loginAck.minorVersion, _loginAck.buildNum));
- }
- }
- /// <summary>
- /// Get boolean that specifies whether an enlisted transaction can be unbound from
- /// the connection when that transaction completes.
- /// </summary>
- /// <value>
- /// This override always returns false.
- /// </value>
- /// <remarks>
- /// The SqlInternalConnectionTds.CheckEnlistedTransactionBinding method handles implicit unbinding for disposed transactions.
- /// </remarks>
- protected override bool UnbindOnTransactionCompletion
- {
- get
- {
- return false;
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- // GENERAL METHODS
- ////////////////////////////////////////////////////////////////////////////////////////
- [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] // copied from Triaged.cs
- override protected void ChangeDatabaseInternal(string database) {
- // MDAC 73598 - add brackets around database
- database = SqlConnection.FixupDatabaseTransactionName(database);
- Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch("use " + database, ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj, sync: true);
- Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");
- _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
- }
- override public void Dispose() {
- if (Bid.AdvancedOn) {
- Bid.Trace("<sc.SqlInternalConnectionTds.Dispose|ADV> %d# disposing\n", base.ObjectID);
- }
- try {
- TdsParser parser = Interlocked.Exchange(ref _parser, null); // guard against multiple concurrent dispose calls -- Delegated Transactions might cause this.
-
- Debug.Assert(parser != null && _fConnectionOpen || parser == null && !_fConnectionOpen, "Unexpected state on dispose");
- if (null != parser) {
- parser.Disconnect();
- }
- }
- finally { // UNDONE: MDAC 77928
- // close will always close, even if exception is thrown
- // remember to null out any object references
- _loginAck = null;
- _fConnectionOpen = false; // mark internal connection as closed
- }
- base.Dispose();
- }
- override internal void ValidateConnectionForExecute(SqlCommand command) {
- TdsParser parser = _parser;
- if ((parser == null) || (parser.State == TdsParserState.Broken) || (parser.State == TdsParserState.Closed)) {
- throw ADP.ClosedConnectionError();
- }
- else {
- SqlDataReader reader = null;
- if (parser.MARSOn) {
- if (null != command) { // command can't have datareader already associated with it
- reader = FindLiveReader(command);
- }
- }
- else { // single execution/datareader per connection
- if (_asyncCommandCount > 0) {
- throw SQL.MARSUnspportedOnConnection();
- }
- reader = FindLiveReader(null);
- }
- if (null != reader) {
- // if MARS is on, then a datareader associated with the command exists
- // or if MARS is off, then a datareader exists
- throw ADP.OpenReaderExists(); // MDAC 66411
- }
- else if (!parser.MARSOn && parser._physicalStateObj._pendingData) {
- parser.DrainData(parser._physicalStateObj);
- }
- Debug.Assert(!parser._physicalStateObj._pendingData, "Should not have a busy physicalStateObject at this point!");
- parser.RollbackOrphanedAPITransactions();
- }
- }
- /// <summary>
- /// Validate the enlisted transaction state, taking into consideration the ambient transaction and transaction unbinding mode.
- /// If there is no enlisted transaction, this method is a nop.
- /// </summary>
- /// <remarks>
- /// <para>
- /// This method must be called while holding a lock on the SqlInternalConnection instance,
- /// to ensure we don't accidentally execute after the transaction has completed on a different thread,
- /// causing us to unwittingly execute in auto-commit mode.
- /// </para>
- ///
- /// <para>
- /// When using Explicit transaction unbinding,
- /// verify that the enlisted transaction is active and equal to the current ambient transaction.
- /// </para>
- ///
- /// <para>
- /// When using Implicit transaction unbinding,
- /// verify that the enlisted transaction is active.
- /// If it is not active, and the transaction object has been diposed, unbind from the transaction.
- /// If it is not active and not disposed, throw an exception.
- /// </para>
- /// </remarks>
- internal void CheckEnlistedTransactionBinding()
- {
- // If we are enlisted in a transaction, check that transaction is active.
- // When using explicit transaction unbinding, also verify that the enlisted transaction is the current transaction.
- SysTx.Transaction enlistedTransaction = EnlistedTransaction;
- if (enlistedTransaction != null)
- {
- bool requireExplicitTransactionUnbind = ConnectionOptions.TransactionBinding == SqlConnectionString.TransactionBindingEnum.ExplicitUnbind;
- if (requireExplicitTransactionUnbind)
- {
- SysTx.Transaction currentTransaction = SysTx.Transaction.Current;
- if (SysTx.TransactionStatus.Active != enlistedTransaction.TransactionInformation.Status || !enlistedTransaction.Equals(currentTransaction))
- {
- throw ADP.TransactionConnectionMismatch();
- }
- }
- else // implicit transaction unbind
- {
- if (SysTx.TransactionStatus.Active != enlistedTransaction.TransactionInformation.Status)
- {
- if (EnlistedTransactionDisposed)
- {
- DetachTransaction(enlistedTransaction, true);
- }
- else
- {
- throw ADP.TransactionCompletedButNotDisposed();
- }
- }
- }
- }
- }
- internal override bool IsConnectionAlive(bool throwOnException)
- {
- bool isAlive = false;
- #if DEBUG
- TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
- RuntimeHelpers.PrepareConstrainedRegions();
- try
- {
- tdsReliabilitySection.Start();
- #endif //DEBUG
- isAlive = _parser._physicalStateObj.IsConnectionAlive(throwOnException);
- #if DEBUG
- }
- finally
- {
- tdsReliabilitySection.Stop();
- }
- #endif //DEBUG
- return isAlive;
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- // POOLING METHODS
- ////////////////////////////////////////////////////////////////////////////////////////
- override protected void Activate(SysTx.Transaction transaction) {
- FailoverPermissionDemand(); // Demand for unspecified failover pooled connections
- // When we're required to automatically enlist in transactions and
- // there is one we enlist in it. On the other hand, if there isn't a
- // transaction and we are currently enlisted in one, then we
- // unenlist from it.
- //
- // Regardless of whether we're required to automatically enlist,
- // when there is not a current transaction, we cannot leave the
- // connection enlisted in a transaction.
- if (null != transaction){
- if (ConnectionOptions.Enlist) {
- Enlist(transaction);
- }
- }
- else {
- Enlist(null);
- }
- }
-
- override protected void InternalDeactivate() {
- // When we're deactivated, the user must have called End on all
- // the async commands, or we don't know that we're in a state that
- // we can recover from. We doom the connection in this case, to
- // prevent odd cases when we go to the wire.
- if (0 != _asyncCommandCount) {
- DoomThisConnection();
- }
- // If we're deactivating with a delegated transaction, we
- // should not be cleaning up the parser just yet, that will
- // cause our transaction to be rolled back and the connection
- // to be reset. We'll get called again once the delegated
- // transaction is completed and we can do it all then.
- if (!IsNonPoolableTransactionRoot) {
- Debug.Assert(null != _parser || IsConnectionDoomed, "Deactivating a disposed connection?");
- if (_parser != null) {
- _parser.Deactivate(IsConnectionDoomed);
- if (!IsConnectionDoomed) {
- ResetConnection();
- }
- }
- }
- }
- [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] // copied from Triaged.cs
- private void ResetConnection() {
- // For implicit pooled connections, if connection reset behavior is specified,
- // reset the database and language properties back to default. It is important
- // to do this on activate so that the hashtable is correct before SqlConnection
- // obtains a clone.
- Debug.Assert(!HasLocalTransactionFromAPI, "Upon ResetConnection SqlInternalConnectionTds has a currently ongoing local transaction.");
- Debug.Assert(!_parser._physicalStateObj._pendingData, "Upon ResetConnection SqlInternalConnectionTds has pending data.");
- if (_fResetConnection) {
- // Ensure we are either going against shiloh, or we are not enlisted in a
- // distributed transaction - otherwise don't reset!
- if (IsShiloh) {
- // Prepare the parser for the connection reset - the next time a trip
- // to the server is made.
- _parser.PrepareResetConnection(IsTransactionRoot && !IsNonPoolableTransactionRoot);
- }
- else if (!IsEnlistedInTransaction) {
- // If not Shiloh, we are going against Sphinx. On Sphinx, we
- // may only reset if not enlisted in a distributed transaction.
- try {
- // execute sp
- Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch("sp_reset_connection", 30, null, _parser._physicalStateObj, sync: true);
- Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");
- _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
- }
- catch (Exception e) {
- //
- if (!ADP.IsCatchableExceptionType(e)) {
- throw;
- }
- DoomThisConnection();
- ADP.TraceExceptionWithoutRethrow(e);
- }
- }
- // Reset hashtable values, since calling reset will not send us env_changes.
- CurrentDatabase = _originalDatabase;
- _currentLanguage = _originalLanguage;
- }
- }
- internal void DecrementAsyncCount() {
- Interlocked.Decrement(ref _asyncCommandCount);
- }
- internal void IncrementAsyncCount() {
- Interlocked.Increment(ref _asyncCommandCount);
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- // LOCAL TRANSACTION METHODS
- ////////////////////////////////////////////////////////////////////////////////////////
- override internal void DisconnectTransaction(SqlInternalTransaction internalTransaction) {
- TdsParser parser = Parser;
- if (null != parser) {
- parser.DisconnectTransaction(internalTransaction);
- }
- }
- internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, IsolationLevel iso) {
- ExecuteTransaction(transactionRequest, name, iso, null, false);
- }
- override internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest) {
- if (IsConnectionDoomed) { // doomed means we can't do anything else...
- if (transactionRequest == TransactionRequest.Rollback
- || transactionRequest == TransactionRequest.IfRollback) {
- return;
- }
- throw SQL.ConnectionDoomed();
- }
- if (transactionRequest == TransactionRequest.Commit
- || transactionRequest == TransactionRequest.Rollback
- || transactionRequest == TransactionRequest.IfRollback) {
- if (!Parser.MARSOn && Parser._physicalStateObj.BcpLock) {
- throw SQL.ConnectionLockedForBcpEvent();
- }
- }
- string transactionName = (null == name) ? String.Empty : name;
- if (!_parser.IsYukonOrNewer) {
- ExecuteTransactionPreYukon(transactionRequest, transactionName, iso, internalTransaction);
- }
- else {
- ExecuteTransactionYukon(transactionRequest, transactionName, iso, internalTransaction, isDelegateControlRequest);
- }
- }
- // This function will not handle idle connection resiliency, as older servers will not support it
- internal void ExecuteTransactionPreYukon(
- TransactionRequest transactionRequest,
- string transactionName,
- IsolationLevel iso,
- SqlInternalTransaction internalTransaction) {
- StringBuilder sqlBatch = new StringBuilder();
- switch (iso) {
- case IsolationLevel.Unspecified:
- break;
- case IsolationLevel.ReadCommitted:
- sqlBatch.Append(TdsEnums.TRANS_READ_COMMITTED);
- sqlBatch.Append(";");
- break;
- case IsolationLevel.ReadUncommitted:
- sqlBatch.Append(TdsEnums.TRANS_READ_UNCOMMITTED);
- sqlBatch.Append(";");
- break;
- case IsolationLevel.RepeatableRead:
- sqlBatch.Append(TdsEnums.TRANS_REPEATABLE_READ);
- sqlBatch.Append(";");
- break;
- case IsolationLevel.Serializable:
- sqlBatch.Append(TdsEnums.TRANS_SERIALIZABLE);
- sqlBatch.Append(";");
- break;
- case IsolationLevel.Snapshot:
- throw SQL.SnapshotNotSupported(IsolationLevel.Snapshot);
- case IsolationLevel.Chaos:
- throw SQL.NotSupportedIsolationLevel(iso);
- default:
- throw ADP.InvalidIsolationLevel(iso);
- }
- if (!ADP.IsEmpty(transactionName)) {
- transactionName = " " + SqlConnection.FixupDatabaseTransactionName(transactionName);
- }
-
- switch (transactionRequest) {
- case TransactionRequest.Begin:
- sqlBatch.Append(TdsEnums.TRANS_BEGIN);
- sqlBatch.Append(transactionName);
- break;
- case TransactionRequest.Promote:
- Debug.Assert(false, "Promote called with transaction name or on pre-Yukon!");
- break;
- case TransactionRequest.Commit:
- sqlBatch.Append(TdsEnums.TRANS_COMMIT);
- sqlBatch.Append(transactionName);
- break;
- case TransactionRequest.Rollback:
- sqlBatch.Append(TdsEnums.TRANS_ROLLBACK);
- sqlBatch.Append(transactionName);
- break;
- case TransactionRequest.IfRollback:
- sqlBatch.Append(TdsEnums.TRANS_IF_ROLLBACK);
- sqlBatch.Append(transactionName);
- break;
- case TransactionRequest.Save:
- sqlBatch.Append(TdsEnums.TRANS_SAVE);
- sqlBatch.Append(transactionName);
- break;
- default:
- Debug.Assert(false, "Unknown transaction type");
- break;
- }
- Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch(sqlBatch.ToString(), ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj, sync: true);
- Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");
- _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
- // Prior to Yukon, we didn't have any transaction tokens to manage,
- // or any feedback to know when one was created, so we just presume
- // that successful execution of the request caused the transaction
- // to be created, and we set that on the parser.
- if (TransactionRequest.Begin == transactionRequest) {
- Debug.Assert(null != internalTransaction, "Begin Transaction request without internal transaction");
- _parser.CurrentTransaction = internalTransaction;
- }
- }
-
- internal void ExecuteTransactionYukon(
- TransactionRequest transactionRequest,
- string transactionName,
- IsolationLevel iso,
- SqlInternalTransaction internalTransaction,
- bool isDelegateControlRequest) {
- TdsEnums.TransactionManagerRequestType requestType = TdsEnums.TransactionManagerRequestType.Begin;
- TdsEnums.TransactionManagerIsolationLevel isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadCommitted;
- switch (iso) {
- case IsolationLevel.Unspecified:
- isoLevel = TdsEnums.TransactionManagerIsolationLevel.Unspecified;
- break;
- case IsolationLevel.ReadCommitted:
- isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadCommitted;
- break;
- case IsolationLevel.ReadUncommitted:
- isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadUncommitted;
- break;
- case IsolationLevel.RepeatableRead:
- isoLevel = TdsEnums.TransactionManagerIsolationLevel.RepeatableRead;
- break;
- case IsolationLevel.Serializable:
- isoLevel = TdsEnums.TransactionManagerIsolationLevel.Serializable;
- break;
- case IsolationLevel.Snapshot:
- isoLevel = TdsEnums.TransactionManagerIsolationLevel.Snapshot;
- break;
- case IsolationLevel.Chaos:
- throw SQL.NotSupportedIsolationLevel(iso);
- default:
- throw ADP.InvalidIsolationLevel(iso);
- }
- TdsParserStateObject stateObj = _parser._physicalStateObj;
- TdsParser parser = _parser;
- bool mustPutSession = false;
- bool releaseConnectionLock = false;
- Debug.Assert(!ThreadHasParserLockForClose || _parserLock.ThreadMayHaveLock(), "Thread claims to have parser lock, but lock is not taken");
- if (!ThreadHasParserLockForClose) {
- _parserLock.Wait(canReleaseFromAnyThread:false);
- ThreadHasParserLockForClose = true; // In case of error, let the connection know that we already own the parser lock
- releaseConnectionLock = true;
- }
- try {
- switch (transactionRequest) {
- case TransactionRequest.Begin:
- requestType = TdsEnums.TransactionManagerRequestType.Begin;
- break;
- case TransactionRequest.Promote:
- requestType = TdsEnums.TransactionManagerRequestType.Promote;
- break;
- case TransactionRequest.Commit:
- requestType = TdsEnums.TransactionManagerRequestType.Commit;
- break;
- case TransactionRequest.IfRollback:
- // Map IfRollback to Rollback since with Yukon and beyond we should never need
- // the if since the server will inform us when transactions have completed
- // as a result of an error on the server.
- case TransactionRequest.Rollback:
- requestType = TdsEnums.TransactionManagerRequestType.Rollback;
- break;
- case TransactionRequest.Save:
- requestType = TdsEnums.TransactionManagerRequestType.Save;
- break;
- default:
- Debug.Assert(false, "Unknown transaction type");
- break;
- }
- // only restore if connection lock has been taken within the function
- if (internalTransaction != null && internalTransaction.RestoreBrokenConnection && releaseConnectionLock) {
- Task reconnectTask = internalTransaction.Parent.Connection.ValidateAndReconnect(() => {
- ThreadHasParserLockForClose = false;
- _parserLock.Release();
- releaseConnectionLock = false;
- }, 0);
- if (reconnectTask != null) {
- AsyncHelper.WaitForCompletion(reconnectTask, 0); // there is no specific timeout for BeginTransaction, uses ConnectTimeout
- internalTransaction.ConnectionHasBeenRestored = true;
- return;
- }
- }
-
- // SQLBUDT #20010853 - Promote, Commit and Rollback requests for
- // delegated transactions often happen while there is an open result
- // set, so we need to handle them by using a different MARS session,
- // otherwise we'll write on the physical state objects while someone
- // else is using it. When we don't have MARS enabled, we need to
- // lock the physical state object to syncronize it's use at least
- // until we increment the open results count. Once it's been
- // incremented the delegated transaction requests will fail, so they
- // won't stomp on anything.
- //
- // We need to keep this lock through the duration of the TM reqeuest
- // so that we won't hijack a different request's data stream and a
- // different request won't hijack ours, so we have a lock here on
- // an object that the ExecTMReq will also lock, but since we're on
- // the same thread, the lock is a no-op.
- if (null != internalTransaction && internalTransaction.IsDelegated) {
- if (_parser.MARSOn) {
- stateObj = _parser.GetSession(this);
- mustPutSession = true;
- }
- else if (internalTransaction.OpenResultsCount != 0) {
- throw SQL.CannotCompleteDelegatedTransactionWithOpenResults(this);
- }
- }
- // SQLBU #406778 - _parser may be nulled out during TdsExecuteTrannsactionManagerRequest.
- // Only use local variable after this call.
- _parser.TdsExecuteTransactionManagerRequest(null, requestType, transactionName, isoLevel,
- ConnectionOptions.ConnectTimeout, internalTransaction, stateObj, isDelegateControlRequest);
- }
- finally {
- if (mustPutSession) {
- parser.PutSession(stateObj);
- }
- if (releaseConnectionLock) {
- ThreadHasParserLockForClose = false;
- _parserLock.Release();
- }
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- // DISTRIBUTED TRANSACTION METHODS
- ////////////////////////////////////////////////////////////////////////////////////////
- override internal void DelegatedTransactionEnded() {
- //
- base.DelegatedTransactionEnded();
- }
-
- override protected byte[] GetDTCAddress() {
- byte[] dtcAddress = _parser.GetDTCAddress(ConnectionOptions.ConnectTimeout, _parser.GetSession(this));
- Debug.Assert(null != dtcAddress, "null dtcAddress?");
- return dtcAddress;
- }
- override protected void PropagateTransactionCookie(byte[] cookie) {
- _parser.PropagateDistributedTransaction(cookie, ConnectionOptions.ConnectTimeout, _parser._physicalStateObj);
- }
-
- ////////////////////////////////////////////////////////////////////////////////////////
- // LOGIN-RELATED METHODS
- ////////////////////////////////////////////////////////////////////////////////////////
- private void CompleteLogin(bool enlistOK) {
- _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
- if (_routingInfo == null) { // ROR should not affect state of connection recovery
- if (!_sessionRecoveryAcknowledged) {
- _currentSessionData = null;
- if (_recoverySessionData != null) {
- throw SQL.CR_NoCRAckAtReconnection(this);
- }
- }
- if (_currentSessionData != null && _recoverySessionData==null) {
- _currentSessionData._initialDatabase = CurrentDatabase;
- _currentSessionData._initialCollation = _currentSessionData._collation;
- _currentSessionData._initialLanguage = _currentLanguage;
- }
- bool isEncrypted = _parser.EncryptionOptions == EncryptionOptions.ON;
- if (_recoverySessionData != null) {
- if (_recoverySessionData._encrypted != isEncrypted) {
- throw SQL.CR_EncryptionChanged(this);
- }
- }
- if (_currentSessionData != null) {
- _currentSessionData._encrypted = isEncrypted;
- }
- _recoverySessionData = null;
- }
- Debug.Assert(SniContext.Snix_Login == Parser._physicalStateObj.SniContext, String.Format((IFormatProvider)null, "SniContext should be Snix_Login; actual Value: {0}", Parser._physicalStateObj.SniContext));
- _parser._physicalStateObj.SniContext = SniContext.Snix_EnableMars;
- _parser.EnableMars();
- _fConnectionOpen = true; // mark connection as open
- if (Bid.AdvancedOn) {
- Bid.Trace("<sc.SqlInternalConnectionTds.CompleteLogin|ADV> Post-Login Phase: Server connection obtained.\n");
- }
- // for non-pooled connections, enlist in a distributed transaction
- // if present - and user specified to enlist
- if(enlistOK && ConnectionOptions.Enlist) {
- _parser._physicalStateObj.SniContext = SniContext.Snix_AutoEnlist;
- SysTx.Transaction tx = ADP.GetCurrentTransaction();
- Enlist(tx);
- }
- _parser._physicalStateObj.SniContext=SniContext.Snix_Login;
- }
- private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, SecureString newSecurePassword) {
- // create a new login record
- SqlLogin login = new SqlLogin();
- // gather all the settings the user set in the connection string or
- // properties and do the login
- CurrentDatabase = server.ResolvedDatabaseName;
- _currentPacketSize = ConnectionOptions.PacketSize;
- _currentLanguage = ConnectionOptions.CurrentLanguage;
- int timeoutInSeconds = 0;
- // If a timeout tick value is specified, compute the timeout based
- // upon the amount of time left in seconds.
- if (!timeout.IsInfinite)
- {
- long t = timeout.MillisecondsRemaining/1000;
- if ((long)Int32.MaxValue > t)
- {
- timeoutInSeconds = (int)t;
- }
- }
- login.timeout = timeoutInSeconds;
- login.userInstance = ConnectionOptions.UserInstance;
- login.hostName = ConnectionOptions.ObtainWorkstationId();
- login.userName = ConnectionOptions.UserID;
- login.password = ConnectionOptions.Password;
- login.applicationName = ConnectionOptions.ApplicationName;
- login.language = _currentLanguage;
- if (!login.userInstance) { // Do not send attachdbfilename or database to SSE primary instance
- login.database = CurrentDatabase;;
- login.attachDBFilename = ConnectionOptions.AttachDBFilename;
- }
- // VSTS#795621 - Ensure ServerName is Sent During TdsLogin To Enable Sql Azure Connectivity.
- // Using server.UserServerName (versus ConnectionOptions.DataSource) since TdsLogin requires
- // serverName to always be non-null.
- login.serverName = server.UserServerName;
- login.useReplication = ConnectionOptions.Replication;
- login.useSSPI = ConnectionOptions.IntegratedSecurity;
- login.packetSize = _currentPacketSize;
- login.newPassword = newPassword;
- login.readOnlyIntent = ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly;
- login.credential = _credential;
- if (newSecurePassword != null) {
- login.newSecurePassword = newSecurePassword;
- }
- TdsEnums.FeatureExtension requestedFeatures = TdsEnums.FeatureExtension.None;
- if (ConnectionOptions.ConnectRetryCount>0) {
- requestedFeatures |= TdsEnums.FeatureExtension.SessionRecovery;
- _sessionRecoveryRequested = true;
- }
- _parser.TdsLogin(login, requestedFeatures, _recoverySessionData);
- }
- private void LoginFailure() {
- Bid.Trace("<sc.SqlInternalConnectionTds.LoginFailure|RES|CPOOL> %d#\n", ObjectID);
- // If the parser was allocated and we failed, then we must have failed on
- // either the Connect or Login, either way we should call Disconnect.
- // Disconnect can be called if the connection is already closed - becomes
- // no-op, so no issues there.
- if (_parser != null) {
- _parser.Disconnect();
- }
- //
- }
- private void OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectionOptions, SqlCredential credential,
- string newPassword, SecureString newSecurePassword, bool redirectedUserInstance) {
- bool useFailoverPartner; // should we use primary or secondary first
- ServerInfo dataSource = new ServerInfo(connectionOptions);
- string failoverPartner;
- if (null != PoolGroupProviderInfo) {
- useFailoverPartner = PoolGroupProviderInfo.UseFailoverPartner;
- failoverPartner = PoolGroupProviderInfo.FailoverPartner;
- }
- else {
- // Only ChangePassword or SSE User Instance comes through this code path.
- useFailoverPartner = false;
- failoverPartner = ConnectionOptions.FailoverPartner;
- }
- timeoutErrorInternal.SetInternalSourceType(useFailoverPartner ? SqlConnectionInternalSourceType.Failover : SqlConnectionInternalSourceType.Principle);
- bool hasFailoverPartner = !ADP.IsEmpty(failoverPartner);
- // Open the connection and Login
- try {
- timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.PreLoginBegin);
- if (hasFailoverPartner) {
- timeoutErrorInternal.SetFailoverScenario(true); // this is a failover scenario
- LoginWithFailover(
- useFailoverPartner,
- dataSource,
- failoverPartner,
- newPassword,
- newSecurePassword,
- redirectedUserInstance,
- connectionOptions,
- credential,
- timeout);
- }
- else {
- timeoutErrorInternal.SetFailoverScenario(false); // not a failover scenario
- LoginNoFailover(dataSource, newPassword, newSecurePassword, redirectedUserInstance,
- connectionOptions, credential, timeout);
- }
- timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PostLogin);
- }
- catch (Exception e) {
- //
- if (ADP.IsCatchableExceptionType(e)) {
- LoginFailure();
- }
- throw;
- }
- timeoutErrorInternal.SetAllCompleteMarker();
- #if DEBUG
- _parser._physicalStateObj.InvalidateDebugOnlyCopyOfSniContext();
- #endif
- }
- // Is the given Sql error one that should prevent retrying
- // to connect.
- private bool IsDoNotRetryConnectError(SqlException exc) {
- return (TdsEnums.LOGON_FAILED == exc.Number) // actual logon failed, i.e. bad password
- || (TdsEnums.PASSWORD_EXPIRED == exc.Number) // actual logon failed, i.e. password isExpired
- || (TdsEnums.IMPERSONATION_FAILED == exc.Number) // Insuficient privelege for named pipe, among others
- || exc._doNotReconnect; // Exception explicitly supressed reconnection attempts
- }
- // Attempt to login to a host that does not have a failover partner
- //
- // Will repeatedly attempt to connect, but back off between each attempt so as not to clog the network.
- // Back off period increases for first few failures: 100ms, 200ms, 400ms, 800ms, then 1000ms for subsequent attempts
- //
- // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- // DEVNOTE: The logic in this method is paralleled by the logic in LoginWithFailover.
- // Changes to either one should be examined to see if they need to be reflected in the other
- // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureString newSecurePassword, bool redirectedUserInstance,
- SqlConnectionString connectionOptions, SqlCredential credential, TimeoutTimer timeout) {
- Debug.Assert(object.ReferenceEquals(connectionOptions, this.ConnectionOptions), "ConnectionOptions argument and property must be the same"); // consider removing the argument
- int routingAttempts = 0;
- ServerInfo originalServerInfo = serverInfo; // serverInfo may end up pointing to new object due to routing, original object is used to set CurrentDatasource
- if (Bid.AdvancedOn) {
- Bid.Trace("<sc.SqlInternalConnectionTds.LoginNoFailover|ADV> %d#, host=%ls\n", ObjectID, serverInfo.UserServerName);
- }
- int sleepInterval = 100; //milliseconds to sleep (back off) between attempts.
- ResolveExtendedServerName(serverInfo, !redirectedUserInstance, connectionOptions);
- long timeoutUnitInterval = 0;
- if (connectionOptions.MultiSubnetFailover) {
- // Determine unit interval
- if (timeout.IsInfinite) {
- timeoutUnitInterval = checked((long)(ADP.FailoverTimeoutStep * (1000L * ADP.DefaultConnectionTimeout)));
- }
- else {
- timeoutUnitInterval = checked((long)(ADP.FailoverTimeoutStep * timeout.MillisecondsRemaining));
- }
- }
- // Only three ways out of this loop:
- // 1) Successfully connected
- // 2) Parser threw exception while main timer was expired
- // 3) Parser threw logon failure-related exception
- // 4) Parser threw exception in post-initial connect code,
- // such as pre-login handshake or during actual logon. (parser state != Closed)
- //
- // Of these methods, only #1 exits normally. This preserves the call stack on the exception
- // back into the parser for the error cases.
- int attemptNumber = 0;
- TimeoutTimer intervalTimer = null;
- while(true) {
- if (connectionOptions.MultiSubnetFailover) {
- attemptNumber++;
- // Set timeout for this attempt, but don't exceed original timer
- long nextTimeoutInterval = checked(timeoutUnitInterval * attemptNumber);
- long milliseconds = timeout.MillisecondsRemaining;
- if (nextTimeoutInterval > milliseconds) {
- nextTimeoutInterval = milliseconds;
- }
- intervalTimer = TimeoutTimer.StartMillisecondsTimeout(nextTimeoutInterval);
- }
- // Re-allocate parser each time to make sure state is known
- // RFC 50002652 - if parser was created by previous attempt, dispose it to properly close the socket, if created
- if (_parser != null)
- _parser.Disconnect();
-
- _parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous);
- Debug.Assert(SniContext.Undefined== Parser._physicalStateObj.SniContext, String.Format((IFormatProvider)null, "SniContext should be Undefined; actual Value: {0}", Parser._physicalStateObj.SniContext));
- try {
- //
- AttemptOneLogin( serverInfo,
- newPassword,
- newSecurePassword,
- !connectionOptions.MultiSubnetFailover, // ignore timeout for SniOpen call unless MSF
- connectionOptions.MultiSubnetFailover ? intervalTimer : timeout);
-
- if (connectionOptions.MultiSubnetFailover && null != ServerProvidedFailOverPartner) {
- // connection succeeded: trigger exception if server sends failover partner and MultiSubnetFailover is used
- throw SQL.MultiSubnetFailoverWithFailoverPartner(serverProvidedFailoverPartner: true, internalConnection: this);
- }
- if (_routingInfo != null) {
- Bid.Trace("<sc.SqlInternalConnectionTds.LoginNoFailover> Routed to %ls", serverInfo.ExtendedServerName);
- if (routingAttempts > 0) {
- throw SQL.ROR_RecursiveRoutingNotSupported(this);
- }
- if (timeout.IsExpired) {
- throw SQL.ROR_TimeoutAfterRoutingInfo(this);
- }
- serverInfo = new ServerInfo(ConnectionOptions, _routingInfo, serverInfo.ResolvedServerName);
- timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.RoutingDestination);
- _originalClientConnectionId = _clientConnectionId;
- _routingDestination = serverInfo.UserServerName;
- // restore properties that could be changed by the environment tokens
- _currentPacketSize = ConnectionOptions.PacketSize;
- _currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage;
- CurrentDatabase = _originalDatabase = ConnectionOptions.InitialCatalog;
- _currentFailoverPartner = null;
- _instanceName = String.Empty;
- routingAttempts++;
- continue; // repeat the loop, but skip code reserved for failed connections (after the catch)
- }
- else {
- break; // leave the while loop -- we've successfully connected
- }
- }
- catch (SqlException sqlex) {
- if (null == _parser
- || TdsParserState.Closed != _parser.State
- || IsDoNotRetryConnectError(sqlex)
- || timeout.IsExpired) { // no more time to try again
- throw; // Caller will call LoginFailure()
- }
- // Check sleep interval to make sure we won't exceed the timeout
- // Do this in the catch block so we can re-throw the current exception
- if (timeout.MillisecondsRemaining <= sleepInterval) {
- throw;
- }
- //
- }
- // We only get here when we failed to connect, but are going to re-try
- // Switch to failover logic if the server provided a partner
- if (null != ServerProvidedFailOverPartner) {
- if (connectionOptions.MultiSubnetFailover) {
- // connection failed: do not allow failover to server-provided failover partner if MultiSubnetFailover is set
- throw SQL.MultiSubnetFailoverWithFailoverPartner(serverProvidedFailoverPartner: true, internalConnection: this);
- }
- Debug.Assert(ConnectionOptions.ApplicationIntent != ApplicationIntent.ReadOnly, "FAILOVER+AppIntent=RO: Should already fail (at LOGSHIPNODE in OnEnvChange)");
- timeoutErrorInternal.ResetAndRestartPhase();
- timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.PreLoginBegin);
- timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Failover);
- timeoutErrorInternal.SetFailoverScenario(true); // this is a failover scenario
- LoginWithFailover(
- true, // start by using failover partner, since we already failed to connect to the primary
- serverInfo,
- ServerProvidedFailOverPartner,
- newPassword,
- newSecurePassword,
- redirectedUserInstance,
- connectionOptions,
- credential,
- timeout);
- return; // LoginWithFailover successfully connected and handled entire connection setup
- }
- // Sleep for a bit to prevent clogging the network with requests,
- // then update sleep interval for next iteration (max 1 second interval)
- if (Bid.AdvancedOn) {
- Bid.Trace("<sc.SqlInternalConnectionTds.LoginNoFailover|ADV> %d#, sleeping %d{milisec}\n", ObjectID, sleepInterval);
- }
- Thread.Sleep(sleepInterval);
- sleepInterval = (sleepInterval < 500) ? sleepInterval * 2 : 1000;
- }
- if (null != PoolGroupProviderInfo) {
- // We must wait for CompleteLogin to finish for to have the
- // env change from the server to know its designated failover
- // partner; save this information in _currentFailoverPartner.
- PoolGroupProviderInfo.FailoverCheck(this, false, connectionOptions, ServerProvidedFailOverPartner);
- }
- CurrentDataSource = originalServerInfo.UserServerName;
- }
- // Attempt to login to a host that has a failover partner
- //
- // Connection & timeout sequence is
- // First target, timeout = interval * 1
- // second target, timeout = interval * 1
- // sleep for 100ms
- // First target, timeout = interval * 2
- // Second target, timeout = interval * 2
- // sleep for 200ms
- // First Target, timeout = interval * 3
- // etc.
- //
- // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- // DEVNOTE: The logic in this method is paralleled by the logic in LoginNoFailover.
- // Changes to either one should be examined to see if they need to be reflected in the other
- // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
- private void LoginWithFailover(
- bool useFailoverHost,
- ServerInfo primaryServerInfo,
- string failoverHost,
- string newPassword,
- SecureString newSecurePassword,
- bool redirectedUserInstance,
- SqlConnectionString connectionOptions,
- SqlCredential credential,
- TimeoutTimer timeout
- ) {
- Debug.Assert(!connectionOptions.MultiSubnetFailover, "MultiSubnetFailover should not be set if failover partner is used");
- if (Bid.AdvancedOn) {
- Bid.Trace("<sc.SqlInternalConnectionTds.LoginWithFailover|ADV> %d#, useFailover=%d{bool}, primary=", ObjectID, useFailoverHost);
- Bid.PutStr(primaryServerInfo.UserServerName);
- Bid.PutStr(", failover=");
- Bid.PutStr(failoverHost);
- Bid.PutStr("\n");
- }
- int sleepInterval = 100; //milliseconds to sleep (back off) between attempts.
- long timeoutUnitInterval;
- string protocol = ConnectionOptions.NetworkLibrary;
- ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost);
- ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, connectionOptions);
- if (null == ServerProvidedFailOverPartner) {// No point in resolving the failover partner when we're going to override it below
- // Don't resolve aliases if failover == primary //
- ResolveExtendedServerName(failoverServerInfo, !redirectedUserInstance && failoverHost != primaryServerInfo.UserServerName, connectionOptions);
- }
- // Determine unit interval
- if (timeout.IsInfinite) {
- timeoutUnitInterval = checked((long) ADP.FailoverTimeoutStep * ADP.TimerFromSeconds(ADP.DefaultConnectionTimeout));
- }
- else {
- timeoutUnitInterval = checked((long) (ADP.FailoverTimeoutStep * timeout.MillisecondsRemaining));
- }
- // Initialize loop variables
- bool failoverDemandDone = false; // have we demanded for partner information yet (as necessary)?
- int attemptNumber = 0;
- // Only three ways out of this loop:
- // 1) Successfully connected
- // 2) Parser threw exception while main timer was expired
- // 3) Parser threw logon failure-related exception (LOGON_FAILED, PASSWORD_EXPIRED, etc)
- //
- // Of these methods, only #1 exits normally. This preserves the call stack on the exception
- // back into the parser for the error cases.
- while (true) {
- // Set timeout for this attempt, but don't exceed original timer
- long nextTimeoutInterval = checked(timeoutUnitInterval * ((attemptNumber / 2) + 1));
- long milliseconds = timeout.MillisecondsRemaining;
- if (nextTimeoutInterval > milliseconds) {
- nextTimeoutInterval = milliseconds;
- }
- TimeoutTimer intervalTimer = TimeoutTimer.StartMillisecondsTimeout(nextTimeoutInterval);
- // Re-allocate parser each time to make sure state is known
- // RFC 50002652 - if parser was created by previous attempt, dispose it to properly close the socket, if created
- if (_parser != null)
- _parser.Disconnect();
- _parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous);
- Debug.Assert(SniContext.Undefined== Parser._physicalStateObj.SniContext, String.Format((IFormatProvider)null, "SniContext should be Undefined; actual Value: {0}", Parser._physicalStateObj.SniContext));
- ServerInfo currentServerInfo;
- if (useFailoverHost) {
- if (!failoverDemandDone) {
- FailoverPermissionDemand();
- failoverDemandDone = true;
- }
- // Primary server may give us a different failover partner than the connection string indicates. Update it
- if (null != ServerProvidedFailOverPartner && failoverServerInfo.ResolvedServerName != ServerProvidedFailOverPartner) {
- if (Bid.AdvancedOn) {
- Bid.Trace("<sc.SqlInternalConnectionTds.LoginWithFailover|ADV> %d#, new failover partner=%ls\n", ObjectID, ServerProvidedFailOverPartner);
- }
- failoverServerInfo.SetDerivedNames(protocol, ServerProvidedFailOverPartner);
- }
- currentServerInfo = failoverServerInfo;
- timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Failover);
- }
- else {
- currentServerInfo = primaryServerInfo;
- timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Principle);
- }
- try {
- // Attempt login. Use timerInterval for attempt timeout unless infinite timeout was requested.
- AttemptOneLogin(
- currentServerInfo,
- newPassword,
- newSecurePassword,
- false, // Use timeout in SniOpen
- intervalTimer,
- withFailover:true
- );
- if (_routingInfo != null) {
- // We are in login with failover scenation and server sent routing information
- // If it is read-only routing - we did not supply AppIntent=RO (it should be checked before)
- // If it is something else, not known yet (future server) - this client is not designed to support this.
- // In any case, server should not have sent the routing info.
- Bid.Trace("<sc.SqlInternalConnectionTds.LoginWithFailover> Routed to %ls", _routingInfo.ServerName);
- throw SQL.ROR_UnexpectedRoutingInfo(this);
- }
- break; // leave the while loop -- we've successfully connected
- }
- catch (SqlException sqlex) {
- if (IsDoNotRetryConnectError(sqlex)
- || timeout.IsExpired)
- { // no more time to try again
- throw; // Caller will call LoginFailure()
- }
- if (IsConnectionDoomed) {
- throw;
- }
- if (1 == attemptNumber % 2) {
- // Check sleep interval to make sure we won't exceed the original timeout
- // Do this in the catch block so we can re-throw the current exception
- if (timeout.MillisecondsRemaining <= sleepInterval) {
- throw;
- }
- }
- //
- }
- // We only get here when we failed to connect, but are going to re-try
- // After trying to connect to both servers fails, sleep for a bit to prevent clogging
- // the network with requests, then update sleep interval for next iteration (max 1 second interval)
- if (1 == attemptNumber % 2) {
- if (Bid.AdvancedOn) {
- Bid.Trace("<sc.SqlInternalConnectionTds.LoginWithFailover|ADV> %d#, sleeping %d{milisec}\n", ObjectID, sleepInterval);
- }
- Thread.Sleep(sleepInterval);
- sleepInterval = (sleepInterval < 500) ? sleepInterval * 2 : 1000;
- }
- // Update attempt number and target host
- attemptNumber++;
- useFailoverHost = !useFailoverHost;
- }
- // If we get here, connection/login succeeded! Just a few more checks & record-keeping
- // if connected to failover host, but said host doesn't have DbMirroring set up, throw an error
- if (useFailoverHost && null == ServerProvidedFailOverPartner) {
- throw SQL.InvalidPartnerConfiguration(failoverHost, CurrentDatabase);
- }
- if (null != PoolGroupProviderInfo) {
- // We must wait for CompleteLogin to finish for to have the
- // env change from the server to know its designated failover
- // partner; save this information in _currentFailoverPartner.
- PoolGroupProviderInfo.FailoverCheck(this, useFailoverHost, connectionOptions, ServerProvidedFailOverPartner);
- }
- CurrentDataSource = (useFailoverHost ? failoverHost : primaryServerInfo.UserServerName);
- }
- private void ResolveExtendedServerName(ServerInfo serverInfo, bool aliasLookup, SqlConnectionString options) {
- if (serverInfo.ExtendedServerName == null) {
- string host = serverInfo.UserServerName;
- string protocol = serverInfo.UserProtocol;
- if (aliasLookup) { // We skip this for UserInstances...
- // Perform registry lookup to see if host is an alias. It will appropriately set host and protocol, if an Alias.
- // Check if it was already resolved, during CR reconnection _currentSessionData values will be copied from
- // _reconnectSessonData of the previous connection
- if (_currentSessionData != null && !string.IsNullOrEmpty(host)) {
- Tuple<string, string> hostPortPair;
- if (_currentSessionData._resolvedAliases.TryGetValue(host, out hostPortPair)) {
- host = hostPortPair.Item1;
- protocol = hostPortPair.Item2;
- }
- else {
- TdsParserStaticMethods.AliasRegistryLookup(ref host, ref protocol);
- _currentSessionData._resolvedAliases.Add(serverInfo.UserServerName, new Tuple<string, string>(host, protocol));
- }
- }
- else {
- TdsParserStaticMethods.AliasRegistryLookup(ref host, ref protocol);
- }
- //
- if (options.EnforceLocalHost) {
- // verify LocalHost for |DataDirectory| usage
- SqlConnectionString.VerifyLocalHostAndFixup(ref host, true, true /*fix-up to "."*/);
- }
- }
- serverInfo.SetDerivedNames(protocol, host);
- }
- }
- // Common code path for making one attempt to establish a connection and log in to server.
- private void AttemptOneLogin(ServerInfo serverInfo, string newPassword, SecureString newSecurePassword, bool ignoreSniOpenTimeout, TimeoutTimer timeout, bool withFailover = false) {
- if (Bid.AdvancedOn) {
- Bid.Trace("<sc.SqlInternalConnectionTds.AttemptOneLogin|ADV> %d#, timout=%I64d{msec}, server=", ObjectID, timeout.MillisecondsRemaining);
- Bid.PutStr(serverInfo.ExtendedServerName);
- Bid.Trace("\n");
- }
- _routingInfo = null; // forget routing information
- _parser._physicalStateObj.SniContext = SniContext.Snix_Connect;
- _parser.Connect(serverInfo,
- this,
- ignoreSniOpenTimeout,
- timeout.LegacyTimerExpire,
- ConnectionOptions.Encrypt,
- ConnectionOptions.TrustServerCertificate,
- ConnectionOptions.IntegratedSecurity,
- withFailover);
- timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake);
- timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.LoginBegin);
- _parser._physicalStateObj.SniContext = SniContext.Snix_Login;
- this.Login(serverInfo, timeout, newPassword, newSecurePassword);
- timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth);
- timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.PostLogin);
- CompleteLogin(!ConnectionOptions.Pooling);
- timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PostLogin);
- }
- internal void FailoverPermissionDemand() {
- if (null != PoolGroupProviderInfo) {
- PoolGroupProviderInfo.FailoverPermissionDemand();
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- // PREPARED COMMAND METHODS
- ////////////////////////////////////////////////////////////////////////////////////////
-
- protected override object ObtainAdditionalLocksForClose() {
- bool obtainParserLock = !ThreadHasParserLockForClose;
- Debug.Assert(obtainParserLock || _parserLock.ThreadMayHaveLock(), "Thread claims to have lock, but lock is not taken");
- if (obtainParserLock) {
- _parserLock.Wait(canReleaseFromAnyThread: false);
- ThreadHasParserLockForClose = true;
- }
- return obtainParserLock;
- }
- protected override void ReleaseAdditionalLocksForClose(object lockToken) {
- Debug.Assert(lockToken is bool, "Lock token should be boolean");
- if ((bool)lockToken) {
- ThreadHasParserLockForClose = false;
- _parserLock.Release();
- }
- }
- // called by SqlConnection.RepairConnection which is a relatevly expensive way of repair inner connection
- // prior to execution of request, used from EnlistTransaction, EnlistDistributedTransaction and ChangeDatabase
- internal bool GetSessionAndReconnectIfNeeded(SqlConnection parent, int timeout = 0) {
- Debug.Assert(!ThreadHasParserLockForClose, "Cannot call this method if caller has parser lock");
- if (ThreadHasParserLockForClose) {
- return false; // we cannot restore if we cannot release lock
- }
- _parserLock.Wait(canReleaseFromAnyThread: false);
- ThreadHasParserLockForClose = true; // In case of error, let the connection know that we already own the parser lock
- bool releaseConnectionLock = true;
- try {
- RuntimeHelpers.PrepareConstrainedRegions();
- try {
- #if DEBUG
- TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
- RuntimeHelpers.PrepareConstrainedRegions();
- try {
- tdsReliabilitySection.Start();
- #endif //DEBUG
- Task reconnectTask = parent.ValidateAndReconnect(() => {
- ThreadHasParserLockForClose = false;
- _parserLock.Release();
- releaseConnectionLock = false;
- }, timeout);
- if (reconnectTask != null) {
- AsyncHelper.WaitForCompletion(reconnectTask, timeout);
- return true;
- }
- return false;
- #if DEBUG
- }
- finally {
- tdsReliabilitySection.Stop();
- }
- #endif //DEBUG
- }
- catch (System.OutOfMemoryException) {
- DoomThisConnection();
- throw;
- }
- catch (System.StackOverflowException) {
- DoomThisConnection();
- throw;
- }
- catch (System.Threading.ThreadAbortException) {
- DoomThisConnection();
- throw;
- }
- }
- finally {
- if (releaseConnectionLock) {
- ThreadHasParserLockForClose = false;
- _parserLock.Release();
- }
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- // PARSER CALLBACKS
- ////////////////////////////////////////////////////////////////////////////////////////
- internal void BreakConnection() {
- var connection = Connection;
- Bid.Trace("<sc.SqlInternalConnectionTds.BreakConnection|RES|CPOOL> %d#, Breaking connection.\n", ObjectID);
- DoomThisConnection(); // Mark connection as unusable, so it will be destroyed
- if (null != connection) {
- connection.Close();
- }
- }
- internal bool IgnoreEnvChange { // true if we are only draining environment change tokens, used by TdsParser
- get {
- return _routingInfo != null; // connection was routed, ignore rest of env change
- }
- }
- internal void OnEnvChange(SqlEnvChange rec) {
- Debug.Assert(!IgnoreEnvChange,"This function should not be called if IgnoreEnvChange is set!");
- switch (rec.type) {
- case TdsEnums.ENV_DATABASE:
- // If connection is not open and recovery is not in progresss, store the server value as the original.
- if (!_fConnectionOpen && _recoverySessionData == null) {
- _originalDatabase = rec.newValue;
- }
- CurrentDatabase = rec.newValue;
- break;
- case TdsEnums.ENV_LANG:
- // If connection is not open and recovery is not in progresss, store the server value as the original.
- if (!_fConnectionOpen && _recoverySessionData == null) {
- _originalLanguage = rec.newValue;
- }
- _currentLanguage = rec.newValue; // TODO: finish this.
- break;
- case TdsEnums.ENV_PACKETSIZE:
- _currentPacketSize = Int32.Parse(rec.newValue, CultureInfo.InvariantCulture);
- break;
- case TdsEnums.ENV_COLLATION:
- if (_currentSessionData != null) {
- _currentSessionData._collation = rec.newCollation;
- }
- break;
- case TdsEnums.ENV_CHARSET:
- case TdsEnums.ENV_LOCALEID:
- case TdsEnums.ENV_COMPFLAGS:
- case TdsEnums.ENV_BEGINTRAN:
- case TdsEnums.ENV_COMMITTRAN:
- case TdsEnums.ENV_ROLLBACKTRAN:
- case TdsEnums.ENV_ENLISTDTC:
- case TdsEnums.ENV_DEFECTDTC:
- // only used on parser
- break;
- case TdsEnums.ENV_LOGSHIPNODE:
- if (ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly) {
- throw SQL.ROR_FailoverNotSupportedServer(this);
- }
- _currentFailoverPartner = rec.newValue;
- break;
- case TdsEnums.ENV_PROMOTETRANSACTION:
- PromotedDTCToken = rec.newBinValue;
- break;
- case TdsEnums.ENV_TRANSACTIONENDED:
- break;
- case TdsEnums.ENV_TRANSACTIONMANAGERADDRESS:
- // For now we skip these Yukon only env change notifications
- break;
- case TdsEnums.ENV_SPRESETCONNECTIONACK:
- // connection is being reset
- if (_currentSessionData != null) {
- _currentSessionData.Reset();
- }
- break;
- case TdsEnums.ENV_USERINSTANCE:
- _instanceName = rec.newValue;
- break;
- case TdsEnums.ENV_ROUTING:
- if (string.IsNullOrEmpty(rec.newRoutingInfo.ServerName) || rec.newRoutingInfo.Protocol != 0 || rec.newRoutingInfo.Port == 0) {
- throw SQL.ROR_InvalidRoutingInfo(this);
- }
- _routingInfo = rec.newRoutingInfo;
- break;
- default:
- Debug.Assert(false, "Missed token in EnvChange!");
- break;
- }
- }
- internal void OnLoginAck(SqlLoginAck rec) {
- _loginAck = rec;
- //
- if (_recoverySessionData != null) {
- if (_recoverySessionData._tdsVersion != rec.tdsVersion) {
- throw SQL.CR_TDSVersionNotPreserved(this);
- }
- }
- if (_currentSessionData != null) {
- _currentSessionData._tdsVersion = rec.tdsVersion;
- }
- }
- internal void OnFeatureExtAck(int featureId, byte[] data) {
- if (_routingInfo != null) {
- return;
- }
- switch (featureId) {
- case TdsEnums.FEATUREEXT_SRECOVERY: {
- // Session recovery not requested
- if (!_sessionRecoveryRequested) {
- throw SQL.ParsingError();
- }
- _sessionRecoveryAcknowledged = true;
- #if DEBUG
- foreach (var s in _currentSessionData._delta) {
- Debug.Assert(s==null, "Delta should be null at this point");
- }
- #endif
- Debug.Assert(_currentSessionData._unrecoverableStatesCount == 0, "Unrecoverable states count should be 0");
- int i = 0;
- while (i < data.Length) {
- byte stateId = data[i]; i++;
- int len;
- byte bLen = data[i]; i++;
- if (bLen == 0xFF) {
- len = BitConverter.ToInt32(data, i); i += 4;
- }
- else {
- len = bLen;
- }
- byte[] stateData = new byte[len];
- Buffer.BlockCopy(data, i, stateData, 0, len); i += len;
- if (_recoverySessionData == null) {
- _currentSessionData._initialState[stateId] = stateData;
- }
- else {
- _currentSessionData._delta[stateId] = new SessionStateRecord { _data = stateData, _dataLength = len, _recoverable = true, _version = 0 };
- _currentSessionData._deltaDirty = true;
- }
- }
- break;
- }
- default: {
- // Unknown feature ack
- throw SQL.ParsingError();
- }
- }
- }
- ////////////////////////////////////////////////////////////////////////////////////////
- // Helper methods for Locks
- ////////////////////////////////////////////////////////////////////////////////////////
-
- // Indicates if the current thread claims to hold the parser lock
- internal bool ThreadHasParserLockForClose {
- get {
- return _threadIdOwningParserLock == Thread.CurrentThread.ManagedThreadId;
- }
- set {
- Debug.Assert(_parserLock.ThreadMayHaveLock(), "Should not modify ThreadHasParserLockForClose without taking the lock first");
- Debug.Assert(_threadIdOwningParserLock == -1 || _threadIdOwningParserLock == Thread.CurrentThread.ManagedThreadId, "Another thread already claims to own the parser lock");
- if (value) {
- // If setting to true, then the thread owning the lock is the current thread
- _threadIdOwningParserLock = Thread.CurrentThread.ManagedThreadId;
- }
- else if (_threadIdOwningParserLock == Thread.CurrentThread.ManagedThreadId) {
- // If setting to false and currently owns the lock, then no-one owns the lock
- _threadIdOwningParserLock = -1;
- }
- // else This thread didn't own the parser lock and doesn't claim to own it, so do nothing
- }
- }
- internal override bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource<DbConnectionInternal> retry, DbConnectionOptions userOptions) {
- return base.TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions);
- }
- }
- internal sealed class ServerInfo {
- internal string ExtendedServerName { get; private set; } // the resolved servername with protocol
- internal string ResolvedServerName { get; private set; } // the resolved servername only
- internal string ResolvedDatabaseName { get; private set; } // name of target database after resolution
- internal string UserProtocol { get; private set; } // the user specified protocol
- // The original user-supplied server name from the connection string.
- // If connection string has no Data Source, the value is set to string.Empty.
- // In case of routing, will be changed to routing destination
- internal string UserServerName
- {
- get
- {
- return m_userServerName;
- }
- private set
- {
- m_userServerName = value;
- }
- } private string m_userServerName;
- internal readonly string PreRoutingServerName;
-
- // Initialize server info from connection options,
- internal ServerInfo(SqlConnectionString userOptions) : this(userOptions, userOptions.DataSource) {}
- // Initialize server info from connection options, but override DataSource with given server name
- internal ServerInfo (SqlConnectionString userOptions, string serverName) {
- //-----------------
- // Preconditions
- Debug.Assert(null != userOptions);
- //-----------------
- //Method body
-
- Debug.Assert(serverName != null, "server name should never be null");
- UserServerName = (serverName ?? string.Empty); // ensure user server name is not null
- UserProtocol = userOptions.NetworkLibrary;
- ResolvedDatabaseName = userOptions.InitialCatalog;
- PreRoutingServerName = null;
- }
- // Initialize server info from connection options, but override DataSource with given server name
- internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string preRoutingServerName) {
- //-----------------
- // Preconditions
- Debug.Assert(null != userOptions && null!=routing);
- //-----------------
- //Method body
- Debug.Assert(routing.ServerName != null, "server name should never be null");
- if (routing == null || routing.ServerName == null) {
- UserServerName = string.Empty; // ensure user server name is not null
- }
- else {
- UserServerName = string.Format(CultureInfo.InvariantCulture, "{0},{1}", routing.ServerName, routing.Port);
- }
- PreRoutingServerName = preRoutingServerName;
- UserProtocol = TdsEnums.TCP;
- SetDerivedNames(UserProtocol, UserServerName);
- ResolvedDatabaseName = userOptions.InitialCatalog;
- }
- internal void SetDerivedNames(string protocol, string serverName) {
- // The following concatenates the specified netlib network protocol to the host string, if netlib is not null
- // and the flag is on. This allows the user to specify the network protocol for the connection - but only
- // when using the Dbnetlib dll. If the protocol is not specified, the netlib will
- // try all protocols in the order listed in the Client Network Utility. Connect will
- // then fail if all protocols fail.
- if (!ADP.IsEmpty(protocol)) {
- ExtendedServerName = protocol + ":" + serverName;
- }
- else {
- ExtendedServerName = serverName;
- }
- ResolvedServerName = serverName;
- }
- }
- }
|