Skip to content

Commit 3e524f4

Browse files
committed
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 4b4cb0b, and especially 9888a54, 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.
1 parent 4b41af6 commit 3e524f4

6 files changed

Lines changed: 178 additions & 126 deletions

File tree

core/src/main/java/io/grpc/internal/AutoConfiguredLoadBalancerFactory.java

Lines changed: 43 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@
4040
import java.util.Map;
4141
import javax.annotation.Nullable;
4242

43-
public final class AutoConfiguredLoadBalancerFactory {
43+
public final class AutoConfiguredLoadBalancerFactory extends LoadBalancerProvider {
4444

4545
private final LoadBalancerRegistry registry;
46-
private final String defaultPolicy;
46+
private final LoadBalancerProvider defaultProvider;
4747

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

69+
@Override
5870
public AutoConfiguredLoadBalancer newLoadBalancer(Helper helper) {
5971
return new AutoConfiguredLoadBalancer(helper);
6072
}
6173

62-
private static final class NoopLoadBalancer extends LoadBalancer {
63-
64-
@Override
65-
@Deprecated
66-
@SuppressWarnings("InlineMeSuggester")
67-
public void handleResolvedAddresses(ResolvedAddresses resolvedAddresses) {
68-
}
69-
70-
@Override
71-
public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
72-
return Status.OK;
73-
}
74-
75-
@Override
76-
public void handleNameResolutionError(Status error) {}
77-
78-
@Override
79-
public void shutdown() {}
80-
}
81-
8274
@VisibleForTesting
83-
public final class AutoConfiguredLoadBalancer {
75+
public final class AutoConfiguredLoadBalancer extends LoadBalancer {
8476
private final Helper helper;
8577
private LoadBalancer delegate;
8678
private LoadBalancerProvider delegateProvider;
8779

8880
AutoConfiguredLoadBalancer(Helper helper) {
8981
this.helper = helper;
90-
delegateProvider = registry.getProvider(defaultPolicy);
91-
if (delegateProvider == null) {
92-
throw new IllegalStateException("Could not find policy '" + defaultPolicy
93-
+ "'. Make sure its implementation is either registered to LoadBalancerRegistry or"
94-
+ " included in META-INF/services/io.grpc.LoadBalancerProvider from your jar files.");
95-
}
82+
this.delegateProvider = defaultProvider;
9683
delegate = delegateProvider.newLoadBalancer(helper);
9784
}
9885

9986
/**
10087
* Returns non-OK status if the delegate rejects the resolvedAddresses (e.g. if it does not
10188
* support an empty list).
10289
*/
103-
Status tryAcceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
90+
@Override
91+
public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
10492
PolicySelection policySelection =
10593
(PolicySelection) resolvedAddresses.getLoadBalancingPolicyConfig();
10694

10795
if (policySelection == null) {
108-
LoadBalancerProvider defaultProvider;
109-
try {
110-
defaultProvider = getProviderOrThrow(defaultPolicy, "using default policy");
111-
} catch (PolicyException e) {
112-
Status s = Status.INTERNAL.withDescription(e.getMessage());
113-
helper.updateBalancingState(ConnectivityState.TRANSIENT_FAILURE, new FailingPicker(s));
114-
delegate.shutdown();
115-
delegateProvider = null;
116-
delegate = new NoopLoadBalancer();
117-
return Status.OK;
118-
}
11996
policySelection =
12097
new PolicySelection(defaultProvider, /* config= */ null);
12198
}
@@ -145,20 +122,24 @@ Status tryAcceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
145122
.build());
146123
}
147124

148-
void handleNameResolutionError(Status error) {
125+
@Override
126+
public void handleNameResolutionError(Status error) {
149127
getDelegate().handleNameResolutionError(error);
150128
}
151129

130+
@Override
152131
@Deprecated
153-
void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) {
132+
public void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) {
154133
getDelegate().handleSubchannelState(subchannel, stateInfo);
155134
}
156135

157-
void requestConnection() {
136+
@Override
137+
public void requestConnection() {
158138
getDelegate().requestConnection();
159139
}
160140

161-
void shutdown() {
141+
@Override
142+
public void shutdown() {
162143
delegate.shutdown();
163144
delegate = null;
164145
}
@@ -179,16 +160,6 @@ LoadBalancerProvider getDelegateProvider() {
179160
}
180161
}
181162

182-
private LoadBalancerProvider getProviderOrThrow(String policy, String choiceReason)
183-
throws PolicyException {
184-
LoadBalancerProvider provider = registry.getProvider(policy);
185-
if (provider == null) {
186-
throw new PolicyException(
187-
"Trying to load '" + policy + "' because " + choiceReason + ", but it's unavailable");
188-
}
189-
return provider;
190-
}
191-
192163
/**
193164
* Parses first available LoadBalancer policy from service config. Available LoadBalancer should
194165
* be registered to {@link LoadBalancerRegistry}. If the first available LoadBalancer policy is
@@ -209,8 +180,11 @@ private LoadBalancerProvider getProviderOrThrow(String policy, String choiceReas
209180
*
210181
* @return the parsed {@link PolicySelection}, or {@code null} if no selection could be made.
211182
*/
183+
// TODO(ejona): The Provider API doesn't allow null, but ScParser can handle this and it will need
184+
// tweaking to ManagedChannelImpl.defaultServiceConfig to fix.
212185
@Nullable
213-
ConfigOrError parseLoadBalancerPolicy(Map<String, ?> serviceConfig) {
186+
@Override
187+
public ConfigOrError parseLoadBalancingPolicyConfig(Map<String, ?> serviceConfig) {
214188
try {
215189
List<LbConfig> loadBalancerConfigs = null;
216190
if (serviceConfig != null) {
@@ -228,13 +202,19 @@ ConfigOrError parseLoadBalancerPolicy(Map<String, ?> serviceConfig) {
228202
}
229203
}
230204

231-
@VisibleForTesting
232-
static final class PolicyException extends Exception {
233-
private static final long serialVersionUID = 1L;
205+
@Override
206+
public boolean isAvailable() {
207+
return true;
208+
}
234209

235-
private PolicyException(String msg) {
236-
super(msg);
237-
}
210+
@Override
211+
public int getPriority() {
212+
return 5;
213+
}
214+
215+
@Override
216+
public String getPolicyName() {
217+
return "auto_configured_internal";
238218
}
239219

240220
private static final class EmptyPicker extends SubchannelPicker {
@@ -249,17 +229,4 @@ public String toString() {
249229
return MoreObjects.toStringHelper(EmptyPicker.class).toString();
250230
}
251231
}
252-
253-
private static final class FailingPicker extends SubchannelPicker {
254-
private final Status failure;
255-
256-
FailingPicker(Status failure) {
257-
this.failure = failure;
258-
}
259-
260-
@Override
261-
public PickResult pickSubchannel(PickSubchannelArgs args) {
262-
return PickResult.withError(failure);
263-
}
264-
}
265232
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright 2026 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 java.util.Objects.requireNonNull;
20+
21+
import io.grpc.ConnectivityState;
22+
import io.grpc.LoadBalancer;
23+
import io.grpc.LoadBalancerProvider;
24+
import io.grpc.Status;
25+
26+
/** A LB provider whose LB always uses the same picker. */
27+
final class FixedPickerLoadBalancerProvider extends LoadBalancerProvider {
28+
private final ConnectivityState state;
29+
private final LoadBalancer.SubchannelPicker picker;
30+
private final Status acceptAddressesStatus;
31+
32+
public FixedPickerLoadBalancerProvider(
33+
ConnectivityState state, LoadBalancer.SubchannelPicker picker, Status acceptAddressesStatus) {
34+
this.state = requireNonNull(state, "state");
35+
this.picker = requireNonNull(picker, "picker");
36+
this.acceptAddressesStatus = requireNonNull(acceptAddressesStatus, "acceptAddressesStatus");
37+
}
38+
39+
@Override
40+
public boolean isAvailable() {
41+
return true;
42+
}
43+
44+
@Override
45+
public int getPriority() {
46+
return 5;
47+
}
48+
49+
@Override
50+
public String getPolicyName() {
51+
return "fixed_picker_lb_internal";
52+
}
53+
54+
@Override
55+
public LoadBalancer newLoadBalancer(LoadBalancer.Helper helper) {
56+
return new FixedPickerLoadBalancer(helper);
57+
}
58+
59+
private final class FixedPickerLoadBalancer extends LoadBalancer {
60+
private final Helper helper;
61+
62+
public FixedPickerLoadBalancer(Helper helper) {
63+
this.helper = requireNonNull(helper, "helper");
64+
}
65+
66+
@Override
67+
public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
68+
helper.updateBalancingState(state, picker);
69+
return acceptAddressesStatus;
70+
}
71+
72+
@Override
73+
public void handleNameResolutionError(Status error) {
74+
helper.updateBalancingState(state, picker);
75+
}
76+
77+
@Override
78+
public void shutdown() {}
79+
}
80+
}

core/src/main/java/io/grpc/internal/ManagedChannelImpl.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
import io.grpc.LoadBalancer.ResolvedAddresses;
7070
import io.grpc.LoadBalancer.SubchannelPicker;
7171
import io.grpc.LoadBalancer.SubchannelStateListener;
72+
import io.grpc.LoadBalancerProvider;
7273
import io.grpc.ManagedChannel;
7374
import io.grpc.ManagedChannelBuilder;
7475
import io.grpc.Metadata;
@@ -85,7 +86,6 @@
8586
import io.grpc.StatusOr;
8687
import io.grpc.SynchronizationContext;
8788
import io.grpc.SynchronizationContext.ScheduledHandle;
88-
import io.grpc.internal.AutoConfiguredLoadBalancerFactory.AutoConfiguredLoadBalancer;
8989
import io.grpc.internal.ClientCallImpl.ClientStreamProvider;
9090
import io.grpc.internal.ClientTransportFactory.SwapChannelCredentialsResult;
9191
import io.grpc.internal.ManagedChannelImplBuilder.ClientTransportFactoryBuilder;
@@ -162,7 +162,7 @@ public Result selectConfig(PickSubchannelArgs args) {
162162
private final URI targetUri;
163163
private final NameResolverProvider nameResolverProvider;
164164
private final NameResolver.Args nameResolverArgs;
165-
private final AutoConfiguredLoadBalancerFactory loadBalancerFactory;
165+
private final LoadBalancerProvider loadBalancerFactory;
166166
private final ClientTransportFactory originalTransportFactory;
167167
@Nullable
168168
private final ChannelCredentials originalChannelCreds;
@@ -1362,7 +1362,7 @@ void remove(RetriableStream<?> retriableStream) {
13621362
}
13631363

13641364
private final class LbHelperImpl extends LoadBalancer.Helper {
1365-
AutoConfiguredLoadBalancer lb;
1365+
LoadBalancer lb;
13661366

13671367
@Override
13681368
public AbstractSubchannel createSubchannel(CreateSubchannelArgs args) {
@@ -1786,7 +1786,7 @@ public Status onResult2(final ResolutionResult resolutionResult) {
17861786
.setAddresses(serversOrError.getValue())
17871787
.setAttributes(attributes)
17881788
.setLoadBalancingPolicyConfig(effectiveServiceConfig.getLoadBalancingConfig());
1789-
Status addressAcceptanceStatus = helper.lb.tryAcceptResolvedAddresses(
1789+
Status addressAcceptanceStatus = helper.lb.acceptResolvedAddresses(
17901790
resolvedAddresses.build());
17911791
return addressAcceptanceStatus;
17921792
}

core/src/main/java/io/grpc/internal/ScParser.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import static com.google.common.base.Preconditions.checkNotNull;
2020

2121
import com.google.common.annotations.VisibleForTesting;
22+
import io.grpc.LoadBalancerProvider;
2223
import io.grpc.NameResolver;
2324
import io.grpc.NameResolver.ConfigOrError;
2425
import io.grpc.Status;
@@ -31,26 +32,28 @@ public final class ScParser extends NameResolver.ServiceConfigParser {
3132
private final boolean retryEnabled;
3233
private final int maxRetryAttemptsLimit;
3334
private final int maxHedgedAttemptsLimit;
34-
private final AutoConfiguredLoadBalancerFactory autoLoadBalancerFactory;
35+
private final LoadBalancerProvider parser;
3536

3637
/** Creates a parse with global retry settings and an auto configured lb factory. */
3738
public ScParser(
3839
boolean retryEnabled,
3940
int maxRetryAttemptsLimit,
4041
int maxHedgedAttemptsLimit,
41-
AutoConfiguredLoadBalancerFactory autoLoadBalancerFactory) {
42+
LoadBalancerProvider parser) {
4243
this.retryEnabled = retryEnabled;
4344
this.maxRetryAttemptsLimit = maxRetryAttemptsLimit;
4445
this.maxHedgedAttemptsLimit = maxHedgedAttemptsLimit;
45-
this.autoLoadBalancerFactory = checkNotNull(autoLoadBalancerFactory, "autoLoadBalancerFactory");
46+
this.parser = checkNotNull(parser, "parser");
4647
}
4748

4849
@Override
4950
public ConfigOrError parseServiceConfig(Map<String, ?> rawServiceConfig) {
5051
try {
5152
Object loadBalancingPolicySelection;
5253
ConfigOrError choiceFromLoadBalancer =
53-
autoLoadBalancerFactory.parseLoadBalancerPolicy(rawServiceConfig);
54+
parser.parseLoadBalancingPolicyConfig(rawServiceConfig);
55+
// TODO(ejona): The Provider API doesn't allow null, but AutoConfiguredLoadBalancerFactory can
56+
// return null and it will need tweaking to ManagedChannelImpl.defaultServiceConfig to fix.
5457
if (choiceFromLoadBalancer == null) {
5558
loadBalancingPolicySelection = null;
5659
} else if (choiceFromLoadBalancer.getError() != null) {

0 commit comments

Comments
 (0)