@@ -253,53 +253,7 @@ private void OnCheckSocketConnection(object? state)
253
253
{
254
254
CompatibilityHelpers . Assert ( socketAddress != null ) ;
255
255
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 ) ;
303
257
}
304
258
}
305
259
@@ -383,7 +337,7 @@ public async ValueTask<Stream> GetStreamAsync(BalancerAddress address, Cancellat
383
337
SocketConnectivitySubchannelTransportLog . ClosingSocketFromIdleTimeoutOnCreateStream ( _logger , _subchannel . Id , address , _socketIdleTimeout ) ;
384
338
closeSocket = true ;
385
339
}
386
- else if ( IsSocketInBadState ( socket , address ) )
340
+ else if ( ShouldCloseSocket ( socket , address , ref socketData , out _ ) )
387
341
{
388
342
SocketConnectivitySubchannelTransportLog . ClosingUnusableSocketOnCreateStream ( _logger , _subchannel . Id , address ) ;
389
343
closeSocket = true ;
@@ -419,7 +373,75 @@ public async ValueTask<Stream> GetStreamAsync(BalancerAddress address, Cancellat
419
373
return stream ;
420
374
}
421
375
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 )
423
445
{
424
446
// From https://github.com/dotnet/runtime/blob/3195fbbd82fdb7f132d6698591ba6489ad6dd8cf/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs#L158-L168
425
447
try
0 commit comments