Skip to content
This repository has been archived by the owner on Sep 26, 2023. It is now read-only.

Commit

Permalink
feat(java):relocate Netty Native Image configurations from java-core …
Browse files Browse the repository at this point in the history
…to gax
  • Loading branch information
mpeddada1 committed Mar 25, 2022
1 parent 599081f commit 6a0840e
Show file tree
Hide file tree
Showing 5 changed files with 332 additions and 2 deletions.
2 changes: 2 additions & 0 deletions dependencies.properties
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ maven.com_google_http_client_google_http_client=com.google.http-client:google-ht
maven.com_google_http_client_google_http_client_gson=com.google.http-client:google-http-client-gson:1.41.2
maven.org_codehaus_mojo_animal_sniffer_annotations=org.codehaus.mojo:animal-sniffer-annotations:1.18
maven.javax_annotation_javax_annotation_api=javax.annotation:javax.annotation-api:1.3.2
maven.org_graalvm_nativeimage_svm=org.graalvm.nativeimage:svm:22.0.0.2
maven.org_graalvm_sdk=org.graalvm.sdk:graal-sdk:22.0.0.2

# Testing maven artifacts
maven.junit_junit=junit:junit:4.13.2
Expand Down
4 changes: 3 additions & 1 deletion gax-grpc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ dependencies {

runtimeOnly libraries['maven.io_grpc_grpc_xds']

compileOnly libraries['maven.com_google_auto_value_auto_value']
compileOnly(libraries['maven.com_google_auto_value_auto_value'],
libraries['maven.org_graalvm_sdk'],
libraries['maven.org_graalvm_nativeimage_svm'])

testImplementation( project(':gax').sourceSets.test.output,
libraries['maven.junit_junit'],
Expand Down
136 changes: 136 additions & 0 deletions gax-grpc/src/main/java/com/google/nativeimage/GrpcNettyFeature.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright 2022 Google LLC
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.google.nativeimage;

import static com.google.api.gax.nativeimage.NativeImageUtils.registerClassForReflection;
import static com.google.api.gax.nativeimage.NativeImageUtils.registerClassHierarchyForReflection;
import static com.google.api.gax.nativeimage.NativeImageUtils.registerForReflectiveInstantiation;
import static com.google.api.gax.nativeimage.NativeImageUtils.registerForUnsafeFieldAccess;

import com.oracle.svm.core.annotate.AutomaticFeature;
import org.graalvm.nativeimage.hosted.Feature;

/** Configures Native Image settings for the grpc-netty-shaded dependency. */
@AutomaticFeature
final class GrpcNettyFeature implements Feature {

private static final String GRPC_NETTY_SHADED_CLASS =
"io.grpc.netty.shaded.io.grpc.netty.NettyServer";

private static final String GOOGLE_AUTH_CLASS =
"com.google.auth.oauth2.ServiceAccountCredentials";

@Override
public void beforeAnalysis(BeforeAnalysisAccess access) {
loadGoogleAuthClasses(access);
loadGrpcNettyClasses(access);
loadMiscClasses(access);
}

private static void loadGoogleAuthClasses(BeforeAnalysisAccess access) {
// For com.google.auth:google-auth-library-oauth2-http
Class<?> authClass = access.findClassByName(GOOGLE_AUTH_CLASS);
if (authClass != null) {
registerClassHierarchyForReflection(
access, "com.google.auth.oauth2.ServiceAccountCredentials");
registerClassHierarchyForReflection(
access, "com.google.auth.oauth2.ServiceAccountJwtAccessCredentials");
}
}

private static void loadGrpcNettyClasses(BeforeAnalysisAccess access) {
// For io.grpc:grpc-netty-shaded
Class<?> nettyShadedClass = access.findClassByName(GRPC_NETTY_SHADED_CLASS);
if (nettyShadedClass != null) {
// Misc. classes used by grpc-netty-shaded
registerForReflectiveInstantiation(
access, "io.grpc.netty.shaded.io.netty.channel.socket.nio.NioSocketChannel");
registerClassForReflection(
access, "io.grpc.netty.shaded.io.netty.util.internal.NativeLibraryUtil");
registerClassForReflection(access, "io.grpc.netty.shaded.io.netty.util.ReferenceCountUtil");
registerClassForReflection(
access, "io.grpc.netty.shaded.io.netty.buffer.AbstractByteBufAllocator");

// Epoll Libraries
registerClassForReflection(access, "io.grpc.netty.shaded.io.netty.channel.epoll.Epoll");
registerClassForReflection(
access, "io.grpc.netty.shaded.io.netty.channel.epoll.EpollChannelOption");
registerClassForReflection(
access, "io.grpc.netty.shaded.io.netty.channel.epoll.EpollEventLoopGroup");
registerForReflectiveInstantiation(
access, "io.grpc.netty.shaded.io.netty.channel.epoll.EpollServerSocketChannel");
registerForReflectiveInstantiation(
access, "io.grpc.netty.shaded.io.netty.channel.epoll.EpollSocketChannel");

// Unsafe field accesses
registerForUnsafeFieldAccess(
access,
"io.grpc.netty.shaded.io.netty.util.internal.shaded."
+ "org.jctools.queues.MpscArrayQueueProducerIndexField",
"producerIndex");
registerForUnsafeFieldAccess(
access,
"io.grpc.netty.shaded.io.netty.util.internal.shaded."
+ "org.jctools.queues.MpscArrayQueueProducerLimitField",
"producerLimit");
registerForUnsafeFieldAccess(
access,
"io.grpc.netty.shaded.io.netty.util.internal.shaded."
+ "org.jctools.queues.MpscArrayQueueConsumerIndexField",
"consumerIndex");
registerForUnsafeFieldAccess(
access,
"io.grpc.netty.shaded.io.netty.util.internal.shaded."
+ "org.jctools.queues.BaseMpscLinkedArrayQueueProducerFields",
"producerIndex");
registerForUnsafeFieldAccess(
access,
"io.grpc.netty.shaded.io.netty.util.internal.shaded."
+ "org.jctools.queues.BaseMpscLinkedArrayQueueColdProducerFields",
"producerLimit");
registerForUnsafeFieldAccess(
access,
"io.grpc.netty.shaded.io.netty.util.internal.shaded."
+ "org.jctools.queues.BaseMpscLinkedArrayQueueConsumerFields",
"consumerIndex");
}
}

/** Miscellaneous classes that need to be registered coming from various JARs. */
private static void loadMiscClasses(BeforeAnalysisAccess access) {
registerClassHierarchyForReflection(access, "com.google.protobuf.DescriptorProtos");
registerClassForReflection(access, "com.google.api.FieldBehavior");

registerForUnsafeFieldAccess(access, "javax.net.ssl.SSLContext", "contextSpi");
registerClassForReflection(access, "java.lang.management.ManagementFactory");
registerClassForReflection(access, "java.lang.management.RuntimeMXBean");
}
}
4 changes: 3 additions & 1 deletion gax/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ dependencies {
libraries['maven.com_google_guava_guava'],
libraries['maven.io_opencensus_opencensus_api'])

compileOnly libraries['maven.com_google_auto_value_auto_value']
compileOnly(libraries['maven.com_google_auto_value_auto_value'],
libraries['maven.org_graalvm_sdk'],
libraries['maven.org_graalvm_nativeimage_svm'])

testImplementation(libraries['maven.junit_junit'],
libraries['maven.org_mockito_mockito_core'],
Expand Down
188 changes: 188 additions & 0 deletions gax/src/main/java/com/google/api/gax/nativeimage/NativeImageUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
/*
* Copyright 2022 Google LLC
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.google.api.gax.nativeimage;

import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.JarURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Logger;
import org.graalvm.nativeimage.hosted.Feature.FeatureAccess;
import org.graalvm.nativeimage.hosted.RuntimeReflection;

/** Internal class offering helper methods for registering methods/classes for reflection. */
public class NativeImageUtils {

private static final Logger LOGGER = Logger.getLogger(NativeImageUtils.class.getName());

/** Returns the method of a class or fails if it is not present. */
public static Method getMethodOrFail(Class<?> clazz, String methodName, Class<?>... params) {
try {
return clazz.getDeclaredMethod(methodName, params);
} catch (NoSuchMethodException e) {
throw new RuntimeException(
"Failed to find method " + methodName + " for class " + clazz.getName(), e);
}
}

/** Registers a class for reflective construction via its default constructor. */
public static void registerForReflectiveInstantiation(FeatureAccess access, String className) {
Class<?> clazz = access.findClassByName(className);
if (clazz != null) {
RuntimeReflection.register(clazz);
RuntimeReflection.registerForReflectiveInstantiation(clazz);
} else {
LOGGER.warning(
"Failed to find " + className + " on the classpath for reflective instantiation.");
}
}

/** Registers all constructors of a class for reflection. */
public static void registerConstructorsForReflection(FeatureAccess access, String name) {
Class<?> clazz = access.findClassByName(name);
if (clazz != null) {
RuntimeReflection.register(clazz);
RuntimeReflection.register(clazz.getDeclaredConstructors());
} else {
LOGGER.warning("Failed to find " + name + " on the classpath for reflection.");
}
}

/** Registers an entire class for reflection use. */
public static void registerClassForReflection(FeatureAccess access, String name) {
Class<?> clazz = access.findClassByName(name);
if (clazz != null) {
RuntimeReflection.register(clazz);
RuntimeReflection.register(clazz.getDeclaredConstructors());
RuntimeReflection.register(clazz.getDeclaredFields());
RuntimeReflection.register(clazz.getDeclaredMethods());
} else {
LOGGER.warning("Failed to find " + name + " on the classpath for reflection.");
}
}

/**
* Registers the transitive class hierarchy of the provided {@code className} for reflection.
*
* <p>The transitive class hierarchy contains the class itself and its transitive set of
* *non-private* nested subclasses.
*/
public static void registerClassHierarchyForReflection(FeatureAccess access, String className) {
Class<?> clazz = access.findClassByName(className);
if (clazz != null) {
registerClassForReflection(access, className);
for (Class<?> nestedClass : clazz.getDeclaredClasses()) {
if (!Modifier.isPrivate(nestedClass.getModifiers())) {
registerClassHierarchyForReflection(access, nestedClass.getName());
}
}
} else {
LOGGER.warning("Failed to find " + className + " on the classpath for reflection.");
}
}

/** Registers a class for unsafe reflective field access. */
public static void registerForUnsafeFieldAccess(
FeatureAccess access, String className, String... fields) {
Class<?> clazz = access.findClassByName(className);
if (clazz != null) {
RuntimeReflection.register(clazz);
for (String fieldName : fields) {
try {
RuntimeReflection.register(clazz.getDeclaredField(fieldName));
} catch (NoSuchFieldException ex) {
LOGGER.warning("Failed to register field " + fieldName + " for class " + className);
LOGGER.warning(ex.getMessage());
}
}
} else {
LOGGER.warning(
"Failed to find "
+ className
+ " on the classpath for unsafe fields access registration.");
}
}

/** Registers all the classes under the specified package for reflection. */
public static void registerPackageForReflection(FeatureAccess access, String packageName) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();

try {
String path = packageName.replace('.', '/');

Enumeration<URL> resources = classLoader.getResources(path);
while (resources.hasMoreElements()) {
URL url = resources.nextElement();

URLConnection connection = url.openConnection();
if (connection instanceof JarURLConnection) {
List<String> classes = findClassesInJar((JarURLConnection) connection, packageName);
for (String className : classes) {
registerClassHierarchyForReflection(access, className);
}
}
}
} catch (IOException e) {
throw new RuntimeException("Failed to load classes under package name.", e);
}
}

private static List<String> findClassesInJar(JarURLConnection urlConnection, String packageName)
throws IOException {

List<String> result = new ArrayList<>();

final JarFile jarFile = urlConnection.getJarFile();
final Enumeration<JarEntry> entries = jarFile.entries();

while (entries.hasMoreElements()) {
JarEntry entry = entries.nextElement();
String entryName = entry.getName();

if (entryName.endsWith(".class")) {
String javaClassName = entryName.replace(".class", "").replace('/', '.');

if (javaClassName.startsWith(packageName)) {
result.add(javaClassName);
}
}
}

return result;
}
}

0 comments on commit 6a0840e

Please sign in to comment.