e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
245 lines
14 KiB
C#
245 lines
14 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="SqlConnectionTimeoutErrorInternal.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
// <owner current="true" primary="false">[....]</owner>
|
|
//------------------------------------------------------------------------------
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|