//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // Microsoft //------------------------------------------------------------------------------ namespace System.Data.SqlClient { using System; using System.ComponentModel; using System.Data; using System.Data.Common; using System.Diagnostics; using System.Globalization; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using System.Runtime.InteropServices.ComTypes; using System.Security; using System.Security.Permissions; using System.Threading; using SysTx = System.Transactions; using System.Data.SqlClient; using System.Text; // VSTFDevDiv# 643319 - Improve timeout error message reported when SqlConnection.Open fails internal enum SqlConnectionTimeoutErrorPhase { Undefined = 0, PreLoginBegin, // [PRE-LOGIN PHASE] Start of the pre-login phase; Initialize global variables; InitializeConnection, // [PRE-LOGIN PHASE] Create and initialize socket. SendPreLoginHandshake, // [PRE-LOGIN PHASE] Make pre-login handshake request. ConsumePreLoginHandshake, // [PRE-LOGIN PHASE] Receive pre-login handshake response and consume it; Establish an SSL channel. LoginBegin, // [LOGIN PHASE] End of the pre-login phase; Start of the login phase; ProcessConnectionAuth, // [LOGIN PHASE] Process SSPI or SQL Authenticate. PostLogin, // [POST-LOGIN PHASE] End of the login phase; And post-login phase; Complete, // Marker for the succesful completion of the connection Count // ** This is to track the length of the enum. ** Do not add any phase after this. ** } internal enum SqlConnectionInternalSourceType { Principle, Failover, RoutingDestination } // DEVNOTE: Class to capture the duration spent in each SqlConnectionTimeoutErrorPhase. internal class SqlConnectionTimeoutPhaseDuration { Stopwatch swDuration = new Stopwatch(); internal void StartCapture() { Debug.Assert(swDuration != null, "Time capture stopwatch cannot be null."); swDuration.Start(); } internal void StopCapture() { //Debug.Assert(swDuration.IsRunning == true, "The stop opertaion of the stopwatch cannot be called when it is not running."); if (swDuration.IsRunning == true) swDuration.Stop(); } internal long GetMilliSecondDuration() { // DEVNOTE: In a phase fails in between a phase, the stop watch may still be running. // Hence the check to verify if the stop watch is running hasn't been added in. return swDuration.ElapsedMilliseconds; } } internal class SqlConnectionTimeoutErrorInternal { SqlConnectionTimeoutPhaseDuration[] phaseDurations = null; SqlConnectionTimeoutPhaseDuration[] originalPhaseDurations = null; SqlConnectionTimeoutErrorPhase currentPhase = SqlConnectionTimeoutErrorPhase.Undefined; SqlConnectionInternalSourceType currentSourceType = SqlConnectionInternalSourceType.Principle; bool isFailoverScenario = false; internal SqlConnectionTimeoutErrorPhase CurrentPhase { get { return currentPhase; } } public SqlConnectionTimeoutErrorInternal() { phaseDurations = new SqlConnectionTimeoutPhaseDuration[(int)SqlConnectionTimeoutErrorPhase.Count]; for (int i = 0; i < phaseDurations.Length; i++) phaseDurations[i] = null; } public void SetFailoverScenario(bool useFailoverServer) { isFailoverScenario = useFailoverServer; } public void SetInternalSourceType(SqlConnectionInternalSourceType sourceType) { currentSourceType = sourceType; if (currentSourceType == SqlConnectionInternalSourceType.RoutingDestination) { // When we get routed, save the current phase durations so that we can use them in the error message later Debug.Assert(currentPhase == SqlConnectionTimeoutErrorPhase.PostLogin, "Should not be switching to the routing destination until Post Login is completed"); originalPhaseDurations = phaseDurations; phaseDurations = new SqlConnectionTimeoutPhaseDuration[(int)SqlConnectionTimeoutErrorPhase.Count]; SetAndBeginPhase(SqlConnectionTimeoutErrorPhase.PreLoginBegin); } } internal void ResetAndRestartPhase() { currentPhase = SqlConnectionTimeoutErrorPhase.PreLoginBegin; for (int i = 0; i < phaseDurations.Length; i++) phaseDurations[i] = null; } internal void SetAndBeginPhase(SqlConnectionTimeoutErrorPhase timeoutErrorPhase) { currentPhase = timeoutErrorPhase; if (phaseDurations[(int)timeoutErrorPhase] == null) { phaseDurations[(int)timeoutErrorPhase] = new SqlConnectionTimeoutPhaseDuration(); } phaseDurations[(int)timeoutErrorPhase].StartCapture(); } internal void EndPhase(SqlConnectionTimeoutErrorPhase timeoutErrorPhase) { Debug.Assert(phaseDurations[(int)timeoutErrorPhase] != null, "End phase capture cannot be invoked when the phase duration object is a null."); phaseDurations[(int)timeoutErrorPhase].StopCapture(); } internal void SetAllCompleteMarker() { currentPhase = SqlConnectionTimeoutErrorPhase.Complete; } internal string GetErrorMessage() { StringBuilder errorBuilder; string durationString; switch(currentPhase) { case SqlConnectionTimeoutErrorPhase.PreLoginBegin: errorBuilder = new StringBuilder(SQLMessage.Timeout_PreLogin_Begin()); durationString = SQLMessage.Duration_PreLogin_Begin( phaseDurations[(int)SqlConnectionTimeoutErrorPhase.PreLoginBegin].GetMilliSecondDuration()); break; case SqlConnectionTimeoutErrorPhase.InitializeConnection: errorBuilder = new StringBuilder(SQLMessage.Timeout_PreLogin_InitializeConnection()); durationString = SQLMessage.Duration_PreLogin_Begin( phaseDurations[(int)SqlConnectionTimeoutErrorPhase.PreLoginBegin].GetMilliSecondDuration() + phaseDurations[(int)SqlConnectionTimeoutErrorPhase.InitializeConnection].GetMilliSecondDuration()); break; case SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake: errorBuilder = new StringBuilder(SQLMessage.Timeout_PreLogin_SendHandshake()); durationString = SQLMessage.Duration_PreLoginHandshake( phaseDurations[(int)SqlConnectionTimeoutErrorPhase.PreLoginBegin].GetMilliSecondDuration() + phaseDurations[(int)SqlConnectionTimeoutErrorPhase.InitializeConnection].GetMilliSecondDuration(), phaseDurations[(int)SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake].GetMilliSecondDuration()); break; case SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake: errorBuilder = new StringBuilder(SQLMessage.Timeout_PreLogin_ConsumeHandshake()); durationString = SQLMessage.Duration_PreLoginHandshake( phaseDurations[(int)SqlConnectionTimeoutErrorPhase.PreLoginBegin].GetMilliSecondDuration() + phaseDurations[(int)SqlConnectionTimeoutErrorPhase.InitializeConnection].GetMilliSecondDuration(), phaseDurations[(int)SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake].GetMilliSecondDuration() + phaseDurations[(int)SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake].GetMilliSecondDuration()); break; case SqlConnectionTimeoutErrorPhase.LoginBegin: errorBuilder = new StringBuilder(SQLMessage.Timeout_Login_Begin()); durationString = SQLMessage.Duration_Login_Begin( phaseDurations[(int)SqlConnectionTimeoutErrorPhase.PreLoginBegin].GetMilliSecondDuration() + phaseDurations[(int)SqlConnectionTimeoutErrorPhase.InitializeConnection].GetMilliSecondDuration(), phaseDurations[(int)SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake].GetMilliSecondDuration() + phaseDurations[(int)SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake].GetMilliSecondDuration(), phaseDurations[(int)SqlConnectionTimeoutErrorPhase.LoginBegin].GetMilliSecondDuration()); break; case SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth: errorBuilder = new StringBuilder(SQLMessage.Timeout_Login_ProcessConnectionAuth()); durationString = SQLMessage.Duration_Login_ProcessConnectionAuth( phaseDurations[(int)SqlConnectionTimeoutErrorPhase.PreLoginBegin].GetMilliSecondDuration() + phaseDurations[(int)SqlConnectionTimeoutErrorPhase.InitializeConnection].GetMilliSecondDuration(), phaseDurations[(int)SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake].GetMilliSecondDuration() + phaseDurations[(int)SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake].GetMilliSecondDuration(), phaseDurations[(int)SqlConnectionTimeoutErrorPhase.LoginBegin].GetMilliSecondDuration(), phaseDurations[(int)SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth].GetMilliSecondDuration()); break; case SqlConnectionTimeoutErrorPhase.PostLogin: errorBuilder = new StringBuilder(SQLMessage.Timeout_PostLogin()); durationString = SQLMessage.Duration_PostLogin( phaseDurations[(int)SqlConnectionTimeoutErrorPhase.PreLoginBegin].GetMilliSecondDuration() + phaseDurations[(int)SqlConnectionTimeoutErrorPhase.InitializeConnection].GetMilliSecondDuration(), phaseDurations[(int)SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake].GetMilliSecondDuration() + phaseDurations[(int)SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake].GetMilliSecondDuration(), phaseDurations[(int)SqlConnectionTimeoutErrorPhase.LoginBegin].GetMilliSecondDuration(), phaseDurations[(int)SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth].GetMilliSecondDuration(), phaseDurations[(int)SqlConnectionTimeoutErrorPhase.PostLogin].GetMilliSecondDuration()); break; default: errorBuilder = new StringBuilder(SQLMessage.Timeout()); durationString = null; break; } // This message is to be added only when within the various stages of a connection. // In all other cases, it will default to the original error message. if ((currentPhase != SqlConnectionTimeoutErrorPhase.Undefined) && (currentPhase != SqlConnectionTimeoutErrorPhase.Complete)) { // NOTE: In case of a failover scenario, add a string that this failure occured as part of the primary or secondary server if (isFailoverScenario) { errorBuilder.Append(" "); errorBuilder.AppendFormat((IFormatProvider)null, SQLMessage.Timeout_FailoverInfo(), currentSourceType); } else if (currentSourceType == SqlConnectionInternalSourceType.RoutingDestination) { errorBuilder.Append(" "); errorBuilder.AppendFormat((IFormatProvider)null, SQLMessage.Timeout_RoutingDestination(), originalPhaseDurations[(int)SqlConnectionTimeoutErrorPhase.PreLoginBegin].GetMilliSecondDuration() + originalPhaseDurations[(int)SqlConnectionTimeoutErrorPhase.InitializeConnection].GetMilliSecondDuration(), originalPhaseDurations[(int)SqlConnectionTimeoutErrorPhase.SendPreLoginHandshake].GetMilliSecondDuration() + originalPhaseDurations[(int)SqlConnectionTimeoutErrorPhase.ConsumePreLoginHandshake].GetMilliSecondDuration(), originalPhaseDurations[(int)SqlConnectionTimeoutErrorPhase.LoginBegin].GetMilliSecondDuration(), originalPhaseDurations[(int)SqlConnectionTimeoutErrorPhase.ProcessConnectionAuth].GetMilliSecondDuration(), originalPhaseDurations[(int)SqlConnectionTimeoutErrorPhase.PostLogin].GetMilliSecondDuration()); } } // NOTE: To display duration in each phase. if (durationString != null) { errorBuilder.Append(" "); errorBuilder.Append(durationString); } return errorBuilder.ToString(); } } }