• 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

92.65
/../api/src/main/java/io/grpc/NameResolverRegistry.java
1
/*
2
 * Copyright 2019 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

21
import com.google.common.annotations.VisibleForTesting;
22
import com.google.common.collect.ImmutableMap;
23
import com.google.errorprone.annotations.concurrent.GuardedBy;
24
import java.net.URI;
25
import java.util.ArrayList;
26
import java.util.Collections;
27
import java.util.HashMap;
28
import java.util.LinkedHashSet;
29
import java.util.List;
30
import java.util.Locale;
31
import java.util.Map;
32
import java.util.ServiceLoader;
33
import java.util.logging.Level;
34
import java.util.logging.Logger;
35
import javax.annotation.Nullable;
36
import javax.annotation.concurrent.ThreadSafe;
37

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

50
  private final NameResolver.Factory factory = new NameResolverFactory();
1✔
51
  private static final String UNKNOWN_SCHEME = "unknown";
52
  @GuardedBy("this")
1✔
53
  private String defaultScheme = UNKNOWN_SCHEME;
54

55
  @GuardedBy("this")
1✔
56
  private final LinkedHashSet<NameResolverProvider> allProviders = new LinkedHashSet<>();
57
  /** Generated from {@code allProviders}. Is mapping from scheme key to the highest priority
58
   * {@link NameResolverProvider}. Is replaced instead of mutating. */
59
  @GuardedBy("this")
1✔
60
  private ImmutableMap<String, NameResolverProvider> effectiveProviders = ImmutableMap.of();
1✔
61

62
  public synchronized String getDefaultScheme() {
63
    return defaultScheme;
1✔
64
  }
65

66
  public NameResolverProvider getProviderForScheme(String scheme) {
67
    if (scheme == null) {
1✔
68
      return null;
1✔
69
    }
70
    return providers().get(scheme.toLowerCase(Locale.US));
1✔
71
  }
72

73
  /**
74
   * Register a provider.
75
   *
76
   * <p>If the provider's {@link NameResolverProvider#isAvailable isAvailable()} returns
77
   * {@code false}, this method will throw {@link IllegalArgumentException}.
78
   *
79
   * <p>Providers will be used in priority order. In case of ties, providers are used in
80
   * registration order.
81
   */
82
  public synchronized void register(NameResolverProvider provider) {
83
    addProvider(provider);
1✔
84
    refreshProviders();
1✔
85
  }
1✔
86

87
  private synchronized void addProvider(NameResolverProvider provider) {
88
    checkArgument(provider.isAvailable(), "isAvailable() returned false");
1✔
89
    allProviders.add(provider);
1✔
90
  }
1✔
91

92
  /**
93
   * Deregisters a provider.  No-op if the provider is not in the registry.
94
   *
95
   * @param provider the provider that was added to the register via {@link #register}.
96
   */
97
  public synchronized void deregister(NameResolverProvider provider) {
98
    allProviders.remove(provider);
1✔
99
    refreshProviders();
1✔
100
  }
1✔
101

102
  private synchronized void refreshProviders() {
103
    Map<String, NameResolverProvider> refreshedProviders = new HashMap<>();
1✔
104
    int maxPriority = Integer.MIN_VALUE;
1✔
105
    String refreshedDefaultScheme = UNKNOWN_SCHEME;
1✔
106
    // We prefer first-registered providers
107
    for (NameResolverProvider provider : allProviders) {
1✔
108
      String scheme = provider.getScheme();
1✔
109
      NameResolverProvider existing = refreshedProviders.get(scheme);
1✔
110
      if (existing == null || existing.priority() < provider.priority()) {
1✔
111
        refreshedProviders.put(scheme, provider);
1✔
112
      }
113
      if (maxPriority < provider.priority()) {
1✔
114
        maxPriority = provider.priority();
1✔
115
        refreshedDefaultScheme = provider.getScheme();
1✔
116
      }
117
    }
1✔
118
    effectiveProviders = ImmutableMap.copyOf(refreshedProviders);
1✔
119
    defaultScheme = refreshedDefaultScheme;
1✔
120
  }
