@@ -327,18 +327,131 @@ public async Task UpdateAddresses_ConnectIsInProgress_InProgressConnectIsCancele
327327 Assert . AreEqual ( 81 , connectAddress2 . Port ) ;
328328 }
329329
330+ [ Test ]
331+ public async Task PickAsync_DoesNotDeadlockAfterReconnect_WithResolverError ( )
332+ {
333+ // Arrange
334+ var services = new ServiceCollection ( ) ;
335+ services . AddNUnitLogger ( ) ;
336+ await using var serviceProvider = services . BuildServiceProvider ( ) ;
337+ var loggerFactory = serviceProvider . GetRequiredService < ILoggerFactory > ( ) ;
338+
339+ var resolver = new TestResolver ( loggerFactory ) ;
340+
341+ GrpcChannelOptions channelOptions = new GrpcChannelOptions ( ) ;
342+ channelOptions . ServiceConfig = new ServiceConfig ( )
343+ {
344+ LoadBalancingConfigs = { new RoundRobinConfig ( ) }
345+ } ;
346+
347+ var transportFactory = new TestSubchannelTransportFactory ( ) ;
348+ var clientChannel = CreateConnectionManager ( loggerFactory , resolver , transportFactory , new [ ] { new RoundRobinBalancerFactory ( ) } ) ;
349+ // Configure balancer similar to how GrpcChannel constructor does it
350+ clientChannel . ConfigureBalancer ( c => new ChildHandlerLoadBalancer (
351+ c ,
352+ channelOptions . ServiceConfig ,
353+ clientChannel ) ) ;
354+
355+ // Act
356+ var connectTask = clientChannel . ConnectAsync ( waitForReady : true , cancellationToken : CancellationToken . None ) ;
357+ var pickTask = clientChannel . PickAsync (
358+ new PickContext { Request = new HttpRequestMessage ( ) } ,
359+ waitForReady : true ,
360+ CancellationToken . None ) . AsTask ( ) ;
361+
362+ resolver . UpdateAddresses ( new List < BalancerAddress >
363+ {
364+ new BalancerAddress ( "localhost" , 80 )
365+ } ) ;
366+ await Task . WhenAll ( connectTask , pickTask ) . DefaultTimeout ( ) ;
367+
368+ // Simulate transport/network issue
369+ transportFactory . Transports . ForEach ( t => t . Disconnect ( ) ) ;
370+ resolver . UpdateError ( new Status ( StatusCode . Unavailable , "Test error" ) ) ;
371+
372+ pickTask = clientChannel . PickAsync (
373+ new PickContext { Request = new HttpRequestMessage ( ) } ,
374+ waitForReady : true ,
375+ CancellationToken . None ) . AsTask ( ) ;
376+ resolver . UpdateAddresses ( new List < BalancerAddress >
377+ {
378+ new BalancerAddress ( "localhost" , 80 )
379+ } ) ;
380+
381+ // Assert
382+ // Should not timeout (deadlock)
383+ await pickTask . DefaultTimeout ( ) ;
384+ }
385+
386+ [ Test ]
387+ public async Task PickAsync_DoesNotDeadlockAfterReconnect_WithZeroAddressResolved ( )
388+ {
389+ // Arrange
390+ var services = new ServiceCollection ( ) ;
391+ services . AddNUnitLogger ( ) ;
392+ await using var serviceProvider = services . BuildServiceProvider ( ) ;
393+ var loggerFactory = serviceProvider . GetRequiredService < ILoggerFactory > ( ) ;
394+
395+ var resolver = new TestResolver ( loggerFactory ) ;
396+
397+ GrpcChannelOptions channelOptions = new GrpcChannelOptions ( ) ;
398+ channelOptions . ServiceConfig = new ServiceConfig ( )
399+ {
400+ LoadBalancingConfigs = { new RoundRobinConfig ( ) }
401+ } ;
402+
403+ var transportFactory = new TestSubchannelTransportFactory ( ) ;
404+ var clientChannel = CreateConnectionManager ( loggerFactory , resolver , transportFactory , new [ ] { new RoundRobinBalancerFactory ( ) } ) ;
405+ // Configure balancer similar to how GrpcChannel constructor does it
406+ clientChannel . ConfigureBalancer ( c => new ChildHandlerLoadBalancer (
407+ c ,
408+ channelOptions . ServiceConfig ,
409+ clientChannel ) ) ;
410+
411+ // Act
412+ var connectTask = clientChannel . ConnectAsync ( waitForReady : true , cancellationToken : CancellationToken . None ) ;
413+ var pickTask = clientChannel . PickAsync (
414+ new PickContext { Request = new HttpRequestMessage ( ) } ,
415+ waitForReady : true ,
416+ CancellationToken . None ) . AsTask ( ) ;
417+
418+ resolver . UpdateAddresses ( new List < BalancerAddress >
419+ {
420+ new BalancerAddress ( "localhost" , 80 )
421+ } ) ;
422+ await Task . WhenAll ( connectTask , pickTask ) . DefaultTimeout ( ) ;
423+
424+ // Simulate transport/network issue (with resolver reporting no addresses)
425+ transportFactory . Transports . ForEach ( t => t . Disconnect ( ) ) ;
426+ resolver . UpdateAddresses ( new List < BalancerAddress > ( ) ) ;
427+
428+ pickTask = clientChannel . PickAsync (
429+ new PickContext { Request = new HttpRequestMessage ( ) } ,
430+ waitForReady : true ,
431+ CancellationToken . None ) . AsTask ( ) ;
432+ resolver . UpdateAddresses ( new List < BalancerAddress >
433+ {
434+ new BalancerAddress ( "localhost" , 80 )
435+ } ) ;
436+
437+ // Assert
438+ // Should not timeout (deadlock)
439+ await pickTask . DefaultTimeout ( ) ;
440+ }
441+
330442 private static ConnectionManager CreateConnectionManager (
331443 ILoggerFactory loggerFactory ,
332444 Resolver resolver ,
333- TestSubchannelTransportFactory transportFactory )
445+ TestSubchannelTransportFactory transportFactory ,
446+ LoadBalancerFactory [ ] ? loadBalancerFactories = null )
334447 {
335448 return new ConnectionManager (
336449 resolver ,
337450 disableResolverServiceConfig : false ,
338451 loggerFactory ,
339452 new TestBackoffPolicyFactory ( ) ,
340453 transportFactory ,
341- Array . Empty < LoadBalancerFactory > ( ) ) ;
454+ loadBalancerFactories ?? Array . Empty < LoadBalancerFactory > ( ) ) ;
342455 }
343456
344457 private class DropLoadBalancer : LoadBalancer
0 commit comments