grpc-spring-boot-starter icon indicating copy to clipboard operation
grpc-spring-boot-starter copied to clipboard

GRpcExceptionHandler doesn't work [Kotlin]

Open TheHett opened this issue 2 years ago • 16 comments

Hi, thanks for the work you've done. It's really great starter!

I have some trouble, maybe it related with Kotlin CoroutineImpl. The interceptor below does not catch exception:

@GRpcService
class TempUrlService() : TempUrlServiceGrpcKt.TempUrlServiceCoroutineImplBase() {

    private val logger = KotlinLogging.logger {}

    @GRpcExceptionHandler
    fun anotherHandler(e: NullPointerException, scope: GRpcExceptionScope): Status {
        logger.warn { "NPE!" }
        return Status.INTERNAL
    }

    @Throws(Throwable::class)
    override suspend fun createThumbnailTempUrl(request: ThumbRequest): ThumbResponse {
        throw NullPointerException("HELLO")
    }
}

Using 4.5.10 version.

TheHett avatar Dec 12 '21 08:12 TheHett

Can you please try to debug to see the private handler discovered here for your TempUrlService service bean ? The actual handler then resolved here

jvmlet avatar Dec 12 '21 13:12 jvmlet

@jvmlet My handler are presented, but method resolveMethodByThrowable isn't invoked. I could not find the entry point where interceptor should be invoked :(

TheHett avatar Dec 12 '21 14:12 TheHett

for unary call it's called from here

jvmlet avatar Dec 12 '21 14:12 jvmlet

Ohh, I think the issue is this line , I should also check the privateResolvers

jvmlet avatar Dec 12 '21 15:12 jvmlet

Meanwhile, please add dummy global handler as temporary workaround to proceed here ..

jvmlet avatar Dec 12 '21 15:12 jvmlet

Hi, do you mean GRpcServiceAdvice or GRpcGlobalInterceptor ?

First doesn't work too for me

image

While I wrapped in try..catch :)

TheHett avatar Dec 13 '21 08:12 TheHett

The problem is this line , it should check private resolvers as well. So, meanwhile, you can workaround the bug by adding dummy @GRpcServiceAdvice with @GRpcServiceAdvice method, it will force interceptor to proceed (here) and you private handler should be invoked

jvmlet avatar Dec 13 '21 11:12 jvmlet

Please tell, is code ok?

Exception

class TestException(message: String) : Exception(message)

Service

@GRpcService
class FileService() : FileServiceGrpcKt.FileServiceCoroutineImplBase() {

    override suspend fun getInfo(request: FileInfoRequest): FileInfoResponse {
        throw TestException("Hello")
    }
}

Advice

@GRpcServiceAdvice
class GRpcErrorInterceptor {
    private val logger = KotlinLogging.logger {}

    @GRpcExceptionHandler
    fun throwableExceptionHandler(e: TestException, scope: GRpcExceptionScope): Status {
        logger.error(e) { "gRPC unexpected error" }
        return Status.INTERNAL.withCause(e) // should be code 13
    }
}

But no logs in application, and status differs:

hett@STORM:~ $ grpc_cli call 172.23.0.1:9090 getInfo ""
connecting to 172.23.0.1:9090
Rpc failed with status code 2, error message:

I can provide demo app.

TheHett avatar Dec 13 '21 12:12 TheHett

Hmmm, I suspected that the problem is in the coroutines, but not. This doesn't work too:

@GRpcService()
class FileService() : FileServiceGrpc.FileServiceImplBase() {
    private val logger = KotlinLogging.logger {}
    override fun getInfo(request: FileInfoRequest, responseObserver: StreamObserver<FileInfoResponse>) {
        throw TestException("Hello")
    }
}

TheHett avatar Dec 13 '21 13:12 TheHett

You should have both @GRpcExceptionHandler : global and private. Your private handler will be called

jvmlet avatar Dec 13 '21 14:12 jvmlet