1✔
121

122
  /**
123
   * Returns the default registry that loads providers via the Java service loader mechanism.
124
   */
125
  public static synchronized NameResolverRegistry getDefaultRegistry() {
126
    if (instance == null) {
1✔
127
      List<NameResolverProvider> providerList = ServiceProviders.loadAll(
1✔
128
          NameResolverProvider.class,
129
          ServiceLoader
130
            .load(NameResolverProvider.class, NameResolverProvider.class.getClassLoader())
1✔
131
            .iterator(),
1✔
132
          NameResolverRegistry::getHardCodedClasses,
133
          new NameResolverPriorityAccessor());
134
      if (providerList.isEmpty()) {
1✔
135
        logger.warning("No NameResolverProviders found via ServiceLoader, including for DNS. This "
×
136
            + "is probably due to a broken build. If using ProGuard, check your configuration");
137
      }
138
      instance = new NameResolverRegistry();
1✔
139
      for (NameResolverProvider provider : providerList) {
1✔
140
        logger.fine("Service loader found " + provider);
1✔
141
        instance.addProvider(provider);
1✔
142
      }
1✔
143
      instance.refreshProviders();
1✔
144
    }
145
    return instance;
1✔
146
  }
147

148
  /**
149
   * Returns effective providers map from scheme to the highest priority NameResolverProvider of
150
   * that scheme.
151
   */
152
  @VisibleForTesting
153
  synchronized Map<String, NameResolverProvider> providers() {
154
    return effectiveProviders;
1✔
155
  }
156

157
  public NameResolver.Factory asFactory() {
158
    return factory;
1✔
159
  }
160

161
  @VisibleForTesting
162
  static List<Class<?>> getHardCodedClasses() {
163
    // Class.forName(String) is used to remove the need for ProGuard configuration. Note that
164
    // ProGuard does not detect usages of Class.forName(String, boolean, ClassLoader):
165
    // https://sourceforge.net/p/proguard/bugs/418/
166
    ArrayList<Class<?>> list = new ArrayList<>();
1✔
167
    try {
168
      list.add(Class.forName("io.grpc.internal.DnsNameResolverProvider"));
1✔
169
    } catch (ClassNotFoundException e) {
×
170
      logger.log(Level.FINE, "Unable to find DNS NameResolver", e);
×
171
    }
1✔
172
    try {
173
      list.add(Class.forName("io.grpc.binder.internal.IntentNameResolverProvider"));
×
174
    } catch (ClassNotFoundException e) {
1✔
175
      logger.log(Level.FINE, "Unable to find IntentNameResolverProvider", e);
1✔
176
    }
×
177
    return Collections.unmodifiableList(list);
1✔
178
  }
179

180
  private final class NameResolverFactory extends NameResolver.Factory {
1✔
181
    @Override
182
    @Nullable
183
    public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) {
184
      NameResolverProvider provider = getProviderForScheme(targetUri.getScheme());
1✔
185
      return provider == null ? null : provider.newNameResolver(targetUri, args);
1✔
186
    }
187

188
    @Override
189
    @Nullable
190
    public NameResolver newNameResolver(io.grpc.Uri targetUri, NameResolver.Args args) {
191
      NameResolverProvider provider = getProviderForScheme(targetUri.getScheme());
1✔
192
      return provider == null ? null : provider.newNameResolver(targetUri, args);
1✔
193
    }
194

195
    @Override
196
    public String getDefaultScheme() {
197
      return NameResolverRegistry.this.getDefaultScheme();
1✔
198
    }
199
  }
200

201
  private static final class NameResolverPriorityAccessor
202
      implements ServiceProviders.PriorityAccessor<NameResolverProvider> {
203
    @Override
204
    public boolean isAvailable(NameResolverProvider provider) {
205
      return provider.isAvailable();
1✔
206
    }
207

208
    @Override
209
    public int getPriority(NameResolverProvider provider) {
210
      return provider.priority();
1✔
211
    }
212
  }
213
}
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