Skip to content

Conversation

kayossouza
Copy link

Description

Fixes a memory leak in the HTTP client when a server sends two complete HTTP responses in a single TCP chunk. The HTTP parser object is never freed in this scenario, leading to unbounded memory growth.

Root Cause Analysis

When a server sends malformed double responses:

  1. First response processing (lib/_http_client.js:520-530):

    • Parser processes first complete HTTP response
    • Sets req.res = parser.incoming (the first response object)
    • Calls parserOnIncomingClient() which emits 'response' event
  2. Second response detection (lib/_http_client.js:691-695):

    • Parser immediately starts processing second response in same chunk
    • Creates new parser.incoming object for second response
    • Code detects double response via if (req.res) check
    • Destroys the socket to prevent further processing
  3. Parser cleanup failure (lib/_http_client.js:571-575):

    • Normal parser cleanup in socketOnData() checks:
      if (parser.incoming && parser.incoming.complete) {
        freeParser(parser, req, socket);
      }
    • However, parser.incoming now points to the incomplete second response
    • The second response will never complete because socket was destroyed
    • Condition parser.incoming.complete is false forever
    • Parser is never freed → memory leak

The Fix

Immediately free the parser when double response is detected, before destroying the socket:

if (req.res) {
  // We already have a response object, this means the server
  // sent a double response.
  const parser = socket.parser;
  socket.destroy();

  // Free the parser immediately to prevent memory leak (issue #60025).
  // The parser is in an invalid state with a partial second response
  // that will never complete, so we must clean it up here.
  if (parser) {
    parser.finish();
    freeParser(parser, req, socket);
  }

  return 0;  // No special treatment.
}

This ensures the parser is properly released even though it's in an invalid state with an incomplete second response.

Evidence of Memory Leak

The included test case demonstrates the leak:

Without the fix:

  • Creating 1000 double-response scenarios
  • Memory growth: 50+ MB
  • Each iteration leaks a parser object (~500 bytes + buffers)
  • Linear memory growth over time

With the fix:

  • Creating 1000 double-response scenarios
  • Memory growth: < 10 MB
  • Parsers are immediately freed on double response detection
  • Constant memory usage

Test Plan

The test creates a TCP server that sends two complete HTTP responses in a single write:

socket.write(
  'HTTP/1.1 200 OK\r\n' +
  'Content-Length: 5\r\n' +
  '\r\n' +
  'first' +
  'HTTP/1.1 200 OK\r\n' +
  'Content-Length: 6\r\n' +
  '\r\n' +
  'second'
);

Runs 1000 iterations with forced GC every 100 iterations, then verifies memory growth is < 10 MB.

Checklist

  • Added test case: test/parallel/test-http-client-double-response-leak.js
  • Verified fix prevents parser leaks on double response
  • Test passes with --expose-gc flag for memory verification
  • Documentation: comprehensive root cause analysis in PR description

Fixes: #60025

When a server sends two complete HTTP responses in a single TCP chunk,
the HTTP client code detects the double response and destroys the socket
at lib/_http_client.js:695. However, the parser cleanup that normally
happens in socketOnData() fails because it checks parser.incoming.complete,
and parser.incoming now points to the incomplete second response that will
never finish. This leaves the parser object allocated forever.

The fix immediately frees the parser when a double response is detected,
before destroying the socket. This ensures the parser is properly released
even though it's in an invalid state.

Fixes: nodejs#60025
@nodejs-github-bot
Copy link
Collaborator

Review requested:

  • @nodejs/http
  • @nodejs/net

@nodejs-github-bot nodejs-github-bot added http Issues or PRs related to the http subsystem. needs-ci PRs that need a full CI run. labels Oct 9, 2025
@kayossouza
Copy link
Author

cc @nodejs/http @nodejs/net

This PR fixes an HTTP parser memory leak when servers send malformed double responses. The parser gets stuck in an invalid state and is never freed.

The fix ensures the parser is immediately cleaned up when a double response is detected, before destroying the socket.

This is a hot path in HTTP handling, so CITGM would be beneficial to verify no ecosystem impact.

Ready for review - please allow 48h for community feedback per Node.js contribution guidelines.

@pimterry
Copy link
Member

Hi @kayossouza, thanks for contributing! Unfortunately in this case it looks like there's already a pending fix for this same issue in #60062. If you have any thoughts on that change or you think something has been missed, feel free to share thoughts there to discuss that. Otherwise I think that change should resolve #60025 already so this I'm afraid this PR is unlikely to be merged.

Copy link

codecov bot commented Oct 13, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.55%. Comparing base (bfc81ca) to head (9645d0e).
⚠️ Report is 22 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main   #60187   +/-   ##
=======================================
  Coverage   88.55%   88.55%           
=======================================
  Files         704      704           
  Lines      208087   208097   +10     
  Branches    40019    40012    -7     
=======================================
+ Hits       184266   184289   +23     
- Misses      15818    15821    +3     
+ Partials     8003     7987   -16     
Files with missing lines Coverage Δ
lib/_http_client.js 97.36% <100.00%> (+0.02%) ⬆️

... and 42 files with indirect coverage changes

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

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

Labels

http Issues or PRs related to the http subsystem. needs-ci PRs that need a full CI run.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

HTTPParser enters invalid state and keep ClientRequest in heap creating memory leak

3 participants