mirror of
https://github.com/nodejs/node.git
synced 2026-11-09 15:11:11 -05:00
Contexts that are not deserialized from the built-in snapshot -- worker threads, and the main context of embedders that create their own isolate or of `node --no-node-snapshot` -- compile (with the code cache at best) every builtin the bootstrap touches, so each eagerly required builtin is startup time (~0.15-0.4 ms apiece). A number of them are only required eagerly so that they end up in the snapshot, or for features the bootstrap path never uses. Load lazily what those paths do not need: - is_main_thread.js: preload util, url, the ESM loader (translators, resolver, module_job/map, source maps, node:module, vm modules, mime, data_url, the TypeScript stripper), internal/blob and internal/dns/utils only while building a snapshot; they load on first use otherwise. - fs: internal/blob (+ internal/encoding and its tables) is only used by fs.openAsBlob(). - internal/url: internal/data_url (+ internal/mime) is only used by the Buffer-returning file URL helpers. - internal/process/execution, the CommonJS loader, esm/translators and esm/load: the TypeScript stripper and data: URL helpers are only needed for TypeScript sources / data: URLs. - pre_execution: internal/dns/utils (+ internal/net) is only needed up front to validate an explicit --dns-result-order or to register the resolver's snapshot serializer; the default order becomes the variable's initializer. - internal/worker: event_loop_utilization and error_serdes are only needed once a sub-worker's ELU is read or it reports an error. - worker_threads: `locks` is defined lazily, like util's lazy exports. Main-thread startup with the snapshot is unchanged (the same modules are preloaded into it; the bootstrap-modules test lists are adjusted). A bare worker compiles 95 -> 83 builtins (cold start -5%); without the snapshot an empty CommonJS entry point compiles 76 -> 59 builtins and an empty ES module entry point 76 -> 69. Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: https://github.com/nodejs/node/pull/65329 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: Aviv Keller <me@aviv.sh>
364 lines
9.7 KiB
JavaScript
364 lines
9.7 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
ArrayPrototypeForEach,
|
|
ArrayPrototypeMap,
|
|
ArrayPrototypePush,
|
|
FunctionPrototypeBind,
|
|
NumberParseInt,
|
|
RegExpPrototypeExec,
|
|
RegExpPrototypeSymbolReplace,
|
|
Symbol,
|
|
} = primordials;
|
|
|
|
const {
|
|
codes: {
|
|
ERR_DNS_SET_SERVERS_FAILED,
|
|
ERR_INVALID_ARG_VALUE,
|
|
ERR_INVALID_IP_ADDRESS,
|
|
},
|
|
} = require('internal/errors');
|
|
const { isIP } = require('internal/net');
|
|
const { getOptionValue } = require('internal/options');
|
|
const {
|
|
validateArray,
|
|
validateInt32,
|
|
validateOneOf,
|
|
validatePort,
|
|
validateString,
|
|
validateUint32,
|
|
} = require('internal/validators');
|
|
let binding;
|
|
function lazyBinding() {
|
|
binding ??= internalBinding('cares_wrap');
|
|
return binding;
|
|
}
|
|
const IANA_DNS_PORT = 53;
|
|
const IPv6RE = /^\[([^[\]]*)\]/;
|
|
const addrSplitRE = /(^.+?)(?::(\d+))?$/;
|
|
|
|
const {
|
|
namespace: {
|
|
addSerializeCallback,
|
|
addDeserializeCallback,
|
|
isBuildingSnapshot,
|
|
},
|
|
} = require('internal/v8/startup_snapshot');
|
|
|
|
function validateTimeout(options) {
|
|
let { timeout = -1 } = { ...options };
|
|
validateInt32(timeout, 'options.timeout', -1);
|
|
// Coerce -0 to +0.
|
|
timeout += 0;
|
|
return timeout;
|
|
}
|
|
|
|
function validateMaxTimeout(options) {
|
|
let { maxTimeout = 0 } = { ...options };
|
|
validateUint32(maxTimeout, 'options.maxTimeout');
|
|
// Coerce -0 to +0.
|
|
maxTimeout += 0;
|
|
return maxTimeout;
|
|
}
|
|
|
|
function validateTries(options) {
|
|
const { tries = 4 } = { ...options };
|
|
validateInt32(tries, 'options.tries', 1);
|
|
return tries;
|
|
}
|
|
|
|
const kSerializeResolver = Symbol('dns:resolver:serialize');
|
|
const kDeserializeResolver = Symbol('dns:resolver:deserialize');
|
|
const kSnapshotStates = Symbol('dns:resolver:config');
|
|
const kInitializeHandle = Symbol('dns:resolver:initializeHandle');
|
|
const kSetServersInternal = Symbol('dns:resolver:setServers');
|
|
|
|
// Resolver instances correspond 1:1 to c-ares channels.
|
|
|
|
class ResolverBase {
|
|
constructor(options = undefined) {
|
|
const timeout = validateTimeout(options);
|
|
const tries = validateTries(options);
|
|
const maxTimeout = validateMaxTimeout(options);
|
|
// If we are building snapshot, save the states of the resolver along
|
|
// the way.
|
|
if (isBuildingSnapshot()) {
|
|
this[kSnapshotStates] = { timeout, tries, maxTimeout };
|
|
}
|
|
this[kInitializeHandle](timeout, tries, maxTimeout);
|
|
}
|
|
|
|
[kInitializeHandle](timeout, tries, maxTimeout) {
|
|
const { ChannelWrap } = lazyBinding();
|
|
this._handle = new ChannelWrap(timeout, tries, maxTimeout);
|
|
}
|
|
|
|
cancel() {
|
|
this._handle.cancel();
|
|
}
|
|
|
|
getServers() {
|
|
return ArrayPrototypeMap(this._handle.getServers() || [], (val) => {
|
|
if (!val[1] || val[1] === IANA_DNS_PORT)
|
|
return val[0];
|
|
|
|
const host = isIP(val[0]) === 6 ? `[${val[0]}]` : val[0];
|
|
return `${host}:${val[1]}`;
|
|
});
|
|
}
|
|
|
|
setServers(servers) {
|
|
validateArray(servers, 'servers');
|
|
|
|
// Cache the original servers because in the event of an error while
|
|
// setting the servers, c-ares won't have any servers available for
|
|
// resolution.
|
|
const newSet = [];
|
|
ArrayPrototypeForEach(servers, (serv, index) => {
|
|
validateString(serv, `servers[${index}]`);
|
|
let ipVersion = isIP(serv);
|
|
|
|
if (ipVersion !== 0)
|
|
return ArrayPrototypePush(newSet, [ipVersion, serv, IANA_DNS_PORT]);
|
|
|
|
const match = RegExpPrototypeExec(IPv6RE, serv);
|
|
|
|
// Check for an IPv6 in brackets.
|
|
if (match) {
|
|
ipVersion = isIP(match[1]);
|
|
|
|
if (ipVersion !== 0) {
|
|
const port = NumberParseInt(
|
|
RegExpPrototypeSymbolReplace(addrSplitRE, serv, '$2')) || IANA_DNS_PORT;
|
|
validatePort(port);
|
|
return ArrayPrototypePush(newSet, [ipVersion, match[1], port]);
|
|
}
|
|
}
|
|
|
|
// addr::port
|
|
const addrSplitMatch = RegExpPrototypeExec(addrSplitRE, serv);
|
|
|
|
if (addrSplitMatch) {
|
|
const hostIP = addrSplitMatch[1];
|
|
const port = NumberParseInt(addrSplitMatch[2]) || IANA_DNS_PORT;
|
|
|
|
ipVersion = isIP(hostIP);
|
|
|
|
if (ipVersion !== 0) {
|
|
validatePort(port);
|
|
return ArrayPrototypePush(newSet, [ipVersion, hostIP, port]);
|
|
}
|
|
}
|
|
|
|
throw new ERR_INVALID_IP_ADDRESS(serv);
|
|
});
|
|
|
|
this[kSetServersInternal](newSet, servers);
|
|
}
|
|
|
|
[kSetServersInternal](newSet, servers) {
|
|
const orig = ArrayPrototypeMap(this._handle.getServers() || [], (val) => {
|
|
val.unshift(isIP(val[0]));
|
|
return val;
|
|
});
|
|
const errorNumber = this._handle.setServers(newSet);
|
|
|
|
if (errorNumber !== 0) {
|
|
// Reset the servers to the old servers, because ares probably unset them.
|
|
this._handle.setServers(orig);
|
|
const { strerror } = lazyBinding();
|
|
const err = strerror(errorNumber);
|
|
throw new ERR_DNS_SET_SERVERS_FAILED(err, servers);
|
|
}
|
|
|
|
if (isBuildingSnapshot()) {
|
|
this[kSnapshotStates].servers = newSet;
|
|
}
|
|
}
|
|
|
|
|
|
setLocalAddress(ipv4, ipv6) {
|
|
validateString(ipv4, 'ipv4');
|
|
|
|
if (ipv6 !== undefined) {
|
|
validateString(ipv6, 'ipv6');
|
|
}
|
|
|
|
this._handle.setLocalAddress(ipv4, ipv6);
|
|
|
|
if (isBuildingSnapshot()) {
|
|
this[kSnapshotStates].localAddress = { ipv4, ipv6 };
|
|
}
|
|
}
|
|
|
|
// TODO(joyeecheung): consider exposing this if custom DNS resolvers
|
|
// end up being useful for snapshot users.
|
|
[kSerializeResolver]() {
|
|
this._handle = null; // We'll restore it during deserialization.
|
|
addDeserializeCallback(function deserializeResolver(resolver) {
|
|
resolver[kDeserializeResolver]();
|
|
}, this);
|
|
}
|
|
|
|
[kDeserializeResolver]() {
|
|
const { timeout, tries, maxTimeout, localAddress, servers } = this[kSnapshotStates];
|
|
this[kInitializeHandle](timeout, tries, maxTimeout);
|
|
if (localAddress) {
|
|
const { ipv4, ipv6 } = localAddress;
|
|
this._handle.setLocalAddress(ipv4, ipv6);
|
|
}
|
|
if (servers) {
|
|
this[kSetServersInternal](servers, servers);
|
|
}
|
|
}
|
|
}
|
|
|
|
let defaultResolver;
|
|
// May already hold a value chosen by the snapshotted application; a
|
|
// --dns-result-order flag given at runtime overrides it in initializeDns().
|
|
let dnsOrder = 'verbatim';
|
|
const validDnsOrders = ['verbatim', 'ipv4first', 'ipv6first'];
|
|
const validFamilies = [0, 4, 6];
|
|
|
|
function initializeDns() {
|
|
const orderFromCLI = getOptionValue('--dns-result-order');
|
|
if (orderFromCLI) {
|
|
// Allow the deserialized application to override order from CLI.
|
|
validateOneOf(orderFromCLI, '--dns-result-order', validDnsOrders);
|
|
dnsOrder = orderFromCLI;
|
|
}
|
|
|
|
if (!isBuildingSnapshot()) {
|
|
return;
|
|
}
|
|
|
|
addSerializeCallback(() => {
|
|
defaultResolver?.[kSerializeResolver]();
|
|
});
|
|
}
|
|
|
|
const resolverKeys = [
|
|
'getServers',
|
|
'resolve',
|
|
'resolve4',
|
|
'resolve6',
|
|
'resolveAny',
|
|
'resolveCaa',
|
|
'resolveCname',
|
|
'resolveMx',
|
|
'resolveNaptr',
|
|
'resolveNs',
|
|
'resolvePtr',
|
|
'resolveSoa',
|
|
'resolveSrv',
|
|
'resolveTlsa',
|
|
'resolveTxt',
|
|
'reverse',
|
|
];
|
|
|
|
function getDefaultResolver() {
|
|
// We do this here instead of pre-execution so that the default resolver is
|
|
// only ever created when the user loads any dns module.
|
|
if (defaultResolver === undefined) {
|
|
defaultResolver = new ResolverBase();
|
|
}
|
|
return defaultResolver;
|
|
}
|
|
|
|
function setDefaultResolver(resolver) {
|
|
defaultResolver = resolver;
|
|
}
|
|
|
|
function bindDefaultResolver(target, source) {
|
|
const defaultResolver = getDefaultResolver();
|
|
ArrayPrototypeForEach(resolverKeys, (key) => {
|
|
target[key] = FunctionPrototypeBind(source[key], defaultResolver);
|
|
});
|
|
}
|
|
|
|
function validateHints(hints) {
|
|
const { AI_ADDRCONFIG, AI_ALL, AI_V4MAPPED } = lazyBinding();
|
|
if ((hints & ~(AI_ADDRCONFIG | AI_ALL | AI_V4MAPPED)) !== 0) {
|
|
throw new ERR_INVALID_ARG_VALUE('hints', hints);
|
|
}
|
|
}
|
|
|
|
function setDefaultResultOrder(value) {
|
|
validateOneOf(value, 'dnsOrder', validDnsOrders);
|
|
dnsOrder = value;
|
|
}
|
|
|
|
function getDefaultResultOrder() {
|
|
return dnsOrder;
|
|
}
|
|
|
|
function createResolverClass(resolver) {
|
|
const resolveMap = { __proto__: null };
|
|
|
|
class Resolver extends ResolverBase {}
|
|
|
|
Resolver.prototype.resolveAny = resolveMap.ANY = resolver('queryAny');
|
|
Resolver.prototype.resolve4 = resolveMap.A = resolver('queryA');
|
|
Resolver.prototype.resolve6 = resolveMap.AAAA = resolver('queryAaaa');
|
|
Resolver.prototype.resolveCaa = resolveMap.CAA = resolver('queryCaa');
|
|
Resolver.prototype.resolveCname = resolveMap.CNAME = resolver('queryCname');
|
|
Resolver.prototype.resolveMx = resolveMap.MX = resolver('queryMx');
|
|
Resolver.prototype.resolveNs = resolveMap.NS = resolver('queryNs');
|
|
Resolver.prototype.resolveTlsa = resolveMap.TLSA = resolver('queryTlsa');
|
|
Resolver.prototype.resolveTxt = resolveMap.TXT = resolver('queryTxt');
|
|
Resolver.prototype.resolveSrv = resolveMap.SRV = resolver('querySrv');
|
|
Resolver.prototype.resolvePtr = resolveMap.PTR = resolver('queryPtr');
|
|
Resolver.prototype.resolveNaptr = resolveMap.NAPTR = resolver('queryNaptr');
|
|
Resolver.prototype.resolveSoa = resolveMap.SOA = resolver('querySoa');
|
|
Resolver.prototype.reverse = resolver('getHostByAddr');
|
|
|
|
return {
|
|
resolveMap,
|
|
Resolver,
|
|
};
|
|
}
|
|
|
|
// ERROR CODES
|
|
const errorCodes = {
|
|
NODATA: 'ENODATA',
|
|
FORMERR: 'EFORMERR',
|
|
SERVFAIL: 'ESERVFAIL',
|
|
NOTFOUND: 'ENOTFOUND',
|
|
NOTIMP: 'ENOTIMP',
|
|
REFUSED: 'EREFUSED',
|
|
BADQUERY: 'EBADQUERY',
|
|
BADNAME: 'EBADNAME',
|
|
BADFAMILY: 'EBADFAMILY',
|
|
BADRESP: 'EBADRESP',
|
|
CONNREFUSED: 'ECONNREFUSED',
|
|
TIMEOUT: 'ETIMEOUT',
|
|
EOF: 'EOF',
|
|
FILE: 'EFILE',
|
|
NOMEM: 'ENOMEM',
|
|
DESTRUCTION: 'EDESTRUCTION',
|
|
BADSTR: 'EBADSTR',
|
|
BADFLAGS: 'EBADFLAGS',
|
|
NONAME: 'ENONAME',
|
|
BADHINTS: 'EBADHINTS',
|
|
NOTINITIALIZED: 'ENOTINITIALIZED',
|
|
LOADIPHLPAPI: 'ELOADIPHLPAPI',
|
|
ADDRGETNETWORKPARAMS: 'EADDRGETNETWORKPARAMS',
|
|
CANCELLED: 'ECANCELLED',
|
|
};
|
|
|
|
module.exports = {
|
|
bindDefaultResolver,
|
|
getDefaultResolver,
|
|
setDefaultResolver,
|
|
validateHints,
|
|
validateTimeout,
|
|
validateTries,
|
|
getDefaultResultOrder,
|
|
setDefaultResultOrder,
|
|
errorCodes,
|
|
createResolverClass,
|
|
initializeDns,
|
|
validDnsOrders,
|
|
validFamilies,
|
|
};
|