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

grpc / grpc-java / #19440

29 Aug 2024 03:04PM UTC coverage: 84.491% (-0.002%) from 84.493%
#19440

push

github

ejona86
util: Remove child policy config from MultiChildLB state

The child policy config should be refreshed every address update, so it
shouldn't be stored in the ChildLbState. In addition, none of the
current usages actually used what was stored in the ChildLbState in a
meaningful way (it was always null).

ResolvedAddresses was also removed from createChildLbState(), as nothing
in it should be needed for creation; it varies over time and the values
passed at creation are immutable.

33402 of 39533 relevant lines covered (84.49%)

0.84 hits per line

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

89.17
/../xds/src/main/java/io/grpc/xds/LeastRequestLoadBalancer.java
1
/*
2
 * Copyright 2021 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.xds;
18

19
import static com.google.common.base.Preconditions.checkArgument;
20
import static com.google.common.base.Preconditions.checkNotNull;
21
import static io.grpc.ConnectivityState.CONNECTING;
22
import static io.grpc.ConnectivityState.IDLE;
23
import static io.grpc.ConnectivityState.READY;
24
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
25
import static io.grpc.xds.LeastRequestLoadBalancerProvider.DEFAULT_CHOICE_COUNT;
26
import static io.grpc.xds.LeastRequestLoadBalancerProvider.MAX_CHOICE_COUNT;
27
import static io.grpc.xds.LeastRequestLoadBalancerProvider.MIN_CHOICE_COUNT;
28

29
import com.google.common.annotations.VisibleForTesting;
30
import com.google.common.base.MoreObjects;
31
import io.grpc.Attributes;
32
import io.grpc.ClientStreamTracer;
33
import io.grpc.ClientStreamTracer.StreamInfo;
34
import io.grpc.ConnectivityState;
35
import io.grpc.EquivalentAddressGroup;
36
import io.grpc.LoadBalancer;
37
import io.grpc.LoadBalancerProvider;
38
import io.grpc.Metadata;
39
import io.grpc.Status;
40
import io.grpc.util.MultiChildLoadBalancer;
41
import io.grpc.xds.ThreadSafeRandom.ThreadSafeRandomImpl;
42
import java.util.ArrayList;
43
import java.util.HashSet;
44
import java.util.List;
45
import java.util.concurrent.atomic.AtomicInteger;
46

47
/**
48
 * A {@link LoadBalancer} that provides least request load balancing based on
49
 * outstanding request counters.
50
 * It works by sampling a number of subchannels and picking the one with the
51
 * fewest amount of outstanding requests.
52
 * The default sampling amount of two is also known as
53
 * the "power of two choices" (P2C).
54
 */
55
final class LeastRequestLoadBalancer extends MultiChildLoadBalancer {
56
  private final ThreadSafeRandom random;
57

58
  private SubchannelPicker currentPicker = new EmptyPicker();
1✔
59
  private int choiceCount = DEFAULT_CHOICE_COUNT;
1✔
60

61
  LeastRequestLoadBalancer(Helper helper) {
62
    this(helper, ThreadSafeRandomImpl.instance);
1✔
63
  }
1✔
64

65
  @VisibleForTesting
66
  LeastRequestLoadBalancer(Helper helper, ThreadSafeRandom random) {
67
    super(helper);
1✔
68
    this.random = checkNotNull(random, "random");
1✔
69
  }
1✔
70

71
  @Override
72
  public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
73
    // Need to update choiceCount before calling super so that the updateBalancingState call has the
74
    // new value.  However, if the update fails we need to revert it.
75
    int oldChoiceCount = choiceCount;
1✔
76
    LeastRequestConfig config =
1✔
77
        (LeastRequestConfig) resolvedAddresses.getLoadBalancingPolicyConfig();
1✔
78
    if (config != null) {
1✔
79
      choiceCount = config.choiceCount;
1✔
80
    }
81

82
    Status addressAcceptanceStatus = super.acceptResolvedAddresses(resolvedAddresses);
1✔
83

84
    if (!addressAcceptanceStatus.isOk()) {
1✔
85
      choiceCount = oldChoiceCount;
1✔
86
    }
87

88
    return addressAcceptanceStatus;
1✔
89
  }
90

91
  /**
92
   * Updates picker with the list of active subchannels (state == READY).
93
   *
94
   *  <p>
95
   * If no active subchannels exist, but some are in TRANSIENT_FAILURE then returns a picker
96
   * with all of the children in TF so that the application code will get an error from a varying
97
   * random one when it tries to get a subchannel.
98
   * </p>
99
   */
100
  @SuppressWarnings("ReferenceEquality")
101
  @Override
102
  protected void updateOverallBalancingState() {
103
    List<ChildLbState> activeList = getReadyChildren();
1✔
104
    if (activeList.isEmpty()) {
1✔
105
      // No READY subchannels, determine aggregate state and error status
106
      boolean isConnecting = false;
1✔
107
      List<ChildLbState> childrenInTf = new ArrayList<>();
1✔
108
      for (ChildLbState childLbState : getChildLbStates()) {
1✔
109
        ConnectivityState state = childLbState.getCurrentState();
1✔
110
        if (state == CONNECTING || state == IDLE) {
1✔
111
          isConnecting = true;
1✔
112
        } else if (state == TRANSIENT_FAILURE) {
1✔
113
          childrenInTf.add(childLbState);
1✔
114
        }
115
      }
1✔
116
      if (isConnecting) {
1✔
117
        updateBalancingState(CONNECTING, new EmptyPicker());
1✔
118
      } else {
119
        // Give it all the failing children and let it randomly pick among them
120
        updateBalancingState(TRANSIENT_FAILURE,
1✔
121
            new ReadyPicker(childrenInTf, choiceCount, random));
122
      }
123
    } else {
1✔
124
      updateBalancingState(READY, new ReadyPicker(activeList, choiceCount, random));
1✔
125
    }
126
  }
1✔
127

128
  @Override
129
  protected ChildLbState createChildLbState(Object key) {
130
    return new LeastRequestLbState(key, pickFirstLbProvider);
1✔
131
  }
132

133
  private void updateBalancingState(ConnectivityState state, SubchannelPicker picker) {
134
    if (state != currentConnectivityState || !picker.equals(currentPicker)) {
1✔
135
      getHelper().updateBalancingState(state, picker);
1✔
136
      currentConnectivityState = state;
1✔
137
      currentPicker = picker;
1✔
138
    }
139
  }
1✔
140

141
  /**
142
   * This should ONLY be used by tests.
143
   */
144
  @VisibleForTesting
145
  void setResolvingAddresses(boolean newValue) {
146
    super.resolvingAddresses = newValue;
1✔
147
  }
1✔
148

149
  // Expose for tests in this package.
150
  private static AtomicInteger getInFlights(ChildLbState childLbState) {
151
    return ((LeastRequestLbState)childLbState).activeRequests;
1✔
152
  }
153

154
  @VisibleForTesting
155
  static final class ReadyPicker extends SubchannelPicker {
156
    private final List<SubchannelPicker> childPickers; // non-empty
157
    private final List<AtomicInteger> childInFlights; // 1:1 with childPickers
158
    private final List<EquivalentAddressGroup> childEags; // 1:1 with childPickers
159
    private final int choiceCount;
160
    private final ThreadSafeRandom random;
161
    private final int hashCode;
162

163
    ReadyPicker(List<ChildLbState> childLbStates, int choiceCount, ThreadSafeRandom random) {
1✔
164
      checkArgument(!childLbStates.isEmpty(), "empty list");
1✔
165
      this.childPickers = new ArrayList<>(childLbStates.size());
1✔
166
      this.childInFlights = new ArrayList<>(childLbStates.size());
1✔
167
      this.childEags = new ArrayList<>(childLbStates.size());
1✔
168
      for (ChildLbState state : childLbStates) {
1✔
169
        childPickers.add(state.getCurrentPicker());
1✔
170
        childInFlights.add(getInFlights(state));
1✔
171
        childEags.add(state.getEag());
1✔
172
      }
1✔
173
      this.choiceCount = choiceCount;
1✔
174
      this.random = checkNotNull(random, "random");
1✔
175

176
      int sum = 0;
1✔
177
      for (SubchannelPicker child : childPickers) {
1✔
178
        sum += child.hashCode();
1✔
179
      }
1✔
180
      this.hashCode = sum ^ choiceCount;
1✔
181
    }
1✔
182

183
    @Override
184
    public PickResult pickSubchannel(PickSubchannelArgs args) {
185
      int child = nextChildToUse();
1✔
186
      PickResult childResult = childPickers.get(child).pickSubchannel(args);
1✔
187

188
      if (!childResult.getStatus().isOk() || childResult.getSubchannel() == null) {
1✔
189
        return childResult;
×
190
      }
191

192
      if (childResult.getStreamTracerFactory() != null) {
1✔
193
        // Already wrapped, so just use the current picker for selected child
194
        return childResult;
×
195
      } else {
196
        // Wrap the subchannel
197
        OutstandingRequestsTracingFactory factory =
1✔
198
            new OutstandingRequestsTracingFactory(childInFlights.get(child));
1✔
199
        return PickResult.withSubchannel(childResult.getSubchannel(), factory);
1✔
200
      }
201
    }
202

203
    @Override
204
    public String toString() {
205
      return MoreObjects.toStringHelper(ReadyPicker.class)
×
206
                        .add("list", childPickers)
×
207
                        .add("choiceCount", choiceCount)
×
208
                        .toString();
×
209
    }
210

211
    private int nextChildToUse() {
212
      int candidate = random.nextInt(childPickers.size());
1✔
213
      for (int i = 0; i < choiceCount - 1; ++i) {
1✔
214
        int sampled = random.nextInt(childPickers.size());
1✔
215
        if (childInFlights.get(sampled).get() < childInFlights.get(candidate).get()) {
1✔
216
          candidate = sampled;
1✔
217
        }
218
      }
219
      return candidate;
1✔
220
    }
221

222
    @VisibleForTesting
223
    List<SubchannelPicker> getChildPickers() {
224
      return childPickers;
1✔
225
    }
226

227
    @VisibleForTesting
228
    List<EquivalentAddressGroup> getChildEags() {
229
      return childEags;
1✔
230
    }
231

232
    @Override
233
    public int hashCode() {
234
      return hashCode;
×
235
    }
236

237
    @Override
238
    public boolean equals(Object o) {
239
      if (!(o instanceof ReadyPicker)) {
1✔
240
        return false;
1✔
241
      }
242
      ReadyPicker other = (ReadyPicker) o;
1✔
243
      if (other == this) {
1✔
244
        return true;
×
245
      }
246
      // the lists cannot contain duplicate children
247
      return hashCode == other.hashCode
1✔
248
          && choiceCount == other.choiceCount
249
          && childPickers.size() == other.childPickers.size()
1✔
250
          && new HashSet<>(childPickers).containsAll(other.childPickers);
1✔
251
    }
252
  }
253

254
  @VisibleForTesting
255
  static final class EmptyPicker extends SubchannelPicker {
1✔
256
    @Override
257
    public PickResult pickSubchannel(PickSubchannelArgs args) {
258
      return PickResult.withNoResult();
1✔
259
    }
260

261
    @Override
262
    public int hashCode() {
263
      return getClass().hashCode();
×
264
    }
265

266
    @Override
267
    public boolean equals(Object o) {
268
      return o instanceof EmptyPicker;
1✔
269
    }
270

271
    @Override
272
    public String toString() {
273
      return MoreObjects.toStringHelper(EmptyPicker.class).toString();
×
274
    }
275
  }
276

277
  private static final class OutstandingRequestsTracingFactory extends
278
      ClientStreamTracer.Factory {
279
    private final AtomicInteger inFlights;
280

281
    private OutstandingRequestsTracingFactory(AtomicInteger inFlights) {
1✔
282
      this.inFlights = checkNotNull(inFlights, "inFlights");
1✔
283
    }
1✔
284

285
    @Override
286
    public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata headers) {
287
      return new ClientStreamTracer() {
1✔
288
        @Override
289
        public void streamCreated(Attributes transportAttrs, Metadata headers) {
290
          inFlights.incrementAndGet();
1✔
291
        }
1✔
292

293
        @Override
294
        public void streamClosed(Status status) {
295
          inFlights.decrementAndGet();
1✔
296
        }
1✔
297
      };
298
    }
299
  }
300

301
  static final class LeastRequestConfig {
302
    final int choiceCount;
303

304
    LeastRequestConfig(int choiceCount) {
1✔
305
      checkArgument(choiceCount >= MIN_CHOICE_COUNT, "choiceCount <= 1");
1✔
306
      // Even though a choiceCount value larger than 2 is currently considered valid in xDS
307
      // we restrict it to 10 here as specified in "A48: xDS Least Request LB Policy".
308
      this.choiceCount = Math.min(choiceCount, MAX_CHOICE_COUNT);
1✔
309
    }
1✔
310

311
    @Override
312
    public String toString() {
313
      return MoreObjects.toStringHelper(this)
×
314
          .add("choiceCount", choiceCount)
×
315
          .toString();
×
316
    }
317
  }
318

319
  protected class LeastRequestLbState extends ChildLbState {
320
    private final AtomicInteger activeRequests = new AtomicInteger(0);
1✔
321

322
    public LeastRequestLbState(Object key, LoadBalancerProvider policyProvider) {
1✔
323
      super(key, policyProvider);
1✔
324
    }
1✔
325

326
    int getActiveRequests() {
327
      return activeRequests.get();
1✔
328
    }
329

330
    @Override
331
    protected ChildLbStateHelper createChildHelper() {
332
      return new ChildLbStateHelper() {
1✔
333
        @Override
334
        public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) {
335
          super.updateBalancingState(newState, newPicker);
1✔
336
          if (!resolvingAddresses && newState == IDLE) {
1✔
337
            getLb().requestConnection();
1✔
338
          }
339
        }
1✔
340
      };
341
    }
342
  }
343
}
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

© 2025 Coveralls, Inc