Skip to content

Add perMethod, perHost, perPath and builder to RetryConfigMapping #6243

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

schiemon
Copy link
Contributor

@schiemon schiemon commented May 17, 2025

Motivation:

The motivation of this PR is explained in the associated issue.

Modifications:

  • Added perMethod, perHost, perPath and perHostAndMethod to RetryConfigMapping to enable retry configuration on a per method, host and path basis. This API is analogous to CircuitBreakerMapping.
  • Added RetryConfig.noRetry for being more concise when signalling that no retry is expected, especially in the per*methods.
  • Also added RetryConfigMapping.builder and RetryConfigMappingBuilder. This API is analogous to CircuitBreakerMappingBuilder.
  • Moved common utility methods from CircuitBreakerMappingUtil to RequestContextUtil
  • Added (missing) tests for RetryConfigMapping

Result:

@CLAassistant
Copy link

CLAassistant commented May 17, 2025

CLA assistant check
All committers have signed the CLA.

@schiemon schiemon force-pushed the 6242-retry-config-mapping-per-method-path-and-host-basis branch from d338fc6 to 087b025 Compare May 17, 2025 14:41
@schiemon
Copy link
Contributor Author

After the main part of the review I will add the javadocs :)

@schiemon schiemon marked this pull request as ready for review May 17, 2025 14:48
Copy link

codecov bot commented May 17, 2025

Codecov Report

Attention: Patch coverage is 98.27586% with 1 line in your changes missing coverage. Please review.

Project coverage is 74.69%. Comparing base (8150425) to head (cb5fe28).
Report is 102 commits behind head on main.

Files with missing lines Patch % Lines
...rp/armeria/internal/common/RequestContextUtil.java 90.90% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #6243      +/-   ##
============================================
+ Coverage     74.46%   74.69%   +0.23%     
- Complexity    22234    22546     +312     
============================================
  Files          1963     1976      +13     
  Lines         82437    83184     +747     
  Branches      10764    10821      +57     
============================================
+ Hits          61385    62135     +750     
+ Misses        15918    15897      -21     
- Partials       5134     5152      +18     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Contributor

@jrhee17 jrhee17 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do think RetryConfig#noRetry will be useful, and I also appreciate the extensive test coverage.
Left a comment on APIs added to RetryConfigMapping

Comment on lines +62 to +64
static <T extends Response> RetryConfigMappingBuilder<T> builder() {
return new RetryConfigMappingBuilder<>();
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the initiative. I agree intuitively, it makes sense that retry and circuitbreaker share similar APIs.

Having said this, I do think retry is a little different from circuitbreaker in the sense that CircuitBreaker share a common mutable configuration across requests. For this reason, a mapping structure is used to cache the CircuitBreakers used per host/method/path.

On the other hand, RetryConfig is an immutable object which decides how each request will be retried (in case there is a retry). Hence, there is no real reason we should be caching the returned RetryConfigs. It may be worth checking the discussion at #3145 as well.

I do think there is value in pre-parsing method/host/path parameters so that users don't have to extract each value from the ctx though.

e.g.

static <T extends Response> RetryConfigMapping<T> of(RetryConfigFactory<T> retryConfigFactory) {
}

I'm not sure of the usefulness of the builder() or per*() apis though since users can simply choose to ignore certain fields of the RetryConfigFactory anyways.

e.g. users can simply choose not to use the path

RetryConfigMapping.of((host, method, path) -> {
    if ("asdf".equals(host)) {
        return RetryConfig.noRetry();   
    }
    ...
});

Let me know what you think.

cc. other maintainers @line/dx

Copy link
Contributor Author

@schiemon schiemon May 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for your review @jrhee17.

On the other hand, RetryConfig is an immutable object which decides how each request will be retried (in case there is a retry). Hence, there is no real reason we should be caching the returned RetryConfigs.

Ah, so you're effectively saying that RetryConfigMapping doesn't necessarily need a keyFactory in RetryConfigMapping.of:

static <T extends Response> RetryConfigMapping<T> of(
        BiFunction<? super ClientRequestContext, Request, String> keyFactory, // Could be removed and we could just recreate the `RetryConfig`s for every request as they are immutable.
        BiFunction<? super ClientRequestContext, Request, RetryConfig<T>> retryConfigFactory) {
    return new KeyedRetryConfigMapping<>(keyFactory, retryConfigFactory);
}

and that we could offer an "uncached" version like:

// Returns a mapping that regenerates the `RetryConfig` on every request. Would not work with
// `CircuitBreaker`s, as multiple requests hitting the same host may need to mutate the same
// `CircuitBreaker`.
static <T extends Response> RetryConfigMapping<T> of(
        BiFunction<? super ClientRequestContext, Request, RetryConfig<T>> retryConfigFactory) {
    return new KeyedRetryConfigMapping<>(retryConfigFactory);
}

Since this API is not currently offered, and because RetryConfigMapping mentions caching twice, I assumed there was a performance reason for caching — for example, reducing GC pressure or handling expensive factory methods.

If it’s just for ergonomics, I like your suggestion as it avoids specifying the keyFactory. However, given that we already cache in KeyedRetryConfigMapping, and considering that we have CircuitBreakerMapping alongside it, I would lean towards using a per*-style approach.

I'm also happy to hear input from the other maintainers.

@github-actions github-actions bot added Stale and removed Stale labels Jun 20, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Enable creating a RetryConfigMapping on a per method, path, or host basis
3 participants