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

grpc / grpc-java / #20107

04 Dec 2025 09:45PM UTC coverage: 88.523% (-0.004%) from 88.527%
#20107

push

github

ejona86
Bump version to 1.76.3-SNAPSHOT

34646 of 39138 relevant lines covered (88.52%)

0.89 hits per line

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

79.68
/../rls/src/main/java/io/grpc/rls/LbPolicyConfiguration.java
1
/*
2
 * Copyright 2020 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.rls;
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

23
import com.google.common.annotations.VisibleForTesting;
24
import com.google.common.base.MoreObjects;
25
import io.grpc.ChannelLogger.ChannelLogLevel;
26
import io.grpc.ConnectivityState;
27
import io.grpc.LoadBalancer;
28
import io.grpc.LoadBalancer.Helper;
29
import io.grpc.LoadBalancer.Subchannel;
30
import io.grpc.LoadBalancer.SubchannelPicker;
31
import io.grpc.LoadBalancerProvider;
32
import io.grpc.LoadBalancerRegistry;
33
import io.grpc.NameResolver.ConfigOrError;
34
import io.grpc.Status;
35
import io.grpc.internal.ObjectPool;
36
import io.grpc.rls.ChildLoadBalancerHelper.ChildLoadBalancerHelperProvider;
37
import io.grpc.rls.RlsProtoData.RouteLookupConfig;
38
import io.grpc.util.ForwardingLoadBalancerHelper;
39
import java.util.ArrayList;
40
import java.util.Collections;
41
import java.util.HashMap;
42
import java.util.List;
43
import java.util.Map;
44
import java.util.Objects;
45
import javax.annotation.Nullable;
46

47
/** Configuration for RLS load balancing policy. */
48
final class LbPolicyConfiguration {
49

50
  private final RouteLookupConfig routeLookupConfig;
51
  @Nullable
52
  private final Map<String, ?> routeLookupChannelServiceConfig;
53
  private final ChildLoadBalancingPolicy policy;
54

55
  LbPolicyConfiguration(
56
      RouteLookupConfig routeLookupConfig, @Nullable Map<String, ?> routeLookupChannelServiceConfig,
57
      ChildLoadBalancingPolicy policy) {
1✔
58
    this.routeLookupConfig = checkNotNull(routeLookupConfig, "routeLookupConfig");
1✔
59
    this.routeLookupChannelServiceConfig = routeLookupChannelServiceConfig;
1✔
60
    this.policy = checkNotNull(policy, "policy");
1✔
61
  }
1✔
62

63
  RouteLookupConfig getRouteLookupConfig() {
64
    return routeLookupConfig;
1✔
65
  }
66

67
  @Nullable
68
  Map<String, ?> getRouteLookupChannelServiceConfig() {
69
    return routeLookupChannelServiceConfig;
1✔
70
  }
71

72
  ChildLoadBalancingPolicy getLoadBalancingPolicy() {
73
    return policy;
1✔
74
  }
75

76
  @Override
77
  public boolean equals(Object o) {
78
    if (this == o) {
1✔
79
      return true;
×
80
    }
81
    if (o == null || getClass() != o.getClass()) {
1✔
82
      return false;
1✔
83
    }
84
    LbPolicyConfiguration that = (LbPolicyConfiguration) o;
×
85
    return Objects.equals(routeLookupConfig, that.routeLookupConfig)
×
86
        && Objects.equals(routeLookupChannelServiceConfig, that.routeLookupChannelServiceConfig)
×
87
        && Objects.equals(policy, that.policy);
×
88
  }
89

90
  @Override
91
  public int hashCode() {
92
    return Objects.hash(routeLookupConfig, routeLookupChannelServiceConfig, policy);
×
93
  }
94

95
  @Override
96
  public String toString() {
97
    return MoreObjects.toStringHelper(this)
×
98
        .add("routeLookupConfig", routeLookupConfig)
×
99
        .add("routeLookupChannelServiceConfig", routeLookupChannelServiceConfig)
×
100
        .add("policy", policy)
×
101
        .toString();
×
102
  }
103

104
  /** ChildLoadBalancingPolicy is an elected child policy to delegate requests. */
105
  static final class ChildLoadBalancingPolicy {
106

107
    private final Map<String, Object> effectiveRawChildPolicy;
108
    private final LoadBalancerProvider effectiveLbProvider;
109
    private final String targetFieldName;
110

111
    @VisibleForTesting
112
    ChildLoadBalancingPolicy(
113
        String targetFieldName,
114
        Map<String, Object> effectiveRawChildPolicy,
115
        LoadBalancerProvider effectiveLbProvider) {
1✔
116
      checkArgument(
1✔
117
          targetFieldName != null && !targetFieldName.isEmpty(),
1✔
118
          "targetFieldName cannot be empty or null");
119
      this.targetFieldName = targetFieldName;
1✔
120
      this.effectiveRawChildPolicy =
1✔
121
          checkNotNull(effectiveRawChildPolicy, "effectiveRawChildPolicy");
1✔
122
      this.effectiveLbProvider = checkNotNull(effectiveLbProvider, "effectiveLbProvider");
1✔
123
    }
1✔
124

125
    /** Creates ChildLoadBalancingPolicy. */
126
    @SuppressWarnings("unchecked")
127
    static ChildLoadBalancingPolicy create(
128
        String childPolicyConfigTargetFieldName, List<Map<String, ?>> childPolicies)
129
        throws InvalidChildPolicyConfigException {
130
      Map<String, Object> effectiveChildPolicy = null;
1✔
131
      LoadBalancerProvider effectiveLbProvider = null;
1✔
132
      List<String> policyTried = new ArrayList<>();
1✔
133

134
      LoadBalancerRegistry lbRegistry = LoadBalancerRegistry.getDefaultRegistry();
1✔
135
      for (Map<String, ?> childPolicy : childPolicies) {
1✔
136
        if (childPolicy.isEmpty()) {
1✔
137
          continue;
×
138
        }
139
        if (childPolicy.size() != 1) {
1✔
140
          throw
1✔
141
              new InvalidChildPolicyConfigException(
142
                  "childPolicy should have exactly one loadbalancing policy");
143
        }
144
        String policyName = childPolicy.keySet().iterator().next();
1✔
145
        LoadBalancerProvider provider = lbRegistry.getProvider(policyName);
1✔
146
        if (provider != null) {
1✔
147
          effectiveLbProvider = provider;
1✔
148
          effectiveChildPolicy = Collections.unmodifiableMap(childPolicy);
1✔
149
          break;
1✔
150
        }
151
        policyTried.add(policyName);
×
152
      }
×
153
      if (effectiveChildPolicy == null) {
1✔
154
        throw
1✔
155
            new InvalidChildPolicyConfigException(
156
                String.format("no valid childPolicy found, policy tried: %s", policyTried));
1✔
157
      }
158
      return
1✔
159
          new ChildLoadBalancingPolicy(
160
              childPolicyConfigTargetFieldName,
161
              (Map<String, Object>) effectiveChildPolicy.values().iterator().next(),
1✔
162
              effectiveLbProvider);
163
    }
164

165
    /** Creates a child load balancer config for given target from elected raw child policy. */
166
    Map<String, ?> getEffectiveChildPolicy(String target) {
167
      Map<String, Object> childPolicy = new HashMap<>(effectiveRawChildPolicy);
1✔
168
      childPolicy.put(targetFieldName, target);
1✔
169
      return childPolicy;
1✔
170
    }
171

172
    /** Returns the elected child {@link LoadBalancerProvider}. */
173
    LoadBalancerProvider getEffectiveLbProvider() {
174
      return effectiveLbProvider;
1✔
175
    }
176

177
    @Override
178
    public boolean equals(Object o) {
179
      if (this == o) {
×
180
        return true;
×
181
      }
182
      if (o == null || getClass() != o.getClass()) {
×
183
        return false;
×
184
      }
185
      ChildLoadBalancingPolicy that = (ChildLoadBalancingPolicy) o;
×
186
      return Objects.equals(effectiveRawChildPolicy, that.effectiveRawChildPolicy)
×
187
          && Objects.equals(effectiveLbProvider, that.effectiveLbProvider)
×
188
          && Objects.equals(targetFieldName, that.targetFieldName);
×
189
    }
190

191
    @Override
192
    public int hashCode() {
193
      return Objects.hash(effectiveRawChildPolicy, effectiveLbProvider, targetFieldName);
×
194
    }
195

196
    @Override
197
    public String toString() {
198
      return MoreObjects.toStringHelper(this)
×
199
          .add("effectiveRawChildPolicy", effectiveRawChildPolicy)
×
200
          .add("effectiveLbProvider", effectiveLbProvider)
×
201
          .add("childPolicyConfigTargetFieldName", targetFieldName)
×
202
          .toString();
×
203
    }
204
  }
205

206
  /** Factory for {@link ChildPolicyWrapper}. Not thread-safe. */
207
  static final class RefCountedChildPolicyWrapperFactory {
208
    @VisibleForTesting
1✔
209
    final Map<String /* target */, RefCountedChildPolicyWrapper> childPolicyMap =
210
        new HashMap<>();
211

212
    private final ChildLoadBalancerHelperProvider childLbHelperProvider;
213
    private final ChildLbStatusListener childLbStatusListener;
214
    private final ChildLoadBalancingPolicy childPolicy;
215
    private ResolvedAddressFactory childLbResolvedAddressFactory;
216

217
    public RefCountedChildPolicyWrapperFactory(
218
        ChildLoadBalancingPolicy childPolicy,
219
        ResolvedAddressFactory childLbResolvedAddressFactory,
220
        ChildLoadBalancerHelperProvider childLbHelperProvider,
221
        ChildLbStatusListener childLbStatusListener) {
1✔
222
      this.childPolicy = checkNotNull(childPolicy, "childPolicy");
1✔
223
      this.childLbResolvedAddressFactory =
1✔
224
          checkNotNull(childLbResolvedAddressFactory, "childLbResolvedAddressFactory");
1✔
225
      this.childLbHelperProvider = checkNotNull(childLbHelperProvider, "childLbHelperProvider");
1✔
226
      this.childLbStatusListener = checkNotNull(childLbStatusListener, "childLbStatusListener");
1✔
227
    }
1✔
228

229
    void init() {
230
      childLbHelperProvider.init();
1✔
231
    }
1✔
232

233
    Status acceptResolvedAddressFactory(ResolvedAddressFactory childLbResolvedAddressFactory) {
234
      this.childLbResolvedAddressFactory = childLbResolvedAddressFactory;
1✔
235
      Status status = Status.OK;
1✔
236
      for (RefCountedChildPolicyWrapper wrapper : childPolicyMap.values()) {
1✔
237
        Status newStatus =
1✔
238
            wrapper.childPolicyWrapper.acceptResolvedAddressFactory(childLbResolvedAddressFactory);
1✔
239
        if (!newStatus.isOk()) {
1✔
240
          status = newStatus;
×
241
        }
242
      }
1✔
243
      return status;
1✔
244
    }
245

246
    ChildPolicyWrapper createOrGet(String target) {
247
      // TODO(creamsoup) check if the target is valid or not
248
      RefCountedChildPolicyWrapper pooledChildPolicyWrapper = childPolicyMap.get(target);
1✔
249
      if (pooledChildPolicyWrapper == null) {
1✔
250
        ChildPolicyWrapper childPolicyWrapper = new ChildPolicyWrapper(
1✔
251
            target, childPolicy, childLbHelperProvider, childLbStatusListener);
252
        pooledChildPolicyWrapper = RefCountedChildPolicyWrapper.of(childPolicyWrapper);
1✔
253
        childPolicyMap.put(target, pooledChildPolicyWrapper);
1✔
254
        childPolicyWrapper.start(childLbResolvedAddressFactory);
1✔
255
        return pooledChildPolicyWrapper.getObject();
1✔
256
      } else {
257
        ChildPolicyWrapper childPolicyWrapper = pooledChildPolicyWrapper.getObject();
1✔
258
        if (childPolicyWrapper.getPicker() != null) {
1✔
259
          childPolicyWrapper.refreshState();
1✔
260
        }
261
        return childPolicyWrapper;
1✔
262
      }
263
    }
264

265
    List<ChildPolicyWrapper> createOrGet(List<String> targets) {
266
      List<ChildPolicyWrapper> retVal = new ArrayList<>();
1✔
267
      for (String target : targets) {
1✔
268
        retVal.add(createOrGet(target));
1✔
269
      }
1✔
270
      return retVal;
1✔
271
    }
272

273
    void release(ChildPolicyWrapper childPolicyWrapper) {
274
      checkNotNull(childPolicyWrapper, "childPolicyWrapper");
1✔
275
      String target = childPolicyWrapper.getTarget();
1✔
276
      RefCountedChildPolicyWrapper existing = childPolicyMap.get(target);
1✔
277
      checkState(existing != null, "Cannot access already released object");
1✔
278
      existing.returnObject(childPolicyWrapper);
1✔
279
      if (existing.isReleased()) {
1✔
280
        childPolicyMap.remove(target);
1✔
281
      }
282
    }
1✔
283
  }
284

285
  /**
286
   * ChildPolicyWrapper is a wrapper class for child load balancing policy with associated helper /
287
   * utility classes to manage the child policy.
288
   */
289
  static final class ChildPolicyWrapper {
290

291
    private final String target;
292
    private final ChildPolicyReportingHelper helper;
293
    private final LoadBalancer lb;
294
    private final Object childLbConfig;
295
    private volatile SubchannelPicker picker;
296
    private ConnectivityState state;
297

298
    public ChildPolicyWrapper(
299
        String target,
300
        ChildLoadBalancingPolicy childPolicy,
301
        ChildLoadBalancerHelperProvider childLbHelperProvider,
302
        ChildLbStatusListener childLbStatusListener) {
1✔
303
      this.target = target;
1✔
304
      this.helper =
1✔
305
          new ChildPolicyReportingHelper(childLbHelperProvider, childLbStatusListener);
306
      LoadBalancerProvider lbProvider = childPolicy.getEffectiveLbProvider();
1✔
307
      final ConfigOrError lbConfig =
1✔
308
          lbProvider
309
              .parseLoadBalancingPolicyConfig(
1✔
310
                  childPolicy.getEffectiveChildPolicy(target));
1✔
311
      this.lb = lbProvider.newLoadBalancer(helper);
1✔
312
      this.childLbConfig = lbConfig.getConfig();
1✔
313
      helper.getChannelLogger().log(
1✔
314
          ChannelLogLevel.DEBUG, "RLS child lb created. config: {0}", childLbConfig);
315
    }
1✔
316

317
    void start(ResolvedAddressFactory childLbResolvedAddressFactory) {
318
      helper.getSynchronizationContext().execute(
1✔
319
          new Runnable() {
1✔
320
            @Override
321
            public void run() {
322
              if (!acceptResolvedAddressFactory(childLbResolvedAddressFactory).isOk()) {
1✔
323
                helper.refreshNameResolution();
1✔
324
              }
325
              lb.requestConnection();
1✔
326
            }
1✔
327
          });
328
    }
1✔
329

330
    Status acceptResolvedAddressFactory(ResolvedAddressFactory childLbResolvedAddressFactory) {
331
      helper.getSynchronizationContext().throwIfNotInThisSynchronizationContext();
1✔
332
      return lb.acceptResolvedAddresses(childLbResolvedAddressFactory.create(childLbConfig));
1✔
333
    }
334

335
    String getTarget() {
336
      return target;
1✔
337
    }
338

339
    SubchannelPicker getPicker() {
340
      return picker;
1✔
341
    }
342

343
    ChildPolicyReportingHelper getHelper() {
344
      return helper;
1✔
345
    }
346

347
    public ConnectivityState getState() {
348
      return state;
1✔
349
    }
350

351
    void refreshState() {
352
      helper.getSynchronizationContext().execute(
1✔
353
          new Runnable() {
1✔
354
            @Override
355
            public void run() {
356
              helper.updateBalancingState(state, picker);
1✔
357
            }
1✔
358
          }
359
      );
360
    }
1✔
361

362
    void shutdown() {
363
      helper.getSynchronizationContext().execute(
1✔
364
          new Runnable() {
1✔
365
            @Override
366
            public void run() {
367
              lb.shutdown();
1✔
368
            }
1✔
369
          }
370
      );
371
    }
1✔
372

373
    @Override
374
    public String toString() {
375
      return MoreObjects.toStringHelper(this)
×
376
          .add("target", target)
×
377
          .add("picker", picker)
×
378
          .add("state", state)
×
379
          .toString();
×
380
    }
381

382
    /**
383
     * A delegating {@link io.grpc.LoadBalancer.Helper} maintains status of {@link
384
     * ChildPolicyWrapper} when {@link Subchannel} status changed. This helper is used between child
385
     * policy and parent load-balancer where each picker in child policy is governed by a governing
386
     * picker (RlsPicker). The governing picker will be reported back to the parent load-balancer.
387
     */
388
    final class ChildPolicyReportingHelper extends ForwardingLoadBalancerHelper {
389

390
      private final ChildLoadBalancerHelper delegate;
391
      private final ChildLbStatusListener listener;
392

393
      ChildPolicyReportingHelper(
394
          ChildLoadBalancerHelperProvider childHelperProvider,
395
          ChildLbStatusListener listener) {
1✔
396
        checkNotNull(childHelperProvider, "childHelperProvider");
1✔
397
        this.delegate = childHelperProvider.forTarget(getTarget());
1✔
398
        this.listener = checkNotNull(listener, "listener");
1✔
399
      }
1✔
400

401
      @Override
402
      protected Helper delegate() {
403
        return delegate;
1✔
404
      }
405

406
      @Override
407
      public void updateBalancingState(ConnectivityState newState, SubchannelPicker newPicker) {
408
        picker = newPicker;
1✔
409
        state = newState;
1✔
410
        super.updateBalancingState(newState, newPicker);
1✔
411
        listener.onStatusChanged(newState);
1✔
412
      }
1✔
413
    }
414
  }
415

416
  /** Listener for child lb status change events. */
417
  interface ChildLbStatusListener {
418

419
    /** Notifies when child lb status changes. */
420
    void onStatusChanged(ConnectivityState newState);
421
  }
422

423
  private static final class RefCountedChildPolicyWrapper
424
      implements ObjectPool<ChildPolicyWrapper> {
425

426
    private long refCnt;
427
    @Nullable
428
    private ChildPolicyWrapper childPolicyWrapper;
429

430
    private RefCountedChildPolicyWrapper(ChildPolicyWrapper childPolicyWrapper) {
1✔
431
      this.childPolicyWrapper = checkNotNull(childPolicyWrapper, "childPolicyWrapper");
1✔
432
    }
1✔
433

434
    @Override
435
    public ChildPolicyWrapper getObject() {
436
      checkState(!isReleased(), "ChildPolicyWrapper is already released");
1✔
437
      refCnt++;
1✔
438
      return childPolicyWrapper;
1✔
439
    }
440

441
    @Override
442
    @Nullable
443
    public ChildPolicyWrapper returnObject(Object object) {
444
      checkState(
1✔
445
          !isReleased(),
1✔
446
          "cannot return already released ChildPolicyWrapper, this is possibly a bug.");
447
      checkState(
1✔
448
          childPolicyWrapper == object,
449
          "returned object doesn't match the pooled childPolicyWrapper");
450
      long newCnt = --refCnt;
1✔
451
      checkState(newCnt != -1, "Cannot return never pooled childPolicyWrapper");
1✔
452
      if (newCnt == 0) {
1✔
453
        childPolicyWrapper.shutdown();
1✔
454
        childPolicyWrapper = null;
1✔
455
      }
456
      return null;
1✔
457
    }
458

459
    boolean isReleased() {
460
      return childPolicyWrapper == null;
1✔
461
    }
462

463
    static RefCountedChildPolicyWrapper of(ChildPolicyWrapper childPolicyWrapper) {
464
      return new RefCountedChildPolicyWrapper(childPolicyWrapper);
1✔
465
    }
466

467
    @Override
468
    public String toString() {
469
      return MoreObjects.toStringHelper(this)
×
470
          .add("object", childPolicyWrapper)
×
471
          .add("refCnt", refCnt)
×
472
          .toString();
×
473
    }
474
  }
475

476
  /** Exception thrown when attempting to parse child policy encountered parsing issue. */
477
  static final class InvalidChildPolicyConfigException extends Exception {
478

479
    private static final long serialVersionUID = 0L;
480

481
    InvalidChildPolicyConfigException(String message) {
482
      super(message);
1✔
483
    }
1✔
484

485
    @Override
486
    public synchronized Throwable fillInStackTrace() {
487
      // no stack trace above this point
488
      return this;
1✔
489
    }
490
  }
491
}
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