mirror of
https://github.com/nodejs/node.git
synced 2026-10-28 10:15:07 +08: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>
740 lines
21 KiB
JavaScript
740 lines
21 KiB
JavaScript
'use strict';
|
|
|
|
const {
|
|
ArrayIsArray,
|
|
ArrayPrototypeForEach,
|
|
ArrayPrototypeMap,
|
|
ArrayPrototypePush,
|
|
AtomicsAdd,
|
|
Float64Array,
|
|
FunctionPrototypeBind,
|
|
MathMax,
|
|
NumberMAX_SAFE_INTEGER,
|
|
ObjectEntries,
|
|
Promise,
|
|
PromiseResolve,
|
|
PromiseWithResolvers,
|
|
ReflectApply,
|
|
RegExpPrototypeExec,
|
|
SafeArrayIterator,
|
|
SafeMap,
|
|
String,
|
|
StringPrototypeTrim,
|
|
Symbol,
|
|
SymbolAsyncDispose,
|
|
SymbolFor,
|
|
TypedArrayPrototypeFill,
|
|
Uint32Array,
|
|
} = primordials;
|
|
|
|
const EventEmitter = require('events');
|
|
const assert = require('internal/assert');
|
|
const path = require('path');
|
|
const errorCodes = require('internal/errors').codes;
|
|
const {
|
|
ERR_WORKER_NOT_RUNNING,
|
|
ERR_WORKER_PATH,
|
|
ERR_WORKER_UNSERIALIZABLE_ERROR,
|
|
ERR_WORKER_INVALID_EXEC_ARGV,
|
|
ERR_INVALID_ARG_TYPE,
|
|
ERR_INVALID_ARG_VALUE,
|
|
ERR_OPERATION_FAILED,
|
|
} = errorCodes;
|
|
|
|
const workerIo = require('internal/worker/io');
|
|
const {
|
|
drainMessagePort,
|
|
receiveMessageOnPort,
|
|
MessageChannel,
|
|
messageTypes,
|
|
kPort,
|
|
kIncrementsPortRef,
|
|
kWaitingStreams,
|
|
kStdioWantsMoreDataCallback,
|
|
setupPortReferencing,
|
|
ReadableWorkerStdio,
|
|
WritableWorkerStdio,
|
|
} = workerIo;
|
|
const { createMainThreadPort, destroyMainThreadPort } = require('internal/worker/messaging');
|
|
const { fileURLToPath, isURL, pathToFileURL } = require('internal/url');
|
|
const {
|
|
constructSharedArrayBuffer,
|
|
kEmptyObject,
|
|
} = require('internal/util');
|
|
const {
|
|
validateArray,
|
|
validateString,
|
|
validateObject,
|
|
validateNumber,
|
|
} = require('internal/validators');
|
|
const {
|
|
throwIfBuildingSnapshot,
|
|
} = require('internal/v8/startup_snapshot');
|
|
const {
|
|
ownsProcessState,
|
|
isMainThread,
|
|
isInternalThread,
|
|
resourceLimits: resourceLimitsRaw,
|
|
threadId,
|
|
threadName,
|
|
Worker: WorkerImpl,
|
|
kMaxYoungGenerationSizeMb,
|
|
kMaxOldGenerationSizeMb,
|
|
kCodeRangeSizeMb,
|
|
kStackSizeMb,
|
|
kTotalResourceLimitCount,
|
|
} = internalBinding('worker');
|
|
|
|
const kHandle = Symbol('kHandle');
|
|
const kPublicPort = Symbol('kPublicPort');
|
|
const kDispose = Symbol('kDispose');
|
|
const kOnExit = Symbol('kOnExit');
|
|
const kOnMessage = Symbol('kOnMessage');
|
|
const kOnCouldNotSerializeErr = Symbol('kOnCouldNotSerializeErr');
|
|
const kOnErrorMessage = Symbol('kOnErrorMessage');
|
|
const kParentSideStdio = Symbol('kParentSideStdio');
|
|
const kLoopStartTime = Symbol('kLoopStartTime');
|
|
const kIsInternal = Symbol('kIsInternal');
|
|
const kIsOnline = Symbol('kIsOnline');
|
|
const kWebWorkerData = Symbol('kWebWorkerData');
|
|
|
|
const SHARE_ENV = SymbolFor('nodejs.worker_threads.SHARE_ENV');
|
|
let debug = require('internal/util/debuglog').debuglog('worker', (fn) => {
|
|
debug = fn;
|
|
});
|
|
|
|
const dc = require('diagnostics_channel');
|
|
const workerThreadsChannel = dc.channel('worker_threads');
|
|
|
|
let cwdCounter;
|
|
let normalizeHeapProfileOptions;
|
|
let normalizeCpuProfileOptions;
|
|
|
|
const environmentData = new SafeMap();
|
|
|
|
if (isMainThread) {
|
|
cwdCounter = new Uint32Array(constructSharedArrayBuffer(4));
|
|
const originalChdir = process.chdir;
|
|
process.chdir = function(path) {
|
|
originalChdir(path);
|
|
AtomicsAdd(cwdCounter, 0, 1);
|
|
};
|
|
}
|
|
|
|
function setEnvironmentData(key, value) {
|
|
if (value === undefined)
|
|
environmentData.delete(key);
|
|
else
|
|
environmentData.set(key, value);
|
|
}
|
|
|
|
function getEnvironmentData(key) {
|
|
return environmentData.get(key);
|
|
}
|
|
|
|
function assignEnvironmentData(data) {
|
|
if (data === undefined) return;
|
|
data.forEach((value, key) => {
|
|
environmentData.set(key, value);
|
|
});
|
|
}
|
|
|
|
class CPUProfileHandle {
|
|
#worker = null;
|
|
#id = null;
|
|
#promise = null;
|
|
|
|
constructor(worker, id) {
|
|
this.#worker = worker;
|
|
this.#id = id;
|
|
}
|
|
|
|
stop() {
|
|
if (this.#promise) {
|
|
return this.#promise;
|
|
}
|
|
const stopTaker = this.#worker[kHandle]?.stopCpuProfile(this.#id);
|
|
return this.#promise = new Promise((resolve, reject) => {
|
|
if (!stopTaker) return reject(new ERR_WORKER_NOT_RUNNING());
|
|
stopTaker.ondone = (err, profile) => {
|
|
if (err) {
|
|
return reject(err);
|
|
}
|
|
resolve(profile);
|
|
};
|
|
});
|
|
};
|
|
|
|
async [SymbolAsyncDispose]() {
|
|
await this.stop();
|
|
}
|
|
}
|
|
|
|
class HeapProfileHandle {
|
|
#worker = null;
|
|
#promise = null;
|
|
|
|
constructor(worker) {
|
|
this.#worker = worker;
|
|
}
|
|
|
|
stop() {
|
|
if (this.#promise) {
|
|
return this.#promise;
|
|
}
|
|
const stopTaker = this.#worker[kHandle]?.stopHeapProfile();
|
|
return this.#promise = new Promise((resolve, reject) => {
|
|
if (!stopTaker) return reject(new ERR_WORKER_NOT_RUNNING());
|
|
stopTaker.ondone = (err, profile) => {
|
|
if (err) {
|
|
return reject(err);
|
|
}
|
|
resolve(profile);
|
|
};
|
|
});
|
|
};
|
|
|
|
async [SymbolAsyncDispose]() {
|
|
await this.stop();
|
|
}
|
|
}
|
|
|
|
class Worker extends EventEmitter {
|
|
constructor(filename, options = kEmptyObject) {
|
|
throwIfBuildingSnapshot('Creating workers');
|
|
super();
|
|
const isInternal = arguments[2] === kIsInternal;
|
|
debug(
|
|
`[${threadId}] create new worker`,
|
|
filename,
|
|
options,
|
|
`isInternal: ${isInternal}`,
|
|
);
|
|
if (options.execArgv)
|
|
validateArray(options.execArgv, 'options.execArgv');
|
|
|
|
let argv;
|
|
if (options.argv) {
|
|
validateArray(options.argv, 'options.argv');
|
|
argv = ArrayPrototypeMap(options.argv, String);
|
|
}
|
|
|
|
let url, doEval;
|
|
if (isInternal) {
|
|
doEval = 'internal';
|
|
url = `node:${filename}`;
|
|
} else if (options.eval) {
|
|
if (typeof filename !== 'string') {
|
|
throw new ERR_INVALID_ARG_VALUE(
|
|
'options.eval',
|
|
options.eval,
|
|
'must be false when \'filename\' is not a string',
|
|
);
|
|
}
|
|
url = null;
|
|
doEval = 'classic';
|
|
} else if (isURL(filename) && filename.protocol === 'data:') {
|
|
url = null;
|
|
doEval = 'data-url';
|
|
filename = `${filename}`;
|
|
} else {
|
|
doEval = false;
|
|
if (isURL(filename)) {
|
|
url = filename;
|
|
filename = fileURLToPath(filename);
|
|
} else if (typeof filename !== 'string') {
|
|
throw new ERR_INVALID_ARG_TYPE(
|
|
'filename',
|
|
['string', 'URL'],
|
|
filename,
|
|
);
|
|
} else if (path.isAbsolute(filename) ||
|
|
RegExpPrototypeExec(/^\.\.?[\\/]/, filename) !== null) {
|
|
filename = path.resolve(filename);
|
|
url = pathToFileURL(filename);
|
|
} else {
|
|
throw new ERR_WORKER_PATH(filename);
|
|
}
|
|
}
|
|
|
|
let env;
|
|
if (typeof options.env === 'object' && options.env !== null) {
|
|
env = { __proto__: null };
|
|
ArrayPrototypeForEach(
|
|
ObjectEntries(options.env),
|
|
({ 0: key, 1: value }) => { env[key] = `${value}`; },
|
|
);
|
|
} else if (options.env == null) {
|
|
env = process.env;
|
|
} else if (options.env !== SHARE_ENV) {
|
|
throw new ERR_INVALID_ARG_TYPE(
|
|
'options.env',
|
|
['object', 'undefined', 'null', 'worker_threads.SHARE_ENV'],
|
|
options.env);
|
|
}
|
|
|
|
let name = 'WorkerThread';
|
|
if (options.name) {
|
|
validateString(options.name, 'options.name');
|
|
name = StringPrototypeTrim(options.name);
|
|
}
|
|
|
|
debug('instantiating Worker.', `url: ${url}`, `doEval: ${doEval}`);
|
|
// Set up the C++ handle for the worker, as well as some internal wiring.
|
|
this[kHandle] = new WorkerImpl(url,
|
|
env === process.env ? null : env,
|
|
options.execArgv,
|
|
parseResourceLimits(options.resourceLimits),
|
|
!!(options.trackUnmanagedFds ?? true),
|
|
isInternal,
|
|
name);
|
|
if (this[kHandle].invalidExecArgv) {
|
|
throw new ERR_WORKER_INVALID_EXEC_ARGV(this[kHandle].invalidExecArgv);
|
|
}
|
|
if (this[kHandle].invalidNodeOptions) {
|
|
throw new ERR_WORKER_INVALID_EXEC_ARGV(
|
|
this[kHandle].invalidNodeOptions, 'invalid NODE_OPTIONS env variable');
|
|
}
|
|
this[kHandle].onexit = (code, customErr, customErrReason) => {
|
|
this[kOnExit](code, customErr, customErrReason);
|
|
};
|
|
this[kPort] = this[kHandle].messagePort;
|
|
this[kPort].on('message', (data) => this[kOnMessage](data));
|
|
this[kPort].start();
|
|
this[kPort].unref();
|
|
this[kPort][kWaitingStreams] = 0;
|
|
debug(`[${threadId}] created Worker with ID ${this.threadId}`);
|
|
|
|
let stdin = null;
|
|
if (options.stdin)
|
|
stdin = new WritableWorkerStdio(this[kPort], 'stdin');
|
|
const stdout = new ReadableWorkerStdio(this[kPort], 'stdout');
|
|
if (!options.stdout) {
|
|
stdout[kIncrementsPortRef] = false;
|
|
pipeWithoutWarning(stdout, process.stdout);
|
|
}
|
|
const stderr = new ReadableWorkerStdio(this[kPort], 'stderr');
|
|
if (!options.stderr) {
|
|
stderr[kIncrementsPortRef] = false;
|
|
pipeWithoutWarning(stderr, process.stderr);
|
|
}
|
|
|
|
this[kParentSideStdio] = { stdin, stdout, stderr };
|
|
|
|
const mainThreadPortToWorker = createMainThreadPort(this.threadId);
|
|
const {
|
|
port1: publicPortToParent,
|
|
port2: publicPortToWorker,
|
|
} = new MessageChannel();
|
|
const transferList = [mainThreadPortToWorker, publicPortToWorker];
|
|
// If transferList is provided.
|
|
if (options.transferList)
|
|
ArrayPrototypePush(transferList,
|
|
...new SafeArrayIterator(options.transferList));
|
|
|
|
this[kPublicPort] = publicPortToParent;
|
|
ArrayPrototypeForEach(['message', 'messageerror'], (event) => {
|
|
this[kPublicPort].on(event, (message) => {
|
|
// Extract watch messages first if needed and relay events from worker thread to watcher
|
|
if (
|
|
event === 'message' &&
|
|
process.env.WATCH_REPORT_DEPENDENCIES &&
|
|
process.send
|
|
) {
|
|
const { isMainThread } = internalBinding('worker');
|
|
if (isMainThread) {
|
|
if (ArrayIsArray(message?.['watch:require'])) {
|
|
process.send({ 'watch:require': message['watch:require'] });
|
|
}
|
|
if (ArrayIsArray(message?.['watch:import'])) {
|
|
process.send({ 'watch:import': message['watch:import'] });
|
|
}
|
|
}
|
|
}
|
|
this.emit(event, message);
|
|
});
|
|
});
|
|
setupPortReferencing(this[kPublicPort], this, 'message');
|
|
|
|
this[kPort].postMessage({
|
|
argv,
|
|
type: messageTypes.LOAD_SCRIPT,
|
|
filename,
|
|
doEval,
|
|
isInternal,
|
|
cwdCounter: cwdCounter || workerIo.sharedCwdCounter,
|
|
workerData: options.workerData,
|
|
webWorkerData: options[kWebWorkerData],
|
|
environmentData,
|
|
hasStdin: !!options.stdin,
|
|
publicPort: publicPortToWorker,
|
|
mainThreadPort: mainThreadPortToWorker,
|
|
}, transferList);
|
|
// Use this to cache the Worker's loopStart value once available.
|
|
this[kLoopStartTime] = -1;
|
|
this[kIsOnline] = false;
|
|
this.performance = {
|
|
eventLoopUtilization: FunctionPrototypeBind(eventLoopUtilization, this),
|
|
};
|
|
// Actually start the new thread now that everything is in place.
|
|
this[kHandle].startThread();
|
|
|
|
process.nextTick(() => process.emit('worker', this));
|
|
if (workerThreadsChannel.hasSubscribers) {
|
|
workerThreadsChannel.publish({
|
|
worker: this,
|
|
});
|
|
}
|
|
}
|
|
|
|
[kOnExit](code, customErr, customErrReason) {
|
|
debug(`[${threadId}] hears end event for Worker ${this.threadId}`);
|
|
drainMessagePort(this[kPublicPort]);
|
|
drainMessagePort(this[kPort]);
|
|
destroyMainThreadPort(this.threadId);
|
|
this.removeAllListeners('message');
|
|
this.removeAllListeners('messageerrors');
|
|
this[kPublicPort].unref();
|
|
this[kPort].unref();
|
|
this[kDispose]();
|
|
if (customErr) {
|
|
debug(`[${threadId}] failing with custom error ${customErr} \
|
|
and with reason ${customErrReason}`);
|
|
this.emit('error', new errorCodes[customErr](customErrReason));
|
|
}
|
|
this.emit('exit', code);
|
|
this.removeAllListeners();
|
|
}
|
|
|
|
[kOnCouldNotSerializeErr]() {
|
|
this.emit('error', new ERR_WORKER_UNSERIALIZABLE_ERROR());
|
|
}
|
|
|
|
[kOnErrorMessage](serialized) {
|
|
// This is what is called for uncaught exceptions.
|
|
const { deserializeError } = require('internal/error_serdes');
|
|
const error = deserializeError(serialized);
|
|
this.emit('error', error);
|
|
}
|
|
|
|
[kOnMessage](message) {
|
|
switch (message.type) {
|
|
case messageTypes.UP_AND_RUNNING:
|
|
this[kIsOnline] = true;
|
|
return this.emit('online');
|
|
case messageTypes.COULD_NOT_SERIALIZE_ERROR:
|
|
return this[kOnCouldNotSerializeErr]();
|
|
case messageTypes.ERROR_MESSAGE:
|
|
return this[kOnErrorMessage](message.error);
|
|
case messageTypes.STDIO_PAYLOAD:
|
|
{
|
|
const { stream, chunks } = message;
|
|
const readable = this[kParentSideStdio][stream];
|
|
// This is a hot path, use a for(;;) loop
|
|
for (let i = 0; i < chunks.length; i++) {
|
|
const { chunk, encoding } = chunks[i];
|
|
readable.push(chunk, encoding);
|
|
}
|
|
return;
|
|
}
|
|
case messageTypes.STDIO_WANTS_MORE_DATA:
|
|
{
|
|
const { stream } = message;
|
|
return this[kParentSideStdio][stream][kStdioWantsMoreDataCallback]();
|
|
}
|
|
}
|
|
|
|
assert.fail(`Unknown worker message type ${message.type}`);
|
|
}
|
|
|
|
[kDispose]() {
|
|
this[kHandle].onexit = null;
|
|
this[kHandle] = null;
|
|
this[kPort] = null;
|
|
this[kPublicPort] = null;
|
|
|
|
const { stdout, stderr } = this[kParentSideStdio];
|
|
|
|
if (!stdout.readableEnded) {
|
|
debug(`[${threadId}] explicitly closes stdout for ${this.threadId}`);
|
|
stdout.push(null);
|
|
}
|
|
if (!stderr.readableEnded) {
|
|
debug(`[${threadId}] explicitly closes stderr for ${this.threadId}`);
|
|
stderr.push(null);
|
|
}
|
|
}
|
|
|
|
postMessage(...args) {
|
|
if (this[kPublicPort] === null) return;
|
|
|
|
ReflectApply(this[kPublicPort].postMessage, this[kPublicPort], args);
|
|
}
|
|
|
|
terminate() {
|
|
debug(`[${threadId}] terminates Worker with ID ${this.threadId}`);
|
|
|
|
this.ref();
|
|
|
|
if (this[kHandle] === null) return PromiseResolve();
|
|
|
|
this[kHandle].stopThread();
|
|
|
|
// Do not use events.once() here, because the 'exit' event will always be
|
|
// emitted regardless of any errors, and the point is to only resolve
|
|
// once the thread has actually stopped.
|
|
const { promise, resolve } = PromiseWithResolvers();
|
|
this.once('exit', resolve);
|
|
return promise;
|
|
}
|
|
|
|
async [SymbolAsyncDispose]() {
|
|
await this.terminate();
|
|
}
|
|
|
|
ref() {
|
|
if (this[kHandle] === null) return;
|
|
|
|
this[kHandle].ref();
|
|
this[kPublicPort].ref();
|
|
}
|
|
|
|
unref() {
|
|
if (this[kHandle] === null) return;
|
|
|
|
this[kHandle].unref();
|
|
this[kPublicPort].unref();
|
|
}
|
|
|
|
get threadId() {
|
|
if (this[kHandle] === null) return -1;
|
|
|
|
return this[kHandle].threadId;
|
|
}
|
|
|
|
get threadName() {
|
|
if (this[kHandle] === null) return null;
|
|
|
|
return this[kHandle].threadName;
|
|
}
|
|
|
|
get stdin() {
|
|
return this[kParentSideStdio].stdin;
|
|
}
|
|
|
|
get stdout() {
|
|
return this[kParentSideStdio].stdout;
|
|
}
|
|
|
|
get stderr() {
|
|
return this[kParentSideStdio].stderr;
|
|
}
|
|
|
|
get resourceLimits() {
|
|
if (this[kHandle] === null) return {};
|
|
|
|
return makeResourceLimits(this[kHandle].getResourceLimits());
|
|
}
|
|
|
|
getHeapSnapshot(options) {
|
|
const {
|
|
HeapSnapshotStream,
|
|
getHeapSnapshotOptions,
|
|
} = require('internal/heap_utils');
|
|
const optionsArray = getHeapSnapshotOptions(options);
|
|
const heapSnapshotTaker = this[kHandle]?.takeHeapSnapshot(optionsArray);
|
|
return new Promise((resolve, reject) => {
|
|
if (!heapSnapshotTaker) return reject(new ERR_WORKER_NOT_RUNNING());
|
|
heapSnapshotTaker.ondone = (handle) => {
|
|
resolve(new HeapSnapshotStream(handle));
|
|
};
|
|
});
|
|
}
|
|
|
|
getHeapStatistics() {
|
|
const taker = this[kHandle]?.getHeapStatistics();
|
|
|
|
return new Promise((resolve, reject) => {
|
|
if (!taker) return reject(new ERR_WORKER_NOT_RUNNING());
|
|
taker.ondone = (handle) => {
|
|
resolve(handle);
|
|
};
|
|
});
|
|
}
|
|
|
|
cpuUsage(prev) {
|
|
if (prev) {
|
|
validateObject(prev, 'prev');
|
|
validateNumber(prev.user, 'prev.user', 0, NumberMAX_SAFE_INTEGER);
|
|
validateNumber(prev.system, 'prev.system', 0, NumberMAX_SAFE_INTEGER);
|
|
}
|
|
if (process.platform === 'sunos') {
|
|
throw new ERR_OPERATION_FAILED('worker.cpuUsage() is not available on SunOS');
|
|
}
|
|
const taker = this[kHandle]?.cpuUsage();
|
|
return new Promise((resolve, reject) => {
|
|
if (!taker) return reject(new ERR_WORKER_NOT_RUNNING());
|
|
taker.ondone = (err, current) => {
|
|
if (err !== null) {
|
|
return reject(err);
|
|
}
|
|
if (prev) {
|
|
resolve({
|
|
user: current.user - prev.user,
|
|
system: current.system - prev.system,
|
|
});
|
|
} else {
|
|
resolve({
|
|
user: current.user,
|
|
system: current.system,
|
|
});
|
|
}
|
|
};
|
|
});
|
|
}
|
|
|
|
startCpuProfile(options) {
|
|
normalizeCpuProfileOptions ??=
|
|
require('internal/v8/cpu_profiler').normalizeCpuProfileOptions;
|
|
const {
|
|
samplingIntervalMicros,
|
|
maxSamples,
|
|
} = normalizeCpuProfileOptions(options);
|
|
const startTaker = this[kHandle]?.startCpuProfile(
|
|
samplingIntervalMicros,
|
|
maxSamples);
|
|
return new Promise((resolve, reject) => {
|
|
if (!startTaker) return reject(new ERR_WORKER_NOT_RUNNING());
|
|
startTaker.ondone = (err, id) => {
|
|
if (err) {
|
|
return reject(err);
|
|
}
|
|
resolve(new CPUProfileHandle(this, id));
|
|
};
|
|
});
|
|
}
|
|
|
|
/**
|
|
* @param {object} [options]
|
|
* @param {number} [options.sampleInterval]
|
|
* @param {number} [options.stackDepth]
|
|
* @param {boolean} [options.forceGC]
|
|
* @param {boolean} [options.includeObjectsCollectedByMajorGC]
|
|
* @param {boolean} [options.includeObjectsCollectedByMinorGC]
|
|
* @returns {Promise}
|
|
*/
|
|
startHeapProfile(options) {
|
|
normalizeHeapProfileOptions ??=
|
|
require('internal/v8/heap_profile').normalizeHeapProfileOptions;
|
|
const { sampleInterval, stackDepth, flags } =
|
|
normalizeHeapProfileOptions(options);
|
|
const startTaker = this[kHandle]?.startHeapProfile(
|
|
sampleInterval, stackDepth, flags);
|
|
return new Promise((resolve, reject) => {
|
|
if (!startTaker) return reject(new ERR_WORKER_NOT_RUNNING());
|
|
startTaker.ondone = (err) => {
|
|
if (err) {
|
|
return reject(err);
|
|
}
|
|
resolve(new HeapProfileHandle(this));
|
|
};
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A worker which has an internal module for entry point (e.g. internal/module/esm/worker).
|
|
* Internal workers bypass the permission model.
|
|
*/
|
|
class InternalWorker extends Worker {
|
|
constructor(filename, options) {
|
|
super(filename, options, kIsInternal);
|
|
}
|
|
|
|
receiveMessageSync() {
|
|
return receiveMessageOnPort(this[kPublicPort]);
|
|
}
|
|
}
|
|
|
|
function pipeWithoutWarning(source, dest) {
|
|
const sourceMaxListeners = source._maxListeners;
|
|
const destMaxListeners = dest._maxListeners;
|
|
source.setMaxListeners(Infinity);
|
|
dest.setMaxListeners(Infinity);
|
|
|
|
source.pipe(dest);
|
|
|
|
source._maxListeners = sourceMaxListeners;
|
|
dest._maxListeners = destMaxListeners;
|
|
}
|
|
|
|
const resourceLimitsArray = new Float64Array(kTotalResourceLimitCount);
|
|
function parseResourceLimits(obj) {
|
|
const ret = resourceLimitsArray;
|
|
TypedArrayPrototypeFill(ret, -1);
|
|
if (typeof obj !== 'object' || obj === null) return ret;
|
|
|
|
if (typeof obj.maxOldGenerationSizeMb === 'number')
|
|
ret[kMaxOldGenerationSizeMb] = MathMax(obj.maxOldGenerationSizeMb, 2);
|
|
if (typeof obj.maxYoungGenerationSizeMb === 'number')
|
|
ret[kMaxYoungGenerationSizeMb] = obj.maxYoungGenerationSizeMb;
|
|
if (typeof obj.codeRangeSizeMb === 'number')
|
|
ret[kCodeRangeSizeMb] = obj.codeRangeSizeMb;
|
|
if (typeof obj.stackSizeMb === 'number')
|
|
ret[kStackSizeMb] = obj.stackSizeMb;
|
|
return ret;
|
|
}
|
|
|
|
function makeResourceLimits(float64arr) {
|
|
return {
|
|
maxYoungGenerationSizeMb: float64arr[kMaxYoungGenerationSizeMb],
|
|
maxOldGenerationSizeMb: float64arr[kMaxOldGenerationSizeMb],
|
|
codeRangeSizeMb: float64arr[kCodeRangeSizeMb],
|
|
stackSizeMb: float64arr[kStackSizeMb],
|
|
};
|
|
}
|
|
|
|
function eventLoopUtilization(util1, util2) {
|
|
const { internalEventLoopUtilization } = require('internal/perf/event_loop_utilization');
|
|
// TODO(trevnorris): Works to solve the thread-safe read/write issue of
|
|
// loopTime, but has the drawback that it can't be set until the event loop
|
|
// has had a chance to turn. So it will be impossible to read the ELU of
|
|
// a worker thread immediately after it's been created.
|
|
if (!this[kIsOnline] || !this[kHandle]) {
|
|
return { idle: 0, active: 0, utilization: 0 };
|
|
}
|
|
|
|
// Cache loopStart, since it's only written to once.
|
|
if (this[kLoopStartTime] === -1) {
|
|
this[kLoopStartTime] = this[kHandle].loopStartTime();
|
|
if (this[kLoopStartTime] === -1)
|
|
return { idle: 0, active: 0, utilization: 0 };
|
|
}
|
|
|
|
return internalEventLoopUtilization(
|
|
this[kLoopStartTime],
|
|
this[kHandle].loopIdleTime(),
|
|
util1,
|
|
util2,
|
|
);
|
|
}
|
|
|
|
module.exports = {
|
|
ownsProcessState,
|
|
kIsOnline,
|
|
isMainThread,
|
|
isInternalThread,
|
|
SHARE_ENV,
|
|
resourceLimits:
|
|
!isMainThread ? makeResourceLimits(resourceLimitsRaw) : {},
|
|
setEnvironmentData,
|
|
getEnvironmentData,
|
|
assignEnvironmentData,
|
|
threadId,
|
|
threadName,
|
|
InternalWorker,
|
|
Worker,
|
|
kPublicPort,
|
|
kWebWorkerData,
|
|
};
|