From 02aa9b8cbadd6ea3b518abc0ab237f3b7e30ea2f Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Fri, 6 Mar 2026 09:38:29 -0800 Subject: [PATCH 1/2] core: Wait for backoff timer on address update in pick_first The backoff timer is only used when serializeRetries=true, and that exists to match the old/current pick_first's behavior as closely as possible. InternalSubchannel.updateAddresses() would take no action when in TRANSIENT_FAILURE; it would update the addresses and just wait for the backoff timer to expire. Note that this only impacts serializeRetries=true; in the other cases we do want to start trying to the new addresses immediately, because the backoff timers are in the subchannels. --- .../java/io/grpc/internal/PickFirstLeafLoadBalancer.java | 2 +- .../java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java index cd9ff9ab58c..f8f5c94f5ba 100644 --- a/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java +++ b/core/src/main/java/io/grpc/internal/PickFirstLeafLoadBalancer.java @@ -496,7 +496,7 @@ private void shutdownRemaining(SubchannelData activeSubchannelData) { */ @Override public void requestConnection() { - if (!addressIndex.isValid() || rawConnectivityState == SHUTDOWN) { + if (!addressIndex.isValid() || rawConnectivityState == SHUTDOWN || reconnectTask != null) { return; } diff --git a/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java b/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java index cb73d17d682..d8c3abe54c3 100644 --- a/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java +++ b/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java @@ -1446,6 +1446,11 @@ public void updateAddresses_disjoint_transient_failure() { loadBalancer.acceptResolvedAddresses( ResolvedAddresses.newBuilder().setAddresses(newServers).setAttributes(affinity).build()); + if (serializeRetries) { + inOrder.verify(mockSubchannel3, never()).start(stateListenerCaptor.capture()); + fakeClock.forwardTime(1, TimeUnit.SECONDS); + } + // subchannel 3 still attempts a connection even though we stay in transient failure assertEquals(TRANSIENT_FAILURE, loadBalancer.getConcludedConnectivityState()); inOrder.verify(mockSubchannel3).start(stateListenerCaptor.capture()); From cad4063a28256c593ac4880b5d0d5af5dfbc90e2 Mon Sep 17 00:00:00 2001 From: Eric Anderson Date: Mon, 9 Mar 2026 07:33:14 -0700 Subject: [PATCH 2/2] Handle reconnect attempt jitter in test --- .../java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java b/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java index d8c3abe54c3..eb7b40257c0 100644 --- a/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java +++ b/core/src/test/java/io/grpc/internal/PickFirstLeafLoadBalancerTest.java @@ -1448,7 +1448,7 @@ public void updateAddresses_disjoint_transient_failure() { if (serializeRetries) { inOrder.verify(mockSubchannel3, never()).start(stateListenerCaptor.capture()); - fakeClock.forwardTime(1, TimeUnit.SECONDS); + forwardTimeByBackoffDelay(); } // subchannel 3 still attempts a connection even though we stay in transient failure