mirror of https://github.com/grpc/grpc-java.git
examples: Add cancellation example
It uses the echo service for both unary and bidi RPCs, to show the various cancellation circumstances and APIs.
This commit is contained in:
parent
6b7cb9e4a4
commit
39c9ebf180
|
|
@ -51,6 +51,22 @@ java_grpc_library(
|
||||||
deps = [":route_guide_java_proto"],
|
deps = [":route_guide_java_proto"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
proto_library(
|
||||||
|
name = "echo_proto",
|
||||||
|
srcs = ["src/main/proto/grpc/examples/echo/echo.proto"],
|
||||||
|
)
|
||||||
|
|
||||||
|
java_proto_library(
|
||||||
|
name = "echo_java_proto",
|
||||||
|
deps = [":echo_proto"],
|
||||||
|
)
|
||||||
|
|
||||||
|
java_grpc_library(
|
||||||
|
name = "echo_java_grpc",
|
||||||
|
srcs = [":echo_proto"],
|
||||||
|
deps = [":echo_java_proto"],
|
||||||
|
)
|
||||||
|
|
||||||
java_library(
|
java_library(
|
||||||
name = "examples",
|
name = "examples",
|
||||||
testonly = 1,
|
testonly = 1,
|
||||||
|
|
@ -64,6 +80,8 @@ java_library(
|
||||||
"@io_grpc_grpc_java//netty",
|
"@io_grpc_grpc_java//netty",
|
||||||
],
|
],
|
||||||
deps = [
|
deps = [
|
||||||
|
":echo_java_grpc",
|
||||||
|
":echo_java_proto",
|
||||||
":hello_streaming_java_grpc",
|
":hello_streaming_java_grpc",
|
||||||
":hello_streaming_java_proto",
|
":hello_streaming_java_proto",
|
||||||
":helloworld_java_grpc",
|
":helloworld_java_grpc",
|
||||||
|
|
|
||||||
|
|
@ -202,6 +202,20 @@ task keepAliveClient(type: CreateStartScripts) {
|
||||||
classpath = startScripts.classpath
|
classpath = startScripts.classpath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task cancellationClient(type: CreateStartScripts) {
|
||||||
|
mainClass = 'io.grpc.examples.cancellation.CancellationClient'
|
||||||
|
applicationName = 'cancellation-client'
|
||||||
|
outputDir = new File(project.buildDir, 'tmp/scripts/' + name)
|
||||||
|
classpath = startScripts.classpath
|
||||||
|
}
|
||||||
|
|
||||||
|
task cancellationServer(type: CreateStartScripts) {
|
||||||
|
mainClass = 'io.grpc.examples.cancellation.CancellationServer'
|
||||||
|
applicationName = 'cancellation-server'
|
||||||
|
outputDir = new File(project.buildDir, 'tmp/scripts/' + name)
|
||||||
|
classpath = startScripts.classpath
|
||||||
|
}
|
||||||
|
|
||||||
applicationDistribution.into('bin') {
|
applicationDistribution.into('bin') {
|
||||||
from(routeGuideServer)
|
from(routeGuideServer)
|
||||||
from(routeGuideClient)
|
from(routeGuideClient)
|
||||||
|
|
@ -223,5 +237,7 @@ applicationDistribution.into('bin') {
|
||||||
from(deadlineClient)
|
from(deadlineClient)
|
||||||
from(keepAliveServer)
|
from(keepAliveServer)
|
||||||
from(keepAliveClient)
|
from(keepAliveClient)
|
||||||
|
from(cancellationClient)
|
||||||
|
from(cancellationServer)
|
||||||
fileMode = 0755
|
fileMode = 0755
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,204 @@
|
||||||
|
/*
|
||||||
|
* 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.cancellation;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.FutureCallback;
|
||||||
|
import com.google.common.util.concurrent.Futures;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
import io.grpc.Channel;
|
||||||
|
import io.grpc.Context;
|
||||||
|
import io.grpc.Context.CancellableContext;
|
||||||
|
import io.grpc.Grpc;
|
||||||
|
import io.grpc.InsecureChannelCredentials;
|
||||||
|
import io.grpc.ManagedChannel;
|
||||||
|
import io.grpc.Status;
|
||||||
|
import io.grpc.StatusRuntimeException;
|
||||||
|
import io.grpc.examples.echo.EchoGrpc;
|
||||||
|
import io.grpc.examples.echo.EchoRequest;
|
||||||
|
import io.grpc.examples.echo.EchoResponse;
|
||||||
|
import io.grpc.stub.ClientCallStreamObserver;
|
||||||
|
import io.grpc.stub.StreamObserver;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A client that cancels RPCs to an Echo server.
|
||||||
|
*/
|
||||||
|
public class CancellationClient {
|
||||||
|
private final Channel channel;
|
||||||
|
|
||||||
|
public CancellationClient(Channel channel) {
|
||||||
|
this.channel = channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void demonstrateCancellation() throws Exception {
|
||||||
|
echoBlocking("I'M A BLOCKING CLIENT! HEAR ME ROAR!");
|
||||||
|
|
||||||
|
// io.grpc.Context can be used to cancel RPCs using any of the stubs. It is the only way to
|
||||||
|
// cancel blocking stub RPCs. io.grpc.Context is a general-purpose alternative to thread
|
||||||
|
// interruption and can be used outside of gRPC, like to coordinate within your application.
|
||||||
|
//
|
||||||
|
// CancellableContext must always be cancelled or closed at the end of its lifetime, otherwise
|
||||||
|
// it could "leak" memory.
|
||||||
|
try (CancellableContext context = Context.current().withCancellation()) {
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
Thread.sleep(500); // Do some work
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
// Cancellation reasons are never sent to the server. But they are echoed back to the
|
||||||
|
// client as the RPC failure reason.
|
||||||
|
context.cancel(new RuntimeException("Oops. Messed that up, let me try again"));
|
||||||
|
}).start();
|
||||||
|
|
||||||
|
// context.run() attaches the context to this thread for gRPC to observe. It also restores
|
||||||
|
// the previous context before returning.
|
||||||
|
context.run(() -> echoBlocking("RAAWRR haha lol hehe AWWRR GRRR"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Futures cancelled with interruption cancel the RPC.
|
||||||
|
ListenableFuture<EchoResponse> future = echoFuture("Future clie*cough*nt was here!");
|
||||||
|
Thread.sleep(500); // Do some work
|
||||||
|
// We realize we really don't want to hear that echo.
|
||||||
|
future.cancel(true);
|
||||||
|
Thread.sleep(100); // Make logs more obvious. Cancel is async
|
||||||
|
|
||||||
|
ClientCallStreamObserver<EchoRequest> reqCallObserver = echoAsync("Testing, testing, 1, 2, 3");
|
||||||
|
reqCallObserver.onCompleted();
|
||||||
|
Thread.sleep(500); // Make logs more obvious. Wait for completion
|
||||||
|
|
||||||
|
// Async's onError() will cancel. But the method can't be called concurrently with other calls
|
||||||
|
// on the StreamObserver. If you need thread-safety, use CancellableContext as above.
|
||||||
|
StreamObserver<EchoRequest> reqObserver = echoAsync("... async client... is the... best...");
|
||||||
|
try {
|
||||||
|
Thread.sleep(500); // Do some work
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
// Since reqObserver.onCompleted() hasn't been called, we can use onError().
|
||||||
|
reqObserver.onError(new RuntimeException("That was weak..."));
|
||||||
|
Thread.sleep(100); // Make logs more obvious. Cancel is async
|
||||||
|
|
||||||
|
// Async's cancel() will cancel. Also may not be called concurrently with other calls on the
|
||||||
|
// StreamObserver.
|
||||||
|
reqCallObserver = echoAsync("Async client or bust!");
|
||||||
|
reqCallObserver.onCompleted();
|
||||||
|
try {
|
||||||
|
Thread.sleep(250); // Do some work
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
// Since onCompleted() has been called, we can't use onError(). It is safe to use cancel()
|
||||||
|
// regardless of onCompleted() being called.
|
||||||
|
reqCallObserver.cancel("That's enough. I'm bored", null);
|
||||||
|
Thread.sleep(100); // Make logs more obvious. Cancel is async
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Say hello to server, just like in helloworld example. */
|
||||||
|
public void echoBlocking(String text) {
|
||||||
|
System.out.println("\nYelling: " + text);
|
||||||
|
EchoRequest request = EchoRequest.newBuilder().setMessage(text).build();
|
||||||
|
EchoResponse response;
|
||||||
|
try {
|
||||||
|
response = EchoGrpc.newBlockingStub(channel).unaryEcho(request);
|
||||||
|
} catch (StatusRuntimeException e) {
|
||||||
|
System.out.println("RPC failed: " + e.getStatus());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
System.out.println("Echo: " + response.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Say hello to the server, but using future API. */
|
||||||
|
public ListenableFuture<EchoResponse> echoFuture(String text) {
|
||||||
|
System.out.println("\nYelling: " + text);
|
||||||
|
EchoRequest request = EchoRequest.newBuilder().setMessage(text).build();
|
||||||
|
ListenableFuture<EchoResponse> future = EchoGrpc.newFutureStub(channel).unaryEcho(request);
|
||||||
|
Futures.addCallback(future, new FutureCallback<EchoResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onSuccess(EchoResponse response) {
|
||||||
|
System.out.println("Echo: " + response.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFailure(Throwable t) {
|
||||||
|
System.out.println("RPC failed: " + Status.fromThrowable(t));
|
||||||
|
}
|
||||||
|
}, MoreExecutors.directExecutor());
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Say hello to the server, but using async API and cancelling. */
|
||||||
|
public ClientCallStreamObserver<EchoRequest> echoAsync(String text) {
|
||||||
|
System.out.println("\nYelling: " + text);
|
||||||
|
EchoRequest request = EchoRequest.newBuilder().setMessage(text).build();
|
||||||
|
|
||||||
|
// Client-streaming and bidirectional RPCs can cast the returned StreamObserver to
|
||||||
|
// ClientCallStreamObserver.
|
||||||
|
//
|
||||||
|
// Unary and server-streaming stub methods don't return a StreamObserver. For such RPCs, you can
|
||||||
|
// use ClientResponseObserver to get the ClientCallStreamObserver. For example:
|
||||||
|
// EchoGrpc.newStub(channel).unaryEcho(new ClientResponseObserver<EchoResponse>() {...});
|
||||||
|
// Since ClientCallStreamObserver.cancel() is not thread-safe, it isn't safe to call from
|
||||||
|
// another thread until the RPC stub method (e.g., unaryEcho()) returns.
|
||||||
|
ClientCallStreamObserver<EchoRequest> reqObserver = (ClientCallStreamObserver<EchoRequest>)
|
||||||
|
EchoGrpc.newStub(channel).bidirectionalStreamingEcho(new StreamObserver<EchoResponse>() {
|
||||||
|
@Override
|
||||||
|
public void onNext(EchoResponse response) {
|
||||||
|
System.out.println("Echo: " + response.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCompleted() {
|
||||||
|
System.out.println("RPC completed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Throwable t) {
|
||||||
|
System.out.println("RPC failed: " + Status.fromThrowable(t));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
reqObserver.onNext(request);
|
||||||
|
return reqObserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancel RPCs to a server. If provided, the first element of {@code args} is the target server.
|
||||||
|
*/
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
String target = "localhost:50051";
|
||||||
|
if (args.length > 0) {
|
||||||
|
if ("--help".equals(args[0])) {
|
||||||
|
System.err.println("Usage: [target]");
|
||||||
|
System.err.println("");
|
||||||
|
System.err.println(" target The server to connect to. Defaults to " + target);
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
target = args[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
ManagedChannel channel = Grpc.newChannelBuilder(target, InsecureChannelCredentials.create())
|
||||||
|
.build();
|
||||||
|
try {
|
||||||
|
CancellationClient client = new CancellationClient(channel);
|
||||||
|
client.demonstrateCancellation();
|
||||||
|
} finally {
|
||||||
|
channel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,206 @@
|
||||||
|
/*
|
||||||
|
* 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.cancellation;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
|
import io.grpc.Context;
|
||||||
|
import io.grpc.Grpc;
|
||||||
|
import io.grpc.InsecureServerCredentials;
|
||||||
|
import io.grpc.Server;
|
||||||
|
import io.grpc.Status;
|
||||||
|
import io.grpc.examples.echo.EchoGrpc;
|
||||||
|
import io.grpc.examples.echo.EchoRequest;
|
||||||
|
import io.grpc.examples.echo.EchoResponse;
|
||||||
|
import io.grpc.stub.ServerCallStreamObserver;
|
||||||
|
import io.grpc.stub.StreamObserver;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.Future;
|
||||||
|
import java.util.concurrent.FutureTask;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Server that manages startup/shutdown of a {@code Greeter} server.
|
||||||
|
*
|
||||||
|
* <p>Any abort of an ongoing RPC is considered "cancellation" of that RPC. The common causes of
|
||||||
|
* cancellation are the client explicitly cancelling, the deadline expires, and I/O failures. The
|
||||||
|
* service is not informed the reason for the cancellation.
|
||||||
|
*
|
||||||
|
* <p>There are two APIs for services to be notified of RPC cancellation: io.grpc.Context and
|
||||||
|
* ServerCallStreamObserver. Context listeners are called on a different thread, so need to be
|
||||||
|
* thread-safe. The ServerCallStreamObserver cancellation callback is called like other
|
||||||
|
* StreamObserver callbacks, so the application may not need thread-safe handling. Both APIs have
|
||||||
|
* thread-safe isCancelled() polling methods.
|
||||||
|
*/
|
||||||
|
public class CancellationServer {
|
||||||
|
public static void main(String[] args) throws IOException, InterruptedException {
|
||||||
|
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
|
||||||
|
int port = 50051;
|
||||||
|
Server server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
|
||||||
|
.addService(new SlowEcho(scheduler))
|
||||||
|
.build()
|
||||||
|
.start();
|
||||||
|
System.out.println("Server started, listening on " + port);
|
||||||
|
Runtime.getRuntime().addShutdownHook(new Thread() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
server.shutdown().awaitTermination(30, TimeUnit.SECONDS);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace(System.err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
server.awaitTermination();
|
||||||
|
scheduler.shutdown();
|
||||||
|
if (!scheduler.awaitTermination(5, TimeUnit.SECONDS)) {
|
||||||
|
scheduler.shutdownNow();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class SlowEcho extends EchoGrpc.EchoImplBase {
|
||||||
|
private final ScheduledExecutorService scheduler;
|
||||||
|
|
||||||
|
/** {@code scheduler} must be single-threaded. */
|
||||||
|
public SlowEcho(ScheduledExecutorService scheduler) {
|
||||||
|
this.scheduler = scheduler;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Repeatedly echos each request until the client has no more requests. It performs all work
|
||||||
|
* asynchronously on a single thread. It uses ServerCallStreamObserver to be notified of RPC
|
||||||
|
* cancellation.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public StreamObserver<EchoRequest> bidirectionalStreamingEcho(
|
||||||
|
StreamObserver<EchoResponse> responseObserver) {
|
||||||
|
// If the service is truly asynchronous, using ServerCallStreamObserver to receive
|
||||||
|
// cancellation notifications tends to work well.
|
||||||
|
|
||||||
|
// It is safe to cast the provided observer to ServerCallStreamObserver.
|
||||||
|
ServerCallStreamObserver<EchoResponse> responseCallObserver =
|
||||||
|
(ServerCallStreamObserver<EchoResponse>) responseObserver;
|
||||||
|
System.out.println("\nBidi RPC started");
|
||||||
|
class EchoObserver implements StreamObserver<EchoRequest> {
|
||||||
|
private static final int delayMs = 200;
|
||||||
|
private final List<Future<?>> echos = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNext(EchoRequest request) {
|
||||||
|
System.out.println("Bidi RPC received request: " + request.getMessage());
|
||||||
|
EchoResponse response
|
||||||
|
= EchoResponse.newBuilder().setMessage(request.getMessage()).build();
|
||||||
|
Runnable echo = () -> responseObserver.onNext(response);
|
||||||
|
echos.add(scheduler.scheduleAtFixedRate(echo, delayMs, delayMs, TimeUnit.MILLISECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCompleted() {
|
||||||
|
System.out.println("Bidi RPC client finished");
|
||||||
|
// Let each echo happen two more times, and then stop.
|
||||||
|
List<Future<?>> echosCopy = new ArrayList<>(echos);
|
||||||
|
Runnable complete = () -> {
|
||||||
|
stopEchos(echosCopy);
|
||||||
|
responseObserver.onCompleted();
|
||||||
|
System.out.println("Bidi RPC completed");
|
||||||
|
};
|
||||||
|
echos.add(scheduler.schedule(complete, 2*delayMs, TimeUnit.MILLISECONDS));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onError(Throwable t) {
|
||||||
|
System.out.println("Bidi RPC failed: " + Status.fromThrowable(t));
|
||||||
|
stopEchos(echos);
|
||||||
|
scheduler.execute(() -> responseObserver.onError(t));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCancel() {
|
||||||
|
// If onCompleted() hasn't been called by this point, then this method and onError are
|
||||||
|
// both called. If onCompleted() has been called, then just this method is called.
|
||||||
|
System.out.println("Bidi RPC cancelled");
|
||||||
|
stopEchos(echos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void stopEchos(List<Future<?>> echos) {
|
||||||
|
for (Future<?> echo : echos) {
|
||||||
|
echo.cancel(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
EchoObserver requestObserver = new EchoObserver();
|
||||||
|
// onCancel() can be called even after the service completes or fails the RPC, because
|
||||||
|
// callbacks are racy and the response still has to be sent to the client. Use
|
||||||
|
// setOnCloseHandler() to be notified when the RPC completed without cancellation (as best as
|
||||||
|
// the server is able to tell).
|
||||||
|
responseCallObserver.setOnCancelHandler(requestObserver::onCancel);
|
||||||
|
return requestObserver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Echos the request after a delay. It processes the request in-line within the callback. It
|
||||||
|
* uses Context to be notified of RPC cancellation.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void unaryEcho(EchoRequest request, StreamObserver<EchoResponse> responseObserver) {
|
||||||
|
// ServerCallStreamObserver.setOnCancelHandler(Runnable) is not useful for this method, since
|
||||||
|
// this method only returns once it has a result. ServerCallStreamObserver guarantees the
|
||||||
|
// Runnable is not run at the same time as other RPC callback methods (including this method),
|
||||||
|
// so the cancellation notification would be guaranteed to occur too late.
|
||||||
|
System.out.println("\nUnary RPC started: " + request.getMessage());
|
||||||
|
Context currentContext = Context.current();
|
||||||
|
// Let's start a multi-part operation. We can check cancellation periodically.
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
// ServerCallStreamObserver.isCancelled() returns true only if the RPC is cancelled.
|
||||||
|
// Context.isCancelled() is similar, but also returns true when the RPC completes normally.
|
||||||
|
// It doesn't matter which API is used here.
|
||||||
|
if (currentContext.isCancelled()) {
|
||||||
|
System.out.println("Unary RPC cancelled");
|
||||||
|
responseObserver.onError(
|
||||||
|
Status.CANCELLED.withDescription("RPC cancelled").asRuntimeException());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureTask<Void> task = new FutureTask<>(() -> {
|
||||||
|
Thread.sleep(100); // Do some work
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
// Some Java blocking APIs have a method to cancel an ongoing operation, like closing an
|
||||||
|
// InputStream or interrupting the thread. We can use a Context listener to call that API
|
||||||
|
// from another thread if the RPC is cancelled.
|
||||||
|
Context.CancellationListener listener = (Context context) -> task.cancel(true);
|
||||||
|
Context.current().addListener(listener, MoreExecutors.directExecutor());
|
||||||
|
task.run(); // A cancellable operation
|
||||||
|
Context.current().removeListener(listener);
|
||||||
|
|
||||||
|
// gRPC stubs observe io.grpc.Context cancellation, so cancellation is automatically
|
||||||
|
// propagated when performing an RPC. You can use a different Context or use Context.fork()
|
||||||
|
// to disable the automatic propagation. For example,
|
||||||
|
// Context.ROOT.call(() -> futureStub.unaryEcho(request));
|
||||||
|
// context.fork().call(() -> futureStub.unaryEcho(request));
|
||||||
|
}
|
||||||
|
responseObserver.onNext(
|
||||||
|
EchoResponse.newBuilder().setMessage(request.getMessage()).build());
|
||||||
|
responseObserver.onCompleted();
|
||||||
|
System.out.println("Unary RPC completed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2018 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
option go_package = "google.golang.org/grpc/examples/features/proto/echo";
|
||||||
|
option java_multiple_files = true;
|
||||||
|
option java_package = "io.grpc.examples.echo";
|
||||||
|
option java_outer_classname = "EchoProto";
|
||||||
|
|
||||||
|
package grpc.examples.echo;
|
||||||
|
|
||||||
|
// EchoRequest is the request for echo.
|
||||||
|
message EchoRequest {
|
||||||
|
string message = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// EchoResponse is the response for echo.
|
||||||
|
message EchoResponse {
|
||||||
|
string message = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Echo is the echo service.
|
||||||
|
service Echo {
|
||||||
|
// UnaryEcho is unary echo.
|
||||||
|
rpc UnaryEcho(EchoRequest) returns (EchoResponse) {}
|
||||||
|
// ServerStreamingEcho is server side streaming.
|
||||||
|
rpc ServerStreamingEcho(EchoRequest) returns (stream EchoResponse) {}
|
||||||
|
// ClientStreamingEcho is client side streaming.
|
||||||
|
rpc ClientStreamingEcho(stream EchoRequest) returns (EchoResponse) {}
|
||||||
|
// BidirectionalStreamingEcho is bidi streaming.
|
||||||
|
rpc BidirectionalStreamingEcho(stream EchoRequest) returns (stream EchoResponse) {}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue