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

grpc / grpc-java / #18895

13 Nov 2023 10:58PM UTC coverage: 88.221% (+0.009%) from 88.212%
#18895

push

github

web-flow
xds: fix ring hash childLB acceptResolvedAddress not in syncContext (#10664)

30341 of 34392 relevant lines covered (88.22%)

0.88 hits per line

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

89.02
/../xds/src/main/java/io/grpc/xds/RingHashLoadBalancer.java
1
/*
2
 * Copyright 2021 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.xds;
18

19
import static com.google.common.base.Preconditions.checkArgument;
20
import static com.google.common.base.Preconditions.checkNotNull;
21
import static com.google.common.base.Preconditions.checkState;
22
import static io.grpc.ConnectivityState.CONNECTING;
23
import static io.grpc.ConnectivityState.IDLE;
24
import static io.grpc.ConnectivityState.READY;
25
import static io.grpc.ConnectivityState.SHUTDOWN;
26
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
27

28
import com.google.common.base.MoreObjects;
29
import com.google.common.collect.HashMultiset;
30
import com.google.common.collect.ImmutableMap;
31
import com.google.common.collect.Multiset;
32
import com.google.common.primitives.UnsignedInteger;
33
import io.grpc.Attributes;
34
import io.grpc.ConnectivityState;
35
import io.grpc.EquivalentAddressGroup;
36
import io.grpc.InternalLogId;
37
import io.grpc.LoadBalancer;
38
import io.grpc.LoadBalancerProvider;
39
import io.grpc.Status;
40
import io.grpc.SynchronizationContext;
41
import io.grpc.util.GracefulSwitchLoadBalancer;
42
import io.grpc.util.MultiChildLoadBalancer;
43
import io.grpc.xds.XdsLogger.XdsLogLevel;
44
import java.net.SocketAddress;
45
import java.util.ArrayList;
46
import java.util.Collection;
47
import java.util.Collections;
48
import java.util.HashMap;
49
import java.util.HashSet;
50
import java.util.List;
51
import java.util.Map;
52
import java.util.Set;
53
import java.util.stream.Collectors;
54
import javax.annotation.Nullable;
55

56
/**
57
 * A {@link LoadBalancer} that provides consistent hashing based load balancing to upstream hosts.
58
 * It implements the "Ketama" hashing that maps hosts onto a circle (the "ring") by hashing its
59
 * addresses. Each request is routed to a host by hashing some property of the request and finding
60
 * the nearest corresponding host clockwise around the ring. Each host is placed on the ring some
61
 * number of times proportional to its weight. With the ring partitioned appropriately, the
62
 * addition or removal of one host from a set of N hosts will affect only 1/N requests.
63
 */
64
final class RingHashLoadBalancer extends MultiChildLoadBalancer {
65
  private static final Status RPC_HASH_NOT_FOUND =
1✔
66
      Status.INTERNAL.withDescription("RPC hash not found. Probably a bug because xds resolver"
1✔
67
          + " config selector always generates a hash.");
68
  private static final XxHash64 hashFunc = XxHash64.INSTANCE;
1✔
69

70
  private final XdsLogger logger;
71
  private final SynchronizationContext syncContext;
72
  private List<RingEntry> ring;
73

74
  RingHashLoadBalancer(Helper helper) {
75
    super(helper);
1✔
76
    syncContext = checkNotNull(helper.getSynchronizationContext(), "syncContext");
1✔
77
    logger = XdsLogger.withLogId(InternalLogId.allocate("ring_hash_lb", helper.getAuthority()));
1✔
78
    logger.log(XdsLogLevel.INFO, "Created");
1✔
79
  }
1✔
80

81
  @Override
82
  public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
83
    logger.log(XdsLogLevel.DEBUG, "Received resolution result: {0}", resolvedAddresses);
1✔
84
    List<EquivalentAddressGroup> addrList = resolvedAddresses.getAddresses();
1✔
85
    Status addressValidityStatus = validateAddrList(addrList);
1✔
86
    if (!addressValidityStatus.isOk()) {
1✔
87
      return addressValidityStatus;
1✔
88
    }
89

90
    AcceptResolvedAddressRetVal acceptRetVal;
91
    try {
92
      resolvingAddresses = true;
1✔
93
      // Update the child list by creating-adding, updating addresses, and removing
94
      acceptRetVal = super.acceptResolvedAddressesInternal(resolvedAddresses);
1✔
95
      if (!acceptRetVal.status.isOk()) {
1✔
96
        addressValidityStatus = Status.UNAVAILABLE.withDescription(
×
97
            "Ring hash lb error: EDS resolution was successful, but was not accepted by base class"
98
                + " (" + acceptRetVal.status + ")");
99
        handleNameResolutionError(addressValidityStatus);
×
100
        return addressValidityStatus;
×
101
      }
102

103
      // Now do the ringhash specific logic with weights and building the ring
104
      RingHashConfig config = (RingHashConfig) resolvedAddresses.getLoadBalancingPolicyConfig();
1✔
105
      if (config == null) {
1✔
106
        throw new IllegalArgumentException("Missing RingHash configuration");
×
107
      }
108
      Map<EquivalentAddressGroup, Long> serverWeights = new HashMap<>();
1✔
109
      long totalWeight = 0L;
1✔
110
      for (EquivalentAddressGroup eag : addrList) {
1✔
111
        Long weight = eag.getAttributes().get(InternalXdsAttributes.ATTR_SERVER_WEIGHT);
1✔
112
        // Support two ways of server weighing: either multiple instances of the same address
113
        // or each address contains a per-address weight attribute. If a weight is not provided,
114
        // each occurrence of the address will be counted a weight value of one.
115
        if (weight == null) {
1✔
116
          weight = 1L;
×
117
        }
118
        totalWeight += weight;
1✔
119
        EquivalentAddressGroup addrKey = stripAttrs(eag);
1✔
120
        if (serverWeights.containsKey(addrKey)) {
1✔
121
          serverWeights.put(addrKey, serverWeights.get(addrKey) + weight);
×
122
        } else {
123
          serverWeights.put(addrKey, weight);
1✔
124
        }
125
      }
1✔
126
      // Calculate scale
127
      long minWeight = Collections.min(serverWeights.values());
1✔
128
      double normalizedMinWeight = (double) minWeight / totalWeight;
1✔
129
      // Scale up the number of hashes per host such that the least-weighted host gets a whole
130
      // number of hashes on the the ring. Other hosts might not end up with whole numbers, and
131
      // that's fine (the ring-building algorithm can handle this). This preserves the original
132
      // implementation's behavior: when weights aren't provided, all hosts should get an equal
133
      // number of hashes. In the case where this number exceeds the max_ring_size, it's scaled
134
      // back down to fit.
135
      double scale = Math.min(
1✔
136
          Math.ceil(normalizedMinWeight * config.minRingSize) / normalizedMinWeight,
1✔
137
          (double) config.maxRingSize);
138

139
      // Build the ring
140
      ring = buildRing(serverWeights, totalWeight, scale);
1✔
141

142
      // Must update channel picker before return so that new RPCs will not be routed to deleted
143
      // clusters and resolver can remove them in service config.
144
      updateOverallBalancingState();
1✔
145

146
      shutdownRemoved(acceptRetVal.removedChildren);
1✔
147
    } finally {
148
      this.resolvingAddresses = false;
1✔
149
    }
150

151
    return Status.OK;
1✔
152
  }
153

154
  /**
155
   * Updates the overall balancing state by aggregating the connectivity states of all subchannels.
156
   *
157
   * <p>Aggregation rules (in order of dominance):
158
   * <ol>
159
   *   <li>If there is at least one subchannel in READY state, overall state is READY</li>
160
   *   <li>If there are <em>2 or more</em> subchannels in TRANSIENT_FAILURE, overall state is
161
   *   TRANSIENT_FAILURE (to allow timely failover to another policy)</li>
162
   *   <li>If there is at least one subchannel in CONNECTING state, overall state is
163
   *   CONNECTING</li>
164
   *   <li> If there is one subchannel in TRANSIENT_FAILURE state and there is
165
   *    more than one subchannel, report CONNECTING </li>
166
   *   <li>If there is at least one subchannel in IDLE state, overall state is IDLE</li>
167
   *   <li>Otherwise, overall state is TRANSIENT_FAILURE</li>
168
   * </ol>
169
   */
170
  @Override
171
  protected void updateOverallBalancingState() {
172
    checkState(!getChildLbStates().isEmpty(), "no subchannel has been created");
1✔
173
    if (this.currentConnectivityState == SHUTDOWN) {
1✔
174
      // Ignore changes that happen after shutdown is called
175
      logger.log(XdsLogLevel.DEBUG, "UpdateOverallBalancingState called after shutdown");
×
176
      return;
×
177
    }
178

179
    // Calculate the current overall state to report
180
    int numIdle = 0;
1✔
181
    int numReady = 0;
1✔
182
    int numConnecting = 0;
1✔
183
    int numTF = 0;
1✔
184

185
    forloop:
186
    for (ChildLbState childLbState : getChildLbStates()) {
1✔
187
      ConnectivityState state = childLbState.getCurrentState();
1✔
188
      switch (state) {
1✔
189
        case READY:
190
          numReady++;
1✔
191
          break forloop;
1✔
192
        case CONNECTING:
193
          numConnecting++;
1✔
194
          break;
1✔
195
        case IDLE:
196
          numIdle++;
1✔
197
          break;
1✔
198
        case TRANSIENT_FAILURE:
199
          numTF++;
1✔
200
          break;
1✔
201
        default:
202
          // ignore it
203
      }
204
    }
1✔
205

206
    ConnectivityState overallState;
207
    if (numReady > 0) {
1✔
208
      overallState = READY;
1✔
209
    } else if (numTF >= 2) {
1✔
210
      overallState = TRANSIENT_FAILURE;
1✔
211
    } else if (numConnecting > 0) {
1✔
212
      overallState = CONNECTING;
1✔
213
    } else if (numTF == 1 && getChildLbStates().size() > 1) {
1✔
214
      overallState = CONNECTING;
1✔
215
    } else if (numIdle > 0) {
1✔
216
      overallState = IDLE;
1✔
217
    } else {
218
      overallState = TRANSIENT_FAILURE;
×
219
    }
220

221
    RingHashPicker picker = new RingHashPicker(syncContext, ring, getImmutableChildMap());
1✔
222
    getHelper().updateBalancingState(overallState, picker);
1✔
223
    this.currentConnectivityState = overallState;
1✔
224
  }
1✔
225

226
  @Override
227
  protected boolean reconnectOnIdle() {
228
    return false;
1✔
229
  }
230

231
  @Override
232
  protected boolean reactivateChildOnReuse() {
233
    return false;
×
234
  }
235

236
  @Override
237
  protected ChildLbState createChildLbState(Object key, Object policyConfig,
238
      SubchannelPicker initialPicker, ResolvedAddresses resolvedAddresses) {
239
    return new RingHashChildLbState((Endpoint)key,
1✔
240
        getChildAddresses(key, resolvedAddresses, null));
1✔
241
  }
242

243
  private Status validateAddrList(List<EquivalentAddressGroup> addrList) {
244
    if (addrList.isEmpty()) {
1✔
245
      Status unavailableStatus = Status.UNAVAILABLE.withDescription("Ring hash lb error: EDS "
×
246
              + "resolution was successful, but returned server addresses are empty.");
247
      handleNameResolutionError(unavailableStatus);
×
248
      return unavailableStatus;
×
249
    }
250

251
    String dupAddrString = validateNoDuplicateAddresses(addrList);
1✔
252
    if (dupAddrString != null) {
1✔
253
      Status unavailableStatus = Status.UNAVAILABLE.withDescription("Ring hash lb error: EDS "
1✔
254
              + "resolution was successful, but there were duplicate addresses: " + dupAddrString);
255
      handleNameResolutionError(unavailableStatus);
1✔
256
      return unavailableStatus;
1✔
257
    }
258

259
    long totalWeight = 0;
1✔
260
    for (EquivalentAddressGroup eag : addrList) {
1✔
261
      Long weight = eag.getAttributes().get(InternalXdsAttributes.ATTR_SERVER_WEIGHT);
1✔
262

263
      if (weight == null) {
1✔
264
        weight = 1L;
×
265
      }
266

267
      if (weight < 0) {
1✔
268
        Status unavailableStatus = Status.UNAVAILABLE.withDescription(
1✔
269
            String.format("Ring hash lb error: EDS resolution was successful, but returned a "
1✔
270
                        + "negative weight for %s.", stripAttrs(eag)));
1✔
271
        handleNameResolutionError(unavailableStatus);
1✔
272
        return unavailableStatus;
1✔
273
      }
274
      if (weight > UnsignedInteger.MAX_VALUE.longValue()) {
1✔
275
        Status unavailableStatus = Status.UNAVAILABLE.withDescription(
1✔
276
            String.format("Ring hash lb error: EDS resolution was successful, but returned a weight"
1✔
277
                + " too large to fit in an unsigned int for %s.", stripAttrs(eag)));
1✔
278
        handleNameResolutionError(unavailableStatus);
1✔
279
        return unavailableStatus;
1✔
280
      }
281
      totalWeight += weight;
1✔
282
    }
1✔
283

284
    if (totalWeight > UnsignedInteger.MAX_VALUE.longValue()) {
1✔
285
      Status unavailableStatus = Status.UNAVAILABLE.withDescription(
1✔
286
          String.format(
1✔
287
              "Ring hash lb error: EDS resolution was successful, but returned a sum of weights too"
288
                  + " large to fit in an unsigned int (%d).", totalWeight));
1✔
289
      handleNameResolutionError(unavailableStatus);
1✔
290
      return unavailableStatus;
1✔
291
    }
292

293
    return Status.OK;
1✔
294
  }
295

296
  @Nullable
297
  private String validateNoDuplicateAddresses(List<EquivalentAddressGroup> addrList) {
298
    Set<SocketAddress> addresses = new HashSet<>();
1✔
299
    Multiset<String> dups = HashMultiset.create();
1✔
300
    for (EquivalentAddressGroup eag : addrList) {
1✔
301
      for (SocketAddress address : eag.getAddresses()) {
1✔
302
        if (!addresses.add(address)) {
1✔
303
          dups.add(address.toString());
1✔
304
        }
305
      }
1✔
306
    }
1✔
307

308
    if (!dups.isEmpty()) {
1✔
309
      return dups.entrySet().stream()
1✔
310
          .map((dup) ->
1✔
311
              String.format("Address: %s, count: %d", dup.getElement(), dup.getCount() + 1))
1✔
312
          .collect(Collectors.joining("; "));
1✔
313
    }
314

315
    return null;
1✔
316
  }
317

318
  private static List<RingEntry> buildRing(
319
      Map<EquivalentAddressGroup, Long> serverWeights, long totalWeight, double scale) {
320
    List<RingEntry> ring = new ArrayList<>();
1✔
321
    double currentHashes = 0.0;
1✔
322
    double targetHashes = 0.0;
1✔
323
    for (Map.Entry<EquivalentAddressGroup, Long> entry : serverWeights.entrySet()) {
1✔
324
      Endpoint endpoint = new Endpoint(entry.getKey());
1✔
325
      double normalizedWeight = (double) entry.getValue() / totalWeight;
1✔
326
      // Per GRFC A61 use the first address for the hash
327
      StringBuilder sb = new StringBuilder(entry.getKey().getAddresses().get(0).toString());
1✔
328
      sb.append('_');
1✔
329
      int lengthWithoutCounter = sb.length();
1✔
330
      targetHashes += scale * normalizedWeight;
1✔
331
      long i = 0L;
1✔
332
      while (currentHashes < targetHashes) {
1✔
333
        sb.append(i);
1✔
334
        long hash = hashFunc.hashAsciiString(sb.toString());
1✔
335
        ring.add(new RingEntry(hash, endpoint));
1✔
336
        i++;
1✔
337
        currentHashes++;
1✔
338
        sb.setLength(lengthWithoutCounter);
1✔
339
      }
1✔
340
    }
1✔
341
    Collections.sort(ring);
1✔
342
    return Collections.unmodifiableList(ring);
1✔
343
  }
344

345
  @SuppressWarnings("ReferenceEquality")
346
  public static EquivalentAddressGroup stripAttrs(EquivalentAddressGroup eag) {
347
    if (eag.getAttributes() == Attributes.EMPTY) {
1✔
348
      return eag;
×
349
    }
350
    return new EquivalentAddressGroup(eag.getAddresses());
1✔
351
  }
352

353
  private static final class RingHashPicker extends SubchannelPicker {
354
    private final SynchronizationContext syncContext;
355
    private final List<RingEntry> ring;
356
    // Avoid synchronization between pickSubchannel and subchannel's connectivity state change,
357
    // freeze picker's view of subchannel's connectivity state.
358
    // TODO(chengyuanzhang): can be more performance-friendly with
359
    //  IdentityHashMap<Subchannel, ConnectivityStateInfo> and RingEntry contains Subchannel.
360
    private final Map<Endpoint, SubchannelView> pickableSubchannels;  // read-only
361

362
    private RingHashPicker(
363
        SynchronizationContext syncContext, List<RingEntry> ring,
364
        ImmutableMap<Object, ChildLbState> subchannels) {
1✔
365
      this.syncContext = syncContext;
1✔
366
      this.ring = ring;
1✔
367
      pickableSubchannels = new HashMap<>(subchannels.size());
1✔
368
      for (Map.Entry<Object, ChildLbState> entry : subchannels.entrySet()) {
1✔
369
        RingHashChildLbState childLbState = (RingHashChildLbState) entry.getValue();
1✔
370
        pickableSubchannels.put((Endpoint)entry.getKey(),
1✔
371
            new SubchannelView(childLbState, childLbState.getCurrentState()));
1✔
372
      }
1✔
373
    }
1✔
374

375
    // Find the ring entry with hash next to (clockwise) the RPC's hash (binary search).
376
    private int getTargetIndex(Long requestHash) {
377
      if (ring.size() <= 1) {
1✔
378
        return 0;
×
379
      }
380

381
      int low = 0;
1✔
382
      int high = ring.size() - 1;
1✔
383
      int mid = (low + high) / 2;
1✔
384
      do {
385
        long midVal = ring.get(mid).hash;
1✔
386
        long midValL = mid == 0 ? 0 : ring.get(mid - 1).hash;
1✔
387
        if (requestHash <= midVal && requestHash > midValL) {
1✔
388
          break;
1✔
389
        }
390
        if (midVal < requestHash) {
1✔
391
          low = mid + 1;
1✔
392
        } else {
393
          high =  mid - 1;
1✔
394
        }
395
        mid = (low + high) / 2;
1✔
396
      } while (mid < ring.size() && low <= high);
1✔
397
      return mid;
1✔
398
    }
399

400
    @Override
401
    public PickResult pickSubchannel(PickSubchannelArgs args) {
402
      Long requestHash = args.getCallOptions().getOption(XdsNameResolver.RPC_HASH_KEY);
1✔
403
      if (requestHash == null) {
1✔
404
        return PickResult.withError(RPC_HASH_NOT_FOUND);
×
405
      }
406

407
      int targetIndex = getTargetIndex(requestHash);
1✔
408

409
      // Per gRFC A61, because of sticky-TF with PickFirst's auto reconnect on TF, we ignore
410
      // all TF subchannels and find the first ring entry in READY, CONNECTING or IDLE.  If
411
      // CONNECTING or IDLE we return a pick with no results.  Additionally, if that entry is in
412
      // IDLE, we initiate a connection.
413
      for (int i = 0; i < ring.size(); i++) {
1✔
414
        int index = (targetIndex + i) % ring.size();
1✔
415
        SubchannelView subchannelView = pickableSubchannels.get(ring.get(index).addrKey);
1✔
416
        RingHashChildLbState childLbState = subchannelView.childLbState;
1✔
417

418
        if (subchannelView.connectivityState  == READY) {
1✔
419
          return childLbState.getCurrentPicker().pickSubchannel(args);
1✔
420
        }
421

422
        // RPCs can be buffered if the next subchannel is pending (per A62). Otherwise, RPCs
423
        // are failed unless there is a READY connection.
424
        if (subchannelView.connectivityState == CONNECTING) {
1✔
425
          return PickResult.withNoResult();
1✔
426
        }
427

428
        if (subchannelView.connectivityState == IDLE) {
1✔
429
          syncContext.execute(() -> {
1✔
430
            if (childLbState.isDeactivated()) {
1✔
431
              childLbState.activate();
1✔
432
            } else {
433
              childLbState.getLb().requestConnection();
1✔
434
            }
435
          });
1✔
436

437
          return PickResult.withNoResult(); // Indicates that this should be retried after backoff
1✔
438
        }
439
      }
440

441
      // return the pick from the original subchannel hit by hash, which is probably an error
442
      RingHashChildLbState originalSubchannel =
1✔
443
          pickableSubchannels.get(ring.get(targetIndex).addrKey).childLbState;
1✔
444
      return originalSubchannel.getCurrentPicker().pickSubchannel(args);
1✔
445
    }
446

447
  }
448

449
  @Override
450
  protected SubchannelPicker getSubchannelPicker(Map<Object, SubchannelPicker> childPickers) {
451
    throw new UnsupportedOperationException("Not used by RingHash");
×
452
  }
453

454
  /**
455
   * An unmodifiable view of a subchannel with state not subject to its real connectivity
456
   * state changes.
457
   */
458
  private static final class SubchannelView {
459
    private final RingHashChildLbState childLbState;
460
    private final ConnectivityState connectivityState;
461

462
    private SubchannelView(RingHashChildLbState childLbState, ConnectivityState state) {
1✔
463
      this.childLbState = childLbState;
1✔
464
      this.connectivityState = state;
1✔
465
    }
1✔
466
  }
467

468
  private static final class RingEntry implements Comparable<RingEntry> {
469
    private final long hash;
470
    private final Endpoint addrKey;
471

472
    private RingEntry(long hash, Endpoint addrKey) {
1✔
473
      this.hash = hash;
1✔
474
      this.addrKey = addrKey;
1✔
475
    }
1✔
476

477
    @Override
478
    public int compareTo(RingEntry entry) {
479
      return Long.compare(hash, entry.hash);
1✔
480
    }
481
  }
482

483
  /**
484
   * Configures the ring property. The larger the ring is (that is, the more hashes there are
485
   * for each provided host) the better the request distribution will reflect the desired weights.
486
   */
487
  static final class RingHashConfig {
488
    final long minRingSize;
489
    final long maxRingSize;
490

491
    RingHashConfig(long minRingSize, long maxRingSize) {
1✔
492
      checkArgument(minRingSize > 0, "minRingSize <= 0");
1✔
493
      checkArgument(maxRingSize > 0, "maxRingSize <= 0");
1✔
494
      checkArgument(minRingSize <= maxRingSize, "minRingSize > maxRingSize");
1✔
495
      this.minRingSize = minRingSize;
1✔
496
      this.maxRingSize = maxRingSize;
1✔
497
    }
1✔
498

499
    @Override
500
    public String toString() {
501
      return MoreObjects.toStringHelper(this)
×
502
          .add("minRingSize", minRingSize)
×
503
          .add("maxRingSize", maxRingSize)
×
504
          .toString();
×
505
    }
506
  }
507

508
  static Set<EquivalentAddressGroup> getStrippedChildEags(Collection<ChildLbState> states) {
509
    return states.stream()
×
510
        .map(ChildLbState::getEag)
×
511
        .map(RingHashLoadBalancer::stripAttrs)
×
512
        .collect(Collectors.toSet());
×
513
  }
514

515
  @Override
516
  protected Collection<ChildLbState> getChildLbStates() {
517
    return super.getChildLbStates();
1✔
518
  }
519

520
  @Override
521
  protected ChildLbState getChildLbStateEag(EquivalentAddressGroup eag) {
522
    return super.getChildLbStateEag(eag);
1✔
523
  }
524

525
  class RingHashChildLbState extends MultiChildLoadBalancer.ChildLbState {
526

527
    public RingHashChildLbState(Endpoint key, ResolvedAddresses resolvedAddresses) {
1✔
528
      super(key, pickFirstLbProvider, null, EMPTY_PICKER, resolvedAddresses, true);
1✔
529
    }
1✔
530

531
    @Override
532
    protected void reactivate(LoadBalancerProvider policyProvider) {
533
      if (!isDeactivated()) {
1✔
534
        return;
×
535
      }
536
      currentConnectivityState = CONNECTING;
1✔
537
      getLb().switchTo(pickFirstLbProvider);
1✔
538
      markReactivated();
1✔
539
      getLb().acceptResolvedAddresses(this.getResolvedAddresses());
1✔
540
      logger.log(XdsLogLevel.DEBUG, "Child balancer {0} reactivated", getKey());
1✔
541
    }
1✔
542

543
    public void activate() {
544
      reactivate(pickFirstLbProvider);
1✔
545
    }
1✔
546

547
    // Need to expose this to the LB class
548
    @Override
549
    protected void shutdown() {
550
      super.shutdown();
1✔
551
    }
1✔
552

553
    // Need to expose this to the LB class
554
    @Override
555
    protected GracefulSwitchLoadBalancer getLb() {
556
      return super.getLb();
1✔
557
    }
558

559
  }
560
}
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

© 2026 Coveralls, Inc