SqlConnectionTimeoutErrorInternal.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. //------------------------------------------------------------------------------
  2. // <copyright file="SqlConnectionTimeoutErrorInternal.cs" company="Microsoft">
  3. // Copyright (c) Microsoft Corporation. All rights reserved.
  4. // </copyright>
  5. // <owner current="true" primary="false">[....]</owner>
  6. //------------------------------------------------------------------------------
  7. namespace System.Data.SqlClient
  8. {
  9. using System;
  10. using System.ComponentModel;
  11. using System.Data;
  12. using System.Data.Common;
  13. using System.Diagnostics;
  14. using System.Globalization;
  15. using System.Runtime.ConstrainedExecution;
  16. using System.Runtime.InteropServices;
  17. using System.Runtime.InteropServices.ComTypes;
  18. using System.Security;
  19. using System.Security.Permissions;
  20. using System.Threading;
  21. using SysTx = System.Transactions;
  22. using System.Data.SqlClient;
  23. using System.Text;
  24. // VSTFDevDiv# 643319 - Improve timeout error message reported when SqlConnection.Open fails
  25. internal enum SqlConnectionTimeoutErrorPhase
  26. {
  27. Undefined = 0,
  28. PreLoginBegin, // [PRE-LOGIN PHASE] Start of the pre-login phase; Initialize global variables;
  29. InitializeConnection, // [PRE-LOGIN PHASE] Create and initialize socket.
  30. SendPreLoginHandshake, // [PRE-LOGIN PHASE] Make pre-login handshake request.
  31. ConsumePreLoginHandshake, // [PRE-LOGIN PHASE] Receive pre-login handshake response and consume it; Establish an SSL channel.
  32. LoginBegin, // [LOGIN PHASE] End of the pre-login phase; Start of the login phase;
  33. ProcessConnectionAuth, // [LOGIN PHASE] Process SSPI or SQL Authenticate.
  34. PostLogin, // [POST-LOGIN PHASE] End of the login phase; And post-login phase;
  35. Complete, // Marker for the succesful completion of the connection
  36. Count // ** This is to track the length of the enum. ** Do not add any phase after this. **
  37. }
  38. internal enum SqlConnectionInternalSourceType
  39. {
  40. Principle,
  41. Failover,
  42. RoutingDestination
  43. }
  44. // DEVNOTE: Class to capture the duration spent in each SqlConnectionTimeoutErrorPhase.
  45. internal class SqlConnectionTimeoutPhaseDuration
  46. {
  47. Stopwatch swDuration = new Stopwatch();
  48. internal void StartCapture()
  49. {
  50. Debug.Assert(swDuration != null, "Time capture stopwatch cannot be null.");
  51. swDuration.Start();
  52. }
  53. internal void StopCapture()
  54. {
  55. //Debug.Assert(swDuration.IsRunning == true, "The stop opertaion of the stopwatch cannot be called when it is not running.");
  56. if (swDuration.IsRunning == true)
  57. swDuration.Stop();
  58. }
  59. internal long GetMilliSecondDuration()
  60. {
  61. // DEVNOTE: In a phase fails in between a phase, the stop watch may still be running.
  62. // Hence the check to verify if the stop watch is running hasn't been added in.
  63. return swDuration.ElapsedMilliseconds;
  64. }
  65. }
  66. internal class SqlConnectionTimeoutErrorInternal
  67. {
  68. SqlConnectionTimeoutPhaseDuration[] phaseDurations = null;
  69. SqlConnectionTimeoutPhaseDuration[] originalPhaseDurations = null;
  70. SqlConnectionTimeoutErrorPhase currentPhase = SqlConnectionTimeoutErrorPhase.Undefined;
  71. SqlConnectionInternalSourceType currentSourceType = SqlConnectionInternalSourceType.Principle;
  72. bool isFailoverScenario = false;
  73. internal SqlConnectionTimeoutErrorPhase CurrentPhase
  74. {
  75. get { return currentPhase; }
  76. }
  77. public SqlConnectionTimeoutErrorInternal()
  78. {
  79. phaseDurations = new SqlConnectionTimeoutPhaseDuration[(int)SqlConnectionTimeoutErrorPhase.Count];
  80. for (int i = 0; i < phaseDurations.Length; i++)
  81. phaseDurations[i] = null;
  82. }
  83. public void SetFailoverScenario(bool useFailoverServer)
  84. {
  85. isFailoverScenario = useFailoverServer;
  86. }
  87. public void SetInternalSourceType(SqlConnectionInternalSourceType sourceType)
  88. {
  89. currentSourceType = sourceType;
  90. if (currentSourceType == SqlConnectionInternalSourceType.RoutingDestination)
  91. {
  92. // When we get routed, save the current phase durations so that we can use them in the error message later
  93. Debug.Assert(currentPhase == SqlConnectionTimeoutErrorPhase.PostLogin, "Should not be switching to the routing destination until Post Login is completed");
  94. originalPhaseDurations = phaseDurations;
  95. phaseDurations = new SqlConnectionTimeoutPhaseDuration[(int)SqlConnectionTimeoutErrorPhase.Count];
  96. SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.PreLoginBegin);
  97. }
  98. }
  99. internal void ResetAndRestartPhase()
  100. {
  101. currentPhase = SqlConnectionTimeoutErrorPhase.PreLoginBegin;
  102. for (int i = 0; i < phaseDurations.Length; i++)
  103. phaseDurations[i] = null;
  104. }
  105. internal void SetAndBeginPhase(SqlConnectionTimeoutErrorPhase timeoutErrorPhase)
  106. {
  107. currentPhase = timeoutErrorPhase;
  108. if (phaseDurations[(int)timeoutErrorPhase] == null)
  109. {
  110. phaseDurations[(int)timeoutErrorPhase] = new SqlConnectionTimeoutPhaseDuration();
  111. }
  112. phaseDurations[(int)timeoutErrorPhase].StartCapture();
  113. }
  114. internal void EndPhase(SqlConnectionTimeoutErrorPhase timeoutErrorPhase)
  115. {
  116. Debug.Assert(phaseDurations[(int)timeoutErrorPhase] != null, "End phase capture cannot be invoked when the phase duration object is a null.");
  117. phaseDurations[(int)timeoutErrorPhase].StopCapture();
  118. }
  119. internal void SetAllCompleteMarker()
  120. {
  121. currentPhase = SqlConnectionTimeoutErrorPhase.Complete;
  122. }
  123. internal string GetErrorMessage()
  124. {
  125. StringBuilder errorBuilder;
  126. string durationString;
  127. switch(currentPhase)
  128. {
  129. case SqlConnectionTimeoutErrorPhase.PreLoginBegin:
  130. errorBuilder = new StringBuilder(SQLMessage.Timeout_PreLogin_Begin());
  131. durationString = SQLMessage.Duration_PreLogin_Begin(
  132. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.PreLoginBegin].GetMilliSecondDuration());
  133. break;
  134. case SqlConnectionTimeoutErrorPhase.InitializeConnection:
  135. errorBuilder = new StringBuilder(SQLMessage.Timeout_PreLogin_InitializeConnection());
  136. durationString = SQLMessage.Duration_PreLogin_Begin(
  137. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.PreLoginBegin].GetMilliSecondDuration() +
  138. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.InitializeConnection].GetMilliSecondDuration());
  139. break;
  140. case SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake:
  141. errorBuilder = new StringBuilder(SQLMessage.Timeout_PreLogin_SendHandshake());
  142. durationString = SQLMessage.Duration_PreLoginHandshake(
  143. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.PreLoginBegin].GetMilliSecondDuration() +
  144. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.InitializeConnection].GetMilliSecondDuration(),
  145. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake].GetMilliSecondDuration());
  146. break;
  147. case SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake:
  148. errorBuilder = new StringBuilder(SQLMessage.Timeout_PreLogin_ConsumeHandshake());
  149. durationString = SQLMessage.Duration_PreLoginHandshake(
  150. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.PreLoginBegin].GetMilliSecondDuration() +
  151. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.InitializeConnection].GetMilliSecondDuration(),
  152. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake].GetMilliSecondDuration() +
  153. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake].GetMilliSecondDuration());
  154. break;
  155. case SqlConnectionTimeoutErrorPhase.LoginBegin:
  156. errorBuilder = new StringBuilder(SQLMessage.Timeout_Login_Begin());
  157. durationString = SQLMessage.Duration_Login_Begin(
  158. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.PreLoginBegin].GetMilliSecondDuration() +
  159. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.InitializeConnection].GetMilliSecondDuration(),
  160. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake].GetMilliSecondDuration() +
  161. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake].GetMilliSecondDuration(),
  162. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.LoginBegin].GetMilliSecondDuration());
  163. break;
  164. case SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth:
  165. errorBuilder = new StringBuilder(SQLMessage.Timeout_Login_ProcessConnectionAuth());
  166. durationString = SQLMessage.Duration_Login_ProcessConnectionAuth(
  167. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.PreLoginBegin].GetMilliSecondDuration() +
  168. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.InitializeConnection].GetMilliSecondDuration(),
  169. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake].GetMilliSecondDuration() +
  170. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake].GetMilliSecondDuration(),
  171. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.LoginBegin].GetMilliSecondDuration(),
  172. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth].GetMilliSecondDuration());
  173. break;
  174. case SqlConnectionTimeoutErrorPhase.PostLogin:
  175. errorBuilder = new StringBuilder(SQLMessage.Timeout_PostLogin());
  176. durationString = SQLMessage.Duration_PostLogin(
  177. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.PreLoginBegin].GetMilliSecondDuration() +
  178. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.InitializeConnection].GetMilliSecondDuration(),
  179. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake].GetMilliSecondDuration() +
  180. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake].GetMilliSecondDuration(),
  181. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.LoginBegin].GetMilliSecondDuration(),
  182. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth].GetMilliSecondDuration(),
  183. phaseDurations[(int)SqlConnectionTimeoutErrorPhase.PostLogin].GetMilliSecondDuration());
  184. break;
  185. default:
  186. errorBuilder = new StringBuilder(SQLMessage.Timeout());
  187. durationString = null;
  188. break;
  189. }
  190. // This message is to be added only when within the various stages of a connection.
  191. // In all other cases, it will default to the original error message.
  192. if ((currentPhase != SqlConnectionTimeoutErrorPhase.Undefined) || (currentPhase != SqlConnectionTimeoutErrorPhase.Complete))
  193. {
  194. // NOTE: In case of a failover scenario, add a string that this failure occured as part of the primary or secondary server
  195. if (isFailoverScenario)
  196. {
  197. errorBuilder.Append(" ");
  198. errorBuilder.AppendFormat((IFormatProvider)null, SQLMessage.Timeout_FailoverInfo(), currentSourceType);
  199. }
  200. else if (currentSourceType == SqlConnectionInternalSourceType.RoutingDestination) {
  201. errorBuilder.Append(" ");
  202. errorBuilder.AppendFormat((IFormatProvider)null, SQLMessage.Timeout_RoutingDestination(),
  203. originalPhaseDurations[(int)SqlConnectionTimeoutErrorPhase.PreLoginBegin].GetMilliSecondDuration() +
  204. originalPhaseDurations[(int)SqlConnectionTimeoutErrorPhase.InitializeConnection].GetMilliSecondDuration(),
  205. originalPhaseDurations[(int)SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake].GetMilliSecondDuration() +
  206. originalPhaseDurations[(int)SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake].GetMilliSecondDuration(),
  207. originalPhaseDurations[(int)SqlConnectionTimeoutErrorPhase.LoginBegin].GetMilliSecondDuration(),
  208. originalPhaseDurations[(int)SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth].GetMilliSecondDuration(),
  209. originalPhaseDurations[(int)SqlConnectionTimeoutErrorPhase.PostLogin].GetMilliSecondDuration());
  210. }
  211. // NOTE: To display duration in each phase.
  212. if (durationString != null)
  213. {
  214. errorBuilder.Append(" ");
  215. errorBuilder.Append(durationString);
  216. }
  217. }
  218. return errorBuilder.ToString();
  219. }
  220. }
  221. }