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

grpc / grpc-java / #20177

23 Feb 2026 03:46PM UTC coverage: 88.701% (-0.01%) from 88.714%
#20177

push

github

web-flow
Trigger R8's ServiceLoader optimization

This simplifies R8 Full Mode's configuration when paired with R8
optimizations (which is made more difficult to avoid in AGP 9), as when
the optimization is triggered Full Mode will automatically keep the
constructor for the relevant classes.

android-interop-test doesn't currently enable R8 optimizations, so it
doesn't actually demonstrate the benefit, but I have manually confirmed
that enabling proguard-android-optimize.txt does cause the ServiceLoader
optimization in android-interop-test. Note that full mode is a separate
configuration and not necessary to get the ServiceLoader optimization.

35461 of 39978 relevant lines covered (88.7%)

0.89 hits per line

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

91.49
/../api/src/main/java/io/grpc/LoadBalancerRegistry.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;
18

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

22
import com.google.common.annotations.VisibleForTesting;
23
import java.util.ArrayList;
24
import java.util.Collections;
25
import java.util.LinkedHashMap;
26
import java.util.LinkedHashSet;
27
import java.util.List;
28
import java.util.Map;
29
import java.util.ServiceLoader;
30
import java.util.logging.Level;
31
import java.util.logging.Logger;
32
import javax.annotation.Nullable;
33
import javax.annotation.concurrent.ThreadSafe;
34

35
/**
36
 * Registry of {@link LoadBalancerProvider}s.  The {@link #getDefaultRegistry default instance}
37
 * loads providers at runtime through the Java service provider mechanism.
38
 *
39
 * @since 1.17.0
40
 */
41
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/1771")
42
@ThreadSafe
43
public final class LoadBalancerRegistry {
1✔
44
  private static final Logger logger = Logger.getLogger(LoadBalancerRegistry.class.getName());
1✔
45
  private static LoadBalancerRegistry instance;
46

47
  private final LinkedHashSet<LoadBalancerProvider> allProviders =
1✔
48
      new LinkedHashSet<>();
49
  private final LinkedHashMap<String, LoadBalancerProvider> effectiveProviders =
1✔
50
      new LinkedHashMap<>();
51

52
  /**
53
   * Register a provider.
54
   *
55
   * <p>If the provider's {@link LoadBalancerProvider#isAvailable isAvailable()} returns
56
   * {@code false}, this method will throw {@link IllegalArgumentException}.
57
   *
58
   * <p>If more than one provider with the same {@link LoadBalancerProvider#getPolicyName policy
59
   * name} are registered, the one with the highest {@link LoadBalancerProvider#getPriority
60
   * priority} will be effective.  If there are more than one name-sake providers rank the highest
61
   * priority, the one registered first will be effective.
62
   */
63
  public synchronized void register(LoadBalancerProvider provider) {
64
    addProvider(provider);
1✔
65
    refreshProviderMap();
1✔
66
  }
1✔
67

68
  private synchronized void addProvider(LoadBalancerProvider provider) {
69
    checkArgument(provider.isAvailable(), "isAvailable() returned false");
1✔
70
    allProviders.add(provider);
1✔
71
  }
1✔
72

73
  /**
74
   * Deregisters a provider.  No-op if the provider is not in the registry.  If there are more
75
   * than one providers with the same policy name as the deregistered one in the registry, one
76
   * of them will become the effective provider for that policy, per the rule documented in {@link
77
   * #register}.
78
   *
79
   * @param provider the provider that was added to the register via {@link #register}.
80
   */
81
  public synchronized void deregister(LoadBalancerProvider provider) {
82
    allProviders.remove(provider);
1✔
83
    refreshProviderMap();
1✔
84
  }
1✔
85

86
  private synchronized void refreshProviderMap() {
87
    effectiveProviders.clear();
1✔
88
    for (LoadBalancerProvider provider : allProviders) {
1✔
89
      String policy = provider.getPolicyName();
1✔
90
      LoadBalancerProvider existing = effectiveProviders.get(policy);
1✔
91
      if (existing == null || existing.getPriority() < provider.getPriority()) {
1✔
92
        effectiveProviders.put(policy, provider);
1✔
93
      }
94
    }
1✔
95
  }
1✔
96

97
  /**
98
   * Returns the default registry that loads providers via the Java service loader mechanism.
99
   */
100
  public static synchronized LoadBalancerRegistry getDefaultRegistry() {
101
    if (instance == null) {
1✔
102
      List<LoadBalancerProvider> providerList = ServiceProviders.loadAll(
1✔
103
          LoadBalancerProvider.class,
104
          ServiceLoader
105
            .load(LoadBalancerProvider.class, LoadBalancerProvider.class.getClassLoader())
1✔
106
            .iterator(),
1✔
107
          LoadBalancerRegistry::getHardCodedClasses,
108
          new LoadBalancerPriorityAccessor());
109
      instance = new LoadBalancerRegistry();
1✔
110
      for (LoadBalancerProvider provider : providerList) {
1✔
111
        logger.fine("Service loader found " + provider);
1✔
112
        instance.addProvider(provider);
1✔
113
      }
1✔
114
      instance.refreshProviderMap();
1✔
115
    }
116
    return instance;
1✔
117
  }
118

119
  /**
120
   * Returns the effective provider for the given load-balancing policy, or {@code null} if no
121
   * suitable provider can be found.  Each provider declares its policy name via {@link
122
   * LoadBalancerProvider#getPolicyName}.
123
   */
124
  @Nullable
125
  public synchronized LoadBalancerProvider getProvider(String policy) {
126
    return effectiveProviders.get(checkNotNull(policy, "policy"));
1✔
127
  }
128

129
  /**
130
   * Returns effective providers in a new map.
131
   */
132
  @VisibleForTesting
133
  synchronized Map<String, LoadBalancerProvider> providers() {
134
    return new LinkedHashMap<>(effectiveProviders);
1✔
135
  }
136

137
  @VisibleForTesting
138
  static List<Class<?>> getHardCodedClasses() {
139
    // Class.forName(String) is used to remove the need for ProGuard configuration. Note that
140
    // ProGuard does not detect usages of Class.forName(String, boolean, ClassLoader):
141
    // https://sourceforge.net/p/proguard/bugs/418/
142
    ArrayList<Class<?>> list = new ArrayList<>();
1✔
143
    try {
144
      list.add(Class.forName("io.grpc.internal.PickFirstLoadBalancerProvider"));
1✔
145
    } catch (ClassNotFoundException e) {
×
146
      logger.log(Level.WARNING, "Unable to find pick-first LoadBalancer", e);
×
147
    }
1✔
148
    try {
149
      list.add(Class.forName("io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider"));
1✔
150
    } catch (ClassNotFoundException e) {
×
151
      // Since hard-coded list is only used in Android environment, and we don't expect round-robin
152
      // to be actually used there, we log it as a lower level.
153
      logger.log(Level.FINE, "Unable to find round-robin LoadBalancer", e);
×
154
    }
1✔
155
    return Collections.unmodifiableList(list);
1✔
156
  }
157

158
  private static final class LoadBalancerPriorityAccessor
159
      implements ServiceProviders.PriorityAccessor<LoadBalancerProvider> {
160

161
    LoadBalancerPriorityAccessor() {}
1✔
162

163
    @Override
164
    public boolean isAvailable(LoadBalancerProvider provider) {
165
      return provider.isAvailable();
1✔
166
    }
167

168
    @Override
169
    public int getPriority(LoadBalancerProvider provider) {
170
      return provider.getPriority();
1✔
171
    }
172
  }
173
}
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