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

grpc / grpc-java / #19383

31 Jul 2024 08:32PM UTC coverage: 84.448% (+0.001%) from 84.447%
#19383

push

github

ejona86
xds: Stop extending RR in WRR

They share very little code, and we really don't want RoundRobinLb to be
public and non-final. Originally, WRR was expected to share much more
code with RR, and even delegated to RR at times. The delegation was
removed in 111ff60e. After dca89b25, most of the sharing has been moved
out into general-purpose tools that can be used by any LB policy.

FixedResultPicker now has equals to makes it as a EmptyPicker
replacement. RoundRobinLb still uses EmptyPicker because fixing its
tests is a larger change. OutlierDetectionLbTest was changed because
FixedResultPicker is used by PickFirstLeafLb, and now RoundRobinLb can
squelch some of its updates for ready pickers.

33265 of 39391 relevant lines covered (84.45%)

0.84 hits per line

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

88.33
/../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 final AtomicInteger sequence = new AtomicInteger(new Random().nextInt());
1✔
45
  private SubchannelPicker currentPicker = new EmptyPicker();
1✔
46

47
  public RoundRobinLoadBalancer(Helper helper) {
48
    super(helper);
1✔
49
  }
1✔
50

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

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

70
      if (isConnecting) {
1✔
71
        updateBalancingState(CONNECTING, new EmptyPicker());
1✔
72
      } else {
73
        updateBalancingState(TRANSIENT_FAILURE, createReadyPicker(getChildLbStates()));
1✔
74
      }
75
    } else {
1✔
76
      updateBalancingState(READY, createReadyPicker(activeList));
1✔
77
    }
78
  }
1✔
79

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

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

95
    return new ReadyPicker(pickerList, sequence);
1✔
96
  }
97

98
  @VisibleForTesting
99
  static class ReadyPicker extends SubchannelPicker {
100
    private final List<SubchannelPicker> subchannelPickers; // non-empty
101
    private final AtomicInteger index;
102
    private final int hashCode;
103

104
    public ReadyPicker(List<SubchannelPicker> list, AtomicInteger index) {
1✔
105
      checkArgument(!list.isEmpty(), "empty list");
1✔
106
      this.subchannelPickers = list;
1✔
107
      this.index = Preconditions.checkNotNull(index, "index");
1✔
108

109
      // Every created picker is checked for equality in updateBalancingState() at least once.
110
      // Pre-compute the hash so it can be checked cheaply. Using the hash in equals() makes it very
111
      // fast except when the pickers are (very likely) equal.
112
      //
113
      // For equality we treat children as a set; use hash code as defined by Set
114
      int sum = 0;
1✔
115
      for (SubchannelPicker picker : subchannelPickers) {
1✔
116
        sum += picker.hashCode();
1✔
117
      }
1✔
118
      this.hashCode = sum;
1✔
119
    }
1✔
120

121
    @Override
122
    public PickResult pickSubchannel(PickSubchannelArgs args) {
123
      return subchannelPickers.get(nextIndex()).pickSubchannel(args);
1✔
124
    }
125

126
    @Override
127
    public String toString() {
128
      return MoreObjects.toStringHelper(ReadyPicker.class)
×
129
          .add("subchannelPickers", subchannelPickers)
×
130
          .toString();
×
131
    }
132

133
    private int nextIndex() {
134
      int i = index.getAndIncrement() & Integer.MAX_VALUE;
1✔
135
      return i % subchannelPickers.size();
1✔
136
    }
137

138
    @VisibleForTesting
139
    List<SubchannelPicker> getSubchannelPickers() {
140
      return subchannelPickers;
1✔
141
    }
142

143
    @Override
144
    public int hashCode() {
145
      return hashCode;
×
146
    }
147

148
    @Override
149
    public boolean equals(Object o) {
150
      if (!(o instanceof ReadyPicker)) {
1✔
151
        return false;
1✔
152
      }
153
      ReadyPicker other = (ReadyPicker) o;
1✔
154
      if (other == this) {
1✔
155
        return true;
×
156
      }
157
      // the lists cannot contain duplicate subchannels
158
      return hashCode == other.hashCode
1✔
159
          && index == other.index
160
          && subchannelPickers.size() == other.subchannelPickers.size()
1✔
161
          && new HashSet<>(subchannelPickers).containsAll(other.subchannelPickers);
1✔
162
    }
163
  }
164

165
  @VisibleForTesting
166
  static final class EmptyPicker extends SubchannelPicker {
1✔
167
    @Override
168
    public PickResult pickSubchannel(PickSubchannelArgs args) {
169
      return PickResult.withNoResult();
×
170
    }
171

172
    @Override
173
    public int hashCode() {
174
      return getClass().hashCode();
×
175
    }
176

177
    @Override
178
    public boolean equals(Object o) {
179
      return o instanceof EmptyPicker;
1✔
180
    }
181
  }
182
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc