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

grpc / grpc-java / #19097

11 Mar 2024 09:12PM UTC coverage: 88.289% (-0.02%) from 88.31%
#19097

push

github

web-flow
Prepare to switch flag to use new PickFirstLeafLoadBalancer by default (#10998)

* Fix PickFirstLeafLoadBalancer and tests to work when it is used.
* Actually use EAG attributes for subchannels.

31158 of 35291 relevant lines covered (88.29%)

0.88 hits per line

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

94.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 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.Arrays;
41
import java.util.Collection;
42
import java.util.Collections;
43
import java.util.HashMap;
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,
1✔
95
            createChildLbState(endpoint, null, getInitialPicker(), resolvedAddresses));
1✔
96
      }
97
    }
1✔
98
    return childLbMap;
1✔
99
  }
100

101
  /**
102
   * Override to create an instance of a subclass.
103
   */
104
  protected ChildLbState createChildLbState(Object key, Object policyConfig,
105
      SubchannelPicker initialPicker, ResolvedAddresses resolvedAddresses) {
106
    return new ChildLbState(key, pickFirstLbProvider, policyConfig, initialPicker);
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
    try {
115
      resolvingAddresses = true;
1✔
116

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

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

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

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

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

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

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

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

181
  /**
182
   * Handle the name resolution error only for the specified child.
183
   *
184
   * <p/>Override if you need special handling.
185
   */
186
  protected void handleNameResolutionError(ChildLbState child, Status error) {
187
    child.lb.handleNameResolutionError(error);
1✔
188
  }
1✔
189

190
  /**
191
   * Creates a picker representing the state before any connections have been established.
192
   *
193
   * <p/>Override to produce a custom picker.
194
   */
195
  protected SubchannelPicker getInitialPicker() {
196
    return new FixedResultPicker(PickResult.withNoResult());
1✔
197
  }
198

199
  /**
200
   * Creates a new picker representing an error status.
201
   *
202
   * <p/>Override to produce a custom picker when there are errors.
203
   */
204
  protected SubchannelPicker getErrorPicker(Status error)  {
205
    return new FixedResultPicker(PickResult.withError(error));
1✔
206
  }
207

208
  @Override
209
  public void shutdown() {
210
    logger.log(Level.FINE, "Shutdown");
1✔
211
    for (ChildLbState state : childLbStates.values()) {
1✔
212
      state.shutdown();
1✔
213
    }
1✔
214
    childLbStates.clear();
1✔
215
  }
1✔
216

217
  /**
218
   *   This does the work to update the child map and calculate which children have been removed.
219
   *   You must call {@link #updateOverallBalancingState} to update the picker
220
   *   and call {@link #shutdownRemoved(List)} to shutdown the endpoints that have been removed.
221
    */
222
  protected final AcceptResolvedAddrRetVal acceptResolvedAddressesInternal(
223
      ResolvedAddresses resolvedAddresses) {
224
    logger.log(Level.FINE, "Received resolution result: {0}", resolvedAddresses);
1✔
225

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

229
    // Handle error case
230
    if (newChildren.isEmpty()) {
1✔
231
      Status unavailableStatus = Status.UNAVAILABLE.withDescription(
1✔
232
          "NameResolver returned no usable address. " + resolvedAddresses);
233
      handleNameResolutionError(unavailableStatus);
1✔
234
      return new AcceptResolvedAddrRetVal(unavailableStatus, null);
1✔
235
    }
236

237
    Collection<ChildLbState> reusedChildren = addMissingChildrenAndIdReuse(newChildren);
1✔
238

239
    // Raactivate deactivated children
240
    for (ChildLbState reusedChild : reusedChildren) {
1✔
241
      reusedChild.reactivate(reusedChild.getPolicyProvider());
1✔
242
    }
1✔
243

244
    updateChildrenWithResolvedAddresses(resolvedAddresses, newChildren);
1✔
245

246
    return new AcceptResolvedAddrRetVal(Status.OK, getRemovedChildren(newChildren.keySet()));
1✔
247
  }
248

249
  protected final Collection<ChildLbState> addMissingChildrenAndIdReuse(
250
      Map<Object, ChildLbState> newChildren) {
251
    Collection<ChildLbState> reusedChildren = new ArrayList<>();
1✔
252

253
    // Do adds and identify reused children
254
    for (Map.Entry<Object, ChildLbState> entry : newChildren.entrySet()) {
1✔
255
      final Object key = entry.getKey();
1✔
256
      if (!childLbStates.containsKey(key)) {
1✔
257
        childLbStates.put(key, entry.getValue());
1✔
258
      } else {
259
        // Reuse the existing one
260
        ChildLbState existingChildLbState = childLbStates.get(key);
1✔
261
        if (existingChildLbState.isDeactivated()) {
1✔
262
          reusedChildren.add(existingChildLbState);
1✔
263
        }
264
      }
265
    }
1✔
266
    return reusedChildren;
1✔
267
  }
268

269
  protected final void updateChildrenWithResolvedAddresses(ResolvedAddresses resolvedAddresses,
270
                                                     Map<Object, ChildLbState> newChildren) {
271
    for (Map.Entry<Object, ChildLbState> entry : newChildren.entrySet()) {
1✔
272
      Object childConfig = entry.getValue().getConfig();
1✔
273
      ChildLbState childLbState = childLbStates.get(entry.getKey());
1✔
274
      ResolvedAddresses childAddresses =
1✔
275
          getChildAddresses(entry.getKey(), resolvedAddresses, childConfig);
1✔
276
      childLbState.setResolvedAddresses(childAddresses); // update child
1✔
277
      if (!childLbState.deactivated) {
1✔
278
        childLbState.lb.handleResolvedAddresses(childAddresses); // update child LB
1✔
279
      }
280
    }
1✔
281
  }
1✔
282

283
  /**
284
   * Identifies which children have been removed (are not part of the newChildKeys).
285
   */
286
  protected final List<ChildLbState> getRemovedChildren(Set<Object> newChildKeys) {
287
    List<ChildLbState> removedChildren = new ArrayList<>();
1✔
288
    // Do removals
289
    for (Object key : ImmutableList.copyOf(childLbStates.keySet())) {
1✔
290
      if (!newChildKeys.contains(key)) {
1✔
291
        ChildLbState childLbState = childLbStates.get(key);
1✔
292
        childLbState.deactivate();
1✔
293
        removedChildren.add(childLbState);
1✔
294
      }
295
    }
1✔
296
    return removedChildren;
1✔
297
  }
298

299
  protected final void shutdownRemoved(List<ChildLbState> removedChildren) {
300
    // Do shutdowns after updating picker to reduce the chance of failing an RPC by picking a
301
    // subchannel that has been shutdown.
302
    for (ChildLbState childLbState : removedChildren) {
1✔
303
      childLbState.shutdown();
1✔
304
    }
1✔
305
  }
1✔
306

307
  @Nullable
308
  protected static ConnectivityState aggregateState(
309
      @Nullable ConnectivityState overallState, ConnectivityState childState) {
310
    if (overallState == null) {
1✔
311
      return childState;
1✔
312
    }
313
    if (overallState == READY || childState == READY) {
1✔
314
      return READY;
1✔
315
    }
316
    if (overallState == CONNECTING || childState == CONNECTING) {
1✔
317
      return CONNECTING;
1✔
318
    }
319
    if (overallState == IDLE || childState == IDLE) {
1✔
320
      return IDLE;
×
321
    }
322
    return overallState;
1✔
323
  }
324

325
  protected final Helper getHelper() {
326
    return helper;
1✔
327
  }
328

329
  protected final void removeChild(Object key) {
330
    childLbStates.remove(key);
1✔
331
  }
1✔
332

333
  @VisibleForTesting
334
  public final ImmutableMap<Object, ChildLbState> getImmutableChildMap() {
335
    return ImmutableMap.copyOf(childLbStates);
1✔
336
  }
337

338
  @VisibleForTesting
339
  public final Collection<ChildLbState> getChildLbStates() {
340
    return childLbStates.values();
1✔
341
  }
342

343
  @VisibleForTesting
344
  public final ChildLbState getChildLbState(Object key) {
345
    if (key == null) {
1✔
346
      return null;
×
347
    }
348
    if (key instanceof EquivalentAddressGroup) {
1✔
349
      key = new Endpoint((EquivalentAddressGroup) key);
1✔
350
    }
351
    return childLbStates.get(key);
1✔
352
  }
353

354
  @VisibleForTesting
355
  public final ChildLbState getChildLbStateEag(EquivalentAddressGroup eag) {
356
    return getChildLbState(new Endpoint(eag));
1✔
357
  }
358

359
  /**
360
   * Filters out non-ready and deactivated child load balancers (subchannels).
361
   */
362
  protected final List<ChildLbState> getReadyChildren() {
363
    List<ChildLbState> activeChildren = new ArrayList<>();
1✔
364
    for (ChildLbState child : getChildLbStates()) {
1✔
365
      if (!child.isDeactivated() && child.getCurrentState() == READY) {
1✔
366
        activeChildren.add(child);
1✔
367
      }
368
    }
1✔
369
    return activeChildren;
1✔
370
  }
371

372
  /**
373
   * This represents the state of load balancer children.  Each endpoint (represented by an
374
   * EquivalentAddressGroup or EDS string) will have a separate ChildLbState which in turn will
375
   * define a GracefulSwitchLoadBalancer.  When the GracefulSwitchLoadBalancer is activated, a
376
   * single PickFirstLoadBalancer will be created which will then create a subchannel and start
377
   * trying to connect to it.
378
   *
379
   * <p>A ChildLbStateHelper is the glue between ChildLbState and the helpers associated with the
380
   * petiole policy above and the PickFirstLoadBalancer's helper below.
381
   *
382
   * <p>If you wish to store additional state information related to each subchannel, then extend
383
   * this class.
384
   */
385
  public class ChildLbState {
386
    private final Object key;
387
    private ResolvedAddresses resolvedAddresses;
388
    private final Object config;
389

390
    private final GracefulSwitchLoadBalancer lb;
391
    private final LoadBalancerProvider policyProvider;
392
    private ConnectivityState currentState;
393
    private SubchannelPicker currentPicker;
394
    private boolean deactivated;
395

396
    public ChildLbState(Object key, LoadBalancerProvider policyProvider, Object childConfig,
397
        SubchannelPicker initialPicker) {
398
      this(key, policyProvider, childConfig, initialPicker, null, false);
1✔
399
    }
1✔
400

401
    public ChildLbState(Object key, LoadBalancerProvider policyProvider, Object childConfig,
402
          SubchannelPicker initialPicker, ResolvedAddresses resolvedAddrs, boolean deactivated) {
1✔
403
      this.key = key;
1✔
404
      this.policyProvider = policyProvider;
1✔
405
      this.deactivated = deactivated;
1✔
406
      this.currentPicker = initialPicker;
1✔
407
      this.config = childConfig;
1✔
408
      this.lb = new GracefulSwitchLoadBalancer(createChildHelper());
1✔
409
      this.currentState = deactivated ? IDLE : CONNECTING;
1✔
410
      this.resolvedAddresses = resolvedAddrs;
1✔
411
      if (!deactivated) {
1✔
412
        lb.switchTo(policyProvider);
1✔
413
      }
414
    }
1✔
415

416
    protected ChildLbStateHelper createChildHelper() {
417
      return new ChildLbStateHelper();
1✔
418
    }
419

420
    /**
421
     * The default implementation. This not only marks the lb policy as not active, it also removes
422
     * this child from the map of children maintained by the petiole policy.
423
     *
424
     * <p>Note that this does not explicitly shutdown this child.  That will generally be done by
425
     * acceptResolvedAddresses on the LB, but can also be handled by an override such as is done
426
     * in <a href=" https://github.com/grpc/grpc-java/blob/master/xds/src/main/java/io/grpc/xds/ClusterManagerLoadBalancer.java">ClusterManagerLoadBalancer</a>.
427
     *
428
     * <p>If you plan to reactivate, you will probably want to override this to not call
429
     * childLbStates.remove() and handle that cleanup another way.
430
     */
431
    protected void deactivate() {
432
      if (deactivated) {
1✔
433
        return;
×
434
      }
435

436
      childLbStates.remove(key); // This means it can't be reactivated again
1✔
437
      deactivated = true;
1✔
438
      logger.log(Level.FINE, "Child balancer {0} deactivated", key);
1✔
439
    }
1✔
440

441
    /**
442
     * This base implementation does nothing but reset the flag.  If you really want to both
443
     * deactivate and reactivate you should override them both.
444
     */
445
    protected void reactivate(LoadBalancerProvider policyProvider) {
446
      deactivated = false;
1✔
447
    }
1✔
448

449
    /**
450
     * Override for unique behavior such as delayed shutdowns of subchannels.
451
     */
452
    protected void shutdown() {
453
      lb.shutdown();
1✔
454
      this.currentState = SHUTDOWN;
1✔
455
      logger.log(Level.FINE, "Child balancer {0} deleted", key);
1✔
456
    }
1✔
457

458
    @Override
459
    public String toString() {
460
      return "Address = " + key
1✔
461
          + ", state = " + currentState
462
          + ", picker type: " + currentPicker.getClass()
1✔
463
          + ", lb: " + lb.delegate().getClass()
1✔
464
          + (deactivated ? ", deactivated" : "");
1✔
465
    }
466

467
    public final Object getKey() {
468
      return key;
1✔
469
    }
470

471
    @VisibleForTesting
472
    public final GracefulSwitchLoadBalancer getLb() {
473
      return lb;
1✔
474
    }
475

476
    @VisibleForTesting
477
    public final SubchannelPicker getCurrentPicker() {
478
      return currentPicker;
1✔
479
    }
480

481
    protected final LoadBalancerProvider getPolicyProvider() {
482
      return policyProvider;
1✔
483
    }
484

485
    protected final Subchannel getSubchannels(PickSubchannelArgs args) {
486
      if (getCurrentPicker() == null) {
×
487
        return null;
×
488
      }
489
      return getCurrentPicker().pickSubchannel(args).getSubchannel();
×
490
    }
491

492
    public final ConnectivityState getCurrentState() {
493
      return currentState;
1✔
494
    }
495

496
    protected final void setCurrentState(ConnectivityState newState) {
497
      currentState = newState;
1✔
498
    }
1✔
499

500
    protected final void setCurrentPicker(SubchannelPicker newPicker) {
501
      currentPicker = newPicker;
1✔
502
    }
1✔
503

504
    public final EquivalentAddressGroup getEag() {
505
      if (resolvedAddresses == null || resolvedAddresses.getAddresses().isEmpty()) {
1✔
506
        return null;
×
507
      }
508
      return resolvedAddresses.getAddresses().get(0);
1✔
509
    }
510

511
    public final boolean isDeactivated() {
512
      return deactivated;
1✔
513
    }
514

515
    protected final void setDeactivated() {
516
      deactivated = true;
1✔
517
    }
1✔
518

519
    protected final void markReactivated() {
520
      deactivated = false;
1✔
521
    }
1✔
522

523
    protected final void setResolvedAddresses(ResolvedAddresses newAddresses) {
524
      checkNotNull(newAddresses, "Missing address list for child");
1✔
525
      resolvedAddresses = newAddresses;
1✔
526
    }
1✔
527

528
    private Object getConfig() {
529
      return config;
1✔
530
    }
531

532
    @VisibleForTesting
533
    public final ResolvedAddresses getResolvedAddresses() {
534
      return resolvedAddresses;
1✔
535
    }
536

537
    /**
538
     * ChildLbStateHelper is the glue between ChildLbState and the helpers associated with the
539
     * petiole policy above and the PickFirstLoadBalancer's helper below.
540
     *
541
     * <p>The ChildLbState updates happen during updateBalancingState.  Otherwise, it is doing
542
     * simple forwarding.
543
     */
544
    protected class ChildLbStateHelper extends ForwardingLoadBalancerHelper {
1✔
545

546
      /**
547
       * Update current state and picker for this child and then use
548
       * {@link #updateOverallBalancingState()} for the parent LB.
549
       *
550
       * <p/>Override this if you don't want to automatically request a connection when in IDLE
551
       */
552
      @Override
553
      public void updateBalancingState(final ConnectivityState newState,
554
          final SubchannelPicker newPicker) {
555
        // If we are already in the process of resolving addresses, the overall balancing state
556
        // will be updated at the end of it, and we don't need to trigger that update here.
557
        if (!childLbStates.containsKey(key)) {
1✔
558
          return;
1✔
559
        }
560

561
        // Subchannel picker and state are saved, but will only be propagated to the channel
562
        // when the child instance exits deactivated state.
563
        currentState = newState;
1✔
564
        currentPicker = newPicker;
1✔
565
        if (!deactivated && !resolvingAddresses) {
1✔
566
          if (newState == IDLE) {
1✔
567
            lb.requestConnection();
1✔
568
          }
569
          updateOverallBalancingState();
1✔
570
        }
571
      }
1✔
572

573
      @Override
574
      protected Helper delegate() {
575
        return helper;
1✔
576
      }
577
    }
578
  }
579

580
  /**
581
   * Endpoint is an optimization to quickly lookup and compare EquivalentAddressGroup address sets.
582
   * Ignores the attributes, orders the addresses in a deterministic manner and converts each
583
   * address into a string for easy comparison.  Also caches the hashcode.
584
   * Is used as a key for ChildLbState for most load balancers (ClusterManagerLB uses a String).
585
   */
586
  protected static class Endpoint {
587
    final String[] addrs;
588
    final int hashCode;
589

590
    public Endpoint(EquivalentAddressGroup eag) {
1✔
591
      checkNotNull(eag, "eag");
1✔
592

593
      addrs = new String[eag.getAddresses().size()];
1✔
594
      int i = 0;
1✔
595
      for (SocketAddress address : eag.getAddresses()) {
1✔
596
        addrs[i++] = address.toString();
1✔
597
      }
1✔
598
      Arrays.sort(addrs);
1✔
599

600
      hashCode = Arrays.hashCode(addrs);
1✔
601
    }
1✔
602

603
    @Override
604
    public int hashCode() {
605
      return hashCode;
1✔
606
    }
607

608
    @Override
609
    public boolean equals(Object other) {
610
      if (this == other) {
1✔
611
        return true;
×
612
      }
613
      if (other == null) {
1✔
614
        return false;
×
615
      }
616

617
      if (!(other instanceof Endpoint)) {
1✔
618
        return false;
×
619
      }
620
      Endpoint o = (Endpoint) other;
1✔
621
      if (o.hashCode != hashCode || o.addrs.length != addrs.length) {
1✔
622
        return false;
1✔
623
      }
624

625
      return Arrays.equals(o.addrs, this.addrs);
1✔
626
    }
627

628
    @Override
629
    public String toString() {
630
      return Arrays.toString(addrs);
1✔
631
    }
632
  }
633

634
  protected static class AcceptResolvedAddrRetVal {
635
    public final Status status;
636
    public final List<ChildLbState> removedChildren;
637

638
    public AcceptResolvedAddrRetVal(Status status, List<ChildLbState> removedChildren) {
1✔
639
      this.status = status;
1✔
640
      this.removedChildren = removedChildren;
1✔
641
    }
1✔
642
  }
643
}
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