Skip to content

Conversation

@jrhee17
Copy link
Contributor

@jrhee17 jrhee17 commented Oct 28, 2025

Motivation:

This issue was found while working on GrpcServicesPreprocessor refactoring.

Currently, there is a bug where exceptions/response failures in Preprocessor do not complete ctx.whenInitialized.
e.g.

WebClient.of((delegate, ctx, req) -> {
    // ctx.whenInitialized will not be completed
    throw new RuntimeException("error");
}).get("/");

While normally this may not be an issue, gRPC relies on completion of this future to signal errors:
ref:

ctx.whenInitialized().handle((unused1, unused2) -> {

I propose two modifications to resolve this issue:

  • When a Preprocessor throws an exception from the PreClient#execute calling thread, it should complete the ctx
WebClient.of((delegate, ctx, req) -> {
    throw new RuntimeException("error");
}).get("/");
  • When ctx.cancel is called (even before ctx.init) is called, it should complete the ctx
WebClient.of((delegate, ctx, req) -> {
    ctx.cancel(e);
    return HttpResponse.of(400);
}).get("/");

Modifications:

  • Introduced an initialized flag to guard against concurrent initialization attempts
    • finishInitialization is also modified to guard against concurrent calls
    • A new initAndFail method is introduced which acquires an event loop, initializes the cancellation scheduler, and completes the ctx
    • ctx.cancel will trigger initAndFail if the ctx is not initialized yet
    • If PreClient#execute throws an exception, initAndFail is called
    • For derived contexts, if an endpoint does not exist, it means ClientUtil#initContextAnd* needs to be called. Hence, initialization is set only if an endpoint exists.
    • responseCancellationScheduler.finishNow is called in DefaultClientRequestContext#failEarly to record cancellationCause consistently.
    • XdsPreprocessor and RouterFilter now calls ctx.cancel if a request is short-circuited before ctx.init is called.

Result:

  • Requests failed at the XdsPreprocessor-level are propagated to the user correctly when using gRPC.

@jrhee17 jrhee17 added this to the 1.34.0 milestone Oct 28, 2025
@jrhee17 jrhee17 added the defect label Oct 28, 2025
Copy link
Contributor

@minwoox minwoox left a comment

Choose a reason for hiding this comment

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

Looks great. 👍
Left small suggestions. 😉

if (!initializationTriggeredUpdater.compareAndSet(this, 0, 1)) {
return false;
}
acquireEventLoop(endpointGroup);
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably, we don't need to call initializeResponseCancellationScheduler in acquireEventLoop?

ctxExt.runContextCustomizer();
}
try {
return execution.execute(ctx, req);
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think about moving the call to completeLogIfIncomplete from line 111 to here? It seems like it would prevent the case where an http response is returned in the pre decorator.

Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't we also change this line?
futureConverter, errorResponseFactory, req, false);

futureConverter, errorResponseFactory, req, true);

Additionally, this isn't related to this PR but shouldn't tryCompleteLog be true in these lines? cc @ikhoon

(context, cause) -> HttpResponse.ofFailure(cause), ctxReq, false);
} else {
response = executeWithFallback(unwrap(), derivedCtx,
(context, cause) -> HttpResponse.ofFailure(cause), ctxReq, false);

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Shouldn't we also change this line? futureConverter, errorResponseFactory, req, false);

Understood that the intention is that completeLogIfIncomplete is already registered when preclients are invoked, and hence another callback doesn't need to be added

@codecov
Copy link

codecov bot commented Nov 3, 2025

Codecov Report

❌ Patch coverage is 50.84746% with 29 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.16%. Comparing base (8150425) to head (cbdeab3).
⚠️ Report is 231 commits behind head on main.

Files with missing lines Patch % Lines
...corp/armeria/xds/client/endpoint/RouterFilter.java 0.00% 13 Missing ⚠️
...a/internal/client/DefaultClientRequestContext.java 68.57% 5 Missing and 6 partials ⚠️
.../linecorp/armeria/client/retry/RetryingClient.java 0.00% 0 Missing and 1 partial ⚠️
...necorp/armeria/client/retry/RetryingRpcClient.java 0.00% 0 Missing and 1 partial ⚠️
...m/linecorp/armeria/internal/client/ClientUtil.java 83.33% 0 Missing and 1 partial ⚠️
...ria/internal/common/NoopCancellationScheduler.java 0.00% 1 Missing ⚠️
...p/armeria/xds/client/endpoint/XdsPreprocessor.java 0.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #6466      +/-   ##
============================================
- Coverage     74.46%   74.16%   -0.30%     
- Complexity    22234    23023     +789     
============================================
  Files          1963     2061      +98     
  Lines         82437    86190    +3753     
  Branches      10764    11305     +541     
============================================
+ Hits          61385    63926    +2541     
- Misses        15918    16868     +950     
- Partials       5134     5396     +262     

☔ 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.

@jrhee17 jrhee17 marked this pull request as ready for review November 3, 2025 06:39
Copy link
Contributor

@ikhoon ikhoon left a comment

Choose a reason for hiding this comment

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

👍 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants