Skip to content
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

Instrumented Java 11 HttpClient does not re-throw exceptions in sendAsync call #5136

Open
alex-inv opened this issue May 22, 2024 · 1 comment
Labels
bug A general bug help wanted An issue that a contributor can help us with instrumentation An issue that is related to instrumenting a component
Milestone

Comments

@alex-inv
Copy link

Describe the bug
After instrumenting our java.net.HttpClient with newly released MicrometerHttpClient decorator, we've noticed our tests started failing. It seems that if any exception (e.g. network error) occurs, it will be caught in MicrometerHttpClient and not propagated further to the next CompletionStage available to the user, contrary to what happens in the original HttpClient implementation.

Implementation of sendAsync decorator in MicrometerHttpClient does not re-throw exception in the CompletableFuture.handle() method, but returns only response. In case handle got an exception as its input, null result will be returned instead:

return client.sendAsync(request, bodyHandler, pushPromiseHandler).handle((response, throwable) -> {
            if (throwable != null) {
                instrumentation.setThrowable(throwable);
            }
            instrumentation.setResponse(response);
            stopObservationOrTimer(instrumentation, request, response);
            return response;
        });

Environment

  • Micrometer version: 1.13.0
  • OS: macOS Sonoma 14.4.1
  • Java version: Corretto-17.0.6.10.1

To Reproduce
The easiest way to reproduce the bug is to write a test simulating a faulty HTTP server, for example using WireMock library. I've attached a minimal demo application (built with JDK 17 and Gradle) with a failing test.

Here I show two tests, first passing and second failing, highlighting the reported issue:

@Test
public void testError() {
        WireMock.stubFor(WireMock.get(WireMock.urlEqualTo("/test-url"))
                .willReturn(new ResponseDefinitionBuilder().withFault(Fault.CONNECTION_RESET_BY_PEER)));

        var client = HttpClient.newBuilder().build();

        var request = HttpRequest.newBuilder(URI.create(wireMockServer.baseUrl() + "/test-url"))
                .GET().build();

        var response = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());

        assertThrows(CompletionException.class, response::join);
}

@Test
public void testErrorMicrometer() {
        WireMock.stubFor(WireMock.get(WireMock.urlEqualTo("/test-url"))
                .willReturn(new ResponseDefinitionBuilder().withFault(Fault.CONNECTION_RESET_BY_PEER)));

        var client = MicrometerHttpClient.instrumentationBuilder(
                HttpClient.newBuilder().build(),
                new SimpleMeterRegistry()
        ).build();

        var request = HttpRequest.newBuilder(URI.create(wireMockServer.baseUrl() + "/test-url"))
                .GET().build();

        var response = client.sendAsync(request, HttpResponse.BodyHandlers.ofString());

        // This test fails - nothing is thrown
        assertThrows(CompletionException.class, response::join);
}

Expected behavior
Underlying error should be re-thrown and CompletionStage returned to the user should be completed exceptionally too, as per original API behavior.

While it's not possible to re-throw a generic Throwable in CompletableFuture.handle method due to limitations of checked exceptions handling, it's possible to wrap the original Throwable in a CompletionException, which would not break the CompletableFuture flow of executions.

@shakuzen
Copy link
Member

shakuzen commented Sep 3, 2024

Thanks for all the analysis and code. Would you be willing to submit a pull request for this?

@shakuzen shakuzen added bug A general bug instrumentation An issue that is related to instrumenting a component and removed waiting-for-triage labels Sep 3, 2024
@shakuzen shakuzen added this to the 1.13.x milestone Sep 3, 2024
@shakuzen shakuzen added the help wanted An issue that a contributor can help us with label Sep 3, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug A general bug help wanted An issue that a contributor can help us with instrumentation An issue that is related to instrumenting a component
Projects
None yet
Development

No branches or pull requests

2 participants