Skip to content

Commit 73e2db6

Browse files
committed
http_parser: don't reuse HTTPParser
As discussed in nodejs/diagnostics#248 and nodejs#21313, reusing HTTPParser resource is a blocker for landing `require('async_hooks').currentAsyncResource()`, since reusing an async resource interanlly would make it infeasible to use them reliably in WeakMaps. Two suggestions came up: have a wrapper around the HTTPParser which we would use as the async resource, or stop reusing HTTPParser in our HTTP server/client code. This commit implements the latter, since we have a better GC now and reusing HTTPParser might make sense anymore. This also will avoid one extra JS->C++ call in some cases, which should improve performance for these cases.
1 parent 49b0f7f commit 73e2db6

8 files changed

+19
-66
lines changed

benchmark/http/bench-parser.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,10 @@ function main({ len, n }) {
1919
const CRLF = '\r\n';
2020

2121
function processHeader(header, n) {
22-
const parser = newParser(REQUEST);
23-
2422
bench.start();
2523
for (var i = 0; i < n; i++) {
24+
const parser = newParser(REQUEST);
2625
parser.execute(header, 0, header.length);
27-
parser.reinitialize(REQUEST, i > 0);
2826
}
2927
bench.end(n);
3028
}

lib/_http_client.js

+3-5
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ const assert = require('assert').ok;
2929
const {
3030
_checkIsHttpToken: checkIsHttpToken,
3131
debug,
32+
createParser,
3233
freeParser,
33-
httpSocketSetup,
34-
parsers
34+
httpSocketSetup
3535
} = require('_http_common');
3636
const { OutgoingMessage } = require('_http_outgoing');
3737
const Agent = require('_http_agent');
@@ -47,7 +47,6 @@ const {
4747
ERR_UNESCAPED_CHARACTERS
4848
} = require('internal/errors').codes;
4949
const { validateTimerDuration } = require('internal/timers');
50-
const is_reused_symbol = require('internal/freelist').symbols.is_reused_symbol;
5150

5251
const INVALID_PATH_REGEX = /[^\u0021-\u00ff]/;
5352

@@ -629,10 +628,9 @@ function emitFreeNT(socket) {
629628
}
630629

631630
function tickOnSocket(req, socket) {
632-
var parser = parsers.alloc();
631+
const parser = createParser(HTTPParser.RESPONSE);
633632
req.socket = socket;
634633
req.connection = socket;
635-
parser.reinitialize(HTTPParser.RESPONSE, parser[is_reused_symbol]);
636634
parser.socket = socket;
637635
parser.outgoing = req;
638636
req.parser = parser;

lib/_http_common.js

+6-18
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323

2424
const { methods, HTTPParser } = internalBinding('http_parser');
2525

26-
const { FreeList } = require('internal/freelist');
2726
const { ondrain } = require('internal/http');
2827
const incoming = require('_http_incoming');
2928
const {
@@ -147,9 +146,8 @@ function parserOnMessageComplete() {
147146
readStart(parser.socket);
148147
}
149148

150-
151-
const parsers = new FreeList('parsers', 1000, function parsersCb() {
152-
const parser = new HTTPParser(HTTPParser.REQUEST);
149+
function createParser(type) {
150+
const parser = new HTTPParser(type);
153151

154152
cleanParser(parser);
155153

@@ -160,31 +158,21 @@ const parsers = new FreeList('parsers', 1000, function parsersCb() {
160158
parser[kOnMessageComplete] = parserOnMessageComplete;
161159

162160
return parser;
163-
});
161+
}
164162

165163
function closeParserInstance(parser) { parser.close(); }
166164

167165
// Free the parser and also break any links that it
168166
// might have to any other things.
169167
// TODO: All parser data should be attached to a
170168
// single object, so that it can be easily cleaned
171-
// up by doing `parser.data = {}`, which should
172-
// be done in FreeList.free. `parsers.free(parser)`
173-
// should be all that is needed.
169+
// up by doing `parser.data = {}`.
174170
function freeParser(parser, req, socket) {
175171
if (parser) {
176172
if (parser._consumed)
177173
parser.unconsume();
178174
cleanParser(parser);
179-
if (parsers.free(parser) === false) {
180-
// Make sure the parser's stack has unwound before deleting the
181-
// corresponding C++ object through .close().
182-
setImmediate(closeParserInstance, parser);
183-
} else {
184-
// Since the Parser destructor isn't going to run the destroy() callbacks
185-
// it needs to be triggered manually.
186-
parser.free();
187-
}
175+
setImmediate(closeParserInstance, parser);
188176
}
189177
if (req) {
190178
req.parser = null;
@@ -239,9 +227,9 @@ module.exports = {
239227
continueExpression: /(?:^|\W)100-continue(?:$|\W)/i,
240228
CRLF: '\r\n',
241229
debug,
230+
createParser,
242231
freeParser,
243232
httpSocketSetup,
244233
methods,
245-
parsers,
246234
kIncomingMessage
247235
};

lib/_http_server.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const net = require('net');
2626
const { HTTPParser } = internalBinding('http_parser');
2727
const assert = require('assert').ok;
2828
const {
29-
parsers,
29+
createParser,
3030
freeParser,
3131
debug,
3232
CRLF,
@@ -42,7 +42,6 @@ const {
4242
defaultTriggerAsyncIdScope,
4343
getOrSetAsyncId
4444
} = require('internal/async_hooks');
45-
const is_reused_symbol = require('internal/freelist').symbols.is_reused_symbol;
4645
const { IncomingMessage } = require('_http_incoming');
4746
const {
4847
ERR_HTTP_HEADERS_SENT,
@@ -338,8 +337,7 @@ function connectionListenerInternal(server, socket) {
338337
socket.setTimeout(server.timeout);
339338
socket.on('timeout', socketOnTimeout);
340339

341-
var parser = parsers.alloc();
342-
parser.reinitialize(HTTPParser.REQUEST, parser[is_reused_symbol]);
340+
const parser = createParser(HTTPParser.REQUEST);
343341
parser.socket = socket;
344342
socket.parser = parser;
345343

src/node_http_parser.cc

-25
Original file line numberDiff line numberDiff line change
@@ -494,30 +494,6 @@ class Parser : public AsyncWrap, public StreamListener {
494494
}
495495

496496

497-
static void Reinitialize(const FunctionCallbackInfo<Value>& args) {
498-
Environment* env = Environment::GetCurrent(args);
499-
500-
CHECK(args[0]->IsInt32());
501-
CHECK(args[1]->IsBoolean());
502-
bool isReused = args[1]->IsTrue();
503-
parser_type_t type =
504-
static_cast<parser_type_t>(args[0].As<Int32>()->Value());
505-
506-
CHECK(type == HTTP_REQUEST || type == HTTP_RESPONSE);
507-
Parser* parser;
508-
ASSIGN_OR_RETURN_UNWRAP(&parser, args.Holder());
509-
// Should always be called from the same context.
510-
CHECK_EQ(env, parser->env());
511-
// This parser has either just been created or it is being reused.
512-
// We must only call AsyncReset for the latter case, because AsyncReset has
513-
// already been called via the constructor for the former case.
514-
if (isReused) {
515-
parser->AsyncReset();
516-
}
517-
parser->Init(type);
518-
}
519-
520-
521497
template <bool should_pause>
522498
static void Pause(const FunctionCallbackInfo<Value>& args) {
523499
Environment* env = Environment::GetCurrent(args);
@@ -923,7 +899,6 @@ void Initialize(Local<Object> target,
923899
env->SetProtoMethod(t, "free", Parser::Free);
924900
env->SetProtoMethod(t, "execute", Parser::Execute);
925901
env->SetProtoMethod(t, "finish", Parser::Finish);
926-
env->SetProtoMethod(t, "reinitialize", Parser::Reinitialize);
927902
env->SetProtoMethod(t, "pause", Parser::Pause<true>);
928903
env->SetProtoMethod(t, "resume", Parser::Pause<false>);
929904
env->SetProtoMethod(t, "consume", Parser::Consume);

test/parallel/test-http-parser-free.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ server.listen(0, function() {
4141
if (!parser) {
4242
parser = req.parser;
4343
} else {
44-
assert.strictEqual(req.parser, parser);
44+
assert.notStrictEqual(req.parser, parser);
4545
}
4646

4747
countdown.dec();

test/parallel/test-http-parser.js

+4-6
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,6 @@ function expectBody(expected) {
9898
throw new Error('hello world');
9999
};
100100

101-
parser.reinitialize(HTTPParser.REQUEST, false);
102-
103101
assert.throws(
104102
() => { parser.execute(request, 0, request.length); },
105103
{ name: 'Error', message: 'hello world' }
@@ -558,10 +556,10 @@ function expectBody(expected) {
558556
parser[kOnBody] = expectBody('ping');
559557
parser.execute(req1, 0, req1.length);
560558

561-
parser.reinitialize(REQUEST, false);
562-
parser[kOnBody] = expectBody('pong');
563-
parser[kOnHeadersComplete] = onHeadersComplete2;
564-
parser.execute(req2, 0, req2.length);
559+
const parser2 = newParser(REQUEST);
560+
parser2[kOnBody] = expectBody('pong');
561+
parser2[kOnHeadersComplete] = onHeadersComplete2;
562+
parser2.execute(req2, 0, req2.length);
565563
}
566564

567565
// Test parser 'this' safety

test/sequential/test-http-regr-gh-2928.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,14 @@ const common = require('../common');
77
const assert = require('assert');
88
const httpCommon = require('_http_common');
99
const { internalBinding } = require('internal/test/binding');
10-
const is_reused_symbol = require('internal/freelist').symbols.is_reused_symbol;
1110
const { HTTPParser } = internalBinding('http_parser');
1211
const net = require('net');
1312

14-
const COUNT = httpCommon.parsers.max + 1;
13+
const COUNT = 1;
1514

1615
const parsers = new Array(COUNT);
1716
for (let i = 0; i < parsers.length; i++)
18-
parsers[i] = httpCommon.parsers.alloc();
17+
parsers[i] = httpCommon.createParser(HTTPParser.RESPONSE);
1918

2019
let gotRequests = 0;
2120
let gotResponses = 0;
@@ -26,7 +25,6 @@ function execAndClose() {
2625
process.stdout.write('.');
2726

2827
const parser = parsers.pop();
29-
parser.reinitialize(HTTPParser.RESPONSE, parser[is_reused_symbol]);
3028

3129
const socket = net.connect(common.PORT);
3230
socket.on('error', (e) => {

0 commit comments

Comments
 (0)