• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

grpc / grpc-java / #20432

01 Sep 2026 01:58PM UTC coverage: 89.218% (+0.008%) from 89.21%
#20432

push

github

web-flow
fix(pick_first): ignore state updates from shut down or replaced subchannels (#12968)

In legacy PickFirstLoadBalancer, when handleNameResolutionError() or
shutdown() is called, subchannel.shutdown() is invoked. Because
ManagedChannelImpl delays subchannel shutdown by 5 seconds
(SUBCHANNEL_SHUTDOWN_DELAY_SECONDS), the old subchannel may complete a
connection attempt during this window and fire READY state updates.
Without a check verifying if the calling subchannel is still the current
active subchannel, PickFirstLoadBalancer publishes a READY picker for
the obsolete subchannel right before its 5-second delayed shutdown task
fires and kills the transport. This leaves the channel permanently stuck
in READY with a dead subchannel picker.

Partially addresses #12958

38642 of 43312 relevant lines covered (89.22%)

0.89 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.88
/../netty/src/main/java/io/grpc/netty/ClientTransportLifecycleManager.java
1
/*
2
 * Copyright 2016 The gRPC Authors
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 */
16

17
package io.grpc.netty;
18

19
import com.google.errorprone.annotations.CanIgnoreReturnValue;
20
import io.grpc.Attributes;
21
import io.grpc.Status;
22
import io.grpc.internal.DisconnectError;
23
import io.grpc.internal.ManagedClientTransport;
24

25
/** Maintainer of transport lifecycle status. */
26
final class ClientTransportLifecycleManager {
27
  private final ManagedClientTransport.Listener listener;
28
  private boolean transportReady;
29
  private boolean transportShutdown;
30
  private boolean transportInUse;
31
  /** null iff !transportShutdown. */
32
  private Status shutdownStatus;
33
  /** null iff !transportShutdown. */
34
  private boolean transportTerminated;
35

36
  public ClientTransportLifecycleManager(ManagedClientTransport.Listener listener) {
1 ✔
37
    this.listener = listener;
1 ✔
38
  }
1 ✔
39

40
  public Attributes filterAttributes(Attributes attributes) {
41
    if (transportReady || transportShutdown) {
1 ✔
42
      return attributes;
×
43
    }
44
    return listener.filterTransport(attributes);
1 ✔
45
  }
46

47
  public void notifyReady() {
48
    if (transportReady || transportShutdown) {
1 ✔
49
      return;
×
50
    }
51
    transportReady = true;
1 ✔
52
    listener.transportReady();
1 ✔
53
  }
1 ✔
54

55
  /**
56
   * Marks transport as shutdown, but does not set the error status. This must eventually be
57
   * followed by a call to notifyShutdown.
58
   */
59
  public void notifyGracefulShutdown(Status s, DisconnectError disconnectError) {
60
    if (transportShutdown) {
1 ✔
61
      return;
1 ✔
62
    }
63
    transportShutdown = true;
1 ✔
64
    listener.transportShutdown(s, disconnectError);
1 ✔
65
  }
1 ✔
66

67
  /** Returns {@code true} if was the first shutdown. */
68
  @CanIgnoreReturnValue
69
  public boolean notifyShutdown(Status s, DisconnectError disconnectError) {
70
    notifyGracefulShutdown(s, disconnectError);
1 ✔
71
    if (shutdownStatus != null) {
1 ✔
72
      return false;
1 ✔
73
    }
74
    shutdownStatus = s;
1 ✔
75
    return true;
1 ✔
76
  }
77

78
  public void notifyInUse(boolean inUse) {
79
    if (inUse == transportInUse) {
1 ✔
80
      return;
×
81
    }
82
    transportInUse = inUse;
1 ✔
83
    listener.transportInUse(inUse);
1 ✔
84
  }
1 ✔
85

86
  public void notifyTerminated(Status s, DisconnectError disconnectError) {
87
    if (transportTerminated) {
1 ✔
88
      return;
×
89
    }
90
    transportTerminated = true;
1 ✔
91
    notifyShutdown(s, disconnectError);
1 ✔
92
    listener.transportTerminated();
1 ✔
93
  }
1 ✔
94

95
  public Status getShutdownStatus() {
96
    return shutdownStatus;
1 ✔
97
  }
98

99
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc