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

grpc / grpc-java / #19424

14 Aug 2024 04:29AM UTC coverage: 84.471% (-0.03%) from 84.504%
#19424

push

github

ejona86
util: Remove minor convenience functions from MultiChildLB

These were once needed to be overridden (e.g., by RoundRobinLB), but
now nothing overrides them and MultiChildLB doesn't even call one of
them.

33383 of 39520 relevant lines covered (84.47%)

0.84 hits per line

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

96.0
/../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.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.SHUTDOWN;
25
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
26

27
import com.google.common.annotations.VisibleForTesting;
28
import com.google.common.collect.ImmutableList;
29
import com.google.common.collect.ImmutableMap;
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.HashMap;
43
import java.util.HashSet;
44
import java.util.LinkedHashMap;
45
import java.util.List;
46
import java.util.Map;
47
import java.util.Set;
48
import java.util.logging.Level;
49
import java.util.logging.Logger;
50
import javax.annotation.Nullable;
51

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

59
  private static final Logger logger = Logger.getLogger(MultiChildLoadBalancer.class.getName());
1✔
60
  private final Map<Object, ChildLbState> childLbStates = new LinkedHashMap<>();
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
   */
85
  protected Map<Object, ChildLbState> createChildLbMap(ResolvedAddresses resolvedAddresses) {
86
    Map<Object, ChildLbState> childLbMap = new HashMap<>();
1✔
87
    List<EquivalentAddressGroup> addresses = resolvedAddresses.getAddresses();
1✔
88
    for (EquivalentAddressGroup eag : addresses) {
1✔
89
      Endpoint endpoint = new Endpoint(eag); // keys need to be just addresses
1✔
90
      ChildLbState existingChildLbState = childLbStates.get(endpoint);
1✔
91
      if (existingChildLbState != null) {
1✔
92
        childLbMap.put(endpoint, existingChildLbState);
1✔
93
      } else {
94
        childLbMap.put(endpoint, createChildLbState(endpoint, null, resolvedAddresses));
1✔
95
      }
96
    }
1✔
97
    return childLbMap;
1✔
98
  }
99

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

108
  /**
109
   *   Override to completely replace the default logic or to do additional activities.
110
   */
111
  @Override
112
  public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
113
    try {
114
      resolvingAddresses = true;
1✔
115

116
      // process resolvedAddresses to update children
117
      AcceptResolvedAddrRetVal acceptRetVal = acceptResolvedAddressesInternal(resolvedAddresses);
1✔
118
      if (!acceptRetVal.status.isOk()) {
1✔
119
        return acceptRetVal.status;
1✔
120
      }
121

122
      // Update the picker and our connectivity state
123
      updateOverallBalancingState();
1✔
124

125
      // shutdown removed children
126
      shutdownRemoved(acceptRetVal.removedChildren);
1✔
127
      return acceptRetVal.status;
1✔
128
    } finally {
129
      resolvingAddresses = false;
1✔
130
    }
131
  }
132

133
  /**
134
   * Override this if your keys are not of type Endpoint.
135
   * @param key Key to identify the ChildLbState
136
   * @param resolvedAddresses list of addresses which include attributes
137
   * @param childConfig a load balancing policy config. This field is optional.
138
   * @return a fully loaded ResolvedAddresses object for the specified key
139
   */
140
  protected ResolvedAddresses getChildAddresses(Object key, ResolvedAddresses resolvedAddresses,
141
      Object childConfig) {
142
    Endpoint endpointKey;
143
    if (key instanceof EquivalentAddressGroup) {
1✔
144
      endpointKey = new Endpoint((EquivalentAddressGroup) key);
×
145
    } else {
146
      checkArgument(key instanceof Endpoint, "key is wrong type");
1✔
147
      endpointKey = (Endpoint) key;
1✔
148
    }
149

150
    // Retrieve the non-stripped version
151
    EquivalentAddressGroup eagToUse = null;
1✔
152
    for (EquivalentAddressGroup currEag : resolvedAddresses.getAddresses()) {
1✔
153
      if (endpointKey.equals(new Endpoint(currEag))) {
1✔
154
        eagToUse = currEag;
1✔
155
        break;
1✔
156
      }
157
    }
1✔
158

159
    checkNotNull(eagToUse, key + " no longer present in load balancer children");
1✔
160

161
    return resolvedAddresses.toBuilder()
1✔
162
        .setAddresses(Collections.singletonList(eagToUse))
1✔
163
        .setAttributes(Attributes.newBuilder().set(IS_PETIOLE_POLICY, true).build())
1✔
164
        .setLoadBalancingPolicyConfig(childConfig)
1✔
165
        .build();
1✔
166
  }
