SqlInternalConnectionTds.cs 92 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009
  1. //------------------------------------------------------------------------------
  2. // <copyright file="SqlInternalConnectionTds.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // <owner current="true" primary="true">[....]</owner>
  6. // <owner current="true" primary="false">[....]</owner>
  7. //------------------------------------------------------------------------------
  8. namespace System.Data.SqlClient
  9. {
  10. using System;
  11. using System.Collections.Generic;
  12. using System.Data;
  13. using System.Data.Common;
  14. using System.Data.ProviderBase;
  15. using System.Diagnostics;
  16. using System.Globalization;
  17. using System.Reflection;
  18. using System.Runtime.CompilerServices;
  19. using System.Runtime.ConstrainedExecution;
  20. using System.Runtime.InteropServices;
  21. using System.Security;
  22. using System.Security.Permissions;
  23. using System.Text;
  24. using System.Threading;
  25. using SysTx = System.Transactions;
  26. using System.Diagnostics.CodeAnalysis;
  27. using System.Threading.Tasks;
  28. internal class SessionStateRecord {
  29. internal bool _recoverable;
  30. internal UInt32 _version;
  31. internal Int32 _dataLength;
  32. internal byte[] _data;
  33. }
  34. internal class SessionData {
  35. internal const int _maxNumberOfSessionStates = 256;
  36. internal UInt32 _tdsVersion;
  37. internal bool _encrypted;
  38. internal string _database;
  39. internal SqlCollation _collation;
  40. internal string _language;
  41. internal string _initialDatabase;
  42. internal SqlCollation _initialCollation;
  43. internal string _initialLanguage;
  44. internal byte _unrecoverableStatesCount = 0;
  45. internal Dictionary<string, Tuple<string, string>> _resolvedAliases;
  46. #if DEBUG
  47. internal bool _debugReconnectDataApplied;
  48. #endif
  49. internal SessionStateRecord[] _delta = new SessionStateRecord[_maxNumberOfSessionStates];
  50. internal bool _deltaDirty = false;
  51. internal byte[][] _initialState = new byte[_maxNumberOfSessionStates][];
  52. public SessionData(SessionData recoveryData) {
  53. _initialDatabase = recoveryData._initialDatabase;
  54. _initialCollation = recoveryData._initialCollation;
  55. _initialLanguage = recoveryData._initialLanguage;
  56. _resolvedAliases = recoveryData._resolvedAliases;
  57. for (int i = 0; i < _maxNumberOfSessionStates; i++) {
  58. if (recoveryData._initialState[i] != null) {
  59. _initialState[i] = (byte[])recoveryData._initialState[i].Clone();
  60. }
  61. }
  62. }
  63. public SessionData() {
  64. _resolvedAliases = new Dictionary<string, Tuple<string, string>>(2);
  65. }
  66. public void Reset() {
  67. _database = null;
  68. _collation = null;
  69. _language = null;
  70. if (_deltaDirty) {
  71. _delta = new SessionStateRecord[_maxNumberOfSessionStates];
  72. _deltaDirty = false;
  73. }
  74. _unrecoverableStatesCount = 0;
  75. }
  76. [Conditional("DEBUG")]
  77. public void AssertUnrecoverableStateCountIsCorrect() {
  78. byte unrecoverableCount = 0;
  79. foreach (var state in _delta) {
  80. if (state != null && !state._recoverable)
  81. unrecoverableCount++;
  82. }
  83. Debug.Assert(unrecoverableCount == _unrecoverableStatesCount, "Unrecoverable count does not match");
  84. }
  85. }
  86. sealed internal class SqlInternalConnectionTds : SqlInternalConnection, IDisposable {
  87. // CONNECTION AND STATE VARIABLES
  88. private readonly SqlConnectionPoolGroupProviderInfo _poolGroupProviderInfo; // will only be null when called for ChangePassword, or creating SSE User Instance
  89. private TdsParser _parser;
  90. private SqlLoginAck _loginAck;
  91. private SqlCredential _credential;
  92. // Connection Resiliency
  93. private bool _sessionRecoveryRequested;
  94. internal bool _sessionRecoveryAcknowledged;
  95. internal SessionData _currentSessionData; // internal for use from TdsParser only, otehr should use CurrentSessionData property that will fix database and language
  96. private SessionData _recoverySessionData;
  97. internal SessionData CurrentSessionData {
  98. get {
  99. if (_currentSessionData != null) {
  100. _currentSessionData._database = CurrentDatabase;
  101. _currentSessionData._language = _currentLanguage;
  102. }
  103. return _currentSessionData;
  104. }
  105. }
  106. // FOR POOLING
  107. private bool _fConnectionOpen = false;
  108. // FOR CONNECTION RESET MANAGEMENT
  109. private bool _fResetConnection;
  110. private string _originalDatabase;
  111. private string _currentFailoverPartner; // only set by ENV change from server
  112. private string _originalLanguage;
  113. private string _currentLanguage;
  114. private int _currentPacketSize;
  115. private int _asyncCommandCount; // number of async Begins minus number of async Ends.
  116. // FOR SSE
  117. private string _instanceName = String.Empty;
  118. // FOR NOTIFICATIONS
  119. private DbConnectionPoolIdentity _identity; // Used to lookup info for notification matching Start().
  120. // FOR SYNCHRONIZATION IN TdsParser
  121. // How to use these locks:
  122. // 1. Whenever writing to the connection (with the exception of Cancellation) the _parserLock MUST be taken
  123. // 2. _parserLock will also be taken during close (to prevent closing in the middle of a write)
  124. // 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
  125. // * 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)
  126. // * It is safe to set ThreadHasParserLockForClose to true when writing as well, but it is unneccesary
  127. // * 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)
  128. // 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
  129. // 5. ThreadHasParserLockForClose should only be modified if you currently own the _parserLock
  130. // 6. Reading ThreadHasParserLockForClose is thread-safe
  131. internal class SyncAsyncLock
  132. {
  133. SemaphoreSlim semaphore = new SemaphoreSlim(1);
  134. internal void Wait(bool canReleaseFromAnyThread)
  135. {
  136. Monitor.Enter(semaphore); // semaphore is used as lock object, no relation to SemaphoreSlim.Wait/Release methods
  137. if (canReleaseFromAnyThread || semaphore.CurrentCount==0) {
  138. semaphore.Wait();
  139. if (canReleaseFromAnyThread) {
  140. Monitor.Exit(semaphore);
  141. }
  142. else {
  143. semaphore.Release();
  144. }
  145. }
  146. }
  147. internal void Wait(bool canReleaseFromAnyThread, int timeout, ref bool lockTaken) {
  148. lockTaken = false;
  149. bool hasMonitor = false;
  150. try {
  151. Monitor.TryEnter(semaphore, timeout, ref hasMonitor); // semaphore is used as lock object, no relation to SemaphoreSlim.Wait/Release methods
  152. if (hasMonitor) {
  153. if ((canReleaseFromAnyThread) || (semaphore.CurrentCount == 0)) {
  154. if (semaphore.Wait(timeout)) {
  155. if (canReleaseFromAnyThread) {
  156. Monitor.Exit(semaphore);
  157. hasMonitor = false;
  158. }
  159. else {
  160. semaphore.Release();
  161. }
  162. lockTaken = true;
  163. }
  164. }
  165. else {
  166. lockTaken = true;
  167. }
  168. }
  169. }
  170. finally
  171. {
  172. if ((!lockTaken) && (hasMonitor)) {
  173. Monitor.Exit(semaphore);
  174. }
  175. }
  176. }
  177. internal void Release()
  178. {
  179. if (semaphore.CurrentCount==0) { // semaphore methods were used for locking
  180. semaphore.Release();
  181. }
  182. else {
  183. Monitor.Exit(semaphore);
  184. }
  185. }
  186. internal bool CanBeReleasedFromAnyThread {
  187. get {
  188. return semaphore.CurrentCount==0;
  189. }
  190. }
  191. // Necessary but not sufficient condition for thread to have lock (since sempahore may be obtained by any thread)
  192. internal bool ThreadMayHaveLock() {
  193. return Monitor.IsEntered(semaphore) || semaphore.CurrentCount == 0;
  194. }
  195. }
  196. internal SyncAsyncLock _parserLock = new SyncAsyncLock();
  197. private int _threadIdOwningParserLock = -1;
  198. private SqlConnectionTimeoutErrorInternal timeoutErrorInternal;
  199. internal SqlConnectionTimeoutErrorInternal TimeoutErrorInternal
  200. {
  201. get { return timeoutErrorInternal; }
  202. }
  203. // OTHER STATE VARIABLES AND REFERENCES
  204. internal Guid _clientConnectionId = Guid.Empty;
  205. // Routing information (ROR)
  206. RoutingInfo _routingInfo = null;
  207. private Guid _originalClientConnectionId = Guid.Empty;
  208. private string _routingDestination = null;
  209. // although the new password is generally not used it must be passed to the c'tor
  210. // the new Login7 packet will always write out the new password (or a length of zero and no bytes if not present)
  211. //
  212. internal SqlInternalConnectionTds(
  213. DbConnectionPoolIdentity identity,
  214. SqlConnectionString connectionOptions,
  215. SqlCredential credential,
  216. object providerInfo,
  217. string newPassword,
  218. SecureString newSecurePassword,
  219. bool redirectedUserInstance,
  220. SqlConnectionString userConnectionOptions = null, // NOTE: userConnectionOptions may be different to connectionOptions if the connection string has been expanded (see SqlConnectionString.Expand)
  221. SessionData reconnectSessionData = null) : base(connectionOptions) {
  222. #if DEBUG
  223. if (reconnectSessionData != null) {
  224. reconnectSessionData._debugReconnectDataApplied = true;
  225. }
  226. try { // use this to help validate this object is only created after the following permission has been previously demanded in the current codepath
  227. if (userConnectionOptions != null) {
  228. // As mentioned above, userConnectionOptions may be different to connectionOptions, so we need to demand on the correct connection string
  229. userConnectionOptions.DemandPermission();
  230. }
  231. else {
  232. connectionOptions.DemandPermission();
  233. }
  234. }
  235. catch(System.Security.SecurityException) {
  236. System.Diagnostics.Debug.Assert(false, "unexpected SecurityException for current codepath");
  237. throw;
  238. }
  239. #endif
  240. Debug.Assert(reconnectSessionData == null || connectionOptions.ConnectRetryCount > 0, "Reconnect data supplied with CR turned off");
  241. if (connectionOptions.ConnectRetryCount > 0) {
  242. _recoverySessionData = reconnectSessionData;
  243. if (reconnectSessionData == null) {
  244. _currentSessionData = new SessionData();
  245. }
  246. else {
  247. _currentSessionData = new SessionData(_recoverySessionData);
  248. _originalDatabase = _recoverySessionData._initialDatabase;
  249. _originalLanguage = _recoverySessionData._initialLanguage;
  250. }
  251. }
  252. if (connectionOptions.UserInstance && InOutOfProcHelper.InProc) {
  253. throw SQL.UserInstanceNotAvailableInProc();
  254. }
  255. _identity = identity;
  256. Debug.Assert(newSecurePassword != null || newPassword != null, "cannot have both new secure change password and string based change password to be null");
  257. 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");
  258. Debug.Assert(credential == null || !connectionOptions.IntegratedSecurity, "Cannot use SqlCredential and Integrated Security");
  259. Debug.Assert(credential == null || !connectionOptions.ContextConnection, "Cannot use SqlCredential with context connection");
  260. _poolGroupProviderInfo = (SqlConnectionPoolGroupProviderInfo)providerInfo;
  261. _fResetConnection = connectionOptions.ConnectionReset;
  262. if (_fResetConnection && _recoverySessionData == null) {
  263. _originalDatabase = connectionOptions.InitialCatalog;
  264. _originalLanguage = connectionOptions.CurrentLanguage;
  265. }
  266. timeoutErrorInternal = new SqlConnectionTimeoutErrorInternal();
  267. _credential = credential;
  268. _parserLock.Wait(canReleaseFromAnyThread:false);
  269. ThreadHasParserLockForClose = true; // In case of error, let ourselves know that we already own the parser lock
  270. RuntimeHelpers.PrepareConstrainedRegions();
  271. try {
  272. #if DEBUG
  273. TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
  274. RuntimeHelpers.PrepareConstrainedRegions();
  275. try {
  276. tdsReliabilitySection.Start();
  277. #else
  278. {
  279. #endif //DEBUG
  280. var timeout = TimeoutTimer.StartSecondsTimeout(connectionOptions.ConnectTimeout);
  281. OpenLoginEnlist(timeout, connectionOptions, credential, newPassword, newSecurePassword, redirectedUserInstance);
  282. }
  283. #if DEBUG
  284. finally {
  285. tdsReliabilitySection.Stop();
  286. }
  287. #endif //DEBUG
  288. }
  289. catch (System.OutOfMemoryException) {
  290. DoomThisConnection();
  291. throw;
  292. }
  293. catch (System.StackOverflowException) {
  294. DoomThisConnection();
  295. throw;
  296. }
  297. catch (System.Threading.ThreadAbortException) {
  298. DoomThisConnection();
  299. throw;
  300. }
  301. finally {
  302. ThreadHasParserLockForClose = false;
  303. _parserLock.Release();
  304. }
  305. if (Bid.AdvancedOn) {
  306. Bid.Trace("<sc.SqlInternalConnectionTds.ctor|ADV> %d#, constructed new TDS internal connection\n", ObjectID);
  307. }
  308. }
  309. internal Guid ClientConnectionId {
  310. get {
  311. return _clientConnectionId;
  312. }
  313. }
  314. internal Guid OriginalClientConnectionId {
  315. get {
  316. return _originalClientConnectionId;
  317. }
  318. }
  319. internal string RoutingDestination {
  320. get {
  321. return _routingDestination;
  322. }
  323. }
  324. override internal SqlInternalTransaction CurrentTransaction {
  325. get {
  326. return _parser.CurrentTransaction;
  327. }
  328. }
  329. override internal SqlInternalTransaction AvailableInternalTransaction {
  330. get {
  331. return _parser._fResetConnection ? null : CurrentTransaction;
  332. }
  333. }
  334. override internal SqlInternalTransaction PendingTransaction {
  335. get {
  336. return _parser.PendingTransaction;
  337. }
  338. }
  339. internal DbConnectionPoolIdentity Identity {
  340. get {
  341. return _identity;
  342. }
  343. }
  344. internal string InstanceName {
  345. get {
  346. return _instanceName;
  347. }
  348. }
  349. override internal bool IsLockedForBulkCopy {
  350. get {
  351. return (!Parser.MARSOn && Parser._physicalStateObj.BcpLock);
  352. }
  353. }
  354. override protected internal bool IsNonPoolableTransactionRoot {
  355. get {
  356. return IsTransactionRoot && (!IsKatmaiOrNewer || null == Pool);
  357. }
  358. }
  359. override internal bool IsShiloh {
  360. get {
  361. return _loginAck.isVersion8;
  362. }
  363. }
  364. override internal bool IsYukonOrNewer {
  365. get {
  366. return _parser.IsYukonOrNewer;
  367. }
  368. }
  369. override internal bool IsKatmaiOrNewer {
  370. get {
  371. return _parser.IsKatmaiOrNewer;
  372. }
  373. }
  374. internal int PacketSize {
  375. get {
  376. return _currentPacketSize;
  377. }
  378. }
  379. internal TdsParser Parser {
  380. get {
  381. return _parser;
  382. }
  383. }
  384. internal string ServerProvidedFailOverPartner {
  385. get {
  386. return _currentFailoverPartner;
  387. }
  388. }
  389. internal SqlConnectionPoolGroupProviderInfo PoolGroupProviderInfo {
  390. get {
  391. return _poolGroupProviderInfo;
  392. }
  393. }
  394. override protected bool ReadyToPrepareTransaction {
  395. get {
  396. //
  397. bool result = (null == FindLiveReader(null)); // can't prepare with a live data reader...
  398. return result;
  399. }
  400. }
  401. override public string ServerVersion {
  402. get {
  403. return(String.Format((IFormatProvider)null, "{0:00}.{1:00}.{2:0000}", _loginAck.majorVersion,
  404. (short) _loginAck.minorVersion, _loginAck.buildNum));
  405. }
  406. }
  407. /// <summary>
  408. /// Get boolean that specifies whether an enlisted transaction can be unbound from
  409. /// the connection when that transaction completes.
  410. /// </summary>
  411. /// <value>
  412. /// This override always returns false.
  413. /// </value>
  414. /// <remarks>
  415. /// The SqlInternalConnectionTds.CheckEnlistedTransactionBinding method handles implicit unbinding for disposed transactions.
  416. /// </remarks>
  417. protected override bool UnbindOnTransactionCompletion
  418. {
  419. get
  420. {
  421. return false;
  422. }
  423. }
  424. ////////////////////////////////////////////////////////////////////////////////////////
  425. // GENERAL METHODS
  426. ////////////////////////////////////////////////////////////////////////////////////////
  427. [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] // copied from Triaged.cs
  428. override protected void ChangeDatabaseInternal(string database) {
  429. // MDAC 73598 - add brackets around database
  430. database = SqlConnection.FixupDatabaseTransactionName(database);
  431. Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch("use " + database, ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj, sync: true);
  432. Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");
  433. _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
  434. }
  435. override public void Dispose() {
  436. if (Bid.AdvancedOn) {
  437. Bid.Trace("<sc.SqlInternalConnectionTds.Dispose|ADV> %d# disposing\n", base.ObjectID);
  438. }
  439. try {
  440. TdsParser parser = Interlocked.Exchange(ref _parser, null); // guard against multiple concurrent dispose calls -- Delegated Transactions might cause this.
  441. Debug.Assert(parser != null && _fConnectionOpen || parser == null && !_fConnectionOpen, "Unexpected state on dispose");
  442. if (null != parser) {
  443. parser.Disconnect();
  444. }
  445. }
  446. finally { // UNDONE: MDAC 77928
  447. // close will always close, even if exception is thrown
  448. // remember to null out any object references
  449. _loginAck = null;
  450. _fConnectionOpen = false; // mark internal connection as closed
  451. }
  452. base.Dispose();
  453. }
  454. override internal void ValidateConnectionForExecute(SqlCommand command) {
  455. TdsParser parser = _parser;
  456. if ((parser == null) || (parser.State == TdsParserState.Broken) || (parser.State == TdsParserState.Closed)) {
  457. throw ADP.ClosedConnectionError();
  458. }
  459. else {
  460. SqlDataReader reader = null;
  461. if (parser.MARSOn) {
  462. if (null != command) { // command can't have datareader already associated with it
  463. reader = FindLiveReader(command);
  464. }
  465. }
  466. else { // single execution/datareader per connection
  467. if (_asyncCommandCount > 0) {
  468. throw SQL.MARSUnspportedOnConnection();
  469. }
  470. reader = FindLiveReader(null);
  471. }
  472. if (null != reader) {
  473. // if MARS is on, then a datareader associated with the command exists
  474. // or if MARS is off, then a datareader exists
  475. throw ADP.OpenReaderExists(); // MDAC 66411
  476. }
  477. else if (!parser.MARSOn && parser._physicalStateObj._pendingData) {
  478. parser.DrainData(parser._physicalStateObj);
  479. }
  480. Debug.Assert(!parser._physicalStateObj._pendingData, "Should not have a busy physicalStateObject at this point!");
  481. parser.RollbackOrphanedAPITransactions();
  482. }
  483. }
  484. /// <summary>
  485. /// Validate the enlisted transaction state, taking into consideration the ambient transaction and transaction unbinding mode.
  486. /// If there is no enlisted transaction, this method is a nop.
  487. /// </summary>
  488. /// <remarks>
  489. /// <para>
  490. /// This method must be called while holding a lock on the SqlInternalConnection instance,
  491. /// to ensure we don't accidentally execute after the transaction has completed on a different thread,
  492. /// causing us to unwittingly execute in auto-commit mode.
  493. /// </para>
  494. ///
  495. /// <para>
  496. /// When using Explicit transaction unbinding,
  497. /// verify that the enlisted transaction is active and equal to the current ambient transaction.
  498. /// </para>
  499. ///
  500. /// <para>
  501. /// When using Implicit transaction unbinding,
  502. /// verify that the enlisted transaction is active.
  503. /// If it is not active, and the transaction object has been diposed, unbind from the transaction.
  504. /// If it is not active and not disposed, throw an exception.
  505. /// </para>
  506. /// </remarks>
  507. internal void CheckEnlistedTransactionBinding()
  508. {
  509. // If we are enlisted in a transaction, check that transaction is active.
  510. // When using explicit transaction unbinding, also verify that the enlisted transaction is the current transaction.
  511. SysTx.Transaction enlistedTransaction = EnlistedTransaction;
  512. if (enlistedTransaction != null)
  513. {
  514. bool requireExplicitTransactionUnbind = ConnectionOptions.TransactionBinding == SqlConnectionString.TransactionBindingEnum.ExplicitUnbind;
  515. if (requireExplicitTransactionUnbind)
  516. {
  517. SysTx.Transaction currentTransaction = SysTx.Transaction.Current;
  518. if (SysTx.TransactionStatus.Active != enlistedTransaction.TransactionInformation.Status || !enlistedTransaction.Equals(currentTransaction))
  519. {
  520. throw ADP.TransactionConnectionMismatch();
  521. }
  522. }
  523. else // implicit transaction unbind
  524. {
  525. if (SysTx.TransactionStatus.Active != enlistedTransaction.TransactionInformation.Status)
  526. {
  527. if (EnlistedTransactionDisposed)
  528. {
  529. DetachTransaction(enlistedTransaction, true);
  530. }
  531. else
  532. {
  533. throw ADP.TransactionCompletedButNotDisposed();
  534. }
  535. }
  536. }
  537. }
  538. }
  539. internal override bool IsConnectionAlive(bool throwOnException)
  540. {
  541. bool isAlive = false;
  542. #if DEBUG
  543. TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
  544. RuntimeHelpers.PrepareConstrainedRegions();
  545. try
  546. {
  547. tdsReliabilitySection.Start();
  548. #endif //DEBUG
  549. isAlive = _parser._physicalStateObj.IsConnectionAlive(throwOnException);
  550. #if DEBUG
  551. }
  552. finally
  553. {
  554. tdsReliabilitySection.Stop();
  555. }
  556. #endif //DEBUG
  557. return isAlive;
  558. }
  559. ////////////////////////////////////////////////////////////////////////////////////////
  560. // POOLING METHODS
  561. ////////////////////////////////////////////////////////////////////////////////////////
  562. override protected void Activate(SysTx.Transaction transaction) {
  563. FailoverPermissionDemand(); // Demand for unspecified failover pooled connections
  564. // When we're required to automatically enlist in transactions and
  565. // there is one we enlist in it. On the other hand, if there isn't a
  566. // transaction and we are currently enlisted in one, then we
  567. // unenlist from it.
  568. //
  569. // Regardless of whether we're required to automatically enlist,
  570. // when there is not a current transaction, we cannot leave the
  571. // connection enlisted in a transaction.
  572. if (null != transaction){
  573. if (ConnectionOptions.Enlist) {
  574. Enlist(transaction);
  575. }
  576. }
  577. else {
  578. Enlist(null);
  579. }
  580. }
  581. override protected void InternalDeactivate() {
  582. // When we're deactivated, the user must have called End on all
  583. // the async commands, or we don't know that we're in a state that
  584. // we can recover from. We doom the connection in this case, to
  585. // prevent odd cases when we go to the wire.
  586. if (0 != _asyncCommandCount) {
  587. DoomThisConnection();
  588. }
  589. // If we're deactivating with a delegated transaction, we
  590. // should not be cleaning up the parser just yet, that will
  591. // cause our transaction to be rolled back and the connection
  592. // to be reset. We'll get called again once the delegated
  593. // transaction is completed and we can do it all then.
  594. if (!IsNonPoolableTransactionRoot) {
  595. Debug.Assert(null != _parser || IsConnectionDoomed, "Deactivating a disposed connection?");
  596. if (_parser != null) {
  597. _parser.Deactivate(IsConnectionDoomed);
  598. if (!IsConnectionDoomed) {
  599. ResetConnection();
  600. }
  601. }
  602. }
  603. }
  604. [SuppressMessage("Microsoft.Globalization", "CA1303:DoNotPassLiteralsAsLocalizedParameters")] // copied from Triaged.cs
  605. private void ResetConnection() {
  606. // For implicit pooled connections, if connection reset behavior is specified,
  607. // reset the database and language properties back to default. It is important
  608. // to do this on activate so that the hashtable is correct before SqlConnection
  609. // obtains a clone.
  610. Debug.Assert(!HasLocalTransactionFromAPI, "Upon ResetConnection SqlInternalConnectionTds has a currently ongoing local transaction.");
  611. Debug.Assert(!_parser._physicalStateObj._pendingData, "Upon ResetConnection SqlInternalConnectionTds has pending data.");
  612. if (_fResetConnection) {
  613. // Ensure we are either going against shiloh, or we are not enlisted in a
  614. // distributed transaction - otherwise don't reset!
  615. if (IsShiloh) {
  616. // Prepare the parser for the connection reset - the next time a trip
  617. // to the server is made.
  618. _parser.PrepareResetConnection(IsTransactionRoot && !IsNonPoolableTransactionRoot);
  619. }
  620. else if (!IsEnlistedInTransaction) {
  621. // If not Shiloh, we are going against Sphinx. On Sphinx, we
  622. // may only reset if not enlisted in a distributed transaction.
  623. try {
  624. // execute sp
  625. Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch("sp_reset_connection", 30, null, _parser._physicalStateObj, sync: true);
  626. Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");
  627. _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
  628. }
  629. catch (Exception e) {
  630. //
  631. if (!ADP.IsCatchableExceptionType(e)) {
  632. throw;
  633. }
  634. DoomThisConnection();
  635. ADP.TraceExceptionWithoutRethrow(e);
  636. }
  637. }
  638. // Reset hashtable values, since calling reset will not send us env_changes.
  639. CurrentDatabase = _originalDatabase;
  640. _currentLanguage = _originalLanguage;
  641. }
  642. }
  643. internal void DecrementAsyncCount() {
  644. Interlocked.Decrement(ref _asyncCommandCount);
  645. }
  646. internal void IncrementAsyncCount() {
  647. Interlocked.Increment(ref _asyncCommandCount);
  648. }
  649. ////////////////////////////////////////////////////////////////////////////////////////
  650. // LOCAL TRANSACTION METHODS
  651. ////////////////////////////////////////////////////////////////////////////////////////
  652. override internal void DisconnectTransaction(SqlInternalTransaction internalTransaction) {
  653. TdsParser parser = Parser;
  654. if (null != parser) {
  655. parser.DisconnectTransaction(internalTransaction);
  656. }
  657. }
  658. internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, IsolationLevel iso) {
  659. ExecuteTransaction(transactionRequest, name, iso, null, false);
  660. }
  661. override internal void ExecuteTransaction(TransactionRequest transactionRequest, string name, IsolationLevel iso, SqlInternalTransaction internalTransaction, bool isDelegateControlRequest) {
  662. if (IsConnectionDoomed) { // doomed means we can't do anything else...
  663. if (transactionRequest == TransactionRequest.Rollback
  664. || transactionRequest == TransactionRequest.IfRollback) {
  665. return;
  666. }
  667. throw SQL.ConnectionDoomed();
  668. }
  669. if (transactionRequest == TransactionRequest.Commit
  670. || transactionRequest == TransactionRequest.Rollback
  671. || transactionRequest == TransactionRequest.IfRollback) {
  672. if (!Parser.MARSOn && Parser._physicalStateObj.BcpLock) {
  673. throw SQL.ConnectionLockedForBcpEvent();
  674. }
  675. }
  676. string transactionName = (null == name) ? String.Empty : name;
  677. if (!_parser.IsYukonOrNewer) {
  678. ExecuteTransactionPreYukon(transactionRequest, transactionName, iso, internalTransaction);
  679. }
  680. else {
  681. ExecuteTransactionYukon(transactionRequest, transactionName, iso, internalTransaction, isDelegateControlRequest);
  682. }
  683. }
  684. // This function will not handle idle connection resiliency, as older servers will not support it
  685. internal void ExecuteTransactionPreYukon(
  686. TransactionRequest transactionRequest,
  687. string transactionName,
  688. IsolationLevel iso,
  689. SqlInternalTransaction internalTransaction) {
  690. StringBuilder sqlBatch = new StringBuilder();
  691. switch (iso) {
  692. case IsolationLevel.Unspecified:
  693. break;
  694. case IsolationLevel.ReadCommitted:
  695. sqlBatch.Append(TdsEnums.TRANS_READ_COMMITTED);
  696. sqlBatch.Append(";");
  697. break;
  698. case IsolationLevel.ReadUncommitted:
  699. sqlBatch.Append(TdsEnums.TRANS_READ_UNCOMMITTED);
  700. sqlBatch.Append(";");
  701. break;
  702. case IsolationLevel.RepeatableRead:
  703. sqlBatch.Append(TdsEnums.TRANS_REPEATABLE_READ);
  704. sqlBatch.Append(";");
  705. break;
  706. case IsolationLevel.Serializable:
  707. sqlBatch.Append(TdsEnums.TRANS_SERIALIZABLE);
  708. sqlBatch.Append(";");
  709. break;
  710. case IsolationLevel.Snapshot:
  711. throw SQL.SnapshotNotSupported(IsolationLevel.Snapshot);
  712. case IsolationLevel.Chaos:
  713. throw SQL.NotSupportedIsolationLevel(iso);
  714. default:
  715. throw ADP.InvalidIsolationLevel(iso);
  716. }
  717. if (!ADP.IsEmpty(transactionName)) {
  718. transactionName = " " + SqlConnection.FixupDatabaseTransactionName(transactionName);
  719. }
  720. switch (transactionRequest) {
  721. case TransactionRequest.Begin:
  722. sqlBatch.Append(TdsEnums.TRANS_BEGIN);
  723. sqlBatch.Append(transactionName);
  724. break;
  725. case TransactionRequest.Promote:
  726. Debug.Assert(false, "Promote called with transaction name or on pre-Yukon!");
  727. break;
  728. case TransactionRequest.Commit:
  729. sqlBatch.Append(TdsEnums.TRANS_COMMIT);
  730. sqlBatch.Append(transactionName);
  731. break;
  732. case TransactionRequest.Rollback:
  733. sqlBatch.Append(TdsEnums.TRANS_ROLLBACK);
  734. sqlBatch.Append(transactionName);
  735. break;
  736. case TransactionRequest.IfRollback:
  737. sqlBatch.Append(TdsEnums.TRANS_IF_ROLLBACK);
  738. sqlBatch.Append(transactionName);
  739. break;
  740. case TransactionRequest.Save:
  741. sqlBatch.Append(TdsEnums.TRANS_SAVE);
  742. sqlBatch.Append(transactionName);
  743. break;
  744. default:
  745. Debug.Assert(false, "Unknown transaction type");
  746. break;
  747. }
  748. Threading.Tasks.Task executeTask = _parser.TdsExecuteSQLBatch(sqlBatch.ToString(), ConnectionOptions.ConnectTimeout, null, _parser._physicalStateObj, sync: true);
  749. Debug.Assert(executeTask == null, "Shouldn't get a task when doing sync writes");
  750. _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
  751. // Prior to Yukon, we didn't have any transaction tokens to manage,
  752. // or any feedback to know when one was created, so we just presume
  753. // that successful execution of the request caused the transaction
  754. // to be created, and we set that on the parser.
  755. if (TransactionRequest.Begin == transactionRequest) {
  756. Debug.Assert(null != internalTransaction, "Begin Transaction request without internal transaction");
  757. _parser.CurrentTransaction = internalTransaction;
  758. }
  759. }
  760. internal void ExecuteTransactionYukon(
  761. TransactionRequest transactionRequest,
  762. string transactionName,
  763. IsolationLevel iso,
  764. SqlInternalTransaction internalTransaction,
  765. bool isDelegateControlRequest) {
  766. TdsEnums.TransactionManagerRequestType requestType = TdsEnums.TransactionManagerRequestType.Begin;
  767. TdsEnums.TransactionManagerIsolationLevel isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadCommitted;
  768. switch (iso) {
  769. case IsolationLevel.Unspecified:
  770. isoLevel = TdsEnums.TransactionManagerIsolationLevel.Unspecified;
  771. break;
  772. case IsolationLevel.ReadCommitted:
  773. isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadCommitted;
  774. break;
  775. case IsolationLevel.ReadUncommitted:
  776. isoLevel = TdsEnums.TransactionManagerIsolationLevel.ReadUncommitted;
  777. break;
  778. case IsolationLevel.RepeatableRead:
  779. isoLevel = TdsEnums.TransactionManagerIsolationLevel.RepeatableRead;
  780. break;
  781. case IsolationLevel.Serializable:
  782. isoLevel = TdsEnums.TransactionManagerIsolationLevel.Serializable;
  783. break;
  784. case IsolationLevel.Snapshot:
  785. isoLevel = TdsEnums.TransactionManagerIsolationLevel.Snapshot;
  786. break;
  787. case IsolationLevel.Chaos:
  788. throw SQL.NotSupportedIsolationLevel(iso);
  789. default:
  790. throw ADP.InvalidIsolationLevel(iso);
  791. }
  792. TdsParserStateObject stateObj = _parser._physicalStateObj;
  793. TdsParser parser = _parser;
  794. bool mustPutSession = false;
  795. bool releaseConnectionLock = false;
  796. Debug.Assert(!ThreadHasParserLockForClose || _parserLock.ThreadMayHaveLock(), "Thread claims to have parser lock, but lock is not taken");
  797. if (!ThreadHasParserLockForClose) {
  798. _parserLock.Wait(canReleaseFromAnyThread:false);
  799. ThreadHasParserLockForClose = true; // In case of error, let the connection know that we already own the parser lock
  800. releaseConnectionLock = true;
  801. }
  802. try {
  803. switch (transactionRequest) {
  804. case TransactionRequest.Begin:
  805. requestType = TdsEnums.TransactionManagerRequestType.Begin;
  806. break;
  807. case TransactionRequest.Promote:
  808. requestType = TdsEnums.TransactionManagerRequestType.Promote;
  809. break;
  810. case TransactionRequest.Commit:
  811. requestType = TdsEnums.TransactionManagerRequestType.Commit;
  812. break;
  813. case TransactionRequest.IfRollback:
  814. // Map IfRollback to Rollback since with Yukon and beyond we should never need
  815. // the if since the server will inform us when transactions have completed
  816. // as a result of an error on the server.
  817. case TransactionRequest.Rollback:
  818. requestType = TdsEnums.TransactionManagerRequestType.Rollback;
  819. break;
  820. case TransactionRequest.Save:
  821. requestType = TdsEnums.TransactionManagerRequestType.Save;
  822. break;
  823. default:
  824. Debug.Assert(false, "Unknown transaction type");
  825. break;
  826. }
  827. // only restore if connection lock has been taken within the function
  828. if (internalTransaction != null && internalTransaction.RestoreBrokenConnection && releaseConnectionLock) {
  829. Task reconnectTask = internalTransaction.Parent.Connection.ValidateAndReconnect(() => {
  830. ThreadHasParserLockForClose = false;
  831. _parserLock.Release();
  832. releaseConnectionLock = false;
  833. }, 0);
  834. if (reconnectTask != null) {
  835. AsyncHelper.WaitForCompletion(reconnectTask, 0); // there is no specific timeout for BeginTransaction, uses ConnectTimeout
  836. internalTransaction.ConnectionHasBeenRestored = true;
  837. return;
  838. }
  839. }
  840. // SQLBUDT #20010853 - Promote, Commit and Rollback requests for
  841. // delegated transactions often happen while there is an open result
  842. // set, so we need to handle them by using a different MARS session,
  843. // otherwise we'll write on the physical state objects while someone
  844. // else is using it. When we don't have MARS enabled, we need to
  845. // lock the physical state object to syncronize it's use at least
  846. // until we increment the open results count. Once it's been
  847. // incremented the delegated transaction requests will fail, so they
  848. // won't stomp on anything.
  849. //
  850. // We need to keep this lock through the duration of the TM reqeuest
  851. // so that we won't hijack a different request's data stream and a
  852. // different request won't hijack ours, so we have a lock here on
  853. // an object that the ExecTMReq will also lock, but since we're on
  854. // the same thread, the lock is a no-op.
  855. if (null != internalTransaction && internalTransaction.IsDelegated) {
  856. if (_parser.MARSOn) {
  857. stateObj = _parser.GetSession(this);
  858. mustPutSession = true;
  859. }
  860. else if (internalTransaction.OpenResultsCount != 0) {
  861. throw SQL.CannotCompleteDelegatedTransactionWithOpenResults(this);
  862. }
  863. }
  864. // SQLBU #406778 - _parser may be nulled out during TdsExecuteTrannsactionManagerRequest.
  865. // Only use local variable after this call.
  866. _parser.TdsExecuteTransactionManagerRequest(null, requestType, transactionName, isoLevel,
  867. ConnectionOptions.ConnectTimeout, internalTransaction, stateObj, isDelegateControlRequest);
  868. }
  869. finally {
  870. if (mustPutSession) {
  871. parser.PutSession(stateObj);
  872. }
  873. if (releaseConnectionLock) {
  874. ThreadHasParserLockForClose = false;
  875. _parserLock.Release();
  876. }
  877. }
  878. }
  879. ////////////////////////////////////////////////////////////////////////////////////////
  880. // DISTRIBUTED TRANSACTION METHODS
  881. ////////////////////////////////////////////////////////////////////////////////////////
  882. override internal void DelegatedTransactionEnded() {
  883. //
  884. base.DelegatedTransactionEnded();
  885. }
  886. override protected byte[] GetDTCAddress() {
  887. byte[] dtcAddress = _parser.GetDTCAddress(ConnectionOptions.ConnectTimeout, _parser.GetSession(this));
  888. Debug.Assert(null != dtcAddress, "null dtcAddress?");
  889. return dtcAddress;
  890. }
  891. override protected void PropagateTransactionCookie(byte[] cookie) {
  892. _parser.PropagateDistributedTransaction(cookie, ConnectionOptions.ConnectTimeout, _parser._physicalStateObj);
  893. }
  894. ////////////////////////////////////////////////////////////////////////////////////////
  895. // LOGIN-RELATED METHODS
  896. ////////////////////////////////////////////////////////////////////////////////////////
  897. private void CompleteLogin(bool enlistOK) {
  898. _parser.Run(RunBehavior.UntilDone, null, null, null, _parser._physicalStateObj);
  899. if (_routingInfo == null) { // ROR should not affect state of connection recovery
  900. if (!_sessionRecoveryAcknowledged) {
  901. _currentSessionData = null;
  902. if (_recoverySessionData != null) {
  903. throw SQL.CR_NoCRAckAtReconnection(this);
  904. }
  905. }
  906. if (_currentSessionData != null && _recoverySessionData==null) {
  907. _currentSessionData._initialDatabase = CurrentDatabase;
  908. _currentSessionData._initialCollation = _currentSessionData._collation;
  909. _currentSessionData._initialLanguage = _currentLanguage;
  910. }
  911. bool isEncrypted = _parser.EncryptionOptions == EncryptionOptions.ON;
  912. if (_recoverySessionData != null) {
  913. if (_recoverySessionData._encrypted != isEncrypted) {
  914. throw SQL.CR_EncryptionChanged(this);
  915. }
  916. }
  917. if (_currentSessionData != null) {
  918. _currentSessionData._encrypted = isEncrypted;
  919. }
  920. _recoverySessionData = null;
  921. }
  922. Debug.Assert(SniContext.Snix_Login == Parser._physicalStateObj.SniContext, String.Format((IFormatProvider)null, "SniContext should be Snix_Login; actual Value: {0}", Parser._physicalStateObj.SniContext));
  923. _parser._physicalStateObj.SniContext = SniContext.Snix_EnableMars;
  924. _parser.EnableMars();
  925. _fConnectionOpen = true; // mark connection as open
  926. if (Bid.AdvancedOn) {
  927. Bid.Trace("<sc.SqlInternalConnectionTds.CompleteLogin|ADV> Post-Login Phase: Server connection obtained.\n");
  928. }
  929. // for non-pooled connections, enlist in a distributed transaction
  930. // if present - and user specified to enlist
  931. if(enlistOK && ConnectionOptions.Enlist) {
  932. _parser._physicalStateObj.SniContext = SniContext.Snix_AutoEnlist;
  933. SysTx.Transaction tx = ADP.GetCurrentTransaction();
  934. Enlist(tx);
  935. }
  936. _parser._physicalStateObj.SniContext=SniContext.Snix_Login;
  937. }
  938. private void Login(ServerInfo server, TimeoutTimer timeout, string newPassword, SecureString newSecurePassword) {
  939. // create a new login record
  940. SqlLogin login = new SqlLogin();
  941. // gather all the settings the user set in the connection string or
  942. // properties and do the login
  943. CurrentDatabase = server.ResolvedDatabaseName;
  944. _currentPacketSize = ConnectionOptions.PacketSize;
  945. _currentLanguage = ConnectionOptions.CurrentLanguage;
  946. int timeoutInSeconds = 0;
  947. // If a timeout tick value is specified, compute the timeout based
  948. // upon the amount of time left in seconds.
  949. if (!timeout.IsInfinite)
  950. {
  951. long t = timeout.MillisecondsRemaining/1000;
  952. if ((long)Int32.MaxValue > t)
  953. {
  954. timeoutInSeconds = (int)t;
  955. }
  956. }
  957. login.timeout = timeoutInSeconds;
  958. login.userInstance = ConnectionOptions.UserInstance;
  959. login.hostName = ConnectionOptions.ObtainWorkstationId();
  960. login.userName = ConnectionOptions.UserID;
  961. login.password = ConnectionOptions.Password;
  962. login.applicationName = ConnectionOptions.ApplicationName;
  963. login.language = _currentLanguage;
  964. if (!login.userInstance) { // Do not send attachdbfilename or database to SSE primary instance
  965. login.database = CurrentDatabase;;
  966. login.attachDBFilename = ConnectionOptions.AttachDBFilename;
  967. }
  968. // VSTS#795621 - Ensure ServerName is Sent During TdsLogin To Enable Sql Azure Connectivity.
  969. // Using server.UserServerName (versus ConnectionOptions.DataSource) since TdsLogin requires
  970. // serverName to always be non-null.
  971. login.serverName = server.UserServerName;
  972. login.useReplication = ConnectionOptions.Replication;
  973. login.useSSPI = ConnectionOptions.IntegratedSecurity;
  974. login.packetSize = _currentPacketSize;
  975. login.newPassword = newPassword;
  976. login.readOnlyIntent = ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly;
  977. login.credential = _credential;
  978. if (newSecurePassword != null) {
  979. login.newSecurePassword = newSecurePassword;
  980. }
  981. TdsEnums.FeatureExtension requestedFeatures = TdsEnums.FeatureExtension.None;
  982. if (ConnectionOptions.ConnectRetryCount>0) {
  983. requestedFeatures |= TdsEnums.FeatureExtension.SessionRecovery;
  984. _sessionRecoveryRequested = true;
  985. }
  986. _parser.TdsLogin(login, requestedFeatures, _recoverySessionData);
  987. }
  988. private void LoginFailure() {
  989. Bid.Trace("<sc.SqlInternalConnectionTds.LoginFailure|RES|CPOOL> %d#\n", ObjectID);
  990. // If the parser was allocated and we failed, then we must have failed on
  991. // either the Connect or Login, either way we should call Disconnect.
  992. // Disconnect can be called if the connection is already closed - becomes
  993. // no-op, so no issues there.
  994. if (_parser != null) {
  995. _parser.Disconnect();
  996. }
  997. //
  998. }
  999. private void OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectionOptions, SqlCredential credential,
  1000. string newPassword, SecureString newSecurePassword, bool redirectedUserInstance) {
  1001. bool useFailoverPartner; // should we use primary or secondary first
  1002. ServerInfo dataSource = new ServerInfo(connectionOptions);
  1003. string failoverPartner;
  1004. if (null != PoolGroupProviderInfo) {
  1005. useFailoverPartner = PoolGroupProviderInfo.UseFailoverPartner;
  1006. failoverPartner = PoolGroupProviderInfo.FailoverPartner;
  1007. }
  1008. else {
  1009. // Only ChangePassword or SSE User Instance comes through this code path.
  1010. useFailoverPartner = false;
  1011. failoverPartner = ConnectionOptions.FailoverPartner;
  1012. }
  1013. timeoutErrorInternal.SetInternalSourceType(useFailoverPartner ? SqlConnectionInternalSourceType.Failover : SqlConnectionInternalSourceType.Principle);
  1014. bool hasFailoverPartner = !ADP.IsEmpty(failoverPartner);
  1015. // Open the connection and Login
  1016. try {
  1017. timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.PreLoginBegin);
  1018. if (hasFailoverPartner) {
  1019. timeoutErrorInternal.SetFailoverScenario(true); // this is a failover scenario
  1020. LoginWithFailover(
  1021. useFailoverPartner,
  1022. dataSource,
  1023. failoverPartner,
  1024. newPassword,
  1025. newSecurePassword,
  1026. redirectedUserInstance,
  1027. connectionOptions,
  1028. credential,
  1029. timeout);
  1030. }
  1031. else {
  1032. timeoutErrorInternal.SetFailoverScenario(false); // not a failover scenario
  1033. LoginNoFailover(dataSource, newPassword, newSecurePassword, redirectedUserInstance,
  1034. connectionOptions, credential, timeout);
  1035. }
  1036. timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PostLogin);
  1037. }
  1038. catch (Exception e) {
  1039. //
  1040. if (ADP.IsCatchableExceptionType(e)) {
  1041. LoginFailure();
  1042. }
  1043. throw;
  1044. }
  1045. timeoutErrorInternal.SetAllCompleteMarker();
  1046. #if DEBUG
  1047. _parser._physicalStateObj.InvalidateDebugOnlyCopyOfSniContext();
  1048. #endif
  1049. }
  1050. // Is the given Sql error one that should prevent retrying
  1051. // to connect.
  1052. private bool IsDoNotRetryConnectError(SqlException exc) {
  1053. return (TdsEnums.LOGON_FAILED == exc.Number) // actual logon failed, i.e. bad password
  1054. || (TdsEnums.PASSWORD_EXPIRED == exc.Number) // actual logon failed, i.e. password isExpired
  1055. || (TdsEnums.IMPERSONATION_FAILED == exc.Number) // Insuficient privelege for named pipe, among others
  1056. || exc._doNotReconnect; // Exception explicitly supressed reconnection attempts
  1057. }
  1058. // Attempt to login to a host that does not have a failover partner
  1059. //
  1060. // Will repeatedly attempt to connect, but back off between each attempt so as not to clog the network.
  1061. // Back off period increases for first few failures: 100ms, 200ms, 400ms, 800ms, then 1000ms for subsequent attempts
  1062. //
  1063. // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  1064. // DEVNOTE: The logic in this method is paralleled by the logic in LoginWithFailover.
  1065. // Changes to either one should be examined to see if they need to be reflected in the other
  1066. // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  1067. private void LoginNoFailover(ServerInfo serverInfo, string newPassword, SecureString newSecurePassword, bool redirectedUserInstance,
  1068. SqlConnectionString connectionOptions, SqlCredential credential, TimeoutTimer timeout) {
  1069. Debug.Assert(object.ReferenceEquals(connectionOptions, this.ConnectionOptions), "ConnectionOptions argument and property must be the same"); // consider removing the argument
  1070. int routingAttempts = 0;
  1071. ServerInfo originalServerInfo = serverInfo; // serverInfo may end up pointing to new object due to routing, original object is used to set CurrentDatasource
  1072. if (Bid.AdvancedOn) {
  1073. Bid.Trace("<sc.SqlInternalConnectionTds.LoginNoFailover|ADV> %d#, host=%ls\n", ObjectID, serverInfo.UserServerName);
  1074. }
  1075. int sleepInterval = 100; //milliseconds to sleep (back off) between attempts.
  1076. ResolveExtendedServerName(serverInfo, !redirectedUserInstance, connectionOptions);
  1077. long timeoutUnitInterval = 0;
  1078. if (connectionOptions.MultiSubnetFailover) {
  1079. // Determine unit interval
  1080. if (timeout.IsInfinite) {
  1081. timeoutUnitInterval = checked((long)(ADP.FailoverTimeoutStep * (1000L * ADP.DefaultConnectionTimeout)));
  1082. }
  1083. else {
  1084. timeoutUnitInterval = checked((long)(ADP.FailoverTimeoutStep * timeout.MillisecondsRemaining));
  1085. }
  1086. }
  1087. // Only three ways out of this loop:
  1088. // 1) Successfully connected
  1089. // 2) Parser threw exception while main timer was expired
  1090. // 3) Parser threw logon failure-related exception
  1091. // 4) Parser threw exception in post-initial connect code,
  1092. // such as pre-login handshake or during actual logon. (parser state != Closed)
  1093. //
  1094. // Of these methods, only #1 exits normally. This preserves the call stack on the exception
  1095. // back into the parser for the error cases.
  1096. int attemptNumber = 0;
  1097. TimeoutTimer intervalTimer = null;
  1098. while(true) {
  1099. if (connectionOptions.MultiSubnetFailover) {
  1100. attemptNumber++;
  1101. // Set timeout for this attempt, but don't exceed original timer
  1102. long nextTimeoutInterval = checked(timeoutUnitInterval * attemptNumber);
  1103. long milliseconds = timeout.MillisecondsRemaining;
  1104. if (nextTimeoutInterval > milliseconds) {
  1105. nextTimeoutInterval = milliseconds;
  1106. }
  1107. intervalTimer = TimeoutTimer.StartMillisecondsTimeout(nextTimeoutInterval);
  1108. }
  1109. // Re-allocate parser each time to make sure state is known
  1110. // RFC 50002652 - if parser was created by previous attempt, dispose it to properly close the socket, if created
  1111. if (_parser != null)
  1112. _parser.Disconnect();
  1113. _parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous);
  1114. Debug.Assert(SniContext.Undefined== Parser._physicalStateObj.SniContext, String.Format((IFormatProvider)null, "SniContext should be Undefined; actual Value: {0}", Parser._physicalStateObj.SniContext));
  1115. try {
  1116. //
  1117. AttemptOneLogin( serverInfo,
  1118. newPassword,
  1119. newSecurePassword,
  1120. !connectionOptions.MultiSubnetFailover, // ignore timeout for SniOpen call unless MSF
  1121. connectionOptions.MultiSubnetFailover ? intervalTimer : timeout);
  1122. if (connectionOptions.MultiSubnetFailover && null != ServerProvidedFailOverPartner) {
  1123. // connection succeeded: trigger exception if server sends failover partner and MultiSubnetFailover is used
  1124. throw SQL.MultiSubnetFailoverWithFailoverPartner(serverProvidedFailoverPartner: true, internalConnection: this);
  1125. }
  1126. if (_routingInfo != null) {
  1127. Bid.Trace("<sc.SqlInternalConnectionTds.LoginNoFailover> Routed to %ls", serverInfo.ExtendedServerName);
  1128. if (routingAttempts > 0) {
  1129. throw SQL.ROR_RecursiveRoutingNotSupported(this);
  1130. }
  1131. if (timeout.IsExpired) {
  1132. throw SQL.ROR_TimeoutAfterRoutingInfo(this);
  1133. }
  1134. serverInfo = new ServerInfo(ConnectionOptions, _routingInfo, serverInfo.ResolvedServerName);
  1135. timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.RoutingDestination);
  1136. _originalClientConnectionId = _clientConnectionId;
  1137. _routingDestination = serverInfo.UserServerName;
  1138. // restore properties that could be changed by the environment tokens
  1139. _currentPacketSize = ConnectionOptions.PacketSize;
  1140. _currentLanguage = _originalLanguage = ConnectionOptions.CurrentLanguage;
  1141. CurrentDatabase = _originalDatabase = ConnectionOptions.InitialCatalog;
  1142. _currentFailoverPartner = null;
  1143. _instanceName = String.Empty;
  1144. routingAttempts++;
  1145. continue; // repeat the loop, but skip code reserved for failed connections (after the catch)
  1146. }
  1147. else {
  1148. break; // leave the while loop -- we've successfully connected
  1149. }
  1150. }
  1151. catch (SqlException sqlex) {
  1152. if (null == _parser
  1153. || TdsParserState.Closed != _parser.State
  1154. || IsDoNotRetryConnectError(sqlex)
  1155. || timeout.IsExpired) { // no more time to try again
  1156. throw; // Caller will call LoginFailure()
  1157. }
  1158. // Check sleep interval to make sure we won't exceed the timeout
  1159. // Do this in the catch block so we can re-throw the current exception
  1160. if (timeout.MillisecondsRemaining <= sleepInterval) {
  1161. throw;
  1162. }
  1163. //
  1164. }
  1165. // We only get here when we failed to connect, but are going to re-try
  1166. // Switch to failover logic if the server provided a partner
  1167. if (null != ServerProvidedFailOverPartner) {
  1168. if (connectionOptions.MultiSubnetFailover) {
  1169. // connection failed: do not allow failover to server-provided failover partner if MultiSubnetFailover is set
  1170. throw SQL.MultiSubnetFailoverWithFailoverPartner(serverProvidedFailoverPartner: true, internalConnection: this);
  1171. }
  1172. Debug.Assert(ConnectionOptions.ApplicationIntent != ApplicationIntent.ReadOnly, "FAILOVER+AppIntent=RO: Should already fail (at LOGSHIPNODE in OnEnvChange)");
  1173. timeoutErrorInternal.ResetAndRestartPhase();
  1174. timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.PreLoginBegin);
  1175. timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Failover);
  1176. timeoutErrorInternal.SetFailoverScenario(true); // this is a failover scenario
  1177. LoginWithFailover(
  1178. true, // start by using failover partner, since we already failed to connect to the primary
  1179. serverInfo,
  1180. ServerProvidedFailOverPartner,
  1181. newPassword,
  1182. newSecurePassword,
  1183. redirectedUserInstance,
  1184. connectionOptions,
  1185. credential,
  1186. timeout);
  1187. return; // LoginWithFailover successfully connected and handled entire connection setup
  1188. }
  1189. // Sleep for a bit to prevent clogging the network with requests,
  1190. // then update sleep interval for next iteration (max 1 second interval)
  1191. if (Bid.AdvancedOn) {
  1192. Bid.Trace("<sc.SqlInternalConnectionTds.LoginNoFailover|ADV> %d#, sleeping %d{milisec}\n", ObjectID, sleepInterval);
  1193. }
  1194. Thread.Sleep(sleepInterval);
  1195. sleepInterval = (sleepInterval < 500) ? sleepInterval * 2 : 1000;
  1196. }
  1197. if (null != PoolGroupProviderInfo) {
  1198. // We must wait for CompleteLogin to finish for to have the
  1199. // env change from the server to know its designated failover
  1200. // partner; save this information in _currentFailoverPartner.
  1201. PoolGroupProviderInfo.FailoverCheck(this, false, connectionOptions, ServerProvidedFailOverPartner);
  1202. }
  1203. CurrentDataSource = originalServerInfo.UserServerName;
  1204. }
  1205. // Attempt to login to a host that has a failover partner
  1206. //
  1207. // Connection & timeout sequence is
  1208. // First target, timeout = interval * 1
  1209. // second target, timeout = interval * 1
  1210. // sleep for 100ms
  1211. // First target, timeout = interval * 2
  1212. // Second target, timeout = interval * 2
  1213. // sleep for 200ms
  1214. // First Target, timeout = interval * 3
  1215. // etc.
  1216. //
  1217. // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  1218. // DEVNOTE: The logic in this method is paralleled by the logic in LoginNoFailover.
  1219. // Changes to either one should be examined to see if they need to be reflected in the other
  1220. // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  1221. private void LoginWithFailover(
  1222. bool useFailoverHost,
  1223. ServerInfo primaryServerInfo,
  1224. string failoverHost,
  1225. string newPassword,
  1226. SecureString newSecurePassword,
  1227. bool redirectedUserInstance,
  1228. SqlConnectionString connectionOptions,
  1229. SqlCredential credential,
  1230. TimeoutTimer timeout
  1231. ) {
  1232. Debug.Assert(!connectionOptions.MultiSubnetFailover, "MultiSubnetFailover should not be set if failover partner is used");
  1233. if (Bid.AdvancedOn) {
  1234. Bid.Trace("<sc.SqlInternalConnectionTds.LoginWithFailover|ADV> %d#, useFailover=%d{bool}, primary=", ObjectID, useFailoverHost);
  1235. Bid.PutStr(primaryServerInfo.UserServerName);
  1236. Bid.PutStr(", failover=");
  1237. Bid.PutStr(failoverHost);
  1238. Bid.PutStr("\n");
  1239. }
  1240. int sleepInterval = 100; //milliseconds to sleep (back off) between attempts.
  1241. long timeoutUnitInterval;
  1242. string protocol = ConnectionOptions.NetworkLibrary;
  1243. ServerInfo failoverServerInfo = new ServerInfo(connectionOptions, failoverHost);
  1244. ResolveExtendedServerName(primaryServerInfo, !redirectedUserInstance, connectionOptions);
  1245. if (null == ServerProvidedFailOverPartner) {// No point in resolving the failover partner when we're going to override it below
  1246. // Don't resolve aliases if failover == primary //
  1247. ResolveExtendedServerName(failoverServerInfo, !redirectedUserInstance && failoverHost != primaryServerInfo.UserServerName, connectionOptions);
  1248. }
  1249. // Determine unit interval
  1250. if (timeout.IsInfinite) {
  1251. timeoutUnitInterval = checked((long) ADP.FailoverTimeoutStep * ADP.TimerFromSeconds(ADP.DefaultConnectionTimeout));
  1252. }
  1253. else {
  1254. timeoutUnitInterval = checked((long) (ADP.FailoverTimeoutStep * timeout.MillisecondsRemaining));
  1255. }
  1256. // Initialize loop variables
  1257. bool failoverDemandDone = false; // have we demanded for partner information yet (as necessary)?
  1258. int attemptNumber = 0;
  1259. // Only three ways out of this loop:
  1260. // 1) Successfully connected
  1261. // 2) Parser threw exception while main timer was expired
  1262. // 3) Parser threw logon failure-related exception (LOGON_FAILED, PASSWORD_EXPIRED, etc)
  1263. //
  1264. // Of these methods, only #1 exits normally. This preserves the call stack on the exception
  1265. // back into the parser for the error cases.
  1266. while (true) {
  1267. // Set timeout for this attempt, but don't exceed original timer
  1268. long nextTimeoutInterval = checked(timeoutUnitInterval * ((attemptNumber / 2) + 1));
  1269. long milliseconds = timeout.MillisecondsRemaining;
  1270. if (nextTimeoutInterval > milliseconds) {
  1271. nextTimeoutInterval = milliseconds;
  1272. }
  1273. TimeoutTimer intervalTimer = TimeoutTimer.StartMillisecondsTimeout(nextTimeoutInterval);
  1274. // Re-allocate parser each time to make sure state is known
  1275. // RFC 50002652 - if parser was created by previous attempt, dispose it to properly close the socket, if created
  1276. if (_parser != null)
  1277. _parser.Disconnect();
  1278. _parser = new TdsParser(ConnectionOptions.MARS, ConnectionOptions.Asynchronous);
  1279. Debug.Assert(SniContext.Undefined== Parser._physicalStateObj.SniContext, String.Format((IFormatProvider)null, "SniContext should be Undefined; actual Value: {0}", Parser._physicalStateObj.SniContext));
  1280. ServerInfo currentServerInfo;
  1281. if (useFailoverHost) {
  1282. if (!failoverDemandDone) {
  1283. FailoverPermissionDemand();
  1284. failoverDemandDone = true;
  1285. }
  1286. // Primary server may give us a different failover partner than the connection string indicates. Update it
  1287. if (null != ServerProvidedFailOverPartner && failoverServerInfo.ResolvedServerName != ServerProvidedFailOverPartner) {
  1288. if (Bid.AdvancedOn) {
  1289. Bid.Trace("<sc.SqlInternalConnectionTds.LoginWithFailover|ADV> %d#, new failover partner=%ls\n", ObjectID, ServerProvidedFailOverPartner);
  1290. }
  1291. failoverServerInfo.SetDerivedNames(protocol, ServerProvidedFailOverPartner);
  1292. }
  1293. currentServerInfo = failoverServerInfo;
  1294. timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Failover);
  1295. }
  1296. else {
  1297. currentServerInfo = primaryServerInfo;
  1298. timeoutErrorInternal.SetInternalSourceType(SqlConnectionInternalSourceType.Principle);
  1299. }
  1300. try {
  1301. // Attempt login. Use timerInterval for attempt timeout unless infinite timeout was requested.
  1302. AttemptOneLogin(
  1303. currentServerInfo,
  1304. newPassword,
  1305. newSecurePassword,
  1306. false, // Use timeout in SniOpen
  1307. intervalTimer,
  1308. withFailover:true
  1309. );
  1310. if (_routingInfo != null) {
  1311. // We are in login with failover scenation and server sent routing information
  1312. // If it is read-only routing - we did not supply AppIntent=RO (it should be checked before)
  1313. // If it is something else, not known yet (future server) - this client is not designed to support this.
  1314. // In any case, server should not have sent the routing info.
  1315. Bid.Trace("<sc.SqlInternalConnectionTds.LoginWithFailover> Routed to %ls", _routingInfo.ServerName);
  1316. throw SQL.ROR_UnexpectedRoutingInfo(this);
  1317. }
  1318. break; // leave the while loop -- we've successfully connected
  1319. }
  1320. catch (SqlException sqlex) {
  1321. if (IsDoNotRetryConnectError(sqlex)
  1322. || timeout.IsExpired)
  1323. { // no more time to try again
  1324. throw; // Caller will call LoginFailure()
  1325. }
  1326. if (IsConnectionDoomed) {
  1327. throw;
  1328. }
  1329. if (1 == attemptNumber % 2) {
  1330. // Check sleep interval to make sure we won't exceed the original timeout
  1331. // Do this in the catch block so we can re-throw the current exception
  1332. if (timeout.MillisecondsRemaining <= sleepInterval) {
  1333. throw;
  1334. }
  1335. }
  1336. //
  1337. }
  1338. // We only get here when we failed to connect, but are going to re-try
  1339. // After trying to connect to both servers fails, sleep for a bit to prevent clogging
  1340. // the network with requests, then update sleep interval for next iteration (max 1 second interval)
  1341. if (1 == attemptNumber % 2) {
  1342. if (Bid.AdvancedOn) {
  1343. Bid.Trace("<sc.SqlInternalConnectionTds.LoginWithFailover|ADV> %d#, sleeping %d{milisec}\n", ObjectID, sleepInterval);
  1344. }
  1345. Thread.Sleep(sleepInterval);
  1346. sleepInterval = (sleepInterval < 500) ? sleepInterval * 2 : 1000;
  1347. }
  1348. // Update attempt number and target host
  1349. attemptNumber++;
  1350. useFailoverHost = !useFailoverHost;
  1351. }
  1352. // If we get here, connection/login succeeded! Just a few more checks & record-keeping
  1353. // if connected to failover host, but said host doesn't have DbMirroring set up, throw an error
  1354. if (useFailoverHost && null == ServerProvidedFailOverPartner) {
  1355. throw SQL.InvalidPartnerConfiguration(failoverHost, CurrentDatabase);
  1356. }
  1357. if (null != PoolGroupProviderInfo) {
  1358. // We must wait for CompleteLogin to finish for to have the
  1359. // env change from the server to know its designated failover
  1360. // partner; save this information in _currentFailoverPartner.
  1361. PoolGroupProviderInfo.FailoverCheck(this, useFailoverHost, connectionOptions, ServerProvidedFailOverPartner);
  1362. }
  1363. CurrentDataSource = (useFailoverHost ? failoverHost : primaryServerInfo.UserServerName);
  1364. }
  1365. private void ResolveExtendedServerName(ServerInfo serverInfo, bool aliasLookup, SqlConnectionString options) {
  1366. if (serverInfo.ExtendedServerName == null) {
  1367. string host = serverInfo.UserServerName;
  1368. string protocol = serverInfo.UserProtocol;
  1369. if (aliasLookup) { // We skip this for UserInstances...
  1370. // Perform registry lookup to see if host is an alias. It will appropriately set host and protocol, if an Alias.
  1371. // Check if it was already resolved, during CR reconnection _currentSessionData values will be copied from
  1372. // _reconnectSessonData of the previous connection
  1373. if (_currentSessionData != null && !string.IsNullOrEmpty(host)) {
  1374. Tuple<string, string> hostPortPair;
  1375. if (_currentSessionData._resolvedAliases.TryGetValue(host, out hostPortPair)) {
  1376. host = hostPortPair.Item1;
  1377. protocol = hostPortPair.Item2;
  1378. }
  1379. else {
  1380. TdsParserStaticMethods.AliasRegistryLookup(ref host, ref protocol);
  1381. _currentSessionData._resolvedAliases.Add(serverInfo.UserServerName, new Tuple<string, string>(host, protocol));
  1382. }
  1383. }
  1384. else {
  1385. TdsParserStaticMethods.AliasRegistryLookup(ref host, ref protocol);
  1386. }
  1387. //
  1388. if (options.EnforceLocalHost) {
  1389. // verify LocalHost for |DataDirectory| usage
  1390. SqlConnectionString.VerifyLocalHostAndFixup(ref host, true, true /*fix-up to "."*/);
  1391. }
  1392. }
  1393. serverInfo.SetDerivedNames(protocol, host);
  1394. }
  1395. }
  1396. // Common code path for making one attempt to establish a connection and log in to server.
  1397. private void AttemptOneLogin(ServerInfo serverInfo, string newPassword, SecureString newSecurePassword, bool ignoreSniOpenTimeout, TimeoutTimer timeout, bool withFailover = false) {
  1398. if (Bid.AdvancedOn) {
  1399. Bid.Trace("<sc.SqlInternalConnectionTds.AttemptOneLogin|ADV> %d#, timout=%I64d{msec}, server=", ObjectID, timeout.MillisecondsRemaining);
  1400. Bid.PutStr(serverInfo.ExtendedServerName);
  1401. Bid.Trace("\n");
  1402. }
  1403. _routingInfo = null; // forget routing information
  1404. _parser._physicalStateObj.SniContext = SniContext.Snix_Connect;
  1405. _parser.Connect(serverInfo,
  1406. this,
  1407. ignoreSniOpenTimeout,
  1408. timeout.LegacyTimerExpire,
  1409. ConnectionOptions.Encrypt,
  1410. ConnectionOptions.TrustServerCertificate,
  1411. ConnectionOptions.IntegratedSecurity,
  1412. withFailover);
  1413. timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake);
  1414. timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.LoginBegin);
  1415. _parser._physicalStateObj.SniContext = SniContext.Snix_Login;
  1416. this.Login(serverInfo, timeout, newPassword, newSecurePassword);
  1417. timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth);
  1418. timeoutErrorInternal.SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.PostLogin);
  1419. CompleteLogin(!ConnectionOptions.Pooling);
  1420. timeoutErrorInternal.EndPhase(SqlConnectionTimeoutErrorPhase.PostLogin);
  1421. }
  1422. internal void FailoverPermissionDemand() {
  1423. if (null != PoolGroupProviderInfo) {
  1424. PoolGroupProviderInfo.FailoverPermissionDemand();
  1425. }
  1426. }
  1427. ////////////////////////////////////////////////////////////////////////////////////////
  1428. // PREPARED COMMAND METHODS
  1429. ////////////////////////////////////////////////////////////////////////////////////////
  1430. protected override object ObtainAdditionalLocksForClose() {
  1431. bool obtainParserLock = !ThreadHasParserLockForClose;
  1432. Debug.Assert(obtainParserLock || _parserLock.ThreadMayHaveLock(), "Thread claims to have lock, but lock is not taken");
  1433. if (obtainParserLock) {
  1434. _parserLock.Wait(canReleaseFromAnyThread: false);
  1435. ThreadHasParserLockForClose = true;
  1436. }
  1437. return obtainParserLock;
  1438. }
  1439. protected override void ReleaseAdditionalLocksForClose(object lockToken) {
  1440. Debug.Assert(lockToken is bool, "Lock token should be boolean");
  1441. if ((bool)lockToken) {
  1442. ThreadHasParserLockForClose = false;
  1443. _parserLock.Release();
  1444. }
  1445. }
  1446. // called by SqlConnection.RepairConnection which is a relatevly expensive way of repair inner connection
  1447. // prior to execution of request, used from EnlistTransaction, EnlistDistributedTransaction and ChangeDatabase
  1448. internal bool GetSessionAndReconnectIfNeeded(SqlConnection parent, int timeout = 0) {
  1449. Debug.Assert(!ThreadHasParserLockForClose, "Cannot call this method if caller has parser lock");
  1450. if (ThreadHasParserLockForClose) {
  1451. return false; // we cannot restore if we cannot release lock
  1452. }
  1453. _parserLock.Wait(canReleaseFromAnyThread: false);
  1454. ThreadHasParserLockForClose = true; // In case of error, let the connection know that we already own the parser lock
  1455. bool releaseConnectionLock = true;
  1456. try {
  1457. RuntimeHelpers.PrepareConstrainedRegions();
  1458. try {
  1459. #if DEBUG
  1460. TdsParser.ReliabilitySection tdsReliabilitySection = new TdsParser.ReliabilitySection();
  1461. RuntimeHelpers.PrepareConstrainedRegions();
  1462. try {
  1463. tdsReliabilitySection.Start();
  1464. #endif //DEBUG
  1465. Task reconnectTask = parent.ValidateAndReconnect(() => {
  1466. ThreadHasParserLockForClose = false;
  1467. _parserLock.Release();
  1468. releaseConnectionLock = false;
  1469. }, timeout);
  1470. if (reconnectTask != null) {
  1471. AsyncHelper.WaitForCompletion(reconnectTask, timeout);
  1472. return true;
  1473. }
  1474. return false;
  1475. #if DEBUG
  1476. }
  1477. finally {
  1478. tdsReliabilitySection.Stop();
  1479. }
  1480. #endif //DEBUG
  1481. }
  1482. catch (System.OutOfMemoryException) {
  1483. DoomThisConnection();
  1484. throw;
  1485. }
  1486. catch (System.StackOverflowException) {
  1487. DoomThisConnection();
  1488. throw;
  1489. }
  1490. catch (System.Threading.ThreadAbortException) {
  1491. DoomThisConnection();
  1492. throw;
  1493. }
  1494. }
  1495. finally {
  1496. if (releaseConnectionLock) {
  1497. ThreadHasParserLockForClose = false;
  1498. _parserLock.Release();
  1499. }
  1500. }
  1501. }
  1502. ////////////////////////////////////////////////////////////////////////////////////////
  1503. // PARSER CALLBACKS
  1504. ////////////////////////////////////////////////////////////////////////////////////////
  1505. internal void BreakConnection() {
  1506. var connection = Connection;
  1507. Bid.Trace("<sc.SqlInternalConnectionTds.BreakConnection|RES|CPOOL> %d#, Breaking connection.\n", ObjectID);
  1508. DoomThisConnection(); // Mark connection as unusable, so it will be destroyed
  1509. if (null != connection) {
  1510. connection.Close();
  1511. }
  1512. }
  1513. internal bool IgnoreEnvChange { // true if we are only draining environment change tokens, used by TdsParser
  1514. get {
  1515. return _routingInfo != null; // connection was routed, ignore rest of env change
  1516. }
  1517. }
  1518. internal void OnEnvChange(SqlEnvChange rec) {
  1519. Debug.Assert(!IgnoreEnvChange,"This function should not be called if IgnoreEnvChange is set!");
  1520. switch (rec.type) {
  1521. case TdsEnums.ENV_DATABASE:
  1522. // If connection is not open and recovery is not in progresss, store the server value as the original.
  1523. if (!_fConnectionOpen && _recoverySessionData == null) {
  1524. _originalDatabase = rec.newValue;
  1525. }
  1526. CurrentDatabase = rec.newValue;
  1527. break;
  1528. case TdsEnums.ENV_LANG:
  1529. // If connection is not open and recovery is not in progresss, store the server value as the original.
  1530. if (!_fConnectionOpen && _recoverySessionData == null) {
  1531. _originalLanguage = rec.newValue;
  1532. }
  1533. _currentLanguage = rec.newValue; // TODO: finish this.
  1534. break;
  1535. case TdsEnums.ENV_PACKETSIZE:
  1536. _currentPacketSize = Int32.Parse(rec.newValue, CultureInfo.InvariantCulture);
  1537. break;
  1538. case TdsEnums.ENV_COLLATION:
  1539. if (_currentSessionData != null) {
  1540. _currentSessionData._collation = rec.newCollation;
  1541. }
  1542. break;
  1543. case TdsEnums.ENV_CHARSET:
  1544. case TdsEnums.ENV_LOCALEID:
  1545. case TdsEnums.ENV_COMPFLAGS:
  1546. case TdsEnums.ENV_BEGINTRAN:
  1547. case TdsEnums.ENV_COMMITTRAN:
  1548. case TdsEnums.ENV_ROLLBACKTRAN:
  1549. case TdsEnums.ENV_ENLISTDTC:
  1550. case TdsEnums.ENV_DEFECTDTC:
  1551. // only used on parser
  1552. break;
  1553. case TdsEnums.ENV_LOGSHIPNODE:
  1554. if (ConnectionOptions.ApplicationIntent == ApplicationIntent.ReadOnly) {
  1555. throw SQL.ROR_FailoverNotSupportedServer(this);
  1556. }
  1557. _currentFailoverPartner = rec.newValue;
  1558. break;
  1559. case TdsEnums.ENV_PROMOTETRANSACTION:
  1560. PromotedDTCToken = rec.newBinValue;
  1561. break;
  1562. case TdsEnums.ENV_TRANSACTIONENDED:
  1563. break;
  1564. case TdsEnums.ENV_TRANSACTIONMANAGERADDRESS:
  1565. // For now we skip these Yukon only env change notifications
  1566. break;
  1567. case TdsEnums.ENV_SPRESETCONNECTIONACK:
  1568. // connection is being reset
  1569. if (_currentSessionData != null) {
  1570. _currentSessionData.Reset();
  1571. }
  1572. break;
  1573. case TdsEnums.ENV_USERINSTANCE:
  1574. _instanceName = rec.newValue;
  1575. break;
  1576. case TdsEnums.ENV_ROUTING:
  1577. if (string.IsNullOrEmpty(rec.newRoutingInfo.ServerName) || rec.newRoutingInfo.Protocol != 0 || rec.newRoutingInfo.Port == 0) {
  1578. throw SQL.ROR_InvalidRoutingInfo(this);
  1579. }
  1580. _routingInfo = rec.newRoutingInfo;
  1581. break;
  1582. default:
  1583. Debug.Assert(false, "Missed token in EnvChange!");
  1584. break;
  1585. }
  1586. }
  1587. internal void OnLoginAck(SqlLoginAck rec) {
  1588. _loginAck = rec;
  1589. //
  1590. if (_recoverySessionData != null) {
  1591. if (_recoverySessionData._tdsVersion != rec.tdsVersion) {
  1592. throw SQL.CR_TDSVersionNotPreserved(this);
  1593. }
  1594. }
  1595. if (_currentSessionData != null) {
  1596. _currentSessionData._tdsVersion = rec.tdsVersion;
  1597. }
  1598. }
  1599. internal void OnFeatureExtAck(int featureId, byte[] data) {
  1600. if (_routingInfo != null) {
  1601. return;
  1602. }
  1603. switch (featureId) {
  1604. case TdsEnums.FEATUREEXT_SRECOVERY: {
  1605. // Session recovery not requested
  1606. if (!_sessionRecoveryRequested) {
  1607. throw SQL.ParsingError();
  1608. }
  1609. _sessionRecoveryAcknowledged = true;
  1610. #if DEBUG
  1611. foreach (var s in _currentSessionData._delta) {
  1612. Debug.Assert(s==null, "Delta should be null at this point");
  1613. }
  1614. #endif
  1615. Debug.Assert(_currentSessionData._unrecoverableStatesCount == 0, "Unrecoverable states count should be 0");
  1616. int i = 0;
  1617. while (i < data.Length) {
  1618. byte stateId = data[i]; i++;
  1619. int len;
  1620. byte bLen = data[i]; i++;
  1621. if (bLen == 0xFF) {
  1622. len = BitConverter.ToInt32(data, i); i += 4;
  1623. }
  1624. else {
  1625. len = bLen;
  1626. }
  1627. byte[] stateData = new byte[len];
  1628. Buffer.BlockCopy(data, i, stateData, 0, len); i += len;
  1629. if (_recoverySessionData == null) {
  1630. _currentSessionData._initialState[stateId] = stateData;
  1631. }
  1632. else {
  1633. _currentSessionData._delta[stateId] = new SessionStateRecord { _data = stateData, _dataLength = len, _recoverable = true, _version = 0 };
  1634. _currentSessionData._deltaDirty = true;
  1635. }
  1636. }
  1637. break;
  1638. }
  1639. default: {
  1640. // Unknown feature ack
  1641. throw SQL.ParsingError();
  1642. }
  1643. }
  1644. }
  1645. ////////////////////////////////////////////////////////////////////////////////////////
  1646. // Helper methods for Locks
  1647. ////////////////////////////////////////////////////////////////////////////////////////
  1648. // Indicates if the current thread claims to hold the parser lock
  1649. internal bool ThreadHasParserLockForClose {
  1650. get {
  1651. return _threadIdOwningParserLock == Thread.CurrentThread.ManagedThreadId;
  1652. }
  1653. set {
  1654. Debug.Assert(_parserLock.ThreadMayHaveLock(), "Should not modify ThreadHasParserLockForClose without taking the lock first");
  1655. Debug.Assert(_threadIdOwningParserLock == -1 || _threadIdOwningParserLock == Thread.CurrentThread.ManagedThreadId, "Another thread already claims to own the parser lock");
  1656. if (value) {
  1657. // If setting to true, then the thread owning the lock is the current thread
  1658. _threadIdOwningParserLock = Thread.CurrentThread.ManagedThreadId;
  1659. }
  1660. else if (_threadIdOwningParserLock == Thread.CurrentThread.ManagedThreadId) {
  1661. // If setting to false and currently owns the lock, then no-one owns the lock
  1662. _threadIdOwningParserLock = -1;
  1663. }
  1664. // else This thread didn't own the parser lock and doesn't claim to own it, so do nothing
  1665. }
  1666. }
  1667. internal override bool TryReplaceConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource<DbConnectionInternal> retry, DbConnectionOptions userOptions) {
  1668. return base.TryOpenConnectionInternal(outerConnection, connectionFactory, retry, userOptions);
  1669. }
  1670. }
  1671. internal sealed class ServerInfo {
  1672. internal string ExtendedServerName { get; private set; } // the resolved servername with protocol
  1673. internal string ResolvedServerName { get; private set; } // the resolved servername only
  1674. internal string ResolvedDatabaseName { get; private set; } // name of target database after resolution
  1675. internal string UserProtocol { get; private set; } // the user specified protocol
  1676. // The original user-supplied server name from the connection string.
  1677. // If connection string has no Data Source, the value is set to string.Empty.
  1678. // In case of routing, will be changed to routing destination
  1679. internal string UserServerName
  1680. {
  1681. get
  1682. {
  1683. return m_userServerName;
  1684. }
  1685. private set
  1686. {
  1687. m_userServerName = value;
  1688. }
  1689. } private string m_userServerName;
  1690. internal readonly string PreRoutingServerName;
  1691. // Initialize server info from connection options,
  1692. internal ServerInfo(SqlConnectionString userOptions) : this(userOptions, userOptions.DataSource) {}
  1693. // Initialize server info from connection options, but override DataSource with given server name
  1694. internal ServerInfo (SqlConnectionString userOptions, string serverName) {
  1695. //-----------------
  1696. // Preconditions
  1697. Debug.Assert(null != userOptions);
  1698. //-----------------
  1699. //Method body
  1700. Debug.Assert(serverName != null, "server name should never be null");
  1701. UserServerName = (serverName ?? string.Empty); // ensure user server name is not null
  1702. UserProtocol = userOptions.NetworkLibrary;
  1703. ResolvedDatabaseName = userOptions.InitialCatalog;
  1704. PreRoutingServerName = null;
  1705. }
  1706. // Initialize server info from connection options, but override DataSource with given server name
  1707. internal ServerInfo(SqlConnectionString userOptions, RoutingInfo routing, string preRoutingServerName) {
  1708. //-----------------
  1709. // Preconditions
  1710. Debug.Assert(null != userOptions && null!=routing);
  1711. //-----------------
  1712. //Method body
  1713. Debug.Assert(routing.ServerName != null, "server name should never be null");
  1714. if (routing == null || routing.ServerName == null) {
  1715. UserServerName = string.Empty; // ensure user server name is not null
  1716. }
  1717. else {
  1718. UserServerName = string.Format(CultureInfo.InvariantCulture, "{0},{1}", routing.ServerName, routing.Port);
  1719. }
  1720. PreRoutingServerName = preRoutingServerName;
  1721. UserProtocol = TdsEnums.TCP;
  1722. SetDerivedNames(UserProtocol, UserServerName);
  1723. ResolvedDatabaseName = userOptions.InitialCatalog;
  1724. }
  1725. internal void SetDerivedNames(string protocol, string serverName) {
  1726. // The following concatenates the specified netlib network protocol to the host string, if netlib is not null
  1727. // and the flag is on. This allows the user to specify the network protocol for the connection - but only
  1728. // when using the Dbnetlib dll. If the protocol is not specified, the netlib will
  1729. // try all protocols in the order listed in the Client Network Utility. Connect will
  1730. // then fail if all protocols fail.
  1731. if (!ADP.IsEmpty(protocol)) {
  1732. ExtendedServerName = protocol + ":" + serverName;
  1733. }
  1734. else {
  1735. ExtendedServerName = serverName;
  1736. }
  1737. ResolvedServerName = serverName;
  1738. }
  1739. }
  1740. }