• 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

95.88
/../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));
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) {
103
    return new ChildLbState(key, pickFirstLbProvider);
1✔
104
  }
105

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

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

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

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

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

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

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

157
    return resolvedAddresses.toBuilder()
1✔
158
        .setAddresses(Collections.singletonList(eagToUse))
1✔
159
        .setAttributes(Attributes.newBuilder().set(IS_PETIOLE_POLICY, true).build())
1✔
160
        .setLoadBalancingPolicyConfig(null)
1✔
161
        .build();
1✔
162
  }
163

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

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

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

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

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

206
    addMissingChildren(newChildren);
1✔
207

208
    updateChildrenWithResolvedAddresses(resolvedAddresses, newChildren);
1✔
209

210
    return new AcceptResolvedAddrRetVal(Status.OK, getRemovedChildren(newChildren.keySet()));
1✔
211
  }
212

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

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

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

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

256
  @Nullable
257
  protected static ConnectivityState aggregateState(
258
      @Nullable ConnectivityState overallState, ConnectivityState childState) {
259
    if (overallState == null) {
1✔
260
      return childState;
1✔
261
    }
262
    if (overallState == READY || childState == READY) {
1✔
263
      return READY;
1✔
264
    }
265
    if (overallState == CONNECTING || childState == CONNECTING) {
1✔
266
      return CONNECTING;
1✔
267
    }
268
    if (overallState == IDLE || childState == IDLE) {
1✔
269
      return IDLE;
×
270
    }
271
    return overallState;
1✔
272
  }
273

274
  protected final Helper getHelper() {
275
    return helper;
1✔
276
  }
277

278
  @VisibleForTesting
279
  public final Collection<ChildLbState> getChildLbStates() {
280
    return childLbStates.values();
1✔
281
  }
282

283
  @VisibleForTesting
284
  public final ChildLbState getChildLbState(Object key) {
285
    if (key == null) {
1✔
286
      return null;
×
287
    }
288
    if (key instanceof EquivalentAddressGroup) {
1✔
289
      key = new Endpoint((EquivalentAddressGroup) key);
1✔
290
    }
291
    return childLbStates.get(key);
1✔
292
  }
293

294
  @VisibleForTesting
295
  public final ChildLbState getChildLbStateEag(EquivalentAddressGroup eag) {
296
    return getChildLbState(new Endpoint(eag));
1✔
297
  }
298

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

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

327
    private final LoadBalancer lb;
328
    private ConnectivityState currentState;
329
    private SubchannelPicker currentPicker = new FixedResultPicker(PickResult.withNoResult());
1✔
330

331
    public ChildLbState(Object key, LoadBalancer.Factory policyFactory) {
1✔
332
      this.key = key;
1✔
333
      this.lb = policyFactory.newLoadBalancer(createChildHelper());
1✔
334
      this.currentState = CONNECTING;
1✔
335
    }
1✔
336

337
    protected ChildLbStateHelper createChildHelper() {
338
      return new ChildLbStateHelper();
1✔
339
    }
340

341
    /**
342
     * Override for unique behavior such as delayed shutdowns of subchannels.
343
     */
344
    protected void shutdown() {
345
      lb.shutdown();
1✔
346
      this.currentState = SHUTDOWN;
1✔
347
      logger.log(Level.FINE, "Child balancer {0} deleted", key);
1✔
348
    }
1✔
349

350
    @Override
351
    public String toString() {
352
      return "Address = " + key
×
353
          + ", state = " + currentState
354
          + ", picker type: " + currentPicker.getClass()
×
355
          + ", lb: " + lb;
356
    }
357

358
    public final Object getKey() {
359
      return key;
1✔
360
    }
361

362
    @VisibleForTesting
363
    public final LoadBalancer getLb() {
364
      return lb;
1✔
365
    }
366

367
    @VisibleForTesting
368
    public final SubchannelPicker getCurrentPicker() {
369
      return currentPicker;
1✔
370
    }
371

372
    public final ConnectivityState getCurrentState() {
373
      return currentState;
1✔
374
    }
375

376
    protected final void setCurrentState(ConnectivityState newState) {
377
      currentState = newState;
1✔
378
    }
1✔
379

380
    protected final void setCurrentPicker(SubchannelPicker newPicker) {
381
      currentPicker = newPicker;
1✔
382
    }
