Skip to content

Make sure to always close input streams bound to responses - #1970

Merged
kwin merged 3 commits into
masterfrom
bugfix/consume-put-response
Jul 17, 2026
Merged

Make sure to always close input streams bound to responses#1970
kwin merged 3 commits into
masterfrom
bugfix/consume-put-response

Conversation

@kwin

@kwin kwin commented Jul 16, 2026

Copy link
Copy Markdown
Member

Ensure all connections are closed when transporter is closed Add PUT IT where response contains body

This closes #1964

Following this checklist to help us incorporate your
contribution quickly and easily:

  • Your pull request should address just one issue, without pulling in other changes.
  • Write a pull request description that is detailed enough to understand what the pull request does, how, and why.
  • Each commit in the pull request should have a meaningful subject line and body.
    Note that commits might be squashed by a maintainer on merge.
  • Write unit tests that match behavioral changes, where the tests fail if the changes to the runtime are not applied.
    This may not always be possible but is a best-practice.
  • Run mvn verify to make sure basic checks pass.
    A more thorough check will be performed on your pull request automatically.
  • You have run the integration tests successfully (mvn -Prun-its verify).

If your pull request is about ~20 lines of code you don't need to sign an
Individual Contributor License Agreement if you are unsure
please ask on the developers list.

To make clear that you license your contribution under
the Apache License Version 2.0, January 2004
you have to acknowledge this by using the following check-box.

@kwin
kwin force-pushed the bugfix/consume-put-response branch 3 times, most recently from 53d6891 to ab86f9c Compare July 16, 2026 19:33
@kwin

kwin commented Jul 17, 2026

Copy link
Copy Markdown
Member Author

Some investigations ongoing on how to properly test for dangling connections: jetty/jetty.project#15436.

@kwin
kwin force-pushed the bugfix/consume-put-response branch 2 times, most recently from 35d80f9 to 4998677 Compare July 17, 2026 15:44
Ensure all connections are closed when transporter is closed.
Add PUT IT where response contains body.

This closes #1964
@kwin
kwin force-pushed the bugfix/consume-put-response branch from 4998677 to 99df910 Compare July 17, 2026 15:44
@kwin
kwin marked this pull request as ready for review July 17, 2026 15:45
@kwin kwin added the bug Something isn't working label Jul 17, 2026
@kwin
kwin requested a review from cstamas July 17, 2026 15:46

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Verdict: ✅ Approve — excellent fix for a real connection leak that could cause deadlocks in production.

What's great

The production fixes are correct and well-targeted:

  • JdkTransporter implPut(): adds the missing finally { closeBody(response); } — this was the core bug. closeBody is already null-safe (checks streamHttpResponse != null), so the response = null initialization is safe.
  • JdkTransporter implGet(): clean simplification — removes redundant inner closeBody() calls since the outer finally already handles all exit paths.
  • JettyTransporter implPut(): wrapping InputStreamResponseListener in try-with-resources and moving RFC 9457 handling inside ensures the listener is always closed. The HttpTransporterException still propagates correctly (it's not caught by the ExecutionException catch block).
  • Connection leak detection in tearDown() is a force multiplier — every existing HTTP transporter test now doubles as a leak detector.

Suggestions

1. Null guard before Awaitility check (HttpTransporterTest.java, line ~309)

The Awaitility assertion runs before the if (httpServer != null) check. If setUp() fails before creating the server, @AfterEach still runs and the lambda will NPE, masking the real failure:

// Current:
Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() -> httpServer.getNumConnectedEndPoints() == 0);
if (httpServer != null) {

// Suggested — move inside the null guard:
if (httpServer != null) {
    Awaitility.await().atMost(5, TimeUnit.SECONDS).until(() -> httpServer.getNumConnectedEndPoints() == 0);
    httpServer.stop();
    httpServer = null;
}

2. HTTP 204 with response body — may weaken the test (HttpServer.java, PUT handler)

The response body is written, then status is set to SC_NO_CONTENT (204). Per RFC 9110 §6.4.1, 204 responses should have no content. While Jetty will still put the bytes on the wire, some HTTP client implementations may skip draining the body for 204, which could make the test pass even without the fix. Consider using SC_OK (200) when responseBodyForPut is set so the test is unambiguous:

if (responseBodyForPut != null) {
    writeResponseBodyMessage(req, response, responseBodyForPut);
    response.setStatus(HttpServletResponse.SC_OK);
} else {
    response.setStatus(HttpServletResponse.SC_NO_CONTENT);
}

Minor observations (no action needed)

  • Double closeBody in implPut: The inner try { ... } finally { closeBody(response); } around error handling is now redundant with the new outer finally. Harmless (idempotent close), but in implGet the equivalent was cleaned up — consistency could be improved.
  • Hardcoded Awaitility version: 4.3.0 is hardcoded in maven-resolver-test-http/pom.xml rather than managed in the parent POM's <dependencyManagement>.
  • Pre-existing: JettyTransporter.implGet() also creates InputStreamResponseListener without try-with-resources — the same pattern this PR fixes in implPut(). Might be worth a follow-up.

CI: ⏳ All pending (just un-drafted). Production code changes are straightforward enough to review without CI, but will want to confirm all tests pass.


Reviewed with AI assistance on behalf of @gnodet. This review was generated by an AI agent and may contain inaccuracies; please verify all suggestions before applying.

gnodet added a commit to gnodet/maven-resolver that referenced this pull request Jul 17, 2026
gnodet added a commit to gnodet/maven-resolver that referenced this pull request Jul 17, 2026

@gnodet gnodet left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Follow-up review (commit 2/2): ✅ All suggestions addressed — looks great.

  • Null guard: Awaitility check correctly moved inside if (httpServer != null)
  • PUT response status: Using SC_CREATED (201) instead of SC_NO_CONTENT (204) when a body is present — even better than the 200 I suggested, since 201 is the semantically correct success status for PUT-creates-resource ✓
  • Awaitility version: Centralized in parent POM <dependencyManagement>, with a helpful note about the 4.3.1 release issue ✓
  • Double closeBody in implPut: Inner try/finally removed, now consistent with implGet simplification ✓

No further comments.


Reviewed with AI assistance on behalf of @gnodet. This review was generated by an AI agent and may contain inaccuracies; please verify all suggestions before applying.

gnodet added a commit to gnodet/maven-resolver that referenced this pull request Jul 17, 2026
gnodet added a commit to gnodet/maven-resolver that referenced this pull request Jul 17, 2026
@kwin
kwin merged commit dbaf309 into master Jul 17, 2026
23 checks passed
@kwin
kwin deleted the bugfix/consume-put-response branch July 17, 2026 16:22
@github-actions github-actions Bot added this to the 2.0.21 milestone Jul 17, 2026
gnodet added a commit to gnodet/maven-resolver that referenced this pull request Jul 18, 2026
gnodet added a commit to gnodet/maven-resolver that referenced this pull request Jul 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

JettyTransporter and JdkTransporter do not consume response body of PUT potentially leading to dangling connections or deadlocks

3 participants