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

grpc / grpc-java / #19971

01 Sep 2025 07:40AM UTC coverage: 88.568% (+0.02%) from 88.547%
#19971

push

github

web-flow
allow java21 in jre matrix (#12281)

34692 of 39170 relevant lines covered (88.57%)

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
    @SuppressWarnings("this-escape")
272
    // TODO(okshiva): Fix 'this-escape' from the constructor before making the API public.
273
    public ChildLbState(Object key, LoadBalancer.Factory policyFactory) {
1✔
274
      this.key = key;
1✔
275
      this.lb = policyFactory.newLoadBalancer(createChildHelper());
1✔
276
      this.currentState = CONNECTING;
1✔
277
    }
1✔
278

279
    protected ChildLbStateHelper createChildHelper() {
280
      return new ChildLbStateHelper();
1✔
281
    }
282

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

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

300
    public final Object getKey() {
301
      return key;
1✔
302
    }
303

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

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

314
    public final ConnectivityState getCurrentState() {
315
      return currentState;
1✔
316
    }
317

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

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

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

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

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

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

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

371
    public Endpoint(EquivalentAddressGroup eag) {
1✔
372
      checkNotNull(eag, "eag");
1✔
373

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

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

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

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

406
      return o.addrs.containsAll(addrs);
1✔
407
    }
408

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