Skip to content

Commit fda2f07

Browse files
committed
http: fix drain event with cork/uncork
When using cork() and uncork() with ServerResponse, the drain event was not reliably emitted after uncorking. This occurred because the uncork() method did not check if a drain was pending (kNeedDrain flag) after flushing the chunked buffer. This fix ensures that when uncork() successfully flushes buffered data and a drain was needed, the drain event is emitted immediately. This commit is a copy of PR #60437 (abandoned) with minor linting fixes. Fixes: #60432 Signed-off-by: David Evans <davidje13@users.noreply.github.com>
1 parent e3cdb14 commit fda2f07

2 files changed

Lines changed: 74 additions & 1 deletion

File tree

lib/_http_outgoing.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -315,14 +315,20 @@ OutgoingMessage.prototype.uncork = function uncork() {
315315
callbacks.push(buf[n + 2]);
316316
}
317317
}
318-
this._send(crlf_buf, null, callbacks.length ? (err) => {
318+
const ret = this._send(crlf_buf, null, callbacks.length ? (err) => {
319319
for (const callback of callbacks) {
320320
callback(err);
321321
}
322322
} : null);
323323

324324
this[kChunkedBuffer].length = 0;
325325
this[kChunkedLength] = 0;
326+
327+
// If we successfully flushed and had pending drain, emit it
328+
if (ret && this[kNeedDrain]) {
329+
this[kNeedDrain] = false;
330+
this.emit('drain');
331+
}
326332
};
327333

328334
OutgoingMessage.prototype.setTimeout = function setTimeout(msecs, callback) {
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
'use strict';
2+
const common = require('../common');
3+
const http = require('http');
4+
const assert = require('assert');
5+
6+
// Test that drain event is emitted correctly when using cork/uncork
7+
// with ServerResponse and the write buffer is full
8+
9+
const server = http.createServer(common.mustCall(async (req, res) => {
10+
res.cork();
11+
12+
// Write small amount - won't need drain
13+
assert.strictEqual(res.write('1'.repeat(100)), true);
14+
15+
// Write large amount that should require drain
16+
assert.strictEqual(res.write('2'.repeat(1000000)), false);
17+
18+
// Verify writableNeedDrain is set
19+
assert.strictEqual(res.writableNeedDrain, true);
20+
21+
// Wait for drain event after uncorking
22+
const drainPromise = new Promise((resolve) => {
23+
res.once('drain', common.mustCall(() => {
24+
// After drain, writableNeedDrain should be false
25+
assert.strictEqual(res.writableNeedDrain, false);
26+
resolve();
27+
}));
28+
});
29+
30+
// Uncork should trigger drain if needed
31+
res.uncork();
32+
33+
await drainPromise;
34+
35+
// Cork again for next write
36+
res.cork();
37+
38+
// Write more data
39+
res.write('3'.repeat(100));
40+
41+
// Final uncork and end
42+
res.uncork();
43+
res.end();
44+
}));
45+
46+
server.listen(0, common.mustCall(() => {
47+
http.get({
48+
port: server.address().port,
49+
}, common.mustCall((res) => {
50+
let data = '';
51+
res.setEncoding('utf8');
52+
53+
res.on('data', (chunk) => {
54+
data += chunk;
55+
});
56+
57+
res.on('end', common.mustCall(() => {
58+
// Verify we got all the data
59+
assert.strictEqual(data.length, 100 + 1000000 + 100);
60+
assert.strictEqual(data.substring(0, 100), '1'.repeat(100));
61+
assert.strictEqual(data.substring(100, 1000100), '2'.repeat(1000000));
62+
assert.strictEqual(data.substring(1000100), '3'.repeat(100));
63+
64+
server.close();
65+
}));
66+
}));
67+
}));

0 commit comments

Comments
 (0)