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

grpc / grpc-java / #20136

06 Jan 2026 05:27AM UTC coverage: 88.693% (+0.01%) from 88.681%
#20136

push

github

web-flow
core: Implement oobChannel with resolvingOobChannel

The most important part of this change is to ensure that CallCredentials
are not propagated to the OOB channel. Because the authority of the OOB
channel doesn't match the parent channel, we must ensure that any bearer
tokens are not sent to the different server. However, this was not a
problem because resolvingOobChannel has the same constraint. (RLS has a
different constraint, but we were able to let RLS manage that itself.)

This commit does change the behavior of channelz, shutdown, and metrics
for the OOB channel. Previously the OOB channel was registered with
channelz, but it is only a TODO for resolving channel. Channel shutdown
no longer shuts down the OOB channel and it no longer waits for the OOB
channel to terminate before becoming terminated itself. That is also a
pre-existing TODO. Since ManagedChannelImplBuilder is now being used,
global configurators and census are enabled. The proper behavior here is
still being determined, but we would want it to be the same for
resolving OOB channel and OOB channel.

The OOB channel used to refresh the name resolution when the subchannel
went IDLE or TF. That is an older behavior from back when regular
subchannels would also cause the name resolver to refresh. Now-a-days
that goes though the LB tree. gRPC-LB already refreshes name resolution
when its RPC closes, so no longer doing it automatically should be fine.

balancerRpcExecutorPool no longer has its lifetime managed by the child.
It'd be easiest to not use it at all from OOB channel, which wouldn't
actually change the regular behavior, as channels already use the same
executor by default. However, the tests are making use of the executor
being injected, so some propagation needs to be preserved.

Lots of OOB channel tests were deleted, but these were either testing
OobChannel, which is now gone, or things like channelz, which are known
to no longer work like before.

35361 of 39869 relevant lines covered (88.69%)

0.89 hits per line

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

76.54
/../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 {
42

43
  private final LoadBalancerRegistry registry;
44
  private final String defaultPolicy;
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
    this.defaultPolicy = checkNotNull(defaultPolicy, "defaultPolicy");
1✔
54
  }
1✔
55

56
  public AutoConfiguredLoadBalancer newLoadBalancer(Helper helper) {
57
    return new AutoConfiguredLoadBalancer(helper);
1✔
58
  }
59

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

80
  @VisibleForTesting
81
  public final class AutoConfiguredLoadBalancer {
82
    private final Helper helper;
83
    private LoadBalancer delegate;
84
    private LoadBalancerProvider delegateProvider;
85

86
    AutoConfiguredLoadBalancer(Helper helper) {
1✔
87
      this.helper = helper;
1✔
88
      delegateProvider = registry.getProvider(defaultPolicy);
1✔
89
      if (delegateProvider == null) {
1✔
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
      }
94
      delegate = delegateProvider.newLoadBalancer(helper);
1✔
95
    }
1✔
96

97
    /**
98
     * Returns non-OK status if the delegate rejects the resolvedAddresses (e.g. if it does not
99
     * support an empty list).
100
     */
101
    Status tryAcceptResolvedAddresses(ResolvedAddresses resolvedAddresses) {
102
      PolicySelection policySelection =
1✔
103
          (PolicySelection) resolvedAddresses.getLoadBalancingPolicyConfig();
1✔
104

105
      if (policySelection == null) {
1✔
106
        LoadBalancerProvider defaultProvider;
107
        try {
108
          defaultProvider = getProviderOrThrow(defaultPolicy, "using default policy");
1✔
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
        }
1✔
118
        policySelection =
1✔
119
            new PolicySelection(defaultProvider, /* config= */ null);
120
      }
121

122
      if (delegateProvider == null
1✔
123
          || !policySelection.provider.getPolicyName().equals(delegateProvider.getPolicyName())) {
1✔
124
        helper.updateBalancingState(
1✔
125
            ConnectivityState.CONNECTING, new FixedResultPicker(PickResult.withNoResult()));
1✔
126
        delegate.shutdown();
1✔
127
        delegateProvider = policySelection.provider;
1✔
128
        LoadBalancer old = delegate;
1✔
129
        delegate = delegateProvider.newLoadBalancer(helper);
1✔
130
        helper.getChannelLogger().log(
1✔
131
            ChannelLogLevel.INFO, "Load balancer changed from {0} to {1}",
132
            old.getClass().getSimpleName(), delegate.getClass().getSimpleName());
1✔
133
      }
134
      Object lbConfig = policySelection.config;
1✔
135
      if (lbConfig != null) {
1✔
136
        helper.getChannelLogger().log(
1✔
137
            ChannelLogLevel.DEBUG, "Load-balancing config: {0}", policySelection.config);
138
      }
139

140
      return getDelegate().acceptResolvedAddresses(
1✔
141
          ResolvedAddresses.newBuilder()
1✔
142
              .setAddresses(resolvedAddresses.getAddresses())
1✔
143
              .setAttributes(resolvedAddresses.getAttributes())
1✔
144
              .setLoadBalancingPolicyConfig(lbConfig)
1✔
145
              .build());
1✔
146
    }
147

148
    void handleNameResolutionError(Status error) {
149
      getDelegate().handleNameResolutionError(error);
1✔
150
    }
1✔
151

152
    @Deprecated
153
    void handleSubchannelState(Subchannel subchannel, ConnectivityStateInfo stateInfo) {
154
      getDelegate().handleSubchannelState(subchannel, stateInfo);
1✔
155
    }
1✔
156

157
    void requestConnection() {
158
      getDelegate().requestConnection();
1✔
159
    }
1✔
160

161
    void shutdown() {
162
      delegate.shutdown();
1✔
163
      delegate = null;
1✔
164
    }
1✔
165

166
    @VisibleForTesting
167
    public LoadBalancer getDelegate() {
168
      return delegate;
1✔
169
    }
170

171
    @VisibleForTesting
172
    void setDelegate(LoadBalancer lb) {
173
      delegate = lb;
1✔
174
    }
1✔
175

176
    @VisibleForTesting
177
    LoadBalancerProvider getDelegateProvider() {
178
      return delegateProvider;
1✔
179
    }
180
  }
181

182
  private LoadBalancerProvider getProviderOrThrow(String policy, String choiceReason)
183
      throws PolicyException {
184
    LoadBalancerProvider provider = registry.getProvider(policy);
1✔
185
    if (provider == null) {
1✔
186
      throw new PolicyException(
×
187
          "Trying to load '" + policy + "' because " + choiceReason + ", but it's unavailable");
188
    }
189
    return provider;
1✔
190
  }
191

192
  /**
193
   * Parses first available LoadBalancer policy from service config. Available LoadBalancer should
194
   * be registered to {@link LoadBalancerRegistry}. If the first available LoadBalancer policy is
195
   * invalid, it doesn't fall-back to next available policy, instead it returns error. This also
196
   * means, it ignores LoadBalancer policies after the first available one even if any of them are
197
   * invalid.
198
   *
199
   * <p>Order of policy preference:
200
   *
201
   * <ol>
202
   *    <li>Policy from "loadBalancingConfig" if present</li>
203
   *    <li>The policy from deprecated "loadBalancingPolicy" if present</li>
204
   * </ol>
205
   * </p>
206
   *
207
   * <p>Unlike a normal {@link LoadBalancer.Factory}, this accepts a full service config rather than
208
   * the LoadBalancingConfig.
209
   *
210
   * @return the parsed {@link PolicySelection}, or {@code null} if no selection could be made.
211
   */
212
  @Nullable
213
  ConfigOrError parseLoadBalancerPolicy(Map<String, ?> serviceConfig) {
214
    try {
215
      List<LbConfig> loadBalancerConfigs = null;
1✔
216
      if (serviceConfig != null) {
1✔
217
        List<Map<String, ?>> rawLbConfigs =
1✔
218
            ServiceConfigUtil.getLoadBalancingConfigsFromServiceConfig(serviceConfig);
1✔
219
        loadBalancerConfigs = ServiceConfigUtil.unwrapLoadBalancingConfigList(rawLbConfigs);
1✔
220
      }
221
      if (loadBalancerConfigs != null && !loadBalancerConfigs.isEmpty()) {
1✔
222
        return ServiceConfigUtil.selectLbPolicyFromList(loadBalancerConfigs, registry);
1✔
223
      }
224
      return null;
1✔
225
    } catch (RuntimeException e) {
×
226
      return ConfigOrError.fromError(
×
227
          Status.UNKNOWN.withDescription("can't parse load balancer configuration").withCause(e));
×
228
    }
229
  }
230

231
  @VisibleForTesting
232
  static final class PolicyException extends Exception {
233
    private static final long serialVersionUID = 1L;
234

235
    private PolicyException(String msg) {
236
      super(msg);
×
237
    }
×
238
  }
239
}
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