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

grpc / grpc-java / #19429

17 Aug 2024 03:55PM UTC coverage: 84.501% (+0.02%) from 84.482%
#19429

push

github

ejona86
util: Remove MultiChildLB.getImmutableChildMap()

No usages actually needed a map nor a copy.

33411 of 39539 relevant lines covered (84.5%)

0.85 hits per line

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

95.98
/../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 io.grpc.Attributes;
30
import io.grpc.ConnectivityState;
31
import io.grpc.EquivalentAddressGroup;
32
import io.grpc.Internal;
33
import io.grpc.LoadBalancer;
34
import io.grpc.LoadBalancerProvider;
35
import io.grpc.Status;
36
import io.grpc.internal.PickFirstLoadBalancerProvider;
37
import java.net.SocketAddress;
38
import java.util.ArrayList;
39
import java.util.Collection;
40
import java.util.Collections;
41
import java.util.HashMap;
42
import java.util.HashSet;
43
import java.util.LinkedHashMap;
44
import java.util.List;
45
import java.util.Map;
46
import java.util.Set;
47
import java.util.logging.Level;
48
import java.util.logging.Logger;
49
import javax.annotation.Nullable;
50

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

58
  private static final Logger logger = Logger.getLogger(MultiChildLoadBalancer.class.getName());
1✔
59
  private final Map<Object, ChildLbState> childLbStates = new LinkedHashMap<>();
1✔
60
  private final Helper helper;
61
  // Set to true if currently in the process of handling resolved addresses.
62
  protected boolean resolvingAddresses;
63

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

66
  protected ConnectivityState currentConnectivityState;
67

68

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

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

81
  /**
82
   * Override to utilize parsing of the policy configuration or alternative helper/lb generation.
83
   */
84
  protected Map<Object, ChildLbState> createChildLbMap(ResolvedAddresses resolvedAddresses) {
85
    Map<Object, ChildLbState> childLbMap = new HashMap<>();
1✔
86
    List<EquivalentAddressGroup> addresses = resolvedAddresses.getAddresses();
1✔
87
    for (EquivalentAddressGroup eag : addresses) {
1✔
88
      Endpoint endpoint = new Endpoint(eag); // keys need to be just addresses
1✔
89
      ChildLbState existingChildLbState = childLbStates.get(endpoint);
1✔
90
      if (existingChildLbState != null) {
1✔
91
        childLbMap.put(endpoint, existingChildLbState);
1✔
92
      } else {
93
        childLbMap.put(endpoint, createChildLbState(endpoint, null, resolvedAddresses));
1✔
94
      }
95
    }
1✔
96
    return childLbMap;
1✔
97
  }
98

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

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

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

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

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

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

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

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

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

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

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

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

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

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

209
    addMissingChildren(newChildren);
1✔
210

211
    updateChildrenWithResolvedAddresses(resolvedAddresses, newChildren);
1✔
212

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

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

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

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

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

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

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

283
  @VisibleForTesting
284
  public final Collection<ChildLbState> getChildLbStates() {
285
    return childLbStates.values();
1✔
286
  }
287

288
  @VisibleForTesting
289
  public final ChildLbState getChildLbState(Object key) {
290
    if (key == null) {
1✔
291
      return null;
×
292
    }
293
    if (key instanceof EquivalentAddressGroup) {
1✔
294
      key = new Endpoint((EquivalentAddressGroup) key);
1✔
295
    }
296
    return childLbStates.get(key);
1✔
297
  }
298

299
  @VisibleForTesting
300
  public final ChildLbState getChildLbStateEag(EquivalentAddressGroup eag) {
301
    return getChildLbState(new Endpoint(eag));
1✔
302
  }
303

304
  /**
305
   * Filters out non-ready child load balancers (subchannels).
306
   */
307
  protected final List<ChildLbState> getReadyChildren() {
308
    List<ChildLbState> activeChildren = new ArrayList<>();
1✔
309
    for (ChildLbState child : getChildLbStates()) {
1✔
310
      if (child.getCurrentState() == READY) {
1✔
311
        activeChildren.add(child);
1✔
312
      }
313
    }
1✔
314
    return activeChildren;
1✔
315
  }
316

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

333
    private final LoadBalancer lb;
334
    private ConnectivityState currentState;
335
    private SubchannelPicker currentPicker = new FixedResultPicker(PickResult.withNoResult());
1✔
336

337
    public ChildLbState(Object key, LoadBalancer.Factory policyFactory, Object childConfig) {
1✔
338
      this.key = key;
1✔
339
      this.config = childConfig;
1✔
340
      this.lb = policyFactory.newLoadBalancer(createChildHelper());
1✔
341
      this.currentState = CONNECTING;
1✔
342
    }
