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

grpc / grpc-java / #19869

17 Jun 2025 02:14PM UTC coverage: 88.556% (-0.003%) from 88.559%
#19869

push

github

web-flow
util: Deliver addresses in a random order in MultiChildLb

This should often not matter much, but in b/412468630 it was cleary
visible that child creation order can skew load for the first batch of
RPCs. This doesn't solve all the cases, as further-away backends will
still be less likely chosen initially and it is ignorant of the LB
policy. But this doesn't impact correctness, is easy, and is one fewer
cases to worry about.

34560 of 39026 relevant lines covered (88.56%)

0.89 hits per line

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

96.4
/../util/src/main/java/io/grpc/util/MultiChildLoadBalancer.java
1
/*
2
 * Copyright 2023 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.checkNotNull;
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.SHUTDOWN;
24
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
25

26
import com.google.common.annotations.VisibleForTesting;
27
import com.google.common.collect.Iterables;
28
import com.google.common.collect.Maps;
29
import com.google.common.primitives.UnsignedInts;
30
import io.grpc.Attributes;
31
import io.grpc.ConnectivityState;
32
import io.grpc.EquivalentAddressGroup;
33
import io.grpc.Internal;
34
import io.grpc.LoadBalancer;
35
import io.grpc.LoadBalancerProvider;
36
import io.grpc.Status;
37
import io.grpc.internal.PickFirstLoadBalancerProvider;
38
import java.net.SocketAddress;
39
import java.util.ArrayList;
40
import java.util.Collection;
41
import java.util.Collections;
42
import java.util.HashSet;
43
import java.util.List;
44
import java.util.Map;
45
import java.util.Random;
46
import java.util.logging.Level;
47
import java.util.logging.Logger;
48
import javax.annotation.Nullable;
49

50
/**
51
 * A base load balancing policy for those policies which has multiple children such as
52
 * ClusterManager or the petiole policies.  For internal use only.
53
 */
54
@Internal
55
public abstract class MultiChildLoadBalancer extends LoadBalancer {
56

57
  private static final Logger logger = Logger.getLogger(MultiChildLoadBalancer.class.getName());
1✔
58
  private static final int OFFSET_SEED = new Random().nextInt();
1✔
59
  // Modify by replacing the list to release memory when no longer used.
60
  private List<ChildLbState> childLbStates = new ArrayList<>(0);
1✔
61
  private final Helper helper;
62
  // Set to true if currently in the process of handling resolved addresses.
63
  protected boolean resolvingAddresses;
64

65
  protected final LoadBalancerProvider pickFirstLbProvider = new PickFirstLoadBalancerProvider();
1✔
66

67
  protected ConnectivityState currentConnectivityState;
68

69

70
  protected MultiChildLoadBalancer(Helper helper) {
1✔
71
    this.helper = checkNotNull(helper, "helper");
1✔
72
    logger.log(Level.FINE, "Created");
1✔
73
  }
1✔
74

75
  /**
76
   * Using the state of all children will calculate the current connectivity state,
77
   * update fields, generate a picker and then call
78
   * {@link Helper#updateBalancingState(ConnectivityState, SubchannelPicker)}.
79
   */
80
  protected abstract void updateOverallBalancingState();
81

82
  /**
83
   * Override to utilize parsing of the policy configuration or alternative helper/lb generation.
84
   * Override this if keys are not Endpoints or if child policies have configuration. Null map
85
   * values preserve the child without delivering the child an update.
86
   */
87
  protected Map<Object, ResolvedAddresses> createChildAddressesMap(
88
      ResolvedAddresses resolvedAddresses) {
89
    Map<Object, ResolvedAddresses> childAddresses =
1✔
90
        Maps.newLinkedHashMapWithExpectedSize(resolvedAddresses.getAddresses().size());
1✔
91
    for (EquivalentAddressGroup eag : resolvedAddresses.getAddresses()) {
1✔
92
      ResolvedAddresses addresses = resolvedAddresses.toBuilder()
1✔
93
          .setAddresses(Collections.singletonList(eag))
1✔
94
          .setAttributes(Attributes.newBuilder().set(IS_PETIOLE_POLICY, true).build())
1✔
95
          .setLoadBalancingPolicyConfig(null)
1✔
96
          .build();
1✔
97
      childAddresses.put(new Endpoint(eag), addresses);
1✔
98
    }
1✔
99
    return childAddresses;
1✔
100
  }
101

102
  /**
103
   * Override to create an instance of a subclass.
104
   */
105
  protected ChildLbState createChildLbState(Object key) {
106
    return new ChildLbState(key, pickFirstLbProvider);
1✔
107
  }
108

109
  /**
110
   *   Override to completely replace the default logic or to do additional activities.
111
   */
112
  @Override
113
  public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
114
    logger.log(Level.FINE, "Received resolution result: {0}", resolvedAddresses);
1✔
115
    try {
116
      resolvingAddresses = true;
1✔
117

118
      // process resolvedAddresses to update children
119
      Map<Object, ResolvedAddresses> newChildAddresses = createChildAddressesMap(resolvedAddresses);
1✔
120

121
      // Handle error case
122
      if (newChildAddresses.isEmpty()) {
1✔
123
        Status unavailableStatus = Status.UNAVAILABLE.withDescription(
1✔
124
            "NameResolver returned no usable address. " + resolvedAddresses);
125
        handleNameResolutionError(unavailableStatus);
1✔
126
        return unavailableStatus;
1✔
127
      }
128

129
      return updateChildrenWithResolvedAddresses(newChildAddresses);
1✔
130
    } finally {
131
      resolvingAddresses = false;
1✔
132
    }
133
  }
134

135
  /**
136
   * Handle the name resolution error.
137
   *
138
   * <p/>Override if you need special handling.
139
   */
140
  @Override
141
  public void handleNameResolutionError(Status error) {
142
    if (currentConnectivityState != READY)  {
1✔
143
      helper.updateBalancingState(
1✔
144
          TRANSIENT_FAILURE, new FixedResultPicker(PickResult.withError(error)));
1✔
145
    }
146
  }
1✔
147

148
  @Override
149
  public void shutdown() {
150
    logger.log(Level.FINE, "Shutdown");
1✔
151
    for (ChildLbState state : childLbStates) {
1✔
152
      state.shutdown();
1✔
153
    }
1✔
154
    childLbStates.clear();
1✔
155
  }
1✔
156

157
  private Status updateChildrenWithResolvedAddresses(
158
      Map<Object, ResolvedAddresses> newChildAddresses) {
159
    // Create a map with the old values
160
    Map<Object, ChildLbState> oldStatesMap =
1✔
161
        Maps.newLinkedHashMapWithExpectedSize(childLbStates.size());
1✔
162
    for (ChildLbState state : childLbStates) {
1✔
163
      oldStatesMap.put(state.getKey(), state);
1✔
164
    }
1✔
165

166
    // Move ChildLbStates from the map to a new list (preserving the new map's order)
167
    Status status = Status.OK;
1✔
168
    List<ChildLbState> newChildLbStates = new ArrayList<>(newChildAddresses.size());
1✔
169
    for (Map.Entry<Object, ResolvedAddresses> entry : newChildAddresses.entrySet()) {
1✔
170
      ChildLbState childLbState = oldStatesMap.remove(entry.getKey());
1✔
171
      if (childLbState == null) {
1✔
172
        childLbState = createChildLbState(entry.getKey());
1✔
173
      }
174
      newChildLbStates.add(childLbState);
1✔
175
    }
1✔
176
    // Use a random start position for child updates to weakly "shuffle" connection creation order.
177
    // The network will often add noise to the creation order, but this avoids giving earlier
178
    // children a consistent head start.
179
    for (ChildLbState childLbState : offsetIterable(newChildLbStates, OFFSET_SEED)) {
1✔
180
      ResolvedAddresses addresses = newChildAddresses.get(childLbState.getKey());
1✔
181
      if (addresses != null) {
1✔
182
        // update child LB
183
        Status newStatus = childLbState.lb.acceptResolvedAddresses(addresses);
1✔
184
        if (!newStatus.isOk()) {
1✔
185
          status = newStatus;
×
186
        }
187
      }
188
    }
1✔
189

190
    childLbStates = newChildLbStates;
1✔
191
    // Update the picker and our connectivity state
192
    updateOverallBalancingState();
1✔
193

194
    // Remaining entries in map are orphaned
195
    for (ChildLbState childLbState : oldStatesMap.values()) {
1✔
196
      childLbState.shutdown();
1✔
197
    }
1✔
198
    return status;
1✔
199
  }
200

201
  @VisibleForTesting
202
  static <T> Iterable<T> offsetIterable(Collection<T> c, int seed) {
203
    int pos;
204
    if (c.isEmpty()) {
1✔
205
      pos = 0;
1✔
206
    } else {
207
      pos = UnsignedInts.remainder(seed, c.size());
1✔
208
    }
209
    return Iterables.concat(
1✔
210
        Iterables.skip(c, pos),
1✔
211
        Iterables.limit(c, pos));
1✔
212
  }
213

214
  @Nullable
215
  protected static ConnectivityState aggregateState(
216
      @Nullable ConnectivityState overallState, ConnectivityState childState) {
217
    if (overallState == null) {
1✔
218
      return childState;
1✔
219
    }
220
    if (overallState == READY || childState == READY) {
1✔
221
      return READY;
1✔
222
    }
223
    if (overallState == CONNECTING || childState == CONNECTING) {
1✔
224
      return CONNECTING;
1✔
225
    }
226
    if (overallState == IDLE || childState == IDLE) {
1✔
227
      return IDLE;
×
228
    }
229
    return overallState;
1✔
230
  }
231

232
  protected final Helper getHelper() {
233
    return helper;
1✔
234
  }
235

236
  @VisibleForTesting
237
  public final Collection<ChildLbState> getChildLbStates() {
238
    return childLbStates;
1✔
239
  }
240

241
  /**
242
   * Filters out non-ready child load balancers (subchannels).
243
   */
244
  protected final List<ChildLbState> getReadyChildren() {
245
    List<ChildLbState> activeChildren = new ArrayList<>();
1✔
246
    for (ChildLbState child : getChildLbStates()) {
1✔
247
      if (child.getCurrentState() == READY) {
1✔
248
        activeChildren.add(child);
1✔
249
      }
250
    }
1✔
251
    return activeChildren;
1✔
252
  }
253

254
  /**
255
   * This represents the state of load balancer children.  Each endpoint (represented by an
256
   * EquivalentAddressGroup or EDS string) will have a separate ChildLbState which in turn will
257
   * have a single child LoadBalancer created from the provided factory.
258
   *
259
   * <p>A ChildLbStateHelper is the glue between ChildLbState and the helpers associated with the
260
   * petiole policy above and the PickFirstLoadBalancer's helper below.
261
   *
262
   * <p>If you wish to store additional state information related to each subchannel, then extend
263
   * this class.
264
   */
