node/lib/internal/bootstrap/switches/is_main_thread.js
Joyee Cheung 3dbd686218
bootstrap: optimize modules loaded in the built-in snapshot
Preload essential modules and lazy-load non-essential ones.
After this patch, all modules listed by running this snippet:

```
const list = process.moduleLoadList.join('\n');
require('fs').writeSync(1, list, 'utf-8');
```

(which is roughly the same list as the one in
test-bootstrap-module.js for the main thread)
are loaded from the snapshot so no additional compilation cost
is incurred.

PR-URL: https://github.com/nodejs/node/pull/45849
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
2022-12-16 12:44:18 +01:00

318 lines
8.5 KiB
JavaScript

'use strict';
const { ObjectDefineProperty } = primordials;
const rawMethods = internalBinding('process_methods');
const {
namespace: {
addSerializeCallback,
isBuildingSnapshot
},
} = require('internal/v8/startup_snapshot');
// TODO(joyeecheung): deprecate and remove these underscore methods
process._debugProcess = rawMethods._debugProcess;
process._debugEnd = rawMethods._debugEnd;
// See the discussion in https://github.com/nodejs/node/issues/19009 and
// https://github.com/nodejs/node/pull/34010 for why these are no-ops.
// Five word summary: they were broken beyond repair.
process._startProfilerIdleNotifier = () => {};
process._stopProfilerIdleNotifier = () => {};
function defineStream(name, getter) {
ObjectDefineProperty(process, name, {
__proto__: null,
configurable: true,
enumerable: true,
get: getter
});
}
defineStream('stdout', getStdout);
defineStream('stdin', getStdin);
defineStream('stderr', getStderr);
// Worker threads don't receive signals.
const {
startListeningIfSignal,
stopListeningIfSignal
} = require('internal/process/signal');
process.on('newListener', startListeningIfSignal);
process.on('removeListener', stopListeningIfSignal);
// ---- keep the attachment of the wrappers above so that it's easier to ----
// ---- compare the setups side-by-side -----
const { guessHandleType } = internalBinding('util');
function createWritableStdioStream(fd) {
let stream;
// Note stream._type is used for test-module-load-list.js
switch (guessHandleType(fd)) {
case 'TTY': {
const tty = require('tty');
stream = new tty.WriteStream(fd);
stream._type = 'tty';
break;
}
case 'FILE': {
const SyncWriteStream = require('internal/fs/sync_write_stream');
stream = new SyncWriteStream(fd, { autoClose: false });
stream._type = 'fs';
break;
}
case 'PIPE':
case 'TCP': {
const net = require('net');
// If fd is already being used for the IPC channel, libuv will return
// an error when trying to use it again. In that case, create the socket
// using the existing handle instead of the fd.
if (process.channel && process.channel.fd === fd) {
const { kChannelHandle } = require('internal/child_process');
stream = new net.Socket({
handle: process[kChannelHandle],
readable: false,
writable: true
});
} else {
stream = new net.Socket({
fd,
readable: false,
writable: true
});
}
stream._type = 'pipe';
break;
}
default: {
// Provide a dummy black-hole output for e.g. non-console
// Windows applications.
const { Writable } = require('stream');
stream = new Writable({
write(buf, enc, cb) {
cb();
}
});
}
}
// For supporting legacy API we put the FD here.
stream.fd = fd;
stream._isStdio = true;
return stream;
}
function dummyDestroy(err, cb) {
cb(err);
this._undestroy();
// We need to emit 'close' anyway so that the closing
// of the stream is observable. We just make sure we
// are not going to do it twice.
// The 'close' event is needed so that finished and
// pipeline work correctly.
if (!this._writableState.emitClose) {
process.nextTick(() => {
this.emit('close');
});
}
}
let stdin;
let stdout;
let stderr;
let stdoutDestroy;
let stderrDestroy;
function refreshStdoutOnSigWinch() {
stdout._refreshSize();
}
function refreshStderrOnSigWinch() {
stderr._refreshSize();
}
function addCleanup(fn) {
if (isBuildingSnapshot()) {
addSerializeCallback(fn);
}
}
function getStdout() {
if (stdout) return stdout;
stdout = createWritableStdioStream(1);
stdout.destroySoon = stdout.destroy;
// Override _destroy so that the fd is never actually closed.
stdoutDestroy = stdout._destroy;
stdout._destroy = dummyDestroy;
if (stdout.isTTY) {
process.on('SIGWINCH', refreshStdoutOnSigWinch);
}
addCleanup(function cleanupStdout() {
stdout._destroy = stdoutDestroy;
stdout.destroy();
process.removeListener('SIGWINCH', refreshStdoutOnSigWinch);
stdout = undefined;
});
// No need to add deserialize callback because stdout = undefined above
// causes the stream to be lazily initialized again later.
return stdout;
}
function getStderr() {
if (stderr) return stderr;
stderr = createWritableStdioStream(2);
stderr.destroySoon = stderr.destroy;
stderrDestroy = stderr._destroy;
// Override _destroy so that the fd is never actually closed.
stderr._destroy = dummyDestroy;
if (stderr.isTTY) {
process.on('SIGWINCH', refreshStderrOnSigWinch);
}
addCleanup(function cleanupStderr() {
stderr._destroy = stderrDestroy;
stderr.destroy();
process.removeListener('SIGWINCH', refreshStderrOnSigWinch);
stderr = undefined;
});
// No need to add deserialize callback because stderr = undefined above
// causes the stream to be lazily initialized again later.
return stderr;
}
function getStdin() {
if (stdin) return stdin;
const fd = 0;
switch (guessHandleType(fd)) {
case 'TTY': {
const tty = require('tty');
stdin = new tty.ReadStream(fd);
break;
}
case 'FILE': {
const fs = require('fs');
stdin = new fs.ReadStream(null, { fd: fd, autoClose: false });
break;
}
case 'PIPE':
case 'TCP': {
const net = require('net');
// It could be that process has been started with an IPC channel
// sitting on fd=0, in such case the pipe for this fd is already
// present and creating a new one will lead to the assertion failure
// in libuv.
if (process.channel && process.channel.fd === fd) {
stdin = new net.Socket({
handle: process.channel,
readable: true,
writable: false,
manualStart: true
});
} else {
stdin = new net.Socket({
fd: fd,
readable: true,
writable: false,
manualStart: true
});
}
// Make sure the stdin can't be `.end()`-ed
stdin._writableState.ended = true;
break;
}
default: {
// Provide a dummy contentless input for e.g. non-console
// Windows applications.
const { Readable } = require('stream');
stdin = new Readable({ read() {} });
stdin.push(null);
}
}
// For supporting legacy API we put the FD here.
stdin.fd = fd;
// `stdin` starts out life in a paused state, but node doesn't
// know yet. Explicitly to readStop() it to put it in the
// not-reading state.
if (stdin._handle && stdin._handle.readStop) {
stdin._handle.reading = false;
stdin._readableState.reading = false;
stdin._handle.readStop();
}
// If the user calls stdin.pause(), then we need to stop reading
// once the stream implementation does so (one nextTick later),
// so that the process can close down.
stdin.on('pause', () => {
process.nextTick(onpause);
});
function onpause() {
if (!stdin._handle)
return;
if (stdin._handle.reading && !stdin.readableFlowing) {
stdin._readableState.reading = false;
stdin._handle.reading = false;
stdin._handle.readStop();
}
}
addCleanup(function cleanupStdin() {
stdin.destroy();
stdin = undefined;
});
// No need to add deserialize callback because stdin = undefined above
// causes the stream to be lazily initialized again later.
return stdin;
}
// Used by internal tests.
rawMethods.resetStdioForTesting = function() {
stdin = undefined;
stdout = undefined;
stderr = undefined;
};
// Needed by the module loader and generally needed everywhere.
require('fs');
require('util');
require('url');
require('internal/modules/cjs/loader');
require('internal/modules/esm/utils');
require('internal/vm/module');
// Needed to refresh the time origin.
require('internal/perf/utils');
// Needed to register the async hooks.
if (internalBinding('config').hasInspector) {
require('internal/inspector_async_hook');
}
// Needed to set the wasm web API callbacks.
internalBinding('wasm_web_api');
// Needed to detect whether it's on main thread.
internalBinding('worker');
// Needed to setup source maps.
require('internal/source_map/source_map_cache');
// Needed by most execution modes.
require('internal/modules/run_main');
// Needed to refresh DNS configurations.
require('internal/dns/utils');
// Needed by almost all execution modes. It's fine to
// load them into the snapshot as long as we don't run
// any of the initialization.
require('internal/process/pre_execution');