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

grpc / grpc-java / #20137

06 Jan 2026 05:30AM UTC coverage: 88.709% (+0.02%) from 88.693%
#20137

push

github

web-flow
core: Convert AutoConfiguredLB to an actual LB

AutoConfiguredLB wasn't able to be a LB because it needed to be able to
reject configuration to cause the NameResolver to refresh. Since
4b4cb0bd3b, and especially 9888a54abd, the LB API now is able to do this
directly.

The real end-goal of this work is to replace (much of) AutoConfiguredLB
with GracefulSwitchLB. The AutoConfiguredLBFactory will still be needed
for config handling, but the LB itself could just become an instance of
GracefulSwitchLB. Using GracefulSwitchLB will let us reuse more of the
config parsing logic, avoids a latency hit when the top-level policy
changes, and gets rid of the last usage of
ServiceConfigUtil.selectLbPolicyFromList() outside of GracefulSwitchLB.
Go and C are already using GracefulSwitchLB for the top-level policy.

Moving the defaultProvider creation earlier was to allow
parseLoadBalancingPolicyConfig() to never return null. However, that ran
into some simple but annoying test failures because the service config
was now being detected as changed. That's solveable, but turns out to be
more involved than this change itself, so that's left for later. Since
the error handling is nicer now and the earlier creation will be needed
eventually anyway, I left the earlier creation in-place even though it
technically doesn't have to be done as part of this commit.

35372 of 39874 relevant lines covered (88.71%)

0.89 hits per line

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

91.3
/../core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java
1
/*
2
 * Copyright 2018 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.internal;
18

19
import static com.google.common.base.Preconditions.checkNotNull;
20

21
import com.google.common.annotations.VisibleForTesting;
22
import io.grpc.ChannelLogger.ChannelLogLevel;
23
import io.grpc.ConnectivityState;
24
import io.grpc.ConnectivityStateInfo;
25
import io.grpc.LoadBalancer;
26
import io.grpc.LoadBalancer.FixedResultPicker;
27
import io.grpc.LoadBalancer.Helper;
28
import io.grpc.LoadBalancer.PickResult;
29
import io.grpc.LoadBalancer.ResolvedAddresses;
30
import io.grpc.LoadBalancer.Subchannel;
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.ServiceConfigUtil.LbConfig;
36
import io.grpc.internal.ServiceConfigUtil.PolicySelection;
37
import java.util.List;
38
import java.util.Map;
39
import javax.annotation.Nullable;
40

41
public final class AutoConfiguredLoadBalancerFactory extends LoadBalancerProvider {
42

43
  private final LoadBalancerRegistry registry;
44
  private final LoadBalancerProvider defaultProvider;
45

46
  public AutoConfiguredLoadBalancerFactory(String defaultPolicy) {
47
    this(LoadBalancerRegistry.getDefaultRegistry(), defaultPolicy);
1✔
48
  }
1✔
49

50
  @VisibleForTesting
51
  AutoConfiguredLoadBalancerFactory(LoadBalancerRegistry registry, String defaultPolicy) {
1✔
52
    this.registry = checkNotNull(registry, "registry");
1✔
53
    LoadBalancerProvider provider =
1✔
54
        registry.getProvider(checkNotNull(defaultPolicy, "defaultPolicy"));
1✔
55
    if (provider == null) {
1✔
56
      Status status = Status.INTERNAL.withDescription("Could not find policy '" + defaultPolicy
1✔
57
          + "'. Make sure its implementation is either registered to LoadBalancerRegistry or"
58
          + " included in META-INF/services/io.grpc.LoadBalancerProvider from your jar files.");
59
      provider = new FixedPickerLoadBalancerProvider(
1✔
60
          ConnectivityState.TRANSIENT_FAILURE,
61
          new LoadBalancer.FixedResultPicker(PickResult.withError(status)),
1✔
62
          status);
63
    }
64
    this.defaultProvider = provider;
1✔
65
  }
1✔
66

67
  @Override
68
  public AutoConfiguredLoadBalancer newLoadBalancer(Helper helper) {
69
    return new AutoConfiguredLoadBalancer(helper);
1✔
70
  }
71

72
  @VisibleForTesting
73
  public final class AutoConfiguredLoadBalancer extends LoadBalancer {
74
    private final Helper helper;
75
    private LoadBalancer delegate;
76
    private LoadBalancerProvider delegateProvider;
77

78
    AutoConfiguredLoadBalancer(Helper helper) {
1✔
79
      this.helper = helper;
1✔
80
      this.delegateProvider = defaultProvider;
1✔
81
      delegate = delegateProvider.newLoadBalancer(helper);
1✔
82
    }
1✔
83

84
    /**
85
     * Returns non-OK status if the delegate rejects the resolvedAddresses (e.g. if it does not
86
     * support an empty list).
87
     */
88
    @Override
89
    public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
90
      PolicySelection policySelection =
1✔
91
          (PolicySelection) resolvedAddresses.getLoadBalancingPolicyConfig();
1✔
92

93
      if (policySelection == null) {
1✔
94
        policySelection =
1✔
95
            new PolicySelection(defaultProvider, /* config= */ null);
1✔
96
      }
97

98
      if (delegateProvider == null
1✔
99
          || !policySelection.provider.getPolicyName().equals(delegateProvider.getPolicyName())) {
1✔
100
        helper.updateBalancingState(
1✔
101
            ConnectivityState.CONNECTING, new FixedResultPicker(PickResult.withNoResult()));
1✔
102
        delegate.shutdown();
1✔
103
        delegateProvider = policySelection.provider;
1✔
104
        LoadBalancer old = delegate;
1✔
105
        delegate = delegateProvider.newLoadBalancer(helper);
1✔
106
        helper.getChannelLogger().log(
1✔
107
            ChannelLogLevel.INFO, "Load balancer changed from {0} to {1}",
108
            old.getClass().getSimpleName(), delegate.getClass().getSimpleName());
1✔
109
      }
110
      Object lbConfig = policySelection.config;
1✔
111
      if (lbConfig != null) {
1✔
112
        helper.getChannelLogger().log(
1✔
113
            ChannelLogLevel.DEBUG, "Load-balancing config: {0}", policySelection.config);
114
      }
115

116
      return getDelegate().acceptResolvedAddresses(
1✔
117
          ResolvedAddresses.newBuilder()
1✔
118
              .setAddresses(resolvedAddresses.getAddresses())
1✔
119
              .setAttributes(resolvedAddresses.getAttributes())
1✔
120
              .setLoadBalancingPolicyConfig(lbConfig)
1✔
121
              .build());
1✔
122
    }
123

124
    @Override
125
    public void handleNameResolutionError(Status error) {
126
      getDelegate().handleNameResolutionError(error);
1✔
127
    }
1✔
128

129
    @Override
130
    @Deprecated
131
    public void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) {
132
      getDelegate().handleSubchannelState(subchannel, stateInfo);
1✔
133
    }
1✔
134

135
    @Override
136
    public void requestConnection() {
137
      getDelegate().requestConnection();
1✔
138
    }
1✔
139

140
    @Override
141
    public void shutdown() {
142
      delegate.shutdown();
1✔
143
      delegate = null;
1✔
144
    }
1✔
145

146
    @VisibleForTesting
147
    public LoadBalancer getDelegate() {
148
      return delegate;
1✔
149
    }
150

151
    @VisibleForTesting
152
    void setDelegate(LoadBalancer lb) {
153
      delegate = lb;
1✔
154
    }
1✔
155

156
    @VisibleForTesting
157
    LoadBalancerProvider getDelegateProvider() {
158
      return delegateProvider;
1✔
159
    }
160
  }
161

162
  /**
163
   * Parses first available LoadBalancer policy from service config. Available LoadBalancer should
164
   * be registered to {@link LoadBalancerRegistry}. If the first available LoadBalancer policy is
165
   * invalid, it doesn't fall-back to next available policy, instead it returns error. This also
166
   * means, it ignores LoadBalancer policies after the first available one even if any of them are
167
   * invalid.
168
   *
169
   * <p>Order of policy preference:
170
   *
171
   * <ol>
172
   *    <li>Policy from "loadBalancingConfig" if present</li>
173
   *    <li>The policy from deprecated "loadBalancingPolicy" if present</li>
174
   * </ol>
175
   * </p>
176
   *
177
   * <p>Unlike a normal {@link LoadBalancer.Factory}, this accepts a full service config rather than
178
   * the LoadBalancingConfig.
179
   *
180
   * @return the parsed {@link PolicySelection}, or {@code null} if no selection could be made.
181
   */
182
  // TODO(ejona): The Provider API doesn't allow null, but ScParser can handle this and it will need
183
  // tweaking to ManagedChannelImpl.defaultServiceConfig to fix.
184
  @Nullable
185
  @Override
186
  public ConfigOrError parseLoadBalancingPolicyConfig(Map<String, ?> serviceConfig) {
187
    try {
188
      List<LbConfig> loadBalancerConfigs = null;
1✔
189
      if (serviceConfig != null) {
1✔
190
        List<Map<String, ?>> rawLbConfigs =
1✔
191
            ServiceConfigUtil.getLoadBalancingConfigsFromServiceConfig(serviceConfig);
1✔
192
        loadBalancerConfigs = ServiceConfigUtil.unwrapLoadBalancingConfigList(rawLbConfigs);
1✔
193
      }
194
      if (loadBalancerConfigs != null && !loadBalancerConfigs.isEmpty()) {
1✔
195
        return ServiceConfigUtil.selectLbPolicyFromList(loadBalancerConfigs, registry);
1✔
196
      }
197
      return null;
1✔
198
    } catch (RuntimeException e) {
×
199
      return ConfigOrError.fromError(
×
200
          Status.UNKNOWN.withDescription("can't parse load balancer configuration").withCause(e));
×
201
    }
202
  }
203

204
  @Override
205
  public boolean isAvailable() {
206
    return true;
×
207
  }
208

209
  @Override
210
  public int getPriority() {
211
    return 5;
×
212
  }
213

214
  @Override
215
  public String getPolicyName() {
216
    return "auto_configured_internal";
×
217
  }
218
}
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