@@ -253,53 +253,7 @@ private void OnCheckSocketConnection(object? state)
253253 {
254254 CompatibilityHelpers . Assert ( socketAddress != null ) ;
255255
256- try
257- {
258- SocketConnectivitySubchannelTransportLog . CheckingSocket ( _logger , _subchannel . Id , socketAddress ) ;
259-
260- // Poll socket to check if it can be read from. Unfortunatly this requires reading pending data.
261- // The server might send data, e.g. HTTP/2 SETTINGS frame, so we need to read and cache it.
262- //
263- // Available data needs to be read now because the only way to determine whether the connection is closed is to
264- // get the results of polling after available data is received.
265- bool hasReadData ;
266- do
267- {
268- closeSocket = IsSocketInBadState ( socket , socketAddress ) ;
269- var available = socket . Available ;
270- if ( available > 0 )
271- {
272- hasReadData = true ;
273- var serverDataAvailable = CalculateInitialSocketDataLength ( _initialSocketData ) + available ;
274- if ( serverDataAvailable > MaximumInitialSocketDataSize )
275- {
276- // Data sent to the client before a connection is started shouldn't be large.
277- // Put a maximum limit on the buffer size to prevent an unexpected scenario from consuming too much memory.
278- throw new InvalidOperationException ( $ "The server sent { serverDataAvailable } bytes to the client before a connection was established. Maximum allowed data exceeded.") ;
279- }
280-
281- SocketConnectivitySubchannelTransportLog . SocketReceivingAvailable ( _logger , _subchannel . Id , socketAddress , available ) ;
282-
283- // Data is already available so this won't block.
284- var buffer = new byte [ available ] ;
285- var readCount = socket . Receive ( buffer ) ;
286-
287- _initialSocketData ??= new List < ReadOnlyMemory < byte > > ( ) ;
288- _initialSocketData . Add ( buffer . AsMemory ( 0 , readCount ) ) ;
289- }
290- else
291- {
292- hasReadData = false ;
293- }
294- }
295- while ( hasReadData ) ;
296- }
297- catch ( Exception ex )
298- {
299- closeSocket = true ;
300- checkException = ex ;
301- SocketConnectivitySubchannelTransportLog . ErrorCheckingSocket ( _logger , _subchannel . Id , socketAddress , ex ) ;
302- }
256+ closeSocket = ShouldCloseSocket ( socket , socketAddress , ref _initialSocketData , out checkException ) ;
303257 }
304258 }
305259
@@ -383,7 +337,7 @@ public async ValueTask<Stream> GetStreamAsync(BalancerAddress address, Cancellat
383337 SocketConnectivitySubchannelTransportLog . ClosingSocketFromIdleTimeoutOnCreateStream ( _logger , _subchannel . Id , address , _socketIdleTimeout ) ;
384338 closeSocket = true ;
385339 }
386- else if ( IsSocketInBadState ( socket , address ) )
340+ else if ( ShouldCloseSocket ( socket , address , ref socketData , out _ ) )
387341 {
388342 SocketConnectivitySubchannelTransportLog . ClosingUnusableSocketOnCreateStream ( _logger , _subchannel . Id , address ) ;
389343 closeSocket = true ;
@@ -419,7 +373,75 @@ public async ValueTask<Stream> GetStreamAsync(BalancerAddress address, Cancellat
419373 return stream ;
420374 }
421375
422- private bool IsSocketInBadState ( Socket socket , BalancerAddress address )
376+ /// <summary>
377+ /// Checks whether the socket is healthy. May read available data into the passed in buffer.
378+ /// Returns true if the socket should be closed.
379+ /// </summary>
380+ private bool ShouldCloseSocket ( Socket socket , BalancerAddress socketAddress , ref List < ReadOnlyMemory < byte > > ? socketData , out Exception ? checkException )
381+ {
382+ checkException = null ;
383+
384+ try
385+ {
386+ SocketConnectivitySubchannelTransportLog . CheckingSocket ( _logger , _subchannel . Id , socketAddress ) ;
387+
388+ // Poll socket to check if it can be read from. Unfortunately this requires reading pending data.
389+ // The server might send data, e.g. HTTP/2 SETTINGS frame, so we need to read and cache it.
390+ //
391+ // Available data needs to be read now because the only way to determine whether the connection is
392+ // closed is to get the results of polling after available data is received.
393+ // For example, the server may have sent an HTTP/2 SETTINGS or GOAWAY frame.
394+ // We need to cache whatever we read so it isn't dropped.
395+ do
396+ {
397+ if ( PollSocket ( socket , socketAddress ) )
398+ {
399+ // Polling socket reported an unhealthy state.
400+ return true ;
401+ }
402+
403+ var available = socket . Available ;
404+ if ( available > 0 )
405+ {
406+ var serverDataAvailable = CalculateInitialSocketDataLength ( socketData ) + available ;
407+ if ( serverDataAvailable > MaximumInitialSocketDataSize )
408+ {
409+ // Data sent to the client before a connection is started shouldn't be large.
410+ // Put a maximum limit on the buffer size to prevent an unexpected scenario from consuming too much memory.
411+ throw new InvalidOperationException ( $ "The server sent { serverDataAvailable } bytes to the client before a connection was established. Maximum allowed data exceeded.") ;
412+ }
413+
414+ SocketConnectivitySubchannelTransportLog . SocketReceivingAvailable ( _logger , _subchannel . Id , socketAddress , available ) ;
415+
416+ // Data is already available so this won't block.
417+ var buffer = new byte [ available ] ;
418+ var readCount = socket . Receive ( buffer ) ;
419+
420+ socketData ??= new List < ReadOnlyMemory < byte > > ( ) ;
421+ socketData . Add ( buffer . AsMemory ( 0 , readCount ) ) ;
422+ }
423+ else
424+ {
425+ // There is no more available data to read and the socket is healthy.
426+ return false ;
427+ }
428+ }
429+ while ( true ) ;
430+ }
431+ catch ( Exception ex )
432+ {
433+ checkException = ex ;
434+ SocketConnectivitySubchannelTransportLog . ErrorCheckingSocket ( _logger , _subchannel . Id , socketAddress , ex ) ;
435+ return true ;
436+ }
437+ }
438+
439+ /// <summary>
440+ /// Poll the socket to check for health and available data.
441+ /// Shouldn't be used by itself as data needs to be consumed to accurately report the socket health.
442+ /// <see cref="ShouldCloseSocket"/> handles consuming data and getting the socket health.
443+ /// </summary>
444+ private bool PollSocket ( Socket socket , BalancerAddress address )
423445 {
424446 // From https://github.com/dotnet/runtime/blob/3195fbbd82fdb7f132d6698591ba6489ad6dd8cf/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs#L158-L168
425447 try
0 commit comments