http: propagate highWaterMark to ClientRequest OutgoingMessage
Merged
TR
4mo ago+90+90+01234567890123456789-0-0-0123456789
http.request({ highWaterMark }) passes the value to the TCP socket via createConnection() but does not set it on the OutgoingMessage's internal kHighWaterMark. OutgoingMessage._writeRaw() has two mutually exclusive write paths:
- Path A (socket connected):
conn.write()— uses socket's HWM (correct) - Path B (no socket yet):
outputSize < this[kHighWaterMark]— uses OutgoingMessage's own default (64 KB) (incorrect)
Because the OutgoingMessage constructor already accepts options.highWaterMark, the fix is to set kHighWaterMark from the user's options after they are parsed in the ClientRequest constructor.
This resolves two symptoms:
write()returning the wrong boolean for pre-socket writes (the user'shighWaterMarkwas silently ignored on all Node versions).- A deadlock on Node >= 24.16.0 where the incorrect
falsereturn setskNeedDrain, butdrainnever fires because the socket was never backpressured (introduced by the stricter drain gate in #62936).
Test results (compiled from source)
=== UNPATCHED Node v26.5.0 ===
FAIL: write() returned false (expected true)
=== PATCHED Node v27.0.0-pre (this PR) ===
PASS: write() returned true
test-http-client-highwatermark.js passes (exit 0)
test-http-server-options-highwatermark.js passes
test-http-outgoing-drain-writable-length.js passes
test-http-outgoing-end-cork.js passes
test-http-highwatermark.js passes
test-http-response-drain-cork.js passes
Minimal reproduction
node -e "require('http').createServer((req,res)=>{req.resume();req.on('end',()=>res.end('ok'))}).listen(9001)"
const http = require('http');
const req = http.request({ hostname: '127.0.0.1', port: 9001, method: 'POST', highWaterMark: 100_000 });
const ok = req.write(Buffer.alloc(64 * 1024));
console.log(`write() returned ${ok}, expected true (64KB < 100KB highWaterMark)`);
if (!ok) {
setTimeout(() => { console.error('DEADLOCK: drain never fired'); process.exit(1); }, 3000);
req.on('drain', () => { console.log('drain fired'); req.end(); });
} else {
req.end();
req.on('response', (res) => { res.resume(); res.on('end', () => process.exit(0)); });
}
| Node version | write() returns | Drain fires? | Outcome |
|---|---|---|---|
| v22.x | false (wrong) | Yes (old eager drain) | HWM ignored but no hang |
| v26.5.0 | false (wrong) | Never | DEADLOCK |
| This PR | true (correct) | N/A (no backpressure) | Works correctly |
Fixes: https://github.com/nodejs/node/issues/64645
Refs: https://github.com/nodejs/node/pull/62936