diff --git a/.github/workflows/ci-actions.yml b/.github/workflows/ci-actions.yml
index e6ca95ee6b14f..b4fac42d7d91d 100644
--- a/.github/workflows/ci-actions.yml
+++ b/.github/workflows/ci-actions.yml
@@ -575,6 +575,7 @@ jobs:
grpc-interceptors
grpc-mutual-auth
grpc-plain-text
+ grpc-plain-text-mutiny
grpc-proto-v2
grpc-streaming
grpc-tls
diff --git a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java
index af6228a251384..d3b0d5a3bfc8d 100644
--- a/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java
+++ b/extensions/grpc/deployment/src/main/java/io/quarkus/grpc/deployment/GrpcServerProcessor.java
@@ -160,7 +160,13 @@ HealthBuildItem addHealthChecks(GrpcServerBuildTimeConfig config,
NativeImageConfigBuildItem nativeImageConfiguration() {
NativeImageConfigBuildItem.Builder builder = NativeImageConfigBuildItem.builder()
.addRuntimeInitializedClass("io.grpc.netty.Utils$ByteBufAllocatorPreferDirectHolder")
- .addRuntimeInitializedClass("io.grpc.netty.Utils$ByteBufAllocatorPreferHeapHolder");
+ .addRuntimeInitializedClass("io.grpc.netty.Utils$ByteBufAllocatorPreferHeapHolder")
+ // substitutions are runtime-only, Utils have to be substituted until we cannot use EPoll
+ // in native. NettyServerBuilder and NettyChannelBuilder would "bring in" Utils in build time
+ // if they were not marked as runtime initialized:
+ .addRuntimeInitializedClass("io.grpc.netty.Utils")
+ .addRuntimeInitializedClass("io.grpc.netty.NettyServerBuilder")
+ .addRuntimeInitializedClass("io.grpc.netty.NettyChannelBuilder");
return builder.build();
}
diff --git a/extensions/grpc/runtime/pom.xml b/extensions/grpc/runtime/pom.xml
index 4695dc08aa157..96924164b9dc6 100644
--- a/extensions/grpc/runtime/pom.xml
+++ b/extensions/grpc/runtime/pom.xml
@@ -17,6 +17,10 @@
jakarta.annotation
jakarta.annotation-api
+
+ com.google.code.findbugs
+ jsr305
+
io.vertx
vertx-grpc
diff --git a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/graal/GrpcNettySubtitutions.java b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/graal/GrpcNettySubstitutions.java
similarity index 87%
rename from extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/graal/GrpcNettySubtitutions.java
rename to extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/graal/GrpcNettySubstitutions.java
index 9b90c24e54e40..4337cbbb391b1 100644
--- a/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/graal/GrpcNettySubtitutions.java
+++ b/extensions/grpc/runtime/src/main/java/io/quarkus/grpc/runtime/graal/GrpcNettySubstitutions.java
@@ -55,6 +55,20 @@ public static SslContextBuilder configure(SslContextBuilder builder, Provider jd
}
+@TargetClass(className = "io.grpc.netty.Utils")
+final class Target_io_grpc_netty_Utils {
+
+ @Substitute
+ static boolean isEpollAvailable() {
+ return false;
+ }
+
+ @Substitute
+ private static Throwable getEpollUnavailabilityCause() {
+ return null;
+ }
+}
+
@SuppressWarnings("unused")
class GrpcNettySubstitutions {
}
diff --git a/integration-tests/grpc-plain-text-mutiny/pom.xml b/integration-tests/grpc-plain-text-mutiny/pom.xml
new file mode 100644
index 0000000000000..4696971975b71
--- /dev/null
+++ b/integration-tests/grpc-plain-text-mutiny/pom.xml
@@ -0,0 +1,165 @@
+
+
+ 4.0.0
+
+
+ quarkus-integration-tests-parent
+ io.quarkus
+ 999-SNAPSHOT
+ ../
+
+
+ quarkus-integration-test-grpc-plain-text-mutiny
+ Quarkus - Integration Tests - gRPC - Plain Text with Mutiny
+
+
+
+ io.quarkus
+ quarkus-resteasy
+
+
+ io.quarkus
+ quarkus-resteasy-mutiny
+
+
+ io.quarkus
+ quarkus-grpc
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+ org.awaitility
+ awaitility
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+
+ io.quarkus
+ quarkus-grpc-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+ io.quarkus
+ quarkus-resteasy-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+ io.quarkus
+ quarkus-resteasy-mutiny-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
+
+
+
+
+
+ io.quarkus
+ quarkus-maven-plugin
+
+
+
+ generate-code
+ build
+
+
+
+
+
+
+
+
+
+ native-image
+
+
+ native
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ ${native.surefire.skip}
+
+
+
+ org.apache.maven.plugins
+ maven-failsafe-plugin
+
+
+
+ integration-test
+ verify
+
+
+
+ ${project.build.directory}/${project.build.finalName}-runner
+
+
+
+
+
+
+ io.quarkus
+ quarkus-maven-plugin
+
+
+ native-image
+
+ native-image
+
+
+ true
+ true
+ ${graalvmHome}
+
+
+
+
+
+
+
+
+
diff --git a/integration-tests/grpc-plain-text-mutiny/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldEndpoint.java b/integration-tests/grpc-plain-text-mutiny/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldEndpoint.java
new file mode 100644
index 0000000000000..9028734a3105b
--- /dev/null
+++ b/integration-tests/grpc-plain-text-mutiny/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldEndpoint.java
@@ -0,0 +1,43 @@
+package io.quarkus.grpc.examples.hello;
+
+import javax.inject.Inject;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+
+import examples.GreeterGrpc;
+import examples.HelloReply;
+import examples.HelloRequest;
+import examples.MutinyGreeterGrpc;
+import io.quarkus.grpc.runtime.annotations.GrpcService;
+import io.smallrye.mutiny.Uni;
+
+@Path("/hello")
+public class HelloWorldEndpoint {
+
+ @Inject
+ @GrpcService("hello")
+ GreeterGrpc.GreeterBlockingStub blockingHelloService;
+ @Inject
+ @GrpcService("hello")
+ MutinyGreeterGrpc.MutinyGreeterStub mutinyHelloService;
+
+ @GET
+ @Path("/blocking/{name}")
+ public String helloBlocking(@PathParam("name") String name) {
+ HelloReply reply = blockingHelloService.sayHello(HelloRequest.newBuilder().setName(name).build());
+ return generateResponse(reply);
+
+ }
+
+ @GET
+ @Path("/mutiny/{name}")
+ public Uni helloMutiny(@PathParam("name") String name) {
+ return mutinyHelloService.sayHello(HelloRequest.newBuilder().setName(name).build())
+ .onItem().transform((reply) -> generateResponse(reply));
+ }
+
+ public String generateResponse(HelloReply reply) {
+ return String.format("%s! HelloWorldService has been called %d number of times.", reply.getMessage(), reply.getCount());
+ }
+}
diff --git a/integration-tests/grpc-plain-text-mutiny/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldService.java b/integration-tests/grpc-plain-text-mutiny/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldService.java
new file mode 100644
index 0000000000000..667afdbe987e6
--- /dev/null
+++ b/integration-tests/grpc-plain-text-mutiny/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldService.java
@@ -0,0 +1,24 @@
+package io.quarkus.grpc.examples.hello;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.inject.Singleton;
+
+import examples.HelloReply;
+import examples.HelloRequest;
+import examples.MutinyGreeterGrpc;
+import io.smallrye.mutiny.Uni;
+
+@Singleton
+public class HelloWorldService extends MutinyGreeterGrpc.GreeterImplBase {
+
+ AtomicInteger counter = new AtomicInteger();
+
+ @Override
+ public Uni sayHello(HelloRequest request) {
+ int count = counter.incrementAndGet();
+ String name = request.getName();
+ return Uni.createFrom().item("Hello " + name)
+ .map(res -> HelloReply.newBuilder().setMessage(res).setCount(count).build());
+ }
+}
diff --git a/integration-tests/grpc-plain-text-mutiny/src/main/proto/helloworld.proto b/integration-tests/grpc-plain-text-mutiny/src/main/proto/helloworld.proto
new file mode 100644
index 0000000000000..89ccb456d8308
--- /dev/null
+++ b/integration-tests/grpc-plain-text-mutiny/src/main/proto/helloworld.proto
@@ -0,0 +1,54 @@
+// Copyright 2015, Google Inc.
+// All rights reserved.
+//
+// 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 Inc. 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.
+
+syntax = "proto3";
+
+option java_multiple_files = true;
+option java_package = "examples";
+option java_outer_classname = "HelloWorldProto";
+option objc_class_prefix = "HLW";
+
+package helloworld;
+
+// The greeting service definition.
+service Greeter {
+ // Sends a greeting
+ rpc SayHello (HelloRequest) returns (HelloReply) {}
+}
+
+// The request message containing the user's name.
+message HelloRequest {
+ string name = 1;
+}
+
+// The response message containing the greetings
+message HelloReply {
+ string message = 1;
+ int32 count = 2;
+}
diff --git a/integration-tests/grpc-plain-text-mutiny/src/main/resources/application.properties b/integration-tests/grpc-plain-text-mutiny/src/main/resources/application.properties
new file mode 100644
index 0000000000000..f8a95fc9c4c11
--- /dev/null
+++ b/integration-tests/grpc-plain-text-mutiny/src/main/resources/application.properties
@@ -0,0 +1 @@
+quarkus.grpc.clients.hello.host=localhost
\ No newline at end of file
diff --git a/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldEndpointIT.java b/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldEndpointIT.java
new file mode 100644
index 0000000000000..17f195143ebf5
--- /dev/null
+++ b/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldEndpointIT.java
@@ -0,0 +1,8 @@
+package io.quarkus.grpc.examples.hello;
+
+import io.quarkus.test.junit.NativeImageTest;
+
+@NativeImageTest
+class HelloWorldEndpointIT extends HelloWorldEndpointTest {
+
+}
\ No newline at end of file
diff --git a/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldEndpointTest.java b/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldEndpointTest.java
new file mode 100644
index 0000000000000..2c6c673852346
--- /dev/null
+++ b/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldEndpointTest.java
@@ -0,0 +1,25 @@
+package io.quarkus.grpc.examples.hello;
+
+import static io.restassured.RestAssured.get;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+class HelloWorldEndpointTest {
+
+ @Test
+ public void testHelloWorldServiceUsingBlockingStub() {
+ String response = get("/hello/blocking/neo").asString();
+ assertThat(response).startsWith("Hello neo");
+ }
+
+ @Test
+ public void testHelloWorldServiceUsingMutinyStub() {
+ String response = get("/hello/mutiny/neo-mutiny").asString();
+ assertThat(response).startsWith("Hello neo-mutiny");
+ }
+
+}
diff --git a/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldServiceIT.java b/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldServiceIT.java
new file mode 100644
index 0000000000000..addf2badc869b
--- /dev/null
+++ b/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldServiceIT.java
@@ -0,0 +1,8 @@
+package io.quarkus.grpc.examples.hello;
+
+import io.quarkus.test.junit.NativeImageTest;
+
+@NativeImageTest
+class HelloWorldServiceIT extends HelloWorldEndpointTest {
+
+}
\ No newline at end of file
diff --git a/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldServiceTest.java b/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldServiceTest.java
new file mode 100644
index 0000000000000..1f775a5177ae8
--- /dev/null
+++ b/integration-tests/grpc-plain-text-mutiny/src/test/java/io/quarkus/grpc/examples/hello/HelloWorldServiceTest.java
@@ -0,0 +1,52 @@
+package io.quarkus.grpc.examples.hello;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.time.Duration;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import examples.GreeterGrpc;
+import examples.HelloReply;
+import examples.HelloRequest;
+import examples.MutinyGreeterGrpc;
+import io.grpc.ManagedChannel;
+import io.grpc.ManagedChannelBuilder;
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+class HelloWorldServiceTest {
+
+ private ManagedChannel channel;
+
+ @BeforeEach
+ public void init() {
+ channel = ManagedChannelBuilder.forAddress("localhost", 9000).usePlaintext().build();
+ }
+
+ @AfterEach
+ public void cleanup() throws InterruptedException {
+ channel.shutdown();
+ channel.awaitTermination(10, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void testHelloWorldServiceUsingBlockingStub() {
+ GreeterGrpc.GreeterBlockingStub client = GreeterGrpc.newBlockingStub(channel);
+ HelloReply reply = client
+ .sayHello(HelloRequest.newBuilder().setName("neo-blocking").build());
+ assertThat(reply.getMessage()).isEqualTo("Hello neo-blocking");
+ }
+
+ @Test
+ public void testHelloWorldServiceUsingMutinyStub() {
+ HelloReply reply = MutinyGreeterGrpc.newMutinyStub(channel)
+ .sayHello(HelloRequest.newBuilder().setName("neo-blocking").build())
+ .await().atMost(Duration.ofSeconds(5));
+ assertThat(reply.getMessage()).isEqualTo("Hello neo-blocking");
+ }
+
+}
diff --git a/integration-tests/grpc-plain-text/pom.xml b/integration-tests/grpc-plain-text/pom.xml
index 9a3f60042fe46..4f8f259956c22 100644
--- a/integration-tests/grpc-plain-text/pom.xml
+++ b/integration-tests/grpc-plain-text/pom.xml
@@ -88,6 +88,28 @@
+
+
+
+ io.quarkus
+ quarkus-infinispan-client
+
+
+ io.quarkus
+ quarkus-infinispan-client-deployment
+ ${project.version}
+ pom
+ test
+
+
+ *
+ *
+
+
+
diff --git a/integration-tests/grpc-plain-text/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldService.java b/integration-tests/grpc-plain-text/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldService.java
index 667afdbe987e6..aa1967bbd877c 100644
--- a/integration-tests/grpc-plain-text/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldService.java
+++ b/integration-tests/grpc-plain-text/src/main/java/io/quarkus/grpc/examples/hello/HelloWorldService.java
@@ -4,21 +4,22 @@
import javax.inject.Singleton;
+import examples.GreeterGrpc;
import examples.HelloReply;
import examples.HelloRequest;
-import examples.MutinyGreeterGrpc;
-import io.smallrye.mutiny.Uni;
+import io.grpc.stub.StreamObserver;
@Singleton
-public class HelloWorldService extends MutinyGreeterGrpc.GreeterImplBase {
+public class HelloWorldService extends GreeterGrpc.GreeterImplBase {
AtomicInteger counter = new AtomicInteger();
@Override
- public Uni sayHello(HelloRequest request) {
+ public void sayHello(HelloRequest request, StreamObserver responseObserver) {
int count = counter.incrementAndGet();
String name = request.getName();
- return Uni.createFrom().item("Hello " + name)
- .map(res -> HelloReply.newBuilder().setMessage(res).setCount(count).build());
+ String res = "Hello " + name;
+ responseObserver.onNext(HelloReply.newBuilder().setMessage(res).setCount(count).build());
+ responseObserver.onCompleted();
}
}
diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml
index 0795b67316672..99c679702a1d3 100644
--- a/integration-tests/pom.xml
+++ b/integration-tests/pom.xml
@@ -144,8 +144,9 @@
mailer
- grpc-plain-text
grpc-tls
+ grpc-plain-text
+ grpc-plain-text-mutiny
grpc-mutual-auth
grpc-streaming
grpc-interceptors