Skip to content

Commit 4bbf8ee

Browse files
authored
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 3915d02 commit 4bbf8ee

File tree

6 files changed

+178
-114
lines changed

6 files changed

+178
-114
lines changed

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

Lines changed: 43 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@
3838
import java.util.Map;
3939
import javax.annotation.Nullable;
4040

41-
public final class AutoConfiguredLoadBalancerFactory {
41+
public final class AutoConfiguredLoadBalancerFactory extends LoadBalancerProvider {
4242

4343
private final LoadBalancerRegistry registry;
44-
private final String defaultPolicy;
44+
private final LoadBalancerProvider defaultProvider;
4545

4646
public AutoConfiguredLoadBalancerFactory(String defaultPolicy) {
4747
this(LoadBalancerRegistry.getDefaultRegistry(), defaultPolicy);
@@ -50,71 +50,47 @@ public AutoConfiguredLoadBalancerFactory(String defaultPolicy) {
5050
@VisibleForTesting
5151
AutoConfiguredLoadBalancerFactory(LoadBalancerRegistry registry, String defaultPolicy) {
5252
this.registry = checkNotNull(registry, "registry");
53-
this.defaultPolicy = checkNotNull(defaultPolicy, "defaultPolicy");
53+
LoadBalancerProvider provider =
54+
registry.getProvider(checkNotNull(defaultPolicy, "defaultPolicy"));
55+
if (provider == null) {
56+
Status status = Status.INTERNAL.withDescription("Could not find policy '" + defaultPolicy
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(
60+
ConnectivityState.TRANSIENT_FAILURE,
61+
new LoadBalancer.FixedResultPicker(PickResult.withError(status)),
62+
status);
63+
}
64+
this.defaultProvider = provider;
5465
}
5566

67+
@Override
5668
public AutoConfiguredLoadBalancer newLoadBalancer(Helper helper) {
5769
return new AutoConfiguredLoadBalancer(helper);
5870
}
5971

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

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

9784
/**
9885
* Returns non-OK status if the delegate rejects the resolvedAddresses (e.g. if it does not
9986
* support an empty list).
10087
*/
101-
Status tryAcceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
88+
@Override
89+
public Status acceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
10290
PolicySelection policySelection =
10391
(PolicySelection) resolvedAddresses.getLoadBalancingPolicyConfig();
10492

10593
if (policySelection == null) {
106-
LoadBalancerProvider defaultProvider;
107-
try {
108-
defaultProvider = getProviderOrThrow(defaultPolicy, "using default policy");
109-
} catch (PolicyException e) {
110-
Status s = Status.INTERNAL.withDescription(e.getMessage());
111-
helper.updateBalancingState(
112-
ConnectivityState.TRANSIENT_FAILURE, new FixedResultPicker(PickResult.withError(s)));
113-
delegate.shutdown();
114-
delegateProvider = null;
115-
delegate = new NoopLoadBalancer();
116-
return Status.OK;
117-
}
11894
policySelection =
11995
new PolicySelection(defaultProvider, /* config= */ null);
12096
}
@@ -145,20 +121,24 @@ Status tryAcceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
145121
.build());
146122
}
147123

148-
void handleNameResolutionError(Status error) {
124+
@Override
125+
public void handleNameResolutionError(Status error) {
149126
getDelegate().handleNameResolutionError(error);
150127
}
151128

129+
@Override
152130
@Deprecated
153-
void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) {
131+
public void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) {
154132
getDelegate().handleSubchannelState(subchannel, stateInfo);
155133
}
156134

157-
void requestConnection() {
135+
@Override
136+
public void requestConnection() {
158137
getDelegate().requestConnection();
159138
}
160139

161-
void shutdown() {
140+
@Override
141+
public void shutdown() {
162142
delegate.shutdown();
163143
delegate = null;
164144
}
@@ -179,16 +159,6 @@ LoadBalancerProvider getDelegateProvider() {
179159
}
180160
}
181161

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-
192162
/**
193163
* Parses first available LoadBalancer policy from service config. Available LoadBalancer should
194164
* be registered to {@link LoadBalancerRegistry}. If the first available LoadBalancer policy is
@@ -209,8 +179,11 @@ private LoadBalancerProvider getProviderOrThrow(String policy, String choiceReas
209179
*
210180
* @return the parsed {@link PolicySelection}, or {@code null} if no selection could be made.
211181
*/
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.
212184
@Nullable
213-
ConfigOrError parseLoadBalancerPolicy(Map<String, ?> serviceConfig) {
185+
@Override
186+
public ConfigOrError parseLoadBalancingPolicyConfig(Map<String, ?> serviceConfig) {
214187
try {
215188
List<LbConfig> loadBalancerConfigs = null;
216189
if (serviceConfig != null) {
@@ -228,12 +201,18 @@ ConfigOrError parseLoadBalancerPolicy(Map<String, ?> serviceConfig) {
228201
}
229202
}
230203

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

235-
private PolicyException(String msg) {
236-
super(msg);
237-
}
209+
@Override
210+
public int getPriority() {
211+
return 5;
212+
}
213+
214+
@Override
215+
public String getPolicyName() {
216+
return "auto_configured_internal";
238217
}
239218
}
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;
@@ -163,7 +163,7 @@ public Result selectConfig(PickSubchannelArgs args) {
163163
private final URI targetUri;
164164
private final NameResolverProvider nameResolverProvider;
165165
private final NameResolver.Args nameResolverArgs;
166-
private final AutoConfiguredLoadBalancerFactory loadBalancerFactory;
166+
private final LoadBalancerProvider loadBalancerFactory;
167167
private final ClientTransportFactory originalTransportFactory;
168168
@Nullable
169169
private final ChannelCredentials originalChannelCreds;
@@ -1342,7 +1342,7 @@ void remove(RetriableStream<?> retriableStream) {
13421342
}
13431343

13441344
private final class LbHelperImpl extends LoadBalancer.Helper {
1345-
AutoConfiguredLoadBalancer lb;
1345+
LoadBalancer lb;
13461346

13471347
@Override
13481348
public AbstractSubchannel createSubchannel(CreateSubchannelArgs args) {
@@ -1727,7 +1727,7 @@ public Status onResult2(final ResolutionResult resolutionResult) {
17271727
.setAddresses(serversOrError.getValue())
17281728
.setAttributes(attributes)
17291729
.setLoadBalancingPolicyConfig(effectiveServiceConfig.getLoadBalancingConfig());
1730-
Status addressAcceptanceStatus = helper.lb.tryAcceptResolvedAddresses(
1730+
Status addressAcceptanceStatus = helper.lb.acceptResolvedAddresses(
17311731
resolvedAddresses.build());
17321732
return addressAcceptanceStatus;
17331733
}

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)