167

168
  /**
169
   * Handle the name resolution error.
170
   *
171
   * <p/>Override if you need special handling.
172
   */
173
  @Override
174
  public void handleNameResolutionError(Status error) {
175
    if (currentConnectivityState != READY)  {
1✔
176
      helper.updateBalancingState(
1✔
177
          TRANSIENT_FAILURE, new FixedResultPicker(PickResult.withError(error)));
1✔
178
    }
179
  }
1✔
180

181
  @Override
182
  public void shutdown() {
183
    logger.log(Level.FINE, "Shutdown");
1✔
184
    for (ChildLbState state : childLbStates.values()) {
1✔
185
      state.shutdown();
1✔
186
    }
1✔
187
    childLbStates.clear();
1✔
188
  }
1✔
189

190
  /**
191
   *   This does the work to update the child map and calculate which children have been removed.
192
   *   You must call {@link #updateOverallBalancingState} to update the picker
193
   *   and call {@link #shutdownRemoved(List)} to shutdown the endpoints that have been removed.
194
    */
195
  protected final AcceptResolvedAddrRetVal acceptResolvedAddressesInternal(
196
      ResolvedAddresses resolvedAddresses) {
197
    logger.log(Level.FINE, "Received resolution result: {0}", resolvedAddresses);
1✔
198

199
    // Subclass handles any special manipulation to create appropriate types of keyed ChildLbStates
200
    Map<Object, ChildLbState> newChildren = createChildLbMap(resolvedAddresses);
1✔
201

202
    // Handle error case
203
    if (newChildren.isEmpty()) {
1✔
204
      Status unavailableStatus = Status.UNAVAILABLE.withDescription(
1✔
205
          "NameResolver returned no usable address. " + resolvedAddresses);
206
      handleNameResolutionError(unavailableStatus);
1✔
207
      return new AcceptResolvedAddrRetVal(unavailableStatus, null);
1✔
208
    }
209

210
    addMissingChildren(newChildren);
1✔
211

212
    updateChildrenWithResolvedAddresses(resolvedAddresses, newChildren);
1✔
213

214
    return new AcceptResolvedAddrRetVal(Status.OK, getRemovedChildren(newChildren.keySet()));
1✔
215
  }
216

217
  private void addMissingChildren(Map<Object, ChildLbState> newChildren) {
218
    // Do adds and identify reused children
219
    for (Map.Entry<Object, ChildLbState> entry : newChildren.entrySet()) {
1✔
220
      final Object key = entry.getKey();
1✔
221
      if (!childLbStates.containsKey(key)) {
1✔
222
        childLbStates.put(key, entry.getValue());
1✔
223
      }
224
    }
1✔
225
  }
1✔
226

227
  private void updateChildrenWithResolvedAddresses(ResolvedAddresses resolvedAddresses,
228
                                                     Map<Object, ChildLbState> newChildren) {
229
    for (Map.Entry<Object, ChildLbState> entry : newChildren.entrySet()) {
1✔
230
      Object childConfig = entry.getValue().getConfig();
1✔
231
      ChildLbState childLbState = childLbStates.get(entry.getKey());
1✔
232
      ResolvedAddresses childAddresses =
1✔
233
          getChildAddresses(entry.getKey(), resolvedAddresses, childConfig);
1✔
234
      childLbState.setResolvedAddresses(childAddresses); // update child
1✔
235
      childLbState.lb.handleResolvedAddresses(childAddresses); // update child LB
1✔
236
    }
1✔
237
  }
1✔
238

239
  /**
240
   * Identifies which children have been removed (are not part of the newChildKeys).
241
   */
242
  private List<ChildLbState> getRemovedChildren(Set<Object> newChildKeys) {
243
    List<ChildLbState> removedChildren = new ArrayList<>();
1✔
244
    // Do removals
245
    for (Object key : ImmutableList.copyOf(childLbStates.keySet())) {
1✔
246
      if (!newChildKeys.contains(key)) {
1✔
247
        ChildLbState childLbState = childLbStates.remove(key);
1✔
248
        removedChildren.add(childLbState);
1✔
249
      }
250
    }
1✔
251
    return removedChildren;
1✔
252
  }
253

254
  protected final void shutdownRemoved(List<ChildLbState> removedChildren) {
255
    // Do shutdowns after updating picker to reduce the chance of failing an RPC by picking a
256
    // subchannel that has been shutdown.
257
    for (ChildLbState childLbState : removedChildren) {
1✔
258
      childLbState.shutdown();
1✔
259
    }
1✔
260
  }
