mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
Cache the per-message callback lookups on the parser's JS object instead of retaining callback functions in strong v8::Global handles. A callback that captures its parser can otherwise keep the parser alive. Clear the cache when a parser is initialized or freed so reused parsers can load replacement callbacks and idle parsers do not retain them. Header field names remain non-internalized because they are supplied by clients. Assisted-by: pi Signed-off-by: Matteo Collina <hello@matteocollina.com> PR-URL: https://github.com/nodejs/node/pull/66152 Reviewed-By: Robert Nagy <ronagy@icloud.com> Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com> Reviewed-By: Tim Perry <pimterry@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
62 lines
1.9 KiB
JavaScript
62 lines
1.9 KiB
JavaScript
// Flags: --expose-gc
|
|
'use strict';
|
|
|
|
const common = require('../common');
|
|
const assert = require('assert');
|
|
const { HTTPParser } = require('_http_common');
|
|
const { gcUntil } = require('../common/gc');
|
|
|
|
const request = Buffer.from('POST / HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody');
|
|
const kOnHeadersComplete = HTTPParser.kOnHeadersComplete | 0;
|
|
const kOnBody = HTTPParser.kOnBody | 0;
|
|
const kOnMessageComplete = HTTPParser.kOnMessageComplete | 0;
|
|
|
|
const parser = new HTTPParser();
|
|
parser.tag = 'parser';
|
|
parser.initialize(HTTPParser.REQUEST, {});
|
|
|
|
let calls = 0;
|
|
for (const callback of [kOnHeadersComplete, kOnBody, kOnMessageComplete]) {
|
|
parser[callback] = common.mustCall(() => {
|
|
assert.strictEqual(parser.tag, 'parser');
|
|
calls++;
|
|
}, 2);
|
|
}
|
|
|
|
parser.execute(request);
|
|
parser.execute(request);
|
|
assert.strictEqual(calls, 6);
|
|
|
|
// Reinitializing must replace each cached callback.
|
|
parser.initialize(HTTPParser.REQUEST, {});
|
|
for (const callback of [kOnHeadersComplete, kOnBody, kOnMessageComplete]) {
|
|
parser[callback] = common.mustCall(() => {
|
|
assert.strictEqual(parser.tag, 'parser');
|
|
calls += 2;
|
|
});
|
|
}
|
|
parser.execute(request);
|
|
assert.strictEqual(calls, 12);
|
|
|
|
// Freeing a parser must release a cached callback even if the parser itself
|
|
// remains reachable and its JS callback property has been replaced.
|
|
function freedCallback() {
|
|
const parser = new HTTPParser();
|
|
parser.initialize(HTTPParser.REQUEST, {});
|
|
let callback = () => parser;
|
|
parser[kOnHeadersComplete] = callback;
|
|
parser.execute(request);
|
|
const ref = new WeakRef(callback);
|
|
parser[kOnHeadersComplete] = null;
|
|
callback = null;
|
|
parser.free();
|
|
return { parser, ref };
|
|
}
|
|
|
|
const { parser: freedParser, ref } = freedCallback();
|
|
gcUntil('freed HTTPParser callback', () => ref.deref() === undefined)
|
|
.then(common.mustCall(() => {
|
|
// Keep the parser alive while its callback is collected.
|
|
assert.ok(freedParser);
|
|
}));
|