@@ -164,6 +164,8 @@ public SNITCPHandle(
164164 ts = ts . Ticks < 0 ? TimeSpan . FromTicks ( 0 ) : ts ;
165165 }
166166
167+ Stopwatch stopwatch = Stopwatch . StartNew ( ) ;
168+
167169 bool reportError = true ;
168170
169171 SqlClientEventSource . Log . TrySNITraceEvent ( nameof ( SNITCPHandle ) , EventType . INFO , "Connection Id {0}, Connecting to serverName {1} and port {2}" , args0 : _connectionId , args1 : serverName , args2 : port ) ;
@@ -183,6 +185,11 @@ public SNITCPHandle(
183185 }
184186 catch ( Exception ex )
185187 {
188+ TimeSpan timeLeft = ts - stopwatch . Elapsed ;
189+ if ( ! isInfiniteTimeOut && timeLeft <= TimeSpan . Zero )
190+ {
191+ throw ;
192+ }
186193 // Retry with cached IP address
187194 if ( ex is SocketException || ex is ArgumentException || ex is AggregateException )
188195 {
@@ -214,26 +221,31 @@ public SNITCPHandle(
214221 {
215222 if ( parallel )
216223 {
217- _socket = TryConnectParallel ( firstCachedIP , portRetry , ts , isInfiniteTimeOut , ref reportError , cachedFQDN , ref pendingDNSInfo ) ;
224+ _socket = TryConnectParallel ( firstCachedIP , portRetry , timeLeft , isInfiniteTimeOut , ref reportError , cachedFQDN , ref pendingDNSInfo ) ;
218225 }
219226 else
220227 {
221- _socket = Connect ( firstCachedIP , portRetry , ts , isInfiniteTimeOut , ipPreference , cachedFQDN , ref pendingDNSInfo ) ;
228+ _socket = Connect ( firstCachedIP , portRetry , timeLeft , isInfiniteTimeOut , ipPreference , cachedFQDN , ref pendingDNSInfo ) ;
222229 }
223230 }
224231 catch ( Exception exRetry )
225232 {
233+ timeLeft = ts - stopwatch . Elapsed ;
234+ if ( ! isInfiniteTimeOut && timeLeft <= TimeSpan . Zero )
235+ {
236+ throw ;
237+ }
226238 if ( exRetry is SocketException || exRetry is ArgumentNullException
227239 || exRetry is ArgumentException || exRetry is ArgumentOutOfRangeException || exRetry is AggregateException )
228240 {
229241 SqlClientEventSource . Log . TrySNITraceEvent ( nameof ( SNITCPHandle ) , EventType . INFO , "Connection Id {0}, Retrying exception {1}" , args0 : _connectionId , args1 : exRetry ? . Message ) ;
230242 if ( parallel )
231243 {
232- _socket = TryConnectParallel ( secondCachedIP , portRetry , ts , isInfiniteTimeOut , ref reportError , cachedFQDN , ref pendingDNSInfo ) ;
244+ _socket = TryConnectParallel ( secondCachedIP , portRetry , timeLeft , isInfiniteTimeOut , ref reportError , cachedFQDN , ref pendingDNSInfo ) ;
233245 }
234246 else
235247 {
236- _socket = Connect ( secondCachedIP , portRetry , ts , isInfiniteTimeOut , ipPreference , cachedFQDN , ref pendingDNSInfo ) ;
248+ _socket = Connect ( secondCachedIP , portRetry , timeLeft , isInfiniteTimeOut , ipPreference , cachedFQDN , ref pendingDNSInfo ) ;
237249 }
238250 }
239251 else
@@ -249,6 +261,10 @@ public SNITCPHandle(
249261 throw ;
250262 }
251263 }
264+ finally
265+ {
266+ stopwatch . Stop ( ) ;
267+ }
252268
253269 if ( _socket == null || ! _socket . Connected )
254270 {
@@ -304,8 +320,11 @@ private Socket TryConnectParallel(string hostName, int port, TimeSpan ts, bool i
304320 {
305321 Socket availableSocket = null ;
306322 Task < Socket > connectTask ;
323+ TimeSpan timeout = ts ;
307324
308- IPAddress [ ] serverAddresses = SNICommon . GetDnsIpAddresses ( hostName ) ;
325+ IPAddress [ ] serverAddresses = isInfiniteTimeOut
326+ ? SNICommon . GetDnsIpAddresses ( hostName )
327+ : SNICommon . GetDnsIpAddresses ( hostName , ref timeout ) ;
309328
310329 if ( serverAddresses . Length > MaxParallelIpAddresses )
311330 {
@@ -338,7 +357,7 @@ private Socket TryConnectParallel(string hostName, int port, TimeSpan ts, bool i
338357
339358 connectTask = ParallelConnectAsync ( serverAddresses , port ) ;
340359
341- if ( ! ( isInfiniteTimeOut ? connectTask . Wait ( - 1 ) : connectTask . Wait ( ts ) ) )
360+ if ( ! ( isInfiniteTimeOut ? connectTask . Wait ( - 1 ) : connectTask . Wait ( timeout ) ) )
342361 {
343362 callerReportError = false ;
344363 SqlClientEventSource . Log . TrySNITraceEvent ( nameof ( SNITCPHandle ) , EventType . ERR , "Connection Id {0} Connection timed out, Exception: {1}" , args0 : _connectionId , args1 : Strings . SNI_ERROR_40 ) ;
@@ -349,7 +368,7 @@ private Socket TryConnectParallel(string hostName, int port, TimeSpan ts, bool i
349368 availableSocket = connectTask . Result ;
350369 return availableSocket ;
351370 }
352-
371+
353372 /// <summary>
354373 /// Returns array of IP addresses for the given server name, sorted according to the given preference.
355374 /// </summary>
@@ -389,7 +408,7 @@ private static IEnumerable<IPAddress> GetHostAddressesSortedByPreference(string
389408 }
390409 }
391410 }
392-
411+
393412 // Connect to server with hostName and port.
394413 // The IP information will be collected temporarily as the pendingDNSInfo but is not stored in the DNS cache at this point.
395414 // Only write to the DNS cache when we receive IsSupported flag as true in the Feature Ext Ack from server.
@@ -422,26 +441,44 @@ private static Socket Connect(string serverName, int port, TimeSpan timeout, boo
422441 port ,
423442 ipAddress . AddressFamily ,
424443 isInfiniteTimeout ) ;
425-
444+
426445 bool isConnected ;
427446 try // catching SocketException with SocketErrorCode == WouldBlock to run Socket.Select
428447 {
429- socket . Connect ( ipAddress , port ) ;
430- if ( ! isInfiniteTimeout )
448+ if ( isInfiniteTimeout )
449+ {
450+ socket . Connect ( ipAddress , port ) ;
451+ }
452+ else
431453 {
454+ TimeSpan timeLeft = timeout - timeTaken . Elapsed ;
455+ if ( timeLeft <= TimeSpan . Zero )
456+ {
457+ return null ;
458+ }
459+ // Socket.Connect does not support infinite timeouts, so we use Task to simulate it
460+ Task socketConnectTask = new Task ( ( ) => socket . Connect ( ipAddress , port ) ) ;
461+ socketConnectTask . ConfigureAwait ( false ) ;
462+ socketConnectTask . Start ( ) ;
463+ if ( ! socketConnectTask . Wait ( timeLeft ) )
464+ {
465+ throw ADP . TimeoutException ( $ "The socket couldn't connect during the expected { timeLeft } remaining time to connect.") ;
466+ }
432467 throw SQL . SocketDidNotThrow ( ) ;
433468 }
434-
469+
435470 isConnected = true ;
436471 }
437- catch ( SocketException socketException ) when ( ! isInfiniteTimeout &&
438- socketException . SocketErrorCode ==
439- SocketError . WouldBlock )
472+ catch ( AggregateException aggregateException ) when ( ! isInfiniteTimeout
473+ && aggregateException . InnerException is SocketException socketException
474+ && socketException . SocketErrorCode == SocketError . WouldBlock )
440475 {
441476 // https://github.com/dotnet/SqlClient/issues/826#issuecomment-736224118
442477 // Socket.Select is used because it supports timeouts, while Socket.Connect does not
443478
444- List < Socket > checkReadLst ; List < Socket > checkWriteLst ; List < Socket > checkErrorLst ;
479+ List < Socket > checkReadLst ;
480+ List < Socket > checkWriteLst ;
481+ List < Socket > checkErrorLst ;
445482
446483 // Repeating Socket.Select several times if our timeout is greater
447484 // than int.MaxValue microseconds because of
@@ -450,9 +487,10 @@ private static Socket Connect(string serverName, int port, TimeSpan timeout, boo
450487 do
451488 {
452489 TimeSpan timeLeft = timeout - timeTaken . Elapsed ;
453-
454- if ( timeLeft <= TimeSpan . Zero )
490+ if ( ! isInfiniteTimeout && timeLeft <= TimeSpan . Zero )
491+ {
455492 return null ;
493+ }
456494
457495 int socketSelectTimeout =
458496 checked ( ( int ) ( Math . Min ( timeLeft . TotalMilliseconds , int . MaxValue / 1000 ) * 1000 ) ) ;
@@ -487,11 +525,15 @@ private static Socket Connect(string serverName, int port, TimeSpan timeout, boo
487525 return socket ;
488526 }
489527 }
490- catch ( SocketException e )
528+ catch ( AggregateException aggregateException ) when ( aggregateException . InnerException is SocketException socketException )
491529 {
492- SqlClientEventSource . Log . TrySNITraceEvent ( nameof ( SNITCPHandle ) , EventType . ERR , "THIS EXCEPTION IS BEING SWALLOWED: {0}" , args0 : e ? . Message ) ;
530+ SqlClientEventSource . Log . TrySNITraceEvent ( nameof ( SNITCPHandle ) , EventType . ERR , "THIS EXCEPTION IS BEING SWALLOWED: {0}" , args0 : socketException ? . Message ) ;
493531 SqlClientEventSource . Log . TryAdvancedTraceEvent (
494- $ "{ nameof ( SNITCPHandle ) } .{ nameof ( Connect ) } { EventType . ERR } THIS EXCEPTION IS BEING SWALLOWED: { e } ") ;
532+ $ "{ nameof ( SNITCPHandle ) } .{ nameof ( Connect ) } { EventType . ERR } THIS EXCEPTION IS BEING SWALLOWED: { socketException } ") ;
533+ }
534+ catch ( AggregateException aggregateException ) when ( aggregateException . InnerException is TimeoutException timeoutException )
535+ {
536+ Console . WriteLine ( timeoutException ) ; // temporary for testing
495537 }
496538 finally
497539 {
@@ -675,7 +717,7 @@ private bool ValidateServerCertificate(object sender, X509Certificate serverCert
675717 SqlClientEventSource . Log . TrySNITraceEvent ( nameof ( SNITCPHandle ) , EventType . INFO , "Connection Id {0}, Certificate will not be validated." , args0 : _connectionId ) ;
676718 return true ;
677719 }
678-
720+
679721 string serverNameToValidate ;
680722 if ( ! string . IsNullOrEmpty ( _hostNameInCertificate ) )
681723 {
0 commit comments