1✔
383

384
    public final EquivalentAddressGroup getEag() {
385
      if (resolvedAddresses == null || resolvedAddresses.getAddresses().isEmpty()) {
1✔
386
        return null;
×
387
      }
388
      return resolvedAddresses.getAddresses().get(0);
1✔
389
    }
390

391
    protected final void setResolvedAddresses(ResolvedAddresses newAddresses) {
392
      checkNotNull(newAddresses, "Missing address list for child");
1✔
393
      resolvedAddresses = newAddresses;
1✔
394
    }
1✔
395

396
    @VisibleForTesting
397
    public final ResolvedAddresses getResolvedAddresses() {
398
      return resolvedAddresses;
1✔
399
    }
400

401
    /**
402
     * ChildLbStateHelper is the glue between ChildLbState and the helpers associated with the
403
     * petiole policy above and the PickFirstLoadBalancer's helper below.
404
     *
405
     * <p>The ChildLbState updates happen during updateBalancingState.  Otherwise, it is doing
406
     * simple forwarding.
407
     */
408
    protected class ChildLbStateHelper extends ForwardingLoadBalancerHelper {
1✔
409

410
      /**
411
       * Update current state and picker for this child and then use
412
       * {@link #updateOverallBalancingState()} for the parent LB.
413
       */
414
      @Override
415
      public void updateBalancingState(final ConnectivityState newState,
416
          final SubchannelPicker newPicker) {
417
        if (!childLbStates.containsKey(key)) {
1✔
418
          return;
×
419
        }
420

421
        currentState = newState;
1✔
422
        currentPicker = newPicker;
1✔
423
        // If we are already in the process of resolving addresses, the overall balancing state
424
        // will be updated at the end of it, and we don't need to trigger that update here.
425
        if (!resolvingAddresses) {
1✔
426
          updateOverallBalancingState();
1✔
427
        }
428
      }
1✔
429

430
      @Override
431
      protected Helper delegate() {
432
        return helper;
1✔
433
      }
434
    }
435
  }
436

437
  /**
438
   * Endpoint is an optimization to quickly lookup and compare EquivalentAddressGroup address sets.
439
   * It ignores the attributes. Is used as a key for ChildLbState for most load balancers
440
   * (ClusterManagerLB uses a String).
441
   */
442
  protected static class Endpoint {
443
    final Collection<SocketAddress> addrs;
444
    final int hashCode;
445

446
    public Endpoint(EquivalentAddressGroup eag) {
1✔
447
      checkNotNull(eag, "eag");
1✔
448

449
      if (eag.getAddresses().size() < 10) {
1✔
450
        addrs = eag.getAddresses();
1✔
451
      } else {
452
        // This is expected to be very unlikely in practice
453
        addrs = new HashSet<>(eag.getAddresses());
1✔
454
      }
455
      int sum = 0;
1✔
456
      for (SocketAddress address : eag.getAddresses()) {
1✔
457
        sum += address.hashCode();
1✔
458
      }
1✔
459
      hashCode = sum;
1✔
460
    }
1✔
461

462
    @Override
463
    public int hashCode() {
464
      return hashCode;
1✔
465
    }
466

467
    @Override
468
    public boolean equals(Object other) {
469
      if (this == other) {
1✔
470
        return true;
1✔
471
      }
472

473
      if (!(other instanceof Endpoint)) {
1✔
474
        return false;
1✔
475
      }
476
      Endpoint o = (Endpoint) other;
1✔
477
      if (o.hashCode != hashCode || o.addrs.size() != addrs.size()) {
1✔
478
        return false;
1✔
479
      }
480

481
      return o.addrs.containsAll(addrs);
1✔
482
    }
483

484
    @Override
485
    public String toString() {
486
      return addrs.toString();
1✔
487
    }
488
  }
489

490
  protected static class AcceptResolvedAddrRetVal {
491
    public final Status status;
492
    public final List<ChildLbState> removedChildren;
493

494
    public AcceptResolvedAddrRetVal(Status status, List<ChildLbState> removedChildren) {
1✔
495
      this.status = status;
1✔
496
      this.removedChildren = removedChildren;
1✔
497
    }
1✔
498
  }
499
}
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