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

grpc / grpc-java / #20365

27 Jul 2026 06:04AM UTC coverage: 89.199% (+0.07%) from 89.125%
#20365

push

github

web-flow
core: Implement LB Delay Observability (Proposal A121) (#12807)

This PR implements **Attempt-Level RPC Delay Observability** across the core channel transport, built-in load balancers, xDS policies, and the OpenTelemetry telemetry plugin, aligned with [gRPC Proposal A121](https://github.com/grpc/proposal/pull/556).

38276 of 42911 relevant lines covered (89.2%)

0.89 hits per line

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

92.06
/../util/src/main/java/io/grpc/util/RoundRobinLoadBalancer.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.util;
18

19
import static com.google.common.base.Preconditions.checkArgument;
20
import static io.grpc.ConnectivityState.CONNECTING;
21
import static io.grpc.ConnectivityState.IDLE;
22
import static io.grpc.ConnectivityState.READY;
23
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
24

25
import com.google.common.annotations.VisibleForTesting;
26
import com.google.common.base.MoreObjects;
27
import com.google.common.base.Preconditions;
28
import io.grpc.ConnectivityState;
29
import io.grpc.EquivalentAddressGroup;
30
import io.grpc.LoadBalancer;
31
import io.grpc.NameResolver;
32
import java.util.ArrayList;
33
import java.util.Collection;
34
import java.util.HashSet;
35
import java.util.List;
36
import java.util.Random;
37
import java.util.concurrent.atomic.AtomicInteger;
38

39
/**
40
 * A {@link LoadBalancer} that provides round-robin load-balancing over the {@link
41
 * EquivalentAddressGroup}s from the {@link NameResolver}.
42
 */
43
final class RoundRobinLoadBalancer extends MultiChildLoadBalancer {
44
  private static final PickResult CONNECTING_RESULT = PickResult.withNoResult("connecting",
1✔
45
      "round_robin connecting: TCP/TLS handshake in progress to child balancers");
46
  private final AtomicInteger sequence = new AtomicInteger(new Random().nextInt());
1✔
47
  private SubchannelPicker currentPicker = new FixedResultPicker(CONNECTING_RESULT);
1✔
48

49
  public RoundRobinLoadBalancer(Helper helper) {
50
    super(helper);
1✔
51
  }
1✔
52

53
  /**
54
   * Updates picker with the list of active subchannels (state == READY).
55
   */
56
  @Override
57
  protected void updateOverallBalancingState() {
58
    List<ChildLbState> activeList = getReadyChildren();
1✔
59
    if (activeList.isEmpty()) {
1✔
60
      // No READY subchannels
61

62
      // RRLB will request connection immediately on subchannel IDLE.
63
      boolean isConnecting = false;
1✔
64
      for (ChildLbState childLbState : getChildLbStates()) {
1✔
65
        ConnectivityState state = childLbState.getCurrentState();
1✔
66
        if (state == CONNECTING || state == IDLE) {
1✔
67
          isConnecting = true;
1✔
68
          break;
1✔
69
        }
70
      }
1✔
71

72
      if (isConnecting) {
1✔
73
        updateBalancingState(CONNECTING, new FixedResultPicker(CONNECTING_RESULT));
1✔
74
      } else {
75
        updateBalancingState(TRANSIENT_FAILURE, createReadyPicker(getChildLbStates()));
1✔
76
      }
77
    } else {
1✔
78
      updateBalancingState(READY, createReadyPicker(activeList));
1✔
79
    }
80
  }
1✔
81

82
  private void updateBalancingState(ConnectivityState state, SubchannelPicker picker) {
83
    if (state != currentConnectivityState || !picker.equals(currentPicker)) {
1✔
84
      getHelper().updateBalancingState(state, picker);
1✔
85
      currentConnectivityState = state;
1✔
86
      currentPicker = picker;
1✔
87
    }
88
  }
1✔
89

90
  private SubchannelPicker createReadyPicker(Collection<ChildLbState> children) {
91
    List<SubchannelPicker> pickerList = new ArrayList<>();
1✔
92
    for (ChildLbState child : children) {
1✔
93
      SubchannelPicker picker = child.getCurrentPicker();
1✔
94
      pickerList.add(picker);
1✔
95
    }
1✔
96

97
    return new ReadyPicker(pickerList, sequence);
1✔
98
  }
99

100
  @Override
101
  protected ChildLbState createChildLbState(Object key) {
102
    return new ChildLbState(key, pickFirstLbProvider) {
1✔
103
      @Override
104
      protected ChildLbStateHelper createChildHelper() {
105
        return new ChildLbStateHelper() {
1✔
106
          @Override
107
          public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) {
108
            super.updateBalancingState(newState, newPicker);
1✔
109
            if (!resolvingAddresses && newState == IDLE) {
1✔
110
              getLb().requestConnection();
1✔
111
            }
112
          }
1✔
113
        };
114
      }
115
    };
116
  }
117

118
  @VisibleForTesting
119
  static class ReadyPicker extends SubchannelPicker {
120
    private final List<SubchannelPicker> subchannelPickers; // non-empty
121
    private final AtomicInteger index;
122
    private final int hashCode;
123

124
    public ReadyPicker(List<SubchannelPicker> list, AtomicInteger index) {
1✔
125
      checkArgument(!list.isEmpty(), "empty list");
1✔
126
      this.subchannelPickers = list;
1✔
127
      this.index = Preconditions.checkNotNull(index, "index");
1✔
128

129
      // Every created picker is checked for equality in updateBalancingState() at least once.
130
      // Pre-compute the hash so it can be checked cheaply. Using the hash in equals() makes it very
131
      // fast except when the pickers are (very likely) equal.
132
      //
133
      // For equality we treat children as a set; use hash code as defined by Set
134
      int sum = 0;
1✔
135
      for (SubchannelPicker picker : subchannelPickers) {
1✔
136
        sum += picker.hashCode();
1✔
137
      }
1✔
138
      this.hashCode = sum;
1✔
139
    }
1✔
140

141
    @Override
142
    public PickResult pickSubchannel(PickSubchannelArgs args) {
143
      return subchannelPickers.get(nextIndex()).pickSubchannel(args);
1✔
144
    }
145

146
    @Override
147
    public String toString() {
148
      return MoreObjects.toStringHelper(ReadyPicker.class)
×
149
          .add("subchannelPickers", subchannelPickers)
×
150
          .toString();
×
151
    }
152

153
    private int nextIndex() {
154
      int i = index.getAndIncrement() & Integer.MAX_VALUE;
1✔
155
      return i % subchannelPickers.size();
1✔
156
    }
157

158
    @VisibleForTesting
159
    List<SubchannelPicker> getSubchannelPickers() {
160
      return subchannelPickers;
1✔
161
    }
162

163
    @Override
164
    public int hashCode() {
165
      return hashCode;
×
166
    }
167

168
    @Override
169
    public boolean equals(Object o) {
170
      if (!(o instanceof ReadyPicker)) {
1✔
171
        return false;
1✔
172
      }
173
      ReadyPicker other = (ReadyPicker) o;
1✔
174
      if (other == this) {
1✔
175
        return true;
×
176
      }
177
      // the lists cannot contain duplicate subchannels
178
      return hashCode == other.hashCode
1✔
179
          && index == other.index
180
          && subchannelPickers.size() == other.subchannelPickers.size()
1✔
181
          && new HashSet<>(subchannelPickers).containsAll(other.subchannelPickers);
1✔
182
    }
183
  }
184
}
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