Search code examples
javaprotocol-buffersgrpc

gRPC NotImplementedException though method is called


I was trying to play around with protobuf and gRPC in java while following the basic tutorial provided, Although I always tend to create my own scenario and build everything from the ground up. I learn better this way than to copy-paste tutorial code and run it.

I have created the following project (https://github.com/IngoHenkel/protobuf_test) that contains a service class with two services, one synchronous service that simply returns a server info, and an asynchronous service that simulates a longrunning task returning data in a stream.

...
service DeepThoughtServices {
    rpc WhoAreYou (google.protobuf.Empty) returns  (WhoAmI) {}
    rpc RequestIntegerTokens(CalculateIntegerTokens) returns (stream IntegerToken) {}
}
...

The first service (WhoAreYou) works perfectly fine, but for the other service, I always get a NotImplementedException on the client. Strangely, the method itsself gets called on the server perfectly fine (which you can see by the logs), but the moment I try to call the "onNext" method to return data, I get the exception:

java.lang.IllegalStateException: Stream was terminated by error, no further calls are allowed
    at com.google.common.base.Preconditions.checkState(Preconditions.java:502)
    at io.grpc.stub.ServerCalls$ServerCallStreamObserverImpl.onNext(ServerCalls.java:374)
    at de.squirrelsquad.tutorials.protobuf.protos.service.DeepThoughtServices.requestIntegerTokens(DeepThoughtServices.java:56)
    at de.squirrelsquad.tutorials.protobuf.protos.service.DeepThoughtServicesGrpc$MethodHandlers.invoke(DeepThoughtServicesGrpc.java:271)
    at io.grpc.stub.ServerCalls$UnaryServerCallHandler$UnaryServerCallListener.onHalfClose(ServerCalls.java:182)
    at io.grpc.internal.ServerCallImpl$ServerStreamListenerImpl.halfClosed(ServerCallImpl.java:340)
    at io.grpc.internal.ServerImpl$JumpToApplicationThreadServerStreamListener$1HalfClosed.runInContext(ServerImpl.java:866)
    at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
    at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:133)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:833)

. I first thought that it may have to do with me trying to dispatch the "work" into a background thread and sending the response from there, but even though I now simply send the responses directly within the service method, I still get the error.

Both client and server share the same code base, you can run it by executing mvn package and then run: java -jar target/grpc-test-0.0.1-SNAPSHOT-jar-with-dependencies.jar -s 8980 to start the server and ava -jar target/grpc-test-0.0.1-SNAPSHOT-jar-with-dependencies.jar -c localhost:8980 for the client, which will call both methods and then return.

Has anybody got an Idea whats wrong here?

  • tried calling the methods directly in the "main" thread (no dispatching)
  • played around with package and class names

Solution

  • Looking at the beginning of DeepThoughtServices.requestIntegerTokens() we see:

    public void requestIntegerTokens(CalculateIntegerTokens request, StreamObserver<IntegerToken> responseObserver) {
        // TODO Auto-generated method stub
        logger.info("Request integer tokens called");
        super.requestIntegerTokens(request, responseObserver);
        responseObserver.onNext(IntegerToken.newBuilder().setValue(1).build());
    

    The last line there is the one that throws the IllegalStateException.

    The problem is the super.requestIntegerTokens(). DeepThoughtServicesImplBase has default implementations for each method that fail the RPC with UNIMPLEMENTED. This allows adding new methods to the service in the .proto without breaking existing code.

    To fix your problem, simply remove the call to super:

    public void requestIntegerTokens(CalculateIntegerTokens request, StreamObserver<IntegerToken> responseObserver) {
        // TODO Auto-generated method stub
        logger.info("Request integer tokens called");
        // DON'T DELEGATE TO SUPER!
        //super.requestIntegerTokens(request, responseObserver);
        responseObserver.onNext(IntegerToken.newBuilder().setValue(1).build());