265
  public class ChildLbState {
266
    private final Object key;
267
    private final LoadBalancer lb;
268
    private ConnectivityState currentState;
269
    private SubchannelPicker currentPicker = new FixedResultPicker(PickResult.withNoResult());
1✔
270

271
    public ChildLbState(Object key, LoadBalancer.Factory policyFactory) {
1✔
272
      this.key = key;
1✔
273
      this.lb = policyFactory.newLoadBalancer(createChildHelper());
1✔
274
      this.currentState = CONNECTING;
1✔
275
    }
1✔
276

277
    protected ChildLbStateHelper createChildHelper() {
278
      return new ChildLbStateHelper();
1✔
279
    }
280

281
    /**
282
     * Override for unique behavior such as delayed shutdowns of subchannels.
283
     */
284
    protected void shutdown() {
285
      lb.shutdown();
1✔
286
      this.currentState = SHUTDOWN;
1✔
287
      logger.log(Level.FINE, "Child balancer {0} deleted", key);
1✔
288
    }
1✔
289

290
    @Override
291
    public String toString() {
292
      return "Address = " + key
×
293
          + ", state = " + currentState
294
          + ", picker type: " + currentPicker.getClass()
×
295
          + ", lb: " + lb;
296
    }
297

298
    public final Object getKey() {
299
      return key;
1✔
300
    }
301

302
    @VisibleForTesting
303
    public final LoadBalancer getLb() {
304
      return lb;
1✔
305
    }
306

307
    @VisibleForTesting
308
    public final SubchannelPicker getCurrentPicker() {
309
      return currentPicker;
1✔
310
    }
311

312
    public final ConnectivityState getCurrentState() {
313
      return currentState;
1✔
314
    }
315

316
    protected final void setCurrentState(ConnectivityState newState) {
317
      currentState = newState;
1✔
318
    }
1✔
319

320
    protected final void setCurrentPicker(SubchannelPicker newPicker) {
321
      currentPicker = newPicker;
1✔
322
    }
1✔
323

324
    /**
325
     * ChildLbStateHelper is the glue between ChildLbState and the helpers associated with the
326
     * petiole policy above and the PickFirstLoadBalancer's helper below.
327
     *
328
     * <p>The ChildLbState updates happen during updateBalancingState.  Otherwise, it is doing
329
     * simple forwarding.
330
     */
331
    protected class ChildLbStateHelper extends ForwardingLoadBalancerHelper {
1✔
332

333
      /**
334
       * Update current state and picker for this child and then use
335
       * {@link #updateOverallBalancingState()} for the parent LB.
336
       */
337
      @Override
338
      public void updateBalancingState(final ConnectivityState newState,
339
          final SubchannelPicker newPicker) {
340
        if (currentState == SHUTDOWN) {
1✔
341
          return;
×
342
        }
343

344
        currentState = newState;
1✔
345
        currentPicker = newPicker;
1✔
346
        // If we are already in the process of resolving addresses, the overall balancing state
347
        // will be updated at the end of it, and we don't need to trigger that update here.
348
        if (!resolvingAddresses) {
1✔
349
          updateOverallBalancingState();
1✔
350
        }
351
      }
1✔
352

353
      @Override
354
      protected Helper delegate() {
355
        return helper;
1✔
356
      }
357
    }
358
  }
359

360
  /**
361
   * Endpoint is an optimization to quickly lookup and compare EquivalentAddressGroup address sets.
362
   * It ignores the attributes. Is used as a key for ChildLbState for most load balancers
363
   * (ClusterManagerLB uses a String).
364
   */
365
  protected static class Endpoint {
366
    final Collection<SocketAddress> addrs;
367
    final int hashCode;
368

369
    public Endpoint(EquivalentAddressGroup eag) {
1✔
370
      checkNotNull(eag, "eag");
1✔
371

372
      if (eag.getAddresses().size() < 10) {
1✔
373
        addrs = eag.getAddresses();
1✔
374
      } else {
375
        // This is expected to be very unlikely in practice
376
        addrs = new HashSet<>(eag.getAddresses());
1✔
377
      }
378
      int sum = 0;
1✔
379
      for (SocketAddress address : eag.getAddresses()) {
1✔
380
        sum += address.hashCode();
1✔
381
      }
1✔
382
      hashCode = sum;
1✔
383
    }
1✔
384

385
    @Override
386
    public int hashCode() {
387
      return hashCode;
1✔
388
    }
389

390
    @Override
391
    public boolean equals(Object other) {
392
      if (this == other) {
1✔
393
        return true;
1✔
394
      }
395

396
      if (!(other instanceof Endpoint)) {
1✔
397
        return false;
1✔
398
      }
399
      Endpoint o = (Endpoint) other;
1✔
400
      if (o.hashCode != hashCode || o.addrs.size() != addrs.size()) {
1✔
401
        return false;
1✔
402
      }
403

404
      return o.addrs.containsAll(addrs);
1✔
405
    }
406

407
    @Override
408
    public String toString() {
409
      return addrs.toString();
1✔
410
    }
411
  }
412
}
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