You also should wrap your exception with throw new GRpcRuntimeExceptionWrapper(new TestException(),"myHint"), the RuntimeException is caught...

jvmlet avatar Dec 13 '21 14:12 jvmlet

Bingo, with runtime exception it is works! The problem is that the Kotlin does not have throws signature and not differs checked/non-checked exceptions. In may case I testing java checked exception.

If declared next exception, no any logs will produced on error and it big problem:

class TestException(message: String) : java.lang.Exception(message)

The next code with runtime exception works (global handler registered too):

class TestException(message: String) : java.lang.RuntimeException(message)
@GRpcService()
class FileService() : FileServiceGrpc.FileServiceImplBase() {

    @GRpcExceptionHandler
    fun anotherHandler(e: TestException, scope: GRpcExceptionScope): Status {
        logger.warn { "error!" }
        return Status.INTERNAL
    }
    override fun getInfo(request: FileInfoRequest, responseObserver: StreamObserver<FileInfoResponse>) {
        throw TestException("Hello")
    }
}

But if trying to extend kotlin corutine impl we get silence again in the logs :(

@GRpcService()
class FileService() : FileServiceGrpcKt.FileServiceCoroutineImplBase() {
    @GRpcExceptionHandler
    fun anotherHandler(e: TestException, scope: GRpcExceptionScope): Status {
        logger.warn { "error!" }
        return Status.INTERNAL
    }
    override suspend fun getInfo(request: FileInfoRequest): FileInfoResponse {
        throw TestException("Hello")
    }
}

I suspect that it is different troubles.

TheHett avatar Dec 13 '21 16:12 TheHett

I created demo project which reproduces both problems https://github.com/TheHett/grpc_demo/tree/master/src/main/kotlin/com/example/demo/grpc

It should be call manually because I doesn't know how to write test for this

grpc_cli call 127.0.0.1:6565  demo.api.DemoService/test ""

TheHett avatar Dec 13 '21 17:12 TheHett

You can have a look at demo module in this repo to get the idea how to develop unit tests

jvmlet avatar Dec 13 '21 18:12 jvmlet

Hi, It doesn't works for me as for @TheHett. I have checked methodResolver attribute content inside the GRpcExceptionHandlerInterceptor after its initialisation through the constructor. It contains as expected in the mapperHandlers the @GRpcExceptionHandler method defined inside @GRpcServiceAdvice classes, and each grpc services (privateResolvers) contains to their @GRpcExceptionHandler method inside its respective mapperHandlers attribute.

At runtime, when I look at this condition for check global exception handler (declared via @GRpcServiceAdvice) it's return true but when I throw the exception (runtimeEx or not) it's not cached, and when I look too the same place for local exception handler (from @GrpcService) it's return false, and btw exception are not cached too.

Ps: for test with exception, i did used the wrapper as doc advices it.

@jvmlet have you an idea about how I could work around this issue please ?

YannPerthuis avatar Feb 28 '22 15:02 YannPerthuis

Any idea @jvmlet please ?

YannPerthuis avatar Mar 15 '22 14:03 YannPerthuis

i have the same problem with kotlin, @GrpcExceptionHandler in service or @GRpcServiceAdvice on single class doesn't work

littleniannian avatar Apr 10 '23 06:04 littleniannian

Still relevant with 4.9.1, any updates here?

vicmosin avatar Jul 20 '23 11:07 vicmosin

Fixed in latest 5.1.5-SNAPSHOT, please verify and confirm.

jvmlet avatar Aug 14 '23 07:08 jvmlet

5.1.5 was released

jvmlet avatar Sep 27 '23 05:09 jvmlet

I am still getting it in 5.1.5

Maybe it's somehow interfere with the order of interceptors?

grpc.security.auth.interceptor-order=-4
grpc.recovery.interceptor-order=-2
grpc.validation.interceptor-order=-1

vicmosin avatar Oct 06 '23 13:10 vicmosin