diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel index 2a5ed52b35c..3c63d7a6eb7 100644 --- a/examples/BUILD.bazel +++ b/examples/BUILD.bazel @@ -172,3 +172,20 @@ java_binary( ], ) +java_binary( + name = "deadline-server", + testonly = 1, + main_class = "io.grpc.examples.deadline.DeadlineServer", + runtime_deps = [ + ":examples", + ], +) + +java_binary( + name = "deadline-client", + testonly = 1, + main_class = "io.grpc.examples.deadline.DeadlineClient", + runtime_deps = [ + ":examples", + ], +) diff --git a/examples/build.gradle b/examples/build.gradle index 2758892aadd..bd3fe1ec198 100644 --- a/examples/build.gradle +++ b/examples/build.gradle @@ -167,6 +167,13 @@ task nameResolveClient(type: CreateStartScripts) { classpath = startScripts.classpath } +task deadlineServer(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.deadline.DeadlineServer' + applicationName = 'deadline-server' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + task keepAliveServer(type: CreateStartScripts) { mainClass = 'io.grpc.examples.keepalive.KeepAliveServer' applicationName = 'keep-alive-server' @@ -174,6 +181,13 @@ task keepAliveServer(type: CreateStartScripts) { classpath = startScripts.classpath } +task deadlineClient(type: CreateStartScripts) { + mainClass = 'io.grpc.examples.deadline.DeadlineClient' + applicationName = 'deadline-client' + outputDir = new File(project.buildDir, 'tmp/scripts/' + name) + classpath = startScripts.classpath +} + task keepAliveClient(type: CreateStartScripts) { mainClass = 'io.grpc.examples.keepalive.KeepAliveClient' applicationName = 'keep-alive-client' @@ -197,6 +211,8 @@ applicationDistribution.into('bin') { from(loadBalanceClient) from(nameResolveServer) from(nameResolveClient) + from(deadlineServer) + from(deadlineClient) from(keepAliveServer) from(keepAliveClient) fileMode = 0755 diff --git a/examples/src/main/java/io/grpc/examples/deadline/DeadlineClient.java b/examples/src/main/java/io/grpc/examples/deadline/DeadlineClient.java new file mode 100644 index 00000000000..378688ee1cc --- /dev/null +++ b/examples/src/main/java/io/grpc/examples/deadline/DeadlineClient.java @@ -0,0 +1,110 @@ +/* + * Copyright 2023 The 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.deadline; + +import io.grpc.Channel; +import io.grpc.Grpc; +import io.grpc.InsecureChannelCredentials; +import io.grpc.ManagedChannel; +import io.grpc.Status; +import io.grpc.StatusRuntimeException; +import io.grpc.examples.helloworld.GreeterGrpc; +import io.grpc.examples.helloworld.HelloReply; +import io.grpc.examples.helloworld.HelloRequest; +import io.grpc.examples.helloworld.HelloWorldServer; +import java.util.concurrent.TimeUnit; +import java.util.logging.ConsoleHandler; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +/** + * A simple client that requests a greeting from the {@link HelloWorldServer}. + * + *
This is based off the client in the helloworld example with some deadline logic added.
+ */
+public class DeadlineClient {
+ private static final Logger logger = Logger.getLogger(DeadlineClient.class.getName());
+
+ private final GreeterGrpc.GreeterBlockingStub blockingStub;
+
+ /** Construct client for accessing HelloWorld server using the existing channel. */
+ public DeadlineClient(Channel channel) {
+ blockingStub = GreeterGrpc.newBlockingStub(channel);
+ }
+
+ /** Say hello to server. */
+ public Status greet(String name, long timeoutMillis) {
+ logger.info("Will try to greet " + name + " ...");
+ HelloRequest request = HelloRequest.newBuilder().setName(name).build();
+ HelloReply response;
+ try {
+ response = blockingStub.withDeadlineAfter(timeoutMillis, TimeUnit.MILLISECONDS)
+ .sayHello(request);
+ } catch (StatusRuntimeException e) {
+ logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
+ return e.getStatus();
+ }
+ logger.info("Greeting: " + response.getMessage());
+ return Status.OK;
+ }
+
+ /**
+ * Greet server. If provided, the first element of {@code args} is the name to use in the
+ * greeting. The second argument is the target server.
+ */
+ public static void main(String[] args) throws Exception {
+ System.setProperty("java.util.logging.SimpleFormatter.format",
+ "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$s %2$s %5$s%6$s%n");
+
+ // Access a service running on the local machine on port 50051
+ String target = "localhost:50051";
+
+ ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create())
+ .build();
+ try {
+ DeadlineClient client = new DeadlineClient(channel);
+
+ // The server takes 500ms to process the call, so setting a deadline further in the future we
+ // should get a successful response.
+ logger.info("Calling server with a generous deadline, expected to work");
+ client.greet("deadline client", 1000);
+
+ // A smaller deadline will result in us getting a DEADLINE_EXCEEDED error.
+ logger.info(
+ "Calling server with an unrealistic (300ms) deadline, expecting a DEADLINE_EXCEEDED");
+ client.greet("deadline client", 300);
+
+ // Including the "propagate" magic string in the request will cause the server to call itself
+ // to simulate a situation where a server needs to call another server to satisfy the original
+ // request. This will double the time it takes to respond to the client request, but with
+ // an increased deadline we should get a successful response.
+ logger.info("Calling server with propagation and a generous deadline, expected to work");
+ client.greet("deadline client [propagate]", 2000);
+
+ // With this propagated call we reduce the deadline making it impossible for the both the
+ // first server call and the propagated one to succeed. You should see the call fail with
+ // DEADLINE_EXCEEDED, and you should also see DEADLINE_EXCEEDED in the server output as it
+ // runs out of time waiting for the propagated call to finish.
+ logger.info(
+ "Calling server with propagation and a generous deadline, expecting a DEADLINE_EXCEEDED");
+ client.greet("deadline client [propagate]", 1000);
+ } finally {
+ channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
+ }
+ }
+}
diff --git a/examples/src/main/java/io/grpc/examples/deadline/DeadlineServer.java b/examples/src/main/java/io/grpc/examples/deadline/DeadlineServer.java
new file mode 100644
index 00000000000..49214fc77d5
--- /dev/null
+++ b/examples/src/main/java/io/grpc/examples/deadline/DeadlineServer.java
@@ -0,0 +1,119 @@
+
+/*
+ * Copyright 2023 The 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.deadline;
+
+import io.grpc.Grpc;
+import io.grpc.InsecureChannelCredentials;
+import io.grpc.InsecureServerCredentials;
+import io.grpc.Server;
+import io.grpc.examples.helloworld.GreeterGrpc;
+import io.grpc.examples.helloworld.HelloReply;
+import io.grpc.examples.helloworld.HelloRequest;
+import io.grpc.stub.StreamObserver;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.Logger;
+
+public class DeadlineServer {
+ private static final Logger logger = Logger.getLogger(DeadlineServer.class.getName());
+
+ private Server server;
+
+
+ private void start() throws IOException {
+ int port = 50051;
+ SlowGreeter slowGreeter = new SlowGreeter();
+ server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
+ .addService(slowGreeter)
+ .build()
+ .start();
+ logger.info("Server started, listening on " + port);
+
+ // Create a channel to this same server so we can make a recursive call to demonstrate deadline
+ // propagation.
+ String target = "localhost:50051";
+ slowGreeter.setClientStub(GreeterGrpc.newBlockingStub(
+ Grpc.newChannelBuilder(target, InsecureChannelCredentials.create()).build()));
+
+ Runtime.getRuntime().addShutdownHook(new Thread() {
+ @Override
+ public void run() {
+ // Use stderr here since the logger may have been reset by its JVM shutdown hook.
+ System.err.println("*** shutting down gRPC server since JVM is shutting down");
+ try {
+ DeadlineServer.this.stop();
+ } catch (InterruptedException e) {
+ e.printStackTrace(System.err);
+ }
+ System.err.println("*** server shut down");
+ }
+ });
+ }
+
+ private void stop() throws InterruptedException {
+ if (server != null) {
+ server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
+ }
+ }
+
+ /**
+ * Await termination on the main thread since the grpc library uses daemon threads.
+ */
+ private void blockUntilShutdown() throws InterruptedException {
+ if (server != null) {
+ server.awaitTermination();
+ }
+ }
+
+ /**
+ * Main launches the server from the command line.
+ */
+ public static void main(String[] args) throws IOException, InterruptedException {
+ System.setProperty("java.util.logging.SimpleFormatter.format",
+ "%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS %4$s %2$s %5$s%6$s%n");
+
+ final DeadlineServer server = new DeadlineServer();
+ server.start();
+ server.blockUntilShutdown();
+ }
+
+ static class SlowGreeter extends GreeterGrpc.GreeterImplBase {
+ private GreeterGrpc.GreeterBlockingStub clientStub;
+
+ void setClientStub(GreeterGrpc.GreeterBlockingStub clientStub) {
+ this.clientStub = clientStub;
+ }
+
+ @Override
+ public void sayHello(HelloRequest req, StreamObserver