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

grpc / grpc-java / #19553

14 Nov 2024 08:56PM UTC coverage: 84.627% (+0.02%) from 84.607%
#19553

push

github

web-flow
util: Remove resolvedAddresses from MultiChildLb.ChildLbState

It isn't actually used by MultiChildLb, and using the health API gives
us more confidence that health is properly plumbed.

34104 of 40299 relevant lines covered (84.63%)

0.85 hits per line

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

97.08
/../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.checkNotNull;
20
import static io.grpc.ConnectivityState.CONNECTING;
21
import static io.grpc.ConnectivityState.IDLE;
22
import static io.grpc.ConnectivityState.READY;
23
import static io.grpc.ConnectivityState.SHUTDOWN;
24
import static io.grpc.ConnectivityState.TRANSIENT_FAILURE;
25

26
import com.google.common.annotations.VisibleForTesting;
27
import com.google.common.collect.Maps;
28
import io.grpc.Attributes;
29
import io.grpc.ConnectivityState;
30
import io.grpc.EquivalentAddressGroup;
31
import io.grpc.Internal;
32
import io.grpc.LoadBalancer;
33
import io.grpc.LoadBalancerProvider;
34
import io.grpc.Status;
35
import io.grpc.internal.PickFirstLoadBalancerProvider;
36
import java.net.SocketAddress;
37
import java.util.ArrayList;
38
import java.util.Collection;
39
import java.util.Collections;
40
import java.util.HashSet;
41
import java.util.List;
42
import java.util.Map;
43
import java.util.logging.Level;
44
import java.util.logging.Logger;
45
import javax.annotation.Nullable;
46

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

54
  private static final Logger logger = Logger.getLogger(MultiChildLoadBalancer.class.getName());
1✔
55
  // Modify by replacing the list to release memory when no longer used.
56
  private List<ChildLbState> childLbStates = new ArrayList<>(0);
1✔
57
  private final Helper helper;
58
  // Set to true if currently in the process of handling resolved addresses.
59
  protected boolean resolvingAddresses;
60

61
  protected final LoadBalancerProvider pickFirstLbProvider = new PickFirstLoadBalancerProvider();
1✔
62

63
  protected ConnectivityState currentConnectivityState;
64

65

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

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

78
  /**
79
   * Override to utilize parsing of the policy configuration or alternative helper/lb generation.
80
   * Override this if keys are not Endpoints or if child policies have configuration. Null map
81
   * values preserve the child without delivering the child an update.
82
   */
83
  protected Map<Object, ResolvedAddresses> createChildAddressesMap(
84
      ResolvedAddresses resolvedAddresses) {
85
    Map<Object, ResolvedAddresses> childAddresses =
1✔
86
        Maps.newLinkedHashMapWithExpectedSize(resolvedAddresses.getAddresses().size());
1✔
87
    for (EquivalentAddressGroup eag : resolvedAddresses.getAddresses()) {
1✔
88
      ResolvedAddresses addresses = resolvedAddresses.toBuilder()
1✔
89
          .setAddresses(Collections.singletonList(eag))
1✔
90
          .setAttributes(Attributes.newBuilder().set(IS_PETIOLE_POLICY, true).build())
1✔
91
          .setLoadBalancingPolicyConfig(null)
1✔
92
          .build();
1✔
93
      childAddresses.put(new Endpoint(eag), addresses);
1✔
94
    }
1✔
95
    return childAddresses;
1✔
96
  }
97

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

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

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

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

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

130
  /**
131
   * Handle the name resolution error.
132
   *
133
   * <p/>Override if you need special handling.
134
   */
135
  @Override
136
  public void handleNameResolutionError(Status error) {
137
    if (currentConnectivityState != READY)  {
1✔
138
      helper.updateBalancingState(
1✔
139
          TRANSIENT_FAILURE, new FixedResultPicker(PickResult.withError(error)));
1✔
140
    }
141
  }
1✔
142

143
  @Override
144
  public void shutdown() {
145
    logger.log(Level.FINE, "Shutdown");
1✔
146
    for (ChildLbState state : childLbStates) {
1✔
147
      state.shutdown();
1✔
148
    }
1✔
149
    childLbStates.clear();
1✔
150
  }
1✔
151

152
  /**
153
   *   This does the work to update the child map and calculate which children have been removed.
154
   *   You must call {@link #updateOverallBalancingState} to update the picker
155
   *   and call {@link #shutdownRemoved(List)} to shutdown the endpoints that have been removed.
156
    */
157
  protected final AcceptResolvedAddrRetVal acceptResolvedAddressesInternal(
158
      ResolvedAddresses resolvedAddresses) {
159
    logger.log(Level.FINE, "Received resolution result: {0}", resolvedAddresses);
1✔
160

161
    Map<Object, ResolvedAddresses> newChildAddresses = createChildAddressesMap(resolvedAddresses);
1✔
162

163
    // Handle error case
164
    if (newChildAddresses.isEmpty()) {
1✔
165
      Status unavailableStatus = Status.UNAVAILABLE.withDescription(
1✔
166
          "NameResolver returned no usable address. " + resolvedAddresses);
167
      handleNameResolutionError(unavailableStatus);
1✔
168
      return new AcceptResolvedAddrRetVal(unavailableStatus, null);
1✔
169
    }
170

171
    List<ChildLbState> removed = updateChildrenWithResolvedAddresses(newChildAddresses);
1✔
172
    return new AcceptResolvedAddrRetVal(Status.OK, removed);
1✔
173
  }
174

175
  /** Returns removed children. */
176
  private List<ChildLbState> updateChildrenWithResolvedAddresses(
177
      Map<Object, ResolvedAddresses> newChildAddresses) {
178
    // Create a map with the old values
179
    Map<Object, ChildLbState> oldStatesMap =
1✔
180
        Maps.newLinkedHashMapWithExpectedSize(childLbStates.size());
1✔
181
    for (ChildLbState state : childLbStates) {
1✔
182
      oldStatesMap.put(state.getKey(), state);
1✔
183
    }
1✔
184

185
    // Move ChildLbStates from the map to a new list (preserving the new map's order)
186
    List<ChildLbState> newChildLbStates = new ArrayList<>(newChildAddresses.size());
1✔
187
    for (Map.Entry<Object, ResolvedAddresses> entry : newChildAddresses.entrySet()) {
1✔
188
      ChildLbState childLbState = oldStatesMap.remove(entry.getKey());
1✔
189
      if (childLbState == null) {
1✔
190
        childLbState = createChildLbState(entry.getKey());
1✔
191
      }
192
      newChildLbStates.add(childLbState);
1✔
193
      if (entry.getValue() != null) {
1✔
194
        childLbState.lb.handleResolvedAddresses(entry.getValue()); // update child LB
1✔
195
      }
196
    }
1✔
197

198
    childLbStates = newChildLbStates;
1✔
199
    // Remaining entries in map are orphaned
200
    return new ArrayList<>(oldStatesMap.values());
1✔
201
  }
202

203
  protected final void shutdownRemoved(List<ChildLbState> removedChildren) {
204
    // Do shutdowns after updating picker to reduce the chance of failing an RPC by picking a
205
    // subchannel that has been shutdown.
206
    for (ChildLbState childLbState : removedChildren) {
1✔
207
      childLbState.shutdown();
1✔
208
    }
1✔
209
  }
1✔
210

211
  @Nullable
212
  protected static ConnectivityState aggregateState(
213
      @Nullable ConnectivityState overallState, ConnectivityState childState) {
214
    if (overallState == null) {
1✔
215
      return childState;
1✔
216
    }
217
    if (overallState == READY || childState == READY) {
1✔
218
      return READY;
1✔
219
    }
220
    if (overallState == CONNECTING || childState == CONNECTING) {
1✔
221
      return CONNECTING;
1✔
222
    }
223
    if (overallState == IDLE || childState == IDLE) {
1✔
224
      return IDLE;
×
225
    }
226
    return overallState;
1✔
227
  }
228

229
  protected final Helper getHelper() {
230
    return helper;
1✔
231
  }
232

233
  @VisibleForTesting
234
  public final Collection<ChildLbState> getChildLbStates() {
235
    return childLbStates;
1✔
236
  }
237

238
  /**
239
   * Filters out non-ready child load balancers (subchannels).
240
   */
241
  protected final List<ChildLbState> getReadyChildren() {
242
    List<ChildLbState> activeChildren = new ArrayList<>();
1✔
243
    for (ChildLbState child : getChildLbStates()) {
1✔
244
      if (child.getCurrentState() == READY) {
1✔
245
        activeChildren.add(child);
1✔
246
      }
247
    }
1✔
248
    return activeChildren;
1✔
249
  }
250

251
  /**
252
   * This represents the state of load balancer children.  Each endpoint (represented by an
253
   * EquivalentAddressGroup or EDS string) will have a separate ChildLbState which in turn will
254
   * have a single child LoadBalancer created from the provided factory.
255
   *
256
   * <p>A ChildLbStateHelper is the glue between ChildLbState and the helpers associated with the
257
   * petiole policy above and the PickFirstLoadBalancer's helper below.
258
   *
259
   * <p>If you wish to store additional state information related to each subchannel, then extend
260
   * this class.
261
   */
