question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Questions about exception propagation in Armeria gRPC client with a RetryingClient decorator

See original GitHub issue

Hi there! I have 2 questions about how exception propagation behavior in Armeria gRPC client with a RetryingClient decorator. It seems to me that there could be a bug.

Setup

Armeria version: 1.16.0

Client setup

I build a gRPC client stub with a ClientBuilder and I add 3 decorators to the ClientBuilder as shown below:

// Please note that "Site A/B/C/D" mentioned in the comment below will be referred in later discussion.

// Decorator 1
clientBuilder.decorator((delegate, ctx, req) => {
    val httpResponse = delegate.execute(ctx, req)
    httpResponse
        .mapHeaders(respHeaders => {
             // Site A
             // 1. create an exception based on the response header content.
             // 2. throw the created exception.
        }
})

// Decorator 2
clientBuilder.decorator((delegate, ctx, req) => {
    ctx.log() // ClientRequestContext
      .whenComplete()
      .thenAccept(requestLog => {
          val exception = requestLog.responseCause()
          // The above `exception` is always null except in the case where
          // the last retry attempt fails.
          // Site B
       })
    delegate.execute(ctx, req)
})

// Decorator 3
val retryDecorator = RetryingClient.newDecorator(createRetryConfig())
clientBuilder.decorator(retryDecorator)

def createRetryConfig(): RetryConfig = {
   // ...
   val retryRuleBuilder = RetryRule.builder()
   retryRuleBuilder.onException((_: ClientRequestContext, cause: Throwable) => {
     // This callback is NEVER invoked.
     // Site C
   })
   retryRuleBuilder.onResponseTrailers((ctx, trailers) => {
     // This callback is invoked. The retry decision is made based on `trailers`
     // Site D
   })
   // Create a RetryConfig with the above `retryRuleBuilder` and return it.
}

Test setup

I wrote a test where a client with above decorators send one request to a server which always fails the requests and populates the response header with some error information. When the client receives the response, I expect the decorator 1’s httpResponse.mapHeaders callback (Site A) is invoked and an exception is created and thrown there. Then I expect the client to retry up to 3 times and eventually fail (because the server always fails the request and triggers an exception on the client side).

Observations

  1. Retry kicked in and 3 requests were sent sequentially. All 3 attempts failed that lead to client stub invocation failure. This behavior is expected 😃
  2. The order of invocation during each request attempt: Site A --> Site D --> Site B. Note that Site C is never invoked.
  3. In the first 2 request attempts at Site B, requestLog.responseCause() == null. However, in the 3rd/final request attempt at Site B, requestLog.responseCause() == exception_thrown_at_Site A.

Questions

  1. For observation 2, why does Site C never invoked? Is it a bug or I did something wrong?
  2. For observation 3, is this the expected/correct behavior? Specifically, does Armeria intentionally try to hide the exception from Site B (the ctx.log().whenComplete() callback) until the max retry attempt is reached? If it is intentional, could you share the rationale behind this design?

Issue Analytics

  • State:closed
  • Created 10 months ago
  • Comments:12 (6 by maintainers)

github_iconTop GitHub Comments

1reaction
Lincongcommented, Dec 6, 2022

If you want, I can release a patch version.

That’d be great! Here is the reason why it would be super helpful for us:

We are on 1.16.0 and my team currently does not have the resource/bandwidth to upgrade to the newest release version of Armeria. So, a patch fixing the issue in 1.16.0 would be the best.

Thanks! @minwoox

Read more comments on GitHub >

github_iconTop Results From Across the Web

Decorating a client — Armeria documentation
A 'decorating client' (or a 'decorator') is a client that wraps another client to intercept an outgoing request or an incoming response. As...
Read more >
Is it possible to use the ExceptionHandler annotation ... - GitHub
An exception raised while serving a gRPC service could be handled by GrpcServiceBuilder.addExceptionMapping() and GrpcServiceBuilder.
Read more >
Armeria grpc client: how can I set different retry behavior for ...
This enforces this retry behavior across all outgoing requests. Is there a way to provide different retry parameters for each grpc method ...
Read more >
Index (Armeria 1.3.0 API reference) - Javadoc.io
actual() - Method in exception com.linecorp.armeria.client. ... Common classes for handling the gRPC wire protocol without support for gRPC generated code ...
Read more >
Exception Handling and Error Propagation in gRPC Java
In this tutorial, we are going to look at how to handle exceptions in the gRPC Java server and provide information about them...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found