2016-08-03 10:59:49 +00:00
//------------------------------------------------------------------------------
// <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 ;
}
2016-11-10 13:04:39 +00:00
2016-08-03 10:59:49 +00:00
// 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.
2016-11-10 13:04:39 +00:00
if ( ( currentPhase ! = SqlConnectionTimeoutErrorPhase . Undefined ) & & ( currentPhase ! = SqlConnectionTimeoutErrorPhase . Complete ) )
2016-08-03 10:59:49 +00:00
{
// 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 ( ) ) ;
}
2016-11-10 13:04:39 +00:00
}
2016-08-03 10:59:49 +00:00
2016-11-10 13:04:39 +00:00
// NOTE: To display duration in each phase.
if ( durationString ! = null )
{
errorBuilder . Append ( " " ) ;
errorBuilder . Append ( durationString ) ;
2016-08-03 10:59:49 +00:00
}
return errorBuilder . ToString ( ) ;
}
}
}