1✔
343

344
    protected ChildLbStateHelper createChildHelper() {
345
      return new ChildLbStateHelper();
1✔
346
    }
347

348
    /**
349
     * Override for unique behavior such as delayed shutdowns of subchannels.
350
     */
351
    protected void shutdown() {
352
      lb.shutdown();
1✔
353
      this.currentState = SHUTDOWN;
1✔
354
      logger.log(Level.FINE, "Child balancer {0} deleted", key);
1✔
355
    }
1✔
356

357
    @Override
358
    public String toString() {
359
      return "Address = " + key
×
360
          + ", state = " + currentState
361
          + ", picker type: " + currentPicker.getClass()
×
362
          + ", lb: " + lb;
363
    }
364

365
    public final Object getKey() {
366
      return key;
1✔
367
    }
368

369
    @VisibleForTesting
370
    public final LoadBalancer getLb() {
371
      return lb;
1✔
372
    }
373

374
    @VisibleForTesting
375
    public final SubchannelPicker getCurrentPicker() {
376
      return currentPicker;
1✔
377
    }
378

379
    public final ConnectivityState getCurrentState() {
380
      return currentState;
1✔
381
    }
382

383
    protected final void setCurrentState(ConnectivityState newState) {
384
      currentState = newState;
1✔
385
    }
1✔
386

387
    protected final void setCurrentPicker(SubchannelPicker newPicker) {
388
      currentPicker = newPicker;
1✔
389
    }
1✔
390

391
    public final EquivalentAddressGroup getEag() {
392
      if (resolvedAddresses == null || resolvedAddresses.getAddresses().isEmpty()) {
1✔
393
        return null;
×
394
      }
395
      return resolvedAddresses.getAddresses().get(0);
1✔
396
    }
397

398
    protected final void setResolvedAddresses(ResolvedAddresses newAddresses) {
399
      checkNotNull(newAddresses, "Missing address list for child");
1✔
400
      resolvedAddresses = newAddresses;
1✔
401
    }
1✔
402

403
    private Object getConfig() {
404
      return config;
1✔
405
    }
406

407
    @VisibleForTesting
408
    public final ResolvedAddresses getResolvedAddresses() {
409
      return resolvedAddresses;
1✔
410
    }
411

412
    /**
413
     * ChildLbStateHelper is the glue between ChildLbState and the helpers associated with the
414
     * petiole policy above and the PickFirstLoadBalancer's helper below.
415
     *
416
     * <p>The ChildLbState updates happen during updateBalancingState.  Otherwise, it is doing
417
     * simple forwarding.
418
     */
419
    protected class ChildLbStateHelper extends ForwardingLoadBalancerHelper {
1✔
420

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

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

441
      @Override
442
      protected Helper delegate() {
443
        return helper;
1✔
444
      }
445
    }
446
  }
447

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

457
    public Endpoint(EquivalentAddressGroup eag) {
1✔
458
      checkNotNull(eag, "eag");
1✔
459

460
      if (eag.getAddresses().size() < 10) {
1✔
461
        addrs = eag.getAddresses();
1✔
462
      } else {
463
        // This is expected to be very unlikely in practice
464
        addrs = new HashSet<>(eag.getAddresses());
1✔
465
      }
466
      int sum = 0;
1✔
467
      for (SocketAddress address : eag.getAddresses()) {
1✔
468
        sum += address.hashCode();
1✔
469
      }
1✔
470
      hashCode = sum;
1✔
471
    }
1✔
472

473
    @Override
474
    public int hashCode() {
475
      return hashCode;
1✔
476
    }
477

478
    @Override
479
    public boolean equals(Object other) {
480
      if (this == other) {
1✔
481
        return true;
1✔
482
      }
483

484
      if (!(other instanceof Endpoint)) {
1✔
485
        return false;
1✔
486
      }
487
      Endpoint o = (Endpoint) other;
1✔
488
      if (o.hashCode != hashCode || o.addrs.size() != addrs.size()) {
1✔
489
        return false;
1✔
490
      }
491

492
      return o.addrs.containsAll(addrs);
1✔
493
    }
494

495
    @Override
496
    public String toString() {
497
      return addrs.toString();
1✔
498
    }
499
  }
500

501
  protected static class AcceptResolvedAddrRetVal {
502
    public final Status status;
503
    public final List<ChildLbState> removedChildren;
504

505
    public AcceptResolvedAddrRetVal(Status status, List<ChildLbState> removedChildren) {
1✔
506
      this.status = status;
1✔
507
      this.removedChildren = removedChildren;
1✔
508
    }
1✔
509
  }
510
}
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