Skip to content

Commit f5be64e

Browse files
committed
http,net,stream: optimize outgoing write paths
Signed-off-by: GetThatCookie <NimmenKeks@gmx.de>
1 parent f00fb75 commit f5be64e

29 files changed

Lines changed: 2913 additions & 323 deletions

benchmark/http/cork.js

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
'use strict';
2+
3+
const common = require('../common.js');
4+
const protocols = process.versions.openssl ? ['http', 'https'] : ['http'];
5+
6+
const scenarios = {
7+
'end-64': {
8+
len: 64,
9+
chunks: 1,
10+
endChunk: true,
11+
},
12+
'end-1024': {
13+
len: 1024,
14+
chunks: 1,
15+
endChunk: true,
16+
},
17+
'end-1025': {
18+
len: 1025,
19+
chunks: 1,
20+
endChunk: true,
21+
},
22+
'auto-4': {
23+
len: 64,
24+
chunks: 4,
25+
},
26+
'auto-16': {
27+
len: 64,
28+
chunks: 16,
29+
},
30+
'explicit-16': {
31+
len: 64,
32+
chunks: 16,
33+
explicit: true,
34+
},
35+
'content-length-16': {
36+
len: 64,
37+
chunks: 16,
38+
contentLength: true,
39+
},
40+
'next-tick-4': {
41+
len: 64,
42+
chunks: 4,
43+
schedule: process.nextTick,
44+
},
45+
'callbacks-16': {
46+
len: 64,
47+
chunks: 16,
48+
callbacks: true,
49+
},
50+
'fixed-body-128': {
51+
len: 512,
52+
chunks: 128,
53+
},
54+
'large-16k': {
55+
len: 16 * 1024,
56+
chunks: 4,
57+
},
58+
};
59+
60+
const bench = common.createBenchmark(main, {
61+
type: ['string', 'buffer', 'uint8array'],
62+
scenario: Object.keys(scenarios),
63+
protocol: protocols,
64+
c: [50],
65+
duration: [5],
66+
});
67+
68+
function main({ type, scenario, protocol, c, duration }) {
69+
const {
70+
callbacks,
71+
chunks,
72+
contentLength,
73+
endChunk,
74+
explicit,
75+
len,
76+
schedule,
77+
} = scenarios[scenario];
78+
const transport = require(protocol);
79+
const chunk = type === 'string' ? 'a'.repeat(len) :
80+
type === 'buffer' ? Buffer.alloc(len, 'a') :
81+
new Uint8Array(len).fill(0x61);
82+
const writeCallback = callbacks ? (err) => {
83+
if (err) throw err;
84+
} : undefined;
85+
86+
const onRequest = (req, res) => {
87+
if (contentLength) {
88+
res.setHeader('Content-Length', len * chunks);
89+
}
90+
if (explicit) {
91+
res.cork();
92+
}
93+
if (endChunk) {
94+
res.end(chunk, writeCallback);
95+
return;
96+
}
97+
98+
if (schedule === undefined) {
99+
for (let i = 0; i < chunks; i++) {
100+
res.write(chunk, writeCallback);
101+
}
102+
res.end();
103+
return;
104+
}
105+
106+
let written = 0;
107+
function writeNext() {
108+
if (written++ === chunks) {
109+
res.end();
110+
return;
111+
}
112+
res.write(chunk, writeCallback);
113+
schedule(writeNext);
114+
}
115+
writeNext();
116+
};
117+
118+
let server;
119+
if (protocol === 'https') {
120+
const fixtures = require('../../test/common/fixtures');
121+
server = transport.createServer({
122+
key: fixtures.readKey('rsa_private.pem'),
123+
cert: fixtures.readKey('rsa_cert.crt'),
124+
}, onRequest);
125+
} else {
126+
server = transport.createServer(onRequest);
127+
}
128+
129+
server.listen(0, () => {
130+
bench.http({
131+
connections: c,
132+
duration,
133+
port: server.address().port,
134+
scheme: protocol,
135+
}, () => {
136+
server.close();
137+
});
138+
});
139+
}

lib/_http_client.js

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ const {
5858
parseUniqueHeadersOption,
5959
OutgoingMessage,
6060
} = require('_http_outgoing');
61+
const { kDestroyMessageBuffer } = require('internal/streams/utils');
6162
const Agent = require('_http_agent');
6263
const { Buffer } = require('buffer');
6364
const { defaultTriggerAsyncIdScope } = require('internal/async_hooks');
@@ -699,7 +700,11 @@ ClientRequest.prototype.destroy = function destroy(err) {
699700
}
700701

701702
this[kError] = err;
702-
this.socket?.destroy(err);
703+
try {
704+
this[kDestroyMessageBuffer](err);
705+
} finally {
706+
this.socket?.destroy(err);
707+
}
703708

704709
return this;
705710
};
@@ -710,7 +715,7 @@ function emitAbortNT(req) {
710715

711716
function ondrain() {
712717
const msg = this._httpMessage;
713-
if (msg && !msg.finished && msg[kNeedDrain]) {
718+
if (msg && !msg.finished && msg[kNeedDrain] && msg.writableLength === 0) {
714719
msg[kNeedDrain] = false;
715720
msg.emit('drain');
716721
}
@@ -726,6 +731,9 @@ function socketCloseListener() {
726731
const parser = socket.parser;
727732
const res = req.res;
728733

734+
req[kDestroyMessageBuffer](
735+
req[kError] ?? socket._writableState.errored,
736+
);
729737
req.destroyed = true;
730738
if (res) {
731739
// Socket closed before we emitted 'end' below.

0 commit comments

Comments
 (0)