diff --git a/dependencies.properties b/dependencies.properties index c46d5cdf1..b69f779ea 100644 --- a/dependencies.properties +++ b/dependencies.properties @@ -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.5 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 diff --git a/gax-grpc/BUILD.bazel b/gax-grpc/BUILD.bazel index 3a177f4ab..5ae856c4f 100644 --- a/gax-grpc/BUILD.bazel +++ b/gax-grpc/BUILD.bazel @@ -31,6 +31,8 @@ _COMPILE_DEPS = [ "@io_netty_netty_tcnative_boringssl_static//jar", "@javax_annotation_javax_annotation_api//jar", "//gax:gax", + "@org_graalvm_nativeimage_svm//jar", + "@org_graalvm_sdk//jar" ] _TEST_COMPILE_DEPS = [ diff --git a/gax-grpc/build.gradle b/gax-grpc/build.gradle index 14e229d32..f63c5e3d1 100644 --- a/gax-grpc/build.gradle +++ b/gax-grpc/build.gradle @@ -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'], diff --git a/gax-grpc/src/main/java/com/google/api/gax/grpc/nativeimage/GrpcNettyFeature.java b/gax-grpc/src/main/java/com/google/api/gax/grpc/nativeimage/GrpcNettyFeature.java new file mode 100644 index 000000000..1e1167e09 --- /dev/null +++ b/gax-grpc/src/main/java/com/google/api/gax/grpc/nativeimage/GrpcNettyFeature.java @@ -0,0 +1,132 @@ +/* + * 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.grpc.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"; + + private static final String NETTY_SHADED_PACKAGE = + "io.grpc.netty.shaded.io.netty.util.internal.shaded."; + + @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, GOOGLE_AUTH_CLASS); + 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, + NETTY_SHADED_PACKAGE + "org.jctools.queues.MpscArrayQueueProducerIndexField", + "producerIndex"); + registerForUnsafeFieldAccess( + access, + NETTY_SHADED_PACKAGE + "org.jctools.queues.MpscArrayQueueProducerLimitField", + "producerLimit"); + registerForUnsafeFieldAccess( + access, + NETTY_SHADED_PACKAGE + "org.jctools.queues.MpscArrayQueueConsumerIndexField", + "consumerIndex"); + registerForUnsafeFieldAccess( + access, + NETTY_SHADED_PACKAGE + "org.jctools.queues.BaseMpscLinkedArrayQueueProducerFields", + "producerIndex"); + registerForUnsafeFieldAccess( + access, + NETTY_SHADED_PACKAGE + "org.jctools.queues.BaseMpscLinkedArrayQueueColdProducerFields", + "producerLimit"); + registerForUnsafeFieldAccess( + access, + NETTY_SHADED_PACKAGE + "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"); + } +} diff --git a/gax/BUILD.bazel b/gax/BUILD.bazel index b9dd9a067..f35f3fc99 100644 --- a/gax/BUILD.bazel +++ b/gax/BUILD.bazel @@ -24,6 +24,8 @@ _COMPILE_DEPS = [ "@com_google_code_gson_gson//jar", "@com_google_guava_failureaccess//jar", "@javax_annotation_javax_annotation_api//jar", + "@org_graalvm_nativeimage_svm//jar", + "@org_graalvm_sdk//jar" ] _TEST_COMPILE_DEPS = [ diff --git a/gax/build.gradle b/gax/build.gradle index 2b7035ebf..d9ad1da14 100644 --- a/gax/build.gradle +++ b/gax/build.gradle @@ -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'], diff --git a/gax/src/main/java/com/google/api/gax/nativeimage/NativeImageUtils.java b/gax/src/main/java/com/google/api/gax/nativeimage/NativeImageUtils.java new file mode 100644 index 000000000..603d26e3c --- /dev/null +++ b/gax/src/main/java/com/google/api/gax/nativeimage/NativeImageUtils.java @@ -0,0 +1,140 @@ +/* + * 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 com.google.api.core.InternalApi; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.logging.Level; +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. */ +@InternalApi +public class NativeImageUtils { + + private static final Logger LOGGER = Logger.getLogger(NativeImageUtils.class.getName()); + private static final String CLASS_REFLECTION_ERROR_MESSAGE = + "Failed to find {0} on the classpath for reflection."; + + private NativeImageUtils() {} + + /** 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 IllegalStateException( + String.format("Failed to find method %s for class %s", methodName, 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.log( + Level.WARNING, + "Failed to find {0} on the classpath for reflective instantiation.", + className); + } + } + + /** 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.log(Level.WARNING, CLASS_REFLECTION_ERROR_MESSAGE, name); + } + } + + /** 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.log(Level.WARNING, CLASS_REFLECTION_ERROR_MESSAGE, name); + } + } + + /** + * Registers the transitive class hierarchy of the provided {@code className} for reflection. + * + *
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.log(Level.WARNING, CLASS_REFLECTION_ERROR_MESSAGE, className); + } + } + + /** 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.log( + Level.WARNING, + "Failed to find {0} on the classpath for unsafe fields access registration.", + className); + } + } +}