1✔
261

262
  @Nullable
263
  protected static ConnectivityState aggregateState(
264
      @Nullable ConnectivityState overallState, ConnectivityState childState) {
265
    if (overallState == null) {
1✔
266
      return childState;
1✔
267
    }
268
    if (overallState == READY || childState == READY) {
1✔
269
      return READY;
1✔
270
    }
271
    if (overallState == CONNECTING || childState == CONNECTING) {
1✔
272
      return CONNECTING;
1✔
273
    }
274
    if (overallState == IDLE || childState == IDLE) {
1✔
275
      return IDLE;
×
276
    }
277
    return overallState;
1✔
278
  }
279

280
  protected final Helper getHelper() {
281
    return helper;
1✔
282
  }
283

284
  @VisibleForTesting
285
  public final ImmutableMap<Object, ChildLbState> getImmutableChildMap() {
286
    return ImmutableMap.copyOf(childLbStates);
1✔
287
  }
288

289
  @VisibleForTesting
290
  public final Collection<ChildLbState> getChildLbStates() {
291
    return childLbStates.values();
1✔
292
  }
293

294
  @VisibleForTesting
295
  public final ChildLbState getChildLbState(Object key) {
296
    if (key == null) {
1✔
297
      return null;
×
298
    }
299
    if (key instanceof EquivalentAddressGroup) {
1✔
300
      key = new Endpoint((EquivalentAddressGroup) key);
1✔
301
    }
302
    return childLbStates.get(key);
1✔
303
  }
304

305
  @VisibleForTesting
306
  public final ChildLbState getChildLbStateEag(EquivalentAddressGroup eag) {
307
    return getChildLbState(new Endpoint(eag));
1✔
308
  }
309

310
  /**
311
   * Filters out non-ready child load balancers (subchannels).
312
   */
313
  protected final List<ChildLbState> getReadyChildren() {
314
    List<ChildLbState> activeChildren = new ArrayList<>();
1✔
315
    for (ChildLbState child : getChildLbStates()) {
1✔
316
      if (child.getCurrentState() == READY) {
1✔
317
        activeChildren.add(child);
1✔
318
      }
319
    }
1✔
320
    return activeChildren;
1✔
321
  }
322

323
  /**
324
   * This represents the state of load balancer children.  Each endpoint (represented by an
325
   * EquivalentAddressGroup or EDS string) will have a separate ChildLbState which in turn will
326
   * have a single child LoadBalancer created from the provided factory.
327
   *
328
   * <p>A ChildLbStateHelper is the glue between ChildLbState and the helpers associated with the
329
   * petiole policy above and the PickFirstLoadBalancer's helper below.
330
   *
331
   * <p>If you wish to store additional state information related to each subchannel, then extend
332
   * this class.
333
   */
