From 544cfb2285bd86436bce74826e4a11faa05151e6 Mon Sep 17 00:00:00 2001 From: James Ward Date: Mon, 5 Oct 2020 12:56:30 -0600 Subject: [PATCH 1/3] add graalvm example - fixes #152 --- examples/README.md | 17 +++- examples/android/build.gradle.kts | 2 +- examples/native-client/build.gradle.kts | 37 ++++++++ .../src/graal/reflect-config.json | 84 +++++++++++++++++++ .../io/grpc/examples/helloworld/BUILD.bazel | 15 ++++ .../examples/helloworld/HelloWorldClient.kt | 52 ++++++++++++ examples/settings.gradle.kts | 6 +- examples/stub-android/build.gradle.kts | 68 +++++++++++++++ .../src/main/AndroidManifest.xml | 0 examples/stub-lite/build.gradle.kts | 19 ++--- examples/stub/build.gradle.kts | 3 +- 11 files changed, 285 insertions(+), 18 deletions(-) create mode 100644 examples/native-client/build.gradle.kts create mode 100644 examples/native-client/src/graal/reflect-config.json create mode 100644 examples/native-client/src/main/kotlin/io/grpc/examples/helloworld/BUILD.bazel create mode 100644 examples/native-client/src/main/kotlin/io/grpc/examples/helloworld/HelloWorldClient.kt create mode 100644 examples/stub-android/build.gradle.kts rename examples/{stub-lite => stub-android}/src/main/AndroidManifest.xml (100%) diff --git a/examples/README.md b/examples/README.md index 373bb247..f63d7310 100644 --- a/examples/README.md +++ b/examples/README.md @@ -4,9 +4,11 @@ Sub-Project Layout: - `protos` : Only protobufs - `stub` : Creates regular Java & Kotlin stub artifacts from the `protos` sub-project - `stub-lite` : Creates lite Java & Kotlin stub artifacts from the `protos` sub-project +- `stub-android` : Creates Android-compatible Java & Kotlin stub artifacts from the `protos` sub-project - `server` : Kotlin servers based on regular `stub` artifacts - `client` : Kotlin clients based on regular `stub` artifacts -- `android` : Kotlin Android app based on `stub-lite` artifacts and the `server` +- `native-client` : GraalVM Native Image clients based on `stub-lite` artifacts +- `android` : Kotlin Android app based on `stub-android` artifacts ## Hello, World Client & Server Example @@ -46,6 +48,19 @@ In another console, run the client against the "dog", "pig", and "sheep" service ./gradlew :client:AnimalsClient --args=sheep ``` +## GraalVM Native Image Example + +Start the server: +```sh +./gradlew :server:HelloWorldServer +``` + +In another console, create the native image client and run it: +```sh +./gradlew :native-client:nativeImage +native-client/build/graal/hello-world +``` + ## Android Example Start the server: diff --git a/examples/android/build.gradle.kts b/examples/android/build.gradle.kts index 80770a74..b1e780a4 100644 --- a/examples/android/build.gradle.kts +++ b/examples/android/build.gradle.kts @@ -7,7 +7,7 @@ dependencies { implementation(kotlin("stdlib")) implementation("androidx.appcompat:appcompat:1.2.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.8") - implementation(project(":stub-lite")) + implementation(project(":stub-android")) runtimeOnly("io.grpc:grpc-okhttp:${rootProject.ext["grpcVersion"]}") } diff --git a/examples/native-client/build.gradle.kts b/examples/native-client/build.gradle.kts new file mode 100644 index 00000000..9a633adb --- /dev/null +++ b/examples/native-client/build.gradle.kts @@ -0,0 +1,37 @@ +plugins { + application + kotlin("jvm") + id("com.palantir.graal") version "0.7.1" +} + +dependencies { + implementation(kotlin("stdlib-jdk8")) + implementation(project(":stub-lite")) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8") + + runtimeOnly("io.grpc:grpc-okhttp:${rootProject.ext["grpcVersion"]}") +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 +} + +application { + mainClass.set("io.grpc.examples.helloworld.HelloWorldClientKt") +} + +// todo: add graalvm-config-create task +// JAVA_HOME=~/.gradle/caches/com.palantir.graal/20.2.0/8/graalvm-ce-java8-20.2.0 JAVA_OPTS=-agentlib:native-image-agent=config-output-dir=native-client/src/graal native-client/build/install/native-client/bin/native-client + +graal { + graalVersion("20.2.0") + mainClass(application.mainClassName) + outputName("hello-world") + option("--verbose") + option("--no-server") + option("--no-fallback") + option("-H:+ReportExceptionStackTraces") + option("-H:+TraceClassInitialization") + option("-H:+PrintClassInitialization") + option("-H:ReflectionConfigurationFiles=src/graal/reflect-config.json") +} diff --git a/examples/native-client/src/graal/reflect-config.json b/examples/native-client/src/graal/reflect-config.json new file mode 100644 index 00000000..847eff5f --- /dev/null +++ b/examples/native-client/src/graal/reflect-config.json @@ -0,0 +1,84 @@ +[ +{ + "name":"com.sun.jndi.dns.DnsContextFactory" +}, +{ + "name":"io.grpc.examples.helloworld.HelloReply", + "fields":[{"name":"message_", "allowUnsafeAccess":true}] +}, +{ + "name":"io.grpc.examples.helloworld.HelloRequest", + "fields":[{"name":"name_", "allowUnsafeAccess":true}] +}, +{ + "name":"io.grpc.internal.DnsNameResolverProvider" +}, +{ + "name":"io.grpc.internal.JndiResourceResolverFactory", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"io.grpc.internal.PickFirstLoadBalancerProvider" +}, +{ + "name":"io.grpc.util.SecretRoundRobinLoadBalancerProvider$Provider" +}, +{ + "name":"java.net.InetSocketAddress", + "methods":[{"name":"getHostString","parameterTypes":[] }] +}, +{ + "name":"java.nio.Buffer", + "fields":[{"name":"address", "allowUnsafeAccess":true}] +}, +{ + "name":"java.util.concurrent.ScheduledThreadPoolExecutor", + "methods":[{"name":"setRemoveOnCancelPolicy","parameterTypes":["boolean"] }] +}, +{ + "name":"java.util.concurrent.atomic.LongAdder", + "allPublicConstructors":true, + "methods":[ + {"name":"add","parameterTypes":["long"] }, + {"name":"sum","parameterTypes":[] } + ] +}, +{ + "name":"javax.naming.directory.InitialDirContext" +}, +{ + "name":"kotlin.internal.jdk8.JDK8PlatformImplementations", + "methods":[{"name":"","parameterTypes":[] }] +}, +{ + "name":"sun.misc.Unsafe", + "allDeclaredFields":true, + "methods":[ + {"name":"arrayBaseOffset","parameterTypes":["java.lang.Class"] }, + {"name":"arrayIndexScale","parameterTypes":["java.lang.Class"] }, + {"name":"copyMemory","parameterTypes":["long","long","long"] }, + {"name":"copyMemory","parameterTypes":["java.lang.Object","long","java.lang.Object","long","long"] }, + {"name":"getBoolean","parameterTypes":["java.lang.Object","long"] }, + {"name":"getByte","parameterTypes":["long"] }, + {"name":"getByte","parameterTypes":["java.lang.Object","long"] }, + {"name":"getDouble","parameterTypes":["java.lang.Object","long"] }, + {"name":"getFloat","parameterTypes":["java.lang.Object","long"] }, + {"name":"getInt","parameterTypes":["long"] }, + {"name":"getInt","parameterTypes":["java.lang.Object","long"] }, + {"name":"getLong","parameterTypes":["long"] }, + {"name":"getLong","parameterTypes":["java.lang.Object","long"] }, + {"name":"getObject","parameterTypes":["java.lang.Object","long"] }, + {"name":"objectFieldOffset","parameterTypes":["java.lang.reflect.Field"] }, + {"name":"putBoolean","parameterTypes":["java.lang.Object","long","boolean"] }, + {"name":"putByte","parameterTypes":["long","byte"] }, + {"name":"putByte","parameterTypes":["java.lang.Object","long","byte"] }, + {"name":"putDouble","parameterTypes":["java.lang.Object","long","double"] }, + {"name":"putFloat","parameterTypes":["java.lang.Object","long","float"] }, + {"name":"putInt","parameterTypes":["long","int"] }, + {"name":"putInt","parameterTypes":["java.lang.Object","long","int"] }, + {"name":"putLong","parameterTypes":["long","long"] }, + {"name":"putLong","parameterTypes":["java.lang.Object","long","long"] }, + {"name":"putObject","parameterTypes":["java.lang.Object","long","java.lang.Object"] } + ] +} +] diff --git a/examples/native-client/src/main/kotlin/io/grpc/examples/helloworld/BUILD.bazel b/examples/native-client/src/main/kotlin/io/grpc/examples/helloworld/BUILD.bazel new file mode 100644 index 00000000..1c6ff3f6 --- /dev/null +++ b/examples/native-client/src/main/kotlin/io/grpc/examples/helloworld/BUILD.bazel @@ -0,0 +1,15 @@ +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_jvm_binary") + +licenses(["notice"]) + +package(default_visibility = ["//examples/src:__subpackages__"]) + +kt_jvm_binary( + name = "hello_world_client", + srcs = ["HelloWorldClient.kt"], + main_class = "io.grpc.examples.helloworld.HelloWorldClientKt", + deps = [ + "//examples/protos/src/main/proto/io/grpc/examples/helloworld:hello_world_kt_grpc", + "@io_grpc_grpc_java//netty", + ], +) diff --git a/examples/native-client/src/main/kotlin/io/grpc/examples/helloworld/HelloWorldClient.kt b/examples/native-client/src/main/kotlin/io/grpc/examples/helloworld/HelloWorldClient.kt new file mode 100644 index 00000000..6da59722 --- /dev/null +++ b/examples/native-client/src/main/kotlin/io/grpc/examples/helloworld/HelloWorldClient.kt @@ -0,0 +1,52 @@ +/* + * Copyright 2020 gRPC authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.grpc.examples.helloworld + +import io.grpc.ManagedChannel +import io.grpc.ManagedChannelBuilder +import io.grpc.examples.helloworld.GreeterGrpcKt.GreeterCoroutineStub +import java.io.Closeable +import java.util.concurrent.TimeUnit + +class HelloWorldClient(private val channel: ManagedChannel) : Closeable { + private val stub: GreeterCoroutineStub = GreeterCoroutineStub(channel) + + suspend fun greet(name: String) { + val request = HelloRequest.newBuilder().setName(name).build() + val response = stub.sayHello(request) + println("Received: ${response.message}") + } + + override fun close() { + channel.shutdown().awaitTermination(5, TimeUnit.SECONDS) + } +} + +/** + * Greeter, uses first argument as name to greet if present; + * greets "world" otherwise. + */ +suspend fun main(args: Array) { + val port = 50051 + + val channel = ManagedChannelBuilder.forAddress("localhost", port).usePlaintext().build() + + val client = HelloWorldClient(channel) + + val user = args.singleOrNull() ?: "world" + client.greet(user) +} diff --git a/examples/settings.gradle.kts b/examples/settings.gradle.kts index 93f2e062..236dd64e 100644 --- a/examples/settings.gradle.kts +++ b/examples/settings.gradle.kts @@ -1,10 +1,10 @@ rootProject.name = "grpc-kotlin-examples" -// when running the assemble task, ignore the android subproject +// when running the assemble task, ignore the android & graalvm related subprojects if (startParameter.taskRequests.find { it.args.contains("assemble") } == null) { - include("protos", "stub", "client", "server", "stub-lite", "android") + include("protos", "stub", "stub-lite", "client", "native-client", "server", "stub-android", "android") } else { - include("protos", "stub", "client", "server") + include("protos", "stub", "server") } pluginManagement { diff --git a/examples/stub-android/build.gradle.kts b/examples/stub-android/build.gradle.kts new file mode 100644 index 00000000..6da5d2a8 --- /dev/null +++ b/examples/stub-android/build.gradle.kts @@ -0,0 +1,68 @@ +import com.google.protobuf.gradle.generateProtoTasks +import com.google.protobuf.gradle.id +import com.google.protobuf.gradle.plugins +import com.google.protobuf.gradle.protobuf +import com.google.protobuf.gradle.protoc + +plugins { + id("com.android.library") + kotlin("android") + id("com.google.protobuf") +} + +dependencies { + protobuf(project(":protos")) + + implementation(kotlin("stdlib")) + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8") + implementation("javax.annotation:javax.annotation-api:1.3.2") + + api("com.google.protobuf:protobuf-javalite:${rootProject.ext["protobufVersion"]}") + api("io.grpc:grpc-protobuf-lite:${rootProject.ext["grpcVersion"]}") + + // todo: I think we should be able to just include this: + // api("io.grpc:grpc-kotlin-stub-lite:${rootProject.ext["grpcKotlinVersion"]}") + // but it doesn't pull transitives correctly + + api("io.grpc:grpc-stub:${rootProject.ext["grpcVersion"]}") + api("io.grpc:grpc-kotlin-stub:${rootProject.ext["grpcKotlinVersion"]}") { + exclude("io.grpc", "grpc-protobuf") + } +} + +android { + compileSdkVersion(30) + buildToolsVersion = "30.0.2" +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:${rootProject.ext["protobufVersion"]}" + } + plugins { + id("java") { + artifact = "io.grpc:protoc-gen-grpc-java:${rootProject.ext["grpcVersion"]}" + } + id("grpc") { + artifact = "io.grpc:protoc-gen-grpc-java:${rootProject.ext["grpcVersion"]}" + } + id("grpckt") { + artifact = "io.grpc:protoc-gen-grpc-kotlin:${rootProject.ext["grpcKotlinVersion"]}:jdk7@jar" + } + } + generateProtoTasks { + all().forEach { + it.plugins { + id("java") { + option("lite") + } + id("grpc") { + option("lite") + } + id("grpckt") { + option("lite") + } + } + } + } +} diff --git a/examples/stub-lite/src/main/AndroidManifest.xml b/examples/stub-android/src/main/AndroidManifest.xml similarity index 100% rename from examples/stub-lite/src/main/AndroidManifest.xml rename to examples/stub-android/src/main/AndroidManifest.xml diff --git a/examples/stub-lite/build.gradle.kts b/examples/stub-lite/build.gradle.kts index 6da5d2a8..f105fbf1 100644 --- a/examples/stub-lite/build.gradle.kts +++ b/examples/stub-lite/build.gradle.kts @@ -5,15 +5,14 @@ import com.google.protobuf.gradle.protobuf import com.google.protobuf.gradle.protoc plugins { - id("com.android.library") - kotlin("android") + kotlin("jvm") id("com.google.protobuf") } dependencies { protobuf(project(":protos")) - implementation(kotlin("stdlib")) + implementation(kotlin("stdlib-jdk8")) implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.8") implementation("javax.annotation:javax.annotation-api:1.3.2") @@ -30,9 +29,8 @@ dependencies { } } -android { - compileSdkVersion(30) - buildToolsVersion = "30.0.2" +java { + sourceCompatibility = JavaVersion.VERSION_1_8 } protobuf { @@ -40,9 +38,6 @@ protobuf { artifact = "com.google.protobuf:protoc:${rootProject.ext["protobufVersion"]}" } plugins { - id("java") { - artifact = "io.grpc:protoc-gen-grpc-java:${rootProject.ext["grpcVersion"]}" - } id("grpc") { artifact = "io.grpc:protoc-gen-grpc-java:${rootProject.ext["grpcVersion"]}" } @@ -52,10 +47,12 @@ protobuf { } generateProtoTasks { all().forEach { - it.plugins { - id("java") { + it.builtins { + named("java") { option("lite") } + } + it.plugins { id("grpc") { option("lite") } diff --git a/examples/stub/build.gradle.kts b/examples/stub/build.gradle.kts index bddd4ded..170a9a28 100644 --- a/examples/stub/build.gradle.kts +++ b/examples/stub/build.gradle.kts @@ -1,6 +1,5 @@ import com.google.protobuf.gradle.generateProtoTasks import com.google.protobuf.gradle.id -import com.google.protobuf.gradle.ofSourceSet import com.google.protobuf.gradle.plugins import com.google.protobuf.gradle.protobuf import com.google.protobuf.gradle.protoc @@ -40,7 +39,7 @@ protobuf { } } generateProtoTasks { - ofSourceSet("main").forEach { + all().forEach { it.plugins { id("grpc") id("grpckt") From 97da1dbcef60d449e01048028ed594afff361c6a Mon Sep 17 00:00:00 2001 From: James Ward Date: Mon, 5 Oct 2020 13:25:19 -0600 Subject: [PATCH 2/3] try to make tests not flaky --- .../io/grpc/testing/integration/AbstractInteropTest.kt | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/interop_testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.kt b/interop_testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.kt index d727909e..21574be5 100644 --- a/interop_testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.kt +++ b/interop_testing/src/main/java/io/grpc/testing/integration/AbstractInteropTest.kt @@ -188,7 +188,14 @@ abstract class AbstractInteropTest { private fun stopServer() { server?.shutdownNow() - testServiceExecutor?.shutdown() + server?.awaitTermination() + + testServiceExecutor?.let { + it.shutdown() + while (!it.isTerminated) { + it.awaitTermination(1, TimeUnit.SECONDS) + } + } } @get:VisibleForTesting From f594a236df6b39bd091d607e8adc3221e9a57251 Mon Sep 17 00:00:00 2001 From: Brent Shaffer Date: Tue, 13 Oct 2020 11:45:27 -0700 Subject: [PATCH 3/3] adds env var for PORT to graal client --- .../main/kotlin/io/grpc/examples/helloworld/HelloWorldClient.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/native-client/src/main/kotlin/io/grpc/examples/helloworld/HelloWorldClient.kt b/examples/native-client/src/main/kotlin/io/grpc/examples/helloworld/HelloWorldClient.kt index 6da59722..b875ecac 100644 --- a/examples/native-client/src/main/kotlin/io/grpc/examples/helloworld/HelloWorldClient.kt +++ b/examples/native-client/src/main/kotlin/io/grpc/examples/helloworld/HelloWorldClient.kt @@ -41,7 +41,7 @@ class HelloWorldClient(private val channel: ManagedChannel) : Closeable { * greets "world" otherwise. */ suspend fun main(args: Array) { - val port = 50051 + val port = System.getenv("PORT")?.toInt() ?: 50051 val channel = ManagedChannelBuilder.forAddress("localhost", port).usePlaintext().build()