Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bootstrap: support configure-time user-land snapshot #42466

Closed
19 changes: 19 additions & 0 deletions configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,13 @@
default=False,
help='node will load builtin modules from disk instead of from binary')

parser.add_argument('--node-snapshot-main',
action='store',
dest='node_snapshot_main',
default=None,
help='Run a file when building the embedded snapshot. Currently ' +
'experimental.')

# Create compile_commands.json in out/Debug and out/Release.
parser.add_argument('-C',
action='store_true',
Expand Down Expand Up @@ -1216,6 +1223,18 @@ def configure_node(o):

o['variables']['want_separate_host_toolset'] = int(cross_compiling)

if options.node_snapshot_main is not None:
if options.shared:
# This should be possible to fix, but we will need to refactor the
# libnode target to avoid building it twice.
error('--node-snapshot-main is incompatible with --shared')
if options.without_node_snapshot:
error('--node-snapshot-main is incompatible with ' +
'--without-node-snapshot')
if cross_compiling:
error('--node-snapshot-main is incompatible with cross compilation')
o['variables']['node_snapshot_main'] = options.node_snapshot_main

if options.without_node_snapshot or options.node_builtin_modules_path:
o['variables']['node_use_node_snapshot'] = 'false'
else:
Expand Down
26 changes: 23 additions & 3 deletions lib/internal/bootstrap/pre_execution.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const {
const {
getOptionValue,
getEmbedderOptions,
refreshOptions,
} = require('internal/options');
const { reconnectZeroFillToggle } = require('internal/buffer');
const {
Expand All @@ -26,7 +27,10 @@ const { Buffer } = require('buffer');
const { ERR_MANIFEST_ASSERT_INTEGRITY } = require('internal/errors').codes;
const assert = require('internal/assert');

function prepareMainThreadExecution(expandArgv1 = false) {
function prepareMainThreadExecution(expandArgv1 = false,
initialzeModules = true) {
refreshRuntimeOptions();

// TODO(joyeecheung): this is also necessary for workers when they deserialize
// this toggle from the snapshot.
reconnectZeroFillToggle();
Expand Down Expand Up @@ -78,15 +82,23 @@ function prepareMainThreadExecution(expandArgv1 = false) {
initializeSourceMapsHandlers();
initializeDeprecations();
initializeWASI();

if (!initialzeModules) {
return;
}

initializeCJSLoader();
initializeESMLoader();

const CJSLoader = require('internal/modules/cjs/loader');
assert(!CJSLoader.hasLoadedAnyUserCJSModule);
loadPreloadModules();
initializeFrozenIntrinsics();
}

function refreshRuntimeOptions() {
refreshOptions();
}

function patchProcessObject(expandArgv1) {
const binding = internalBinding('process_methods');
binding.patchProcessObject(process);
Expand All @@ -95,7 +107,8 @@ function patchProcessObject(expandArgv1) {

ObjectDefineProperty(process, 'argv0', {
enumerable: true,
configurable: false,
// Only set it to true during snapshot building.
configurable: getOptionValue('--build-snapshot'),
value: process.argv[0]
});
process.argv[0] = process.execPath;
Expand All @@ -111,6 +124,12 @@ function patchProcessObject(expandArgv1) {
}
}

// We need to initialize the global console here again with process.stdout
// and friends for snapshot deserialization.
const globalConsole = require('internal/console/global');
const { initializeGlobalConsole } = require('internal/console/constructor');
initializeGlobalConsole(globalConsole);

// TODO(joyeecheung): most of these should be deprecated and removed,
// except some that we need to be able to mutate during run time.
addReadOnlyProcessAlias('_eval', '--eval');
Expand Down Expand Up @@ -556,6 +575,7 @@ function loadPreloadModules() {
}

module.exports = {
refreshRuntimeOptions,
patchProcessObject,
setupCoverageHooks,
setupWarningHandler,
Expand Down
34 changes: 32 additions & 2 deletions lib/internal/bootstrap/switches/is_main_thread.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,27 +122,53 @@ let stdin;
let stdout;
let stderr;

let stdoutDestroy;
let stderrDestroy;

function refreshStdoutOnSigWinch() {
stdout._refreshSize();
}

function refreshStderrOnSigWinch() {
stderr._refreshSize();
}

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', () => stdout._refreshSize());
process.on('SIGWINCH', refreshStdoutOnSigWinch);
}

internalBinding('mksnapshot').cleanups.push(function cleanupStdout() {
stdout._destroy = stdoutDestroy;
stdout.destroy();
process.removeListener('SIGWINCH', refreshStdoutOnSigWinch);
stdout = undefined;
});
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', () => stderr._refreshSize());
process.on('SIGWINCH', refreshStderrOnSigWinch);
}
internalBinding('mksnapshot').cleanups.push(function cleanupStderr() {
stderr._destroy = stderrDestroy;
stderr.destroy();
process.removeListener('SIGWINCH', refreshStderrOnSigWinch);
stderr = undefined;
});
return stderr;
}

Expand Down Expand Up @@ -229,6 +255,10 @@ function getStdin() {
}
}

internalBinding('mksnapshot').cleanups.push(function cleanupStdin() {
stdin.destroy();
stdin = undefined;
});
return stdin;
}

Expand Down
6 changes: 6 additions & 0 deletions lib/internal/console/constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -669,9 +669,15 @@ Console.prototype.dirxml = Console.prototype.log;
Console.prototype.error = Console.prototype.warn;
Console.prototype.groupCollapsed = Console.prototype.group;

function initializeGlobalConsole(globalConsole) {
globalConsole[kBindStreamsLazy](process);
globalConsole[kBindProperties](true, 'auto');
}

module.exports = {
Console,
kBindStreamsLazy,
kBindProperties,
initializeGlobalConsole,
formatTime // exported for tests
};
7 changes: 1 addition & 6 deletions lib/internal/console/global.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,7 @@ const {
} = primordials;

const {
Console,
kBindStreamsLazy,
kBindProperties
Console
} = require('internal/console/constructor');

const globalConsole = ObjectCreate({});
Expand All @@ -44,9 +42,6 @@ for (const prop of ReflectOwnKeys(Console.prototype)) {
ReflectDefineProperty(globalConsole, prop, desc);
}

globalConsole[kBindStreamsLazy](process);
globalConsole[kBindProperties](true, 'auto');

// This is a legacy feature - the Console constructor is exposed on
// the global console instance.
globalConsole.Console = Console;
Expand Down
143 changes: 143 additions & 0 deletions lib/internal/main/mksnapshot.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
'use strict';

const {
Error,
SafeSet,
SafeArrayIterator
} = primordials;

const binding = internalBinding('mksnapshot');
const { NativeModule } = require('internal/bootstrap/loaders');
const {
compileSnapshotMain,
} = binding;

const {
getOptionValue
} = require('internal/options');

const {
readFileSync
} = require('fs');

const supportedModules = new SafeSet(new SafeArrayIterator([
// '_http_agent',
// '_http_client',
// '_http_common',
// '_http_incoming',
// '_http_outgoing',
// '_http_server',
'_stream_duplex',
'_stream_passthrough',
'_stream_readable',
'_stream_transform',
'_stream_wrap',
'_stream_writable',
// '_tls_common',
// '_tls_wrap',
'assert',
'assert/strict',
// 'async_hooks',
'buffer',
// 'child_process',
// 'cluster',
'console',
'constants',
'crypto',
// 'dgram',
// 'diagnostics_channel',
// 'dns',
// 'dns/promises',
// 'domain',
'events',
'fs',
'fs/promises',
// 'http',
// 'http2',
// 'https',
// 'inspector',
// 'module',
// 'net',
'os',
'path',
'path/posix',
'path/win32',
// 'perf_hooks',
'process',
'punycode',
'querystring',
// 'readline',
// 'repl',
'stream',
'stream/promises',
'string_decoder',
'sys',
'timers',
'timers/promises',
// 'tls',
// 'trace_events',
// 'tty',
'url',
'util',
'util/types',
'v8',
// 'vm',
// 'worker_threads',
// 'zlib',
]));

const warnedModules = new SafeSet();
function supportedInUserSnapshot(id) {
return supportedModules.has(id);
}

function requireForUserSnapshot(id) {
if (!NativeModule.canBeRequiredByUsers(id)) {
// eslint-disable-next-line no-restricted-syntax
const err = new Error(
`Cannot find module '${id}'. `
);
err.code = 'MODULE_NOT_FOUND';
throw err;
}
if (!supportedInUserSnapshot(id)) {
if (!warnedModules.has(id)) {
process.emitWarning(
`built-in module ${id} is not yet supported in user snapshots`);
warnedModules.add(id);
}
}

return require(id);
}

function main() {
const {
prepareMainThreadExecution
} = require('internal/bootstrap/pre_execution');

prepareMainThreadExecution(true, false);
joyeecheung marked this conversation as resolved.
Show resolved Hide resolved
process.on('beforeExit', function runCleanups() {
for (const cleanup of binding.cleanups) {
cleanup();
}
process.off('beforeExit', runCleanups);
});
joyeecheung marked this conversation as resolved.
Show resolved Hide resolved

const file = process.argv[1];
const path = require('path');
const filename = path.resolve(file);
const dirname = path.dirname(filename);
const source = readFileSync(file, 'utf-8');
const snapshotMainFunction = compileSnapshotMain(filename, source);

if (getOptionValue('--inspect-brk')) {
internalBinding('inspector').callAndPauseOnStart(
snapshotMainFunction, undefined,
requireForUserSnapshot, filename, dirname);
} else {
snapshotMainFunction(requireForUserSnapshot, filename, dirname);
}
}

main();
8 changes: 7 additions & 1 deletion lib/internal/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ function getEmbedderOptions() {
return embedderOptions;
}

function refreshOptions() {
optionsMap = undefined;
aliasesMap = undefined;
}

function getOptionValue(optionName) {
const options = getCLIOptionsFromBinding();
if (optionName.startsWith('--no-')) {
Expand Down Expand Up @@ -68,5 +73,6 @@ module.exports = {
},
getOptionValue,
getAllowUnauthorized,
getEmbedderOptions
getEmbedderOptions,
refreshOptions
};