334
  public class ChildLbState {
335
    private final Object key;
336
    private ResolvedAddresses resolvedAddresses;
337
    private final Object config;
338

339
    private final LoadBalancer lb;
340
    private ConnectivityState currentState;
341
    private SubchannelPicker currentPicker = new FixedResultPicker(PickResult.withNoResult());
1✔
342

343
    public ChildLbState(Object key, LoadBalancer.Factory policyFactory, Object childConfig) {
1✔
344
      this.key = key;
1✔
345
      this.config = childConfig;
1✔
346
      this.lb = policyFactory.newLoadBalancer(createChildHelper());
1✔
347
      this.currentState = CONNECTING;
1✔
348
    }
1✔
349

350
    protected ChildLbStateHelper createChildHelper() {
351
      return new ChildLbStateHelper();
1✔
352
    }
353

354
    /**
355
     * Override for unique behavior such as delayed shutdowns of subchannels.
356
     */
357
    protected void shutdown() {
358
      lb.shutdown();
1✔
359
      this.currentState = SHUTDOWN;
1✔
360
      logger.log(Level.FINE, "Child balancer {0} deleted", key);
1✔
361
    }
1✔
362

363
    @Override
364
    public String toString() {
365
      return "Address = " + key
×
366
          + ", state = " + currentState
367
          + ", picker type: " + currentPicker.getClass()
×
368
          + ", lb: " + lb;
369
    }
370

371
    public final Object getKey() {
372
      return key;
1✔
373
    }
374

375
    @VisibleForTesting
376
    public final LoadBalancer getLb() {
377
      return lb;
1✔
378
    }
379

380
    @VisibleForTesting
381
    public final SubchannelPicker getCurrentPicker() {
382
      return currentPicker;
1✔
383
    }
384

385
    public final ConnectivityState getCurrentState() {
386
      return currentState;
1✔
387
    }
388

389
    protected final void setCurrentState(ConnectivityState newState) {
390
      currentState = newState;
1✔
391
    }
1✔
392

393
    protected final void setCurrentPicker(SubchannelPicker newPicker) {
394
      currentPicker = newPicker;
1✔
395
    }
1✔
396

397
    public final EquivalentAddressGroup getEag() {
398
      if (resolvedAddresses == null || resolvedAddresses.getAddresses().isEmpty()) {
1✔
399
        return null;
×
400
      }
401
      return resolvedAddresses.getAddresses().get(0);
1✔
402
    }
403

404
    protected final void setResolvedAddresses(ResolvedAddresses newAddresses) {
405
      checkNotNull(newAddresses, "Missing address list for child");
1✔
406
      resolvedAddresses = newAddresses;
1✔
407
    }
1✔
408

409
    private Object getConfig() {
410
      return config;
1✔
411
    }
412

413
    @VisibleForTesting
414
    public final ResolvedAddresses getResolvedAddresses() {
415
      return resolvedAddresses;
1✔
416
    }
417

418
    /**
419
     * ChildLbStateHelper is the glue between ChildLbState and the helpers associated with the
420
     * petiole policy above and the PickFirstLoadBalancer's helper below.
421
     *
422
     * <p>The ChildLbState updates happen during updateBalancingState.  Otherwise, it is doing
423
     * simple forwarding.
424
     */
425
    protected class ChildLbStateHelper extends ForwardingLoadBalancerHelper {
1✔
426

427
      /**
428
       * Update current state and picker for this child and then use
429
       * {@link #updateOverallBalancingState()} for the parent LB.
430
       */
431
      @Override
432
      public void updateBalancingState(final ConnectivityState newState,
433
          final SubchannelPicker newPicker) {
434
        if (!childLbStates.containsKey(key)) {
1✔
435
          return;
×
436
        }
437

438
        currentState = newState;
1✔
439
        currentPicker = newPicker;
1✔
440
        // If we are already in the process of resolving addresses, the overall balancing state
441
        // will be updated at the end of it, and we don't need to trigger that update here.
442
        if (!resolvingAddresses) {
1✔
443
          updateOverallBalancingState();
1✔
444
        }
445
      }
1✔
446

447
      @Override
448
      protected Helper delegate() {
449
        return helper;
1✔
450
      }
451
    }
452
  }
453

454
  /**
455
   * Endpoint is an optimization to quickly lookup and compare EquivalentAddressGroup address sets.
456
   * It ignores the attributes. Is used as a key for ChildLbState for most load balancers
457
   * (ClusterManagerLB uses a String).
458
   */
459
  protected static class Endpoint {
460
    final Collection<SocketAddress> addrs;
461
    final int hashCode;
462

463
    public Endpoint(EquivalentAddressGroup eag) {
1✔
464
      checkNotNull(eag, "eag");
1✔
465

466
      if (eag.getAddresses().size() < 10) {
1✔
467
        addrs = eag.getAddresses();
1✔
468
      } else {
469
        // This is expected to be very unlikely in practice
470
        addrs = new HashSet<>(eag.getAddresses());
1✔
471
      }
472
      int sum = 0;
1✔
473
      for (SocketAddress address : eag.getAddresses()) {
1✔
474
        sum += address.hashCode();
1✔
475
      }
1✔
476
      hashCode = sum;
1✔
477
    }
1✔
478

479
    @Override
480
    public int hashCode() {
481
      return hashCode;
1✔
482
    }
483

484
    @Override
485
    public boolean equals(Object other) {
486
      if (this == other) {
1✔
487
        return true;
1✔
488
      }
489

490
      if (!(other instanceof Endpoint)) {
1✔
491
        return false;
1✔
492
      }
493
      Endpoint o = (Endpoint) other;
1✔
494
      if (o.hashCode != hashCode || o.addrs.size() != addrs.size()) {
1✔
495
        return false;
1✔
496
      }
497

498
      return o.addrs.containsAll(addrs);
1✔
499
    }
500

501
    @Override
502
    public String toString() {
503
      return addrs.toString();
1✔
504
    }
505
  }
506

507
  protected static class AcceptResolvedAddrRetVal {
508
    public final Status status;
509
    public final List<ChildLbState> removedChildren;
510

511
    public AcceptResolvedAddrRetVal(Status status, List<ChildLbState> removedChildren) {
1✔
512
      this.status = status;
1✔
513
      this.removedChildren = removedChildren;
1✔
514
    }
1✔
515
  }
516
}
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