262
  public class ChildLbState {
263
    private final Object key;
264
    private final LoadBalancer lb;
265
    private ConnectivityState currentState;
266
    private SubchannelPicker currentPicker = new FixedResultPicker(PickResult.withNoResult());
1✔
267

268
    public ChildLbState(Object key, LoadBalancer.Factory policyFactory) {
1✔
269
      this.key = key;
1✔
270
      this.lb = policyFactory.newLoadBalancer(createChildHelper());
1✔
271
      this.currentState = CONNECTING;
1✔
272
    }
1✔
273

274
    protected ChildLbStateHelper createChildHelper() {
275
      return new ChildLbStateHelper();
1✔
276
    }
277

278
    /**
279
     * Override for unique behavior such as delayed shutdowns of subchannels.
280
     */
281
    protected void shutdown() {
282
      lb.shutdown();
1✔
283
      this.currentState = SHUTDOWN;
1✔
284
      logger.log(Level.FINE, "Child balancer {0} deleted", key);
1✔
285
    }
1✔
286

287
    @Override
288
    public String toString() {
289
      return "Address = " + key
×
290
          + ", state = " + currentState
291
          + ", picker type: " + currentPicker.getClass()
×
292
          + ", lb: " + lb;
293
    }
294

295
    public final Object getKey() {
296
      return key;
1✔
297
    }
298

299
    @VisibleForTesting
300
    public final LoadBalancer getLb() {
301
      return lb;
1✔
302
    }
303

304
    @VisibleForTesting
305
    public final SubchannelPicker getCurrentPicker() {
306
      return currentPicker;
1✔
307
    }
308

309
    public final ConnectivityState getCurrentState() {
310
      return currentState;
1✔
311
    }
312

313
    protected final void setCurrentState(ConnectivityState newState) {
314
      currentState = newState;
1✔
315
    }
1✔
316

317
    protected final void setCurrentPicker(SubchannelPicker newPicker) {
318
      currentPicker = newPicker;
1✔
319
    }
1✔
320

321
    /**
322
     * 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>The ChildLbState updates happen during updateBalancingState.  Otherwise, it is doing
326
     * simple forwarding.
327
     */
328
    protected class ChildLbStateHelper extends ForwardingLoadBalancerHelper {
1✔
329

330
      /**
331
       * Update current state and picker for this child and then use
332
       * {@link #updateOverallBalancingState()} for the parent LB.
333
       */
334
      @Override
335
      public void updateBalancingState(final ConnectivityState newState,
336
          final SubchannelPicker newPicker) {
337
        if (currentState == SHUTDOWN) {
1✔
338
          return;
×
339
        }
340

341
        currentState = newState;
1✔
342
        currentPicker = newPicker;
1✔
343
        // If we are already in the process of resolving addresses, the overall balancing state
344
        // will be updated at the end of it, and we don't need to trigger that update here.
345
        if (!resolvingAddresses) {
1✔
346
          updateOverallBalancingState();
1✔
347
        }
348
      }
1✔
349

350
      @Override
351
      protected Helper delegate() {
352
        return helper;
1✔
353
      }
354
    }
355
  }
356

357
  /**
358
   * Endpoint is an optimization to quickly lookup and compare EquivalentAddressGroup address sets.
359
   * It ignores the attributes. Is used as a key for ChildLbState for most load balancers
360
   * (ClusterManagerLB uses a String).
361
   */
362
  protected static class Endpoint {
363
    final Collection<SocketAddress> addrs;
364
    final int hashCode;
365

366
    public Endpoint(EquivalentAddressGroup eag) {
1✔
367
      checkNotNull(eag, "eag");
1✔
368

369
      if (eag.getAddresses().size() < 10) {
1✔
370
        addrs = eag.getAddresses();
1✔
371
      } else {
372
        // This is expected to be very unlikely in practice
373
        addrs = new HashSet<>(eag.getAddresses());
1✔
374
      }
375
      int sum = 0;
1✔
376
      for (SocketAddress address : eag.getAddresses()) {
1✔
377
        sum += address.hashCode();
1✔
378
      }
1✔
379
      hashCode = sum;
1✔
380
    }
1✔
381

382
    @Override
383
    public int hashCode() {
384
      return hashCode;
1✔
385
    }
386

387
    @Override
388
    public boolean equals(Object other) {
389
      if (this == other) {
1✔
390
        return true;
1✔
391
      }
392

393
      if (!(other instanceof Endpoint)) {
1✔
394
        return false;
1✔
395
      }
396
      Endpoint o = (Endpoint) other;
1✔
397
      if (o.hashCode != hashCode || o.addrs.size() != addrs.size()) {
1✔
398
        return false;
1✔
399
      }
400

401
      return o.addrs.containsAll(addrs);
1✔
402
    }
403

404
    @Override
405
    public String toString() {
406
      return addrs.toString();
1✔
407
    }
408
  }
409

410
  protected static class AcceptResolvedAddrRetVal {
411
    public final Status status;
412
    public final List<ChildLbState> removedChildren;
413

414
    public AcceptResolvedAddrRetVal(Status status, List<ChildLbState> removedChildren) {
1✔
415
      this.status = status;
1✔
416
      this.removedChildren = removedChildren;
1✔
417
    }
1✔
418
  }
419
}
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