Skip to content

Commit 470219f

Browse files
authored
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.
1 parent 50ead96 commit 470219f

File tree

8 files changed

+123
-40
lines changed

8 files changed

+123
-40
lines changed

api/src/main/java/io/grpc/InternalServiceProviders.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@
1717
package io.grpc;
1818

1919
import com.google.common.annotations.VisibleForTesting;
20+
import java.util.Iterator;
2021
import java.util.List;
22+
import java.util.ServiceLoader;
2123

2224
@Internal
2325
public final class InternalServiceProviders {
@@ -27,12 +29,28 @@ private InternalServiceProviders() {
2729
/**
2830
* Accessor for method.
2931
*/
32+
@Deprecated
3033
public static <T> List<T> loadAll(
3134
Class<T> klass,
3235
Iterable<Class<?>> hardCodedClasses,
3336
ClassLoader classLoader,
3437
PriorityAccessor<T> priorityAccessor) {
35-
return ServiceProviders.loadAll(klass, hardCodedClasses, classLoader, priorityAccessor);
38+
return loadAll(
39+
klass,
40+
ServiceLoader.load(klass, classLoader).iterator(),
41+
() -> hardCodedClasses,
42+
priorityAccessor);
43+
}
44+
45+
/**
46+
* Accessor for method.
47+
*/
48+
public static <T> List<T> loadAll(
49+
Class<T> klass,
50+
Iterator<T> serviceLoader,
51+
Supplier<Iterable<Class<?>>> hardCodedClasses,
52+
PriorityAccessor<T> priorityAccessor) {
53+
return ServiceProviders.loadAll(klass, serviceLoader, hardCodedClasses::get, priorityAccessor);
3654
}
3755

3856
/**
@@ -60,4 +78,8 @@ public static boolean isAndroid(ClassLoader cl) {
6078
}
6179

6280
public interface PriorityAccessor<T> extends ServiceProviders.PriorityAccessor<T> {}
81+
82+
public interface Supplier<T> {
83+
T get();
84+
}
6385
}

api/src/main/java/io/grpc/LoadBalancerRegistry.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.LinkedHashSet;
2727
import java.util.List;
2828
import java.util.Map;
29+
import java.util.ServiceLoader;
2930
import java.util.logging.Level;
3031
import java.util.logging.Logger;
3132
import javax.annotation.Nullable;
@@ -42,7 +43,6 @@
4243
public final class LoadBalancerRegistry {
4344
private static final Logger logger = Logger.getLogger(LoadBalancerRegistry.class.getName());
4445
private static LoadBalancerRegistry instance;
45-
private static final Iterable<Class<?>> HARDCODED_CLASSES = getHardCodedClasses();
4646

4747
private final LinkedHashSet<LoadBalancerProvider> allProviders =
4848
new LinkedHashSet<>();
@@ -101,8 +101,10 @@ public static synchronized LoadBalancerRegistry getDefaultRegistry() {
101101
if (instance == null) {
102102
List<LoadBalancerProvider> providerList = ServiceProviders.loadAll(
103103
LoadBalancerProvider.class,
104-
HARDCODED_CLASSES,
105-
LoadBalancerProvider.class.getClassLoader(),
104+
ServiceLoader
105+
.load(LoadBalancerProvider.class, LoadBalancerProvider.class.getClassLoader())
106+
.iterator(),
107+
LoadBalancerRegistry::getHardCodedClasses,
106108
new LoadBalancerPriorityAccessor());
107109
instance = new LoadBalancerRegistry();
108110
for (LoadBalancerProvider provider : providerList) {

api/src/main/java/io/grpc/ManagedChannelRegistry.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.Comparator;
3030
import java.util.LinkedHashSet;
3131
import java.util.List;
32+
import java.util.ServiceLoader;
3233
import java.util.logging.Level;
3334
import java.util.logging.Logger;
3435
import javax.annotation.concurrent.ThreadSafe;
@@ -100,8 +101,10 @@ public static synchronized ManagedChannelRegistry getDefaultRegistry() {
100101
if (instance == null) {
101102
List<ManagedChannelProvider> providerList = ServiceProviders.loadAll(
102103
ManagedChannelProvider.class,
103-
getHardCodedClasses(),
104-
ManagedChannelProvider.class.getClassLoader(),
104+
ServiceLoader
105+
.load(ManagedChannelProvider.class, ManagedChannelProvider.class.getClassLoader())
106+
.iterator(),
107+
ManagedChannelRegistry::getHardCodedClasses,
105108
new ManagedChannelPriorityAccessor());
106109
instance = new ManagedChannelRegistry();
107110
for (ManagedChannelProvider provider : providerList) {

api/src/main/java/io/grpc/NameResolverRegistry.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.List;
3030
import java.util.Locale;
3131
import java.util.Map;
32+
import java.util.ServiceLoader;
3233
import java.util.logging.Level;
3334
import java.util.logging.Logger;
3435
import javax.annotation.Nullable;
@@ -125,8 +126,10 @@ public static synchronized NameResolverRegistry getDefaultRegistry() {
125126
if (instance == null) {
126127
List<NameResolverProvider> providerList = ServiceProviders.loadAll(
127128
NameResolverProvider.class,
128-
getHardCodedClasses(),
129-
NameResolverProvider.class.getClassLoader(),
129+
ServiceLoader
130+
.load(NameResolverProvider.class, NameResolverProvider.class.getClassLoader())
131+
.iterator(),
132+
NameResolverRegistry::getHardCodedClasses,
130133
new NameResolverPriorityAccessor());
131134
if (providerList.isEmpty()) {
132135
logger.warning("No NameResolverProviders found via ServiceLoader, including for DNS. This "

api/src/main/java/io/grpc/ServerRegistry.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Comparator;
2525
import java.util.LinkedHashSet;
2626
import java.util.List;
27+
import java.util.ServiceLoader;
2728
import java.util.logging.Level;
2829
import java.util.logging.Logger;
2930
import javax.annotation.concurrent.ThreadSafe;
@@ -93,8 +94,9 @@ public static synchronized ServerRegistry getDefaultRegistry() {
9394
if (instance == null) {
9495
List<ServerProvider> providerList = ServiceProviders.loadAll(
9596
ServerProvider.class,
96-
getHardCodedClasses(),
97-
ServerProvider.class.getClassLoader(),
97+
ServiceLoader.load(ServerProvider.class, ServerProvider.class.getClassLoader())
98+
.iterator(),
99+
ServerRegistry::getHardCodedClasses,
98100
new ServerPriorityAccessor());
99101
instance = new ServerRegistry();
100102
for (ServerProvider provider : providerList) {

api/src/main/java/io/grpc/ServiceProviders.java

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@
1717
package io.grpc;
1818

1919
import com.google.common.annotations.VisibleForTesting;
20+
import com.google.common.base.Supplier;
2021
import java.util.ArrayList;
2122
import java.util.Collections;
2223
import java.util.Comparator;
24+
import java.util.Iterator;
2325
import java.util.List;
26+
import java.util.ListIterator;
2427
import java.util.ServiceConfigurationError;
2528
import java.util.ServiceLoader;
2629

@@ -34,20 +37,39 @@ private ServiceProviders() {
3437
* {@link ServiceLoader}.
3538
* If this is Android, returns all available implementations in {@code hardcoded}.
3639
* The list is sorted in descending priority order.
40+
*
41+
* <p>{@code serviceLoader} should be created with {@code ServiceLoader.load(MyClass.class,
42+
* MyClass.class.getClassLoader()).iterator()} in order to be detected by R8 so that R8 full mode
43+
* will keep the constructors for the provider classes.
3744
*/
3845
public static <T> List<T> loadAll(
3946
Class<T> klass,
40-
Iterable<Class<?>> hardcoded,
41-
ClassLoader cl,
47+
Iterator<T> serviceLoader,
48+
Supplier<Iterable<Class<?>>> hardcoded,
4249
final PriorityAccessor<T> priorityAccessor) {
43-
Iterable<T> candidates;
44-
if (isAndroid(cl)) {
45-
candidates = getCandidatesViaHardCoded(klass, hardcoded);
50+
Iterator<T> candidates;
51+
if (serviceLoader instanceof ListIterator) {
52+
// A rewriting tool has replaced the ServiceLoader with a List of some sort (R8 uses
53+
// ArrayList, AppReduce uses singletonList). We prefer to use such iterators on Android as
54+
// they won't need reflection like the hard-coded list does. In addition, the provider
55+
// instances will have already been created, so it seems we should use them.
56+
//
57+
// R8: https://r8.googlesource.com/r8/+/490bc53d9310d4cc2a5084c05df4aadaec8c885d/src/main/java/com/android/tools/r8/ir/optimize/ServiceLoaderRewriter.java
58+
// AppReduce: service_loader_pass.cc
59+
candidates = serviceLoader;
60+
} else if (isAndroid(klass.getClassLoader())) {
61+
// Avoid getResource() on Android, which must read from a zip which uses a lot of memory
62+
candidates = getCandidatesViaHardCoded(klass, hardcoded.get()).iterator();
63+
} else if (!serviceLoader.hasNext()) {
64+
// Attempt to load using the context class loader and ServiceLoader.
65+
// This allows frameworks like http://aries.apache.org/modules/spi-fly.html to plug in.
66+
candidates = ServiceLoader.load(klass).iterator();
4667
} else {
47-
candidates = getCandidatesViaServiceLoader(klass, cl);
68+
candidates = serviceLoader;
4869
}
4970
List<T> list = new ArrayList<>();
50-
for (T current: candidates) {
71+
while (candidates.hasNext()) {
72+
T current = candidates.next();
5173
if (!priorityAccessor.isAvailable(current)) {
5274
continue;
5375
}
@@ -84,15 +106,14 @@ static boolean isAndroid(ClassLoader cl) {
84106
}
85107

86108
/**
87-
* Loads service providers for the {@code klass} service using {@link ServiceLoader}.
109+
* For testing only: Loads service providers for the {@code klass} service using {@link
110+
* ServiceLoader}. Does not support spi-fly and related tricks.
88111
*/
89112
@VisibleForTesting
90113
public static <T> Iterable<T> getCandidatesViaServiceLoader(Class<T> klass, ClassLoader cl) {
91114
Iterable<T> i = ServiceLoader.load(klass, cl);
92-
// Attempt to load using the context class loader and ServiceLoader.
93-
// This allows frameworks like http://aries.apache.org/modules/spi-fly.html to plug in.
94115
if (!i.iterator().hasNext()) {
95-
i = ServiceLoader.load(klass);
116+
return null;
96117
}
97118
return i;
98119
}

api/src/test/java/io/grpc/ServiceProvidersTest.java

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,30 @@
1616

1717
package io.grpc;
1818

19+
import static com.google.common.truth.Truth.assertThat;
1920
import static org.junit.Assert.assertEquals;
2021
import static org.junit.Assert.assertFalse;
2122
import static org.junit.Assert.assertNull;
2223
import static org.junit.Assert.assertSame;
2324
import static org.junit.Assert.assertTrue;
2425
import static org.junit.Assert.fail;
2526

27+
import com.google.common.base.Supplier;
2628
import com.google.common.collect.ImmutableList;
2729
import io.grpc.InternalServiceProviders.PriorityAccessor;
2830
import java.util.Collections;
2931
import java.util.Iterator;
3032
import java.util.List;
3133
import java.util.ServiceConfigurationError;
34+
import java.util.ServiceLoader;
35+
import org.junit.After;
3236
import org.junit.Test;
3337
import org.junit.runner.RunWith;
3438
import org.junit.runners.JUnit4;
3539

3640
/** Unit tests for {@link ServiceProviders}. */
3741
@RunWith(JUnit4.class)
3842
public class ServiceProvidersTest {
39-
private static final List<Class<?>> NO_HARDCODED = Collections.emptyList();
4043
private static final PriorityAccessor<ServiceProvidersTestAbstractProvider> ACCESSOR =
4144
new PriorityAccessor<ServiceProvidersTestAbstractProvider>() {
4245
@Override
@@ -51,6 +54,19 @@ public int getPriority(ServiceProvidersTestAbstractProvider provider) {
5154
};
5255
private final String serviceFile =
5356
"META-INF/services/io.grpc.ServiceProvidersTestAbstractProvider";
57+
private boolean failingHardCodedAccessed;
58+
private final Supplier<Iterable<Class<?>>> failingHardCoded = new Supplier<Iterable<Class<?>>>() {
59+
@Override
60+
public Iterable<Class<?>> get() {
61+
failingHardCodedAccessed = true;
62+
throw new AssertionError();
63+
}
64+
};
65+
66+
@After
67+
public void tearDown() {
68+
assertThat(failingHardCodedAccessed).isFalse();
69+
}
5470

5571
@Test
5672
public void contextClassLoaderProvider() {
@@ -69,7 +85,8 @@ public void contextClassLoaderProvider() {
6985
Thread.currentThread().setContextClassLoader(rcll);
7086
assertEquals(
7187
Available7Provider.class,
72-
load(ServiceProvidersTestAbstractProvider.class, NO_HARDCODED, cl, ACCESSOR).getClass());
88+
load(ServiceProvidersTestAbstractProvider.class, failingHardCoded, cl, ACCESSOR)
89+
.getClass());
7390
} finally {
7491
Thread.currentThread().setContextClassLoader(ccl);
7592
}
@@ -84,7 +101,7 @@ public void noProvider() {
84101
serviceFile,
85102
"io/grpc/ServiceProvidersTestAbstractProvider-doesNotExist.txt");
86103
Thread.currentThread().setContextClassLoader(cl);
87-
assertNull(load(ServiceProvidersTestAbstractProvider.class, NO_HARDCODED, cl, ACCESSOR));
104+
assertNull(load(ServiceProvidersTestAbstractProvider.class, failingHardCoded, cl, ACCESSOR));
88105
} finally {
89106
Thread.currentThread().setContextClassLoader(ccl);
90107
}
@@ -96,10 +113,11 @@ public void multipleProvider() throws Exception {
96113
"io/grpc/ServiceProvidersTestAbstractProvider-multipleProvider.txt");
97114
assertSame(
98115
Available7Provider.class,
99-
load(ServiceProvidersTestAbstractProvider.class, NO_HARDCODED, cl, ACCESSOR).getClass());
116+
load(ServiceProvidersTestAbstractProvider.class, failingHardCoded, cl, ACCESSOR)
117+
.getClass());
100118

101-
List<ServiceProvidersTestAbstractProvider> providers = ServiceProviders.loadAll(
102-
ServiceProvidersTestAbstractProvider.class, NO_HARDCODED, cl, ACCESSOR);
119+
List<ServiceProvidersTestAbstractProvider> providers = loadAll(
120+
ServiceProvidersTestAbstractProvider.class, failingHardCoded, cl, ACCESSOR);
103121
assertEquals(3, providers.size());
104122
assertEquals(Available7Provider.class, providers.get(0).getClass());
105123
assertEquals(Available5Provider.class, providers.get(1).getClass());
@@ -113,16 +131,16 @@ public void unavailableProvider() {
113131
"io/grpc/ServiceProvidersTestAbstractProvider-unavailableProvider.txt");
114132
assertEquals(
115133
Available7Provider.class,
116-
load(ServiceProvidersTestAbstractProvider.class, NO_HARDCODED, cl, ACCESSOR).getClass());
134+
load(ServiceProvidersTestAbstractProvider.class, failingHardCoded, cl, ACCESSOR)
135+
.getClass());
117136
}
118137

119138
@Test
120139
public void unknownClassProvider() {
121140
ClassLoader cl = new ReplacingClassLoader(getClass().getClassLoader(), serviceFile,
122141
"io/grpc/ServiceProvidersTestAbstractProvider-unknownClassProvider.txt");
123142
try {
124-
ServiceProviders.loadAll(
125-
ServiceProvidersTestAbstractProvider.class, NO_HARDCODED, cl, ACCESSOR);
143+
loadAll(ServiceProvidersTestAbstractProvider.class, failingHardCoded, cl, ACCESSOR);
126144
fail("Exception expected");
127145
} catch (ServiceConfigurationError e) {
128146
// noop
@@ -136,8 +154,7 @@ public void exceptionSurfacedToCaller_failAtInit() {
136154
try {
137155
// Even though there is a working provider, if any providers fail then we should fail
138156
// completely to avoid returning something unexpected.
139-
ServiceProviders.loadAll(
140-
ServiceProvidersTestAbstractProvider.class, NO_HARDCODED, cl, ACCESSOR);
157+
loadAll(ServiceProvidersTestAbstractProvider.class, failingHardCoded, cl, ACCESSOR);
141158
fail("Expected exception");
142159
} catch (ServiceConfigurationError expected) {
143160
// noop
@@ -150,8 +167,7 @@ public void exceptionSurfacedToCaller_failAtPriority() {
150167
"io/grpc/ServiceProvidersTestAbstractProvider-failAtPriorityProvider.txt");
151168
try {
152169
// The exception should be surfaced to the caller
153-
ServiceProviders.loadAll(
154-
ServiceProvidersTestAbstractProvider.class, NO_HARDCODED, cl, ACCESSOR);
170+
loadAll(ServiceProvidersTestAbstractProvider.class, failingHardCoded, cl, ACCESSOR);
155171
fail("Expected exception");
156172
} catch (FailAtPriorityProvider.PriorityException expected) {
157173
// noop
@@ -164,8 +180,7 @@ public void exceptionSurfacedToCaller_failAtAvailable() {
164180
"io/grpc/ServiceProvidersTestAbstractProvider-failAtAvailableProvider.txt");
165181
try {
166182
// The exception should be surfaced to the caller
167-
ServiceProviders.loadAll(
168-
ServiceProvidersTestAbstractProvider.class, NO_HARDCODED, cl, ACCESSOR);
183+
loadAll(ServiceProvidersTestAbstractProvider.class, failingHardCoded, cl, ACCESSOR);
169184
fail("Expected exception");
170185
} catch (FailAtAvailableProvider.AvailableException expected) {
171186
// noop
@@ -242,16 +257,28 @@ class RandomClass {}
242257

243258
private static <T> T load(
244259
Class<T> klass,
245-
Iterable<Class<?>> hardcoded,
260+
Supplier<Iterable<Class<?>>> hardCoded,
246261
ClassLoader cl,
247262
PriorityAccessor<T> priorityAccessor) {
248-
List<T> candidates = ServiceProviders.loadAll(klass, hardcoded, cl, priorityAccessor);
263+
List<T> candidates = loadAll(klass, hardCoded, cl, priorityAccessor);
249264
if (candidates.isEmpty()) {
250265
return null;
251266
}
252267
return candidates.get(0);
253268
}
254269

270+
private static <T> List<T> loadAll(
271+
Class<T> klass,
272+
Supplier<Iterable<Class<?>>> hardCoded,
273+
ClassLoader classLoader,
274+
PriorityAccessor<T> priorityAccessor) {
275+
return ServiceProviders.loadAll(
276+
klass,
277+
ServiceLoader.load(klass, classLoader).iterator(),
278+
hardCoded,
279+
priorityAccessor);
280+
}
281+
255282
private static class BaseProvider extends ServiceProvidersTestAbstractProvider {
256283
private final boolean isAvailable;
257284
private final int priority;

0 commit comments

Comments
 (0)