diff options
author | Matt Mastracci <matthew@mastracci.com> | 2024-03-22 13:49:07 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-03-22 20:49:07 +0000 |
commit | 08ec6e5831478f6d15188e91d9a8e3c00d80c1ed (patch) | |
tree | 13a830c1ab1ee2ce5ebcffe5c0fcecd77146c8bc /ext/node | |
parent | 9c2f9f14e749e4dd63ad02e3ca8af5fc8bd112ee (diff) |
perf: warm expensive init code at snapshot time (#22714)
Slightly different approach to similar changes in #22386
Note that this doesn't use a warmup script -- we are actually just doing
more work at snapshot time.
Diffstat (limited to 'ext/node')
-rw-r--r-- | ext/node/polyfills/02_init.js | 67 | ||||
-rw-r--r-- | ext/node/polyfills/_process/streams.mjs | 16 | ||||
-rw-r--r-- | ext/node/polyfills/process.ts | 120 |
3 files changed, 120 insertions, 83 deletions
diff --git a/ext/node/polyfills/02_init.js b/ext/node/polyfills/02_init.js index 85f924493..752f518bf 100644 --- a/ext/node/polyfills/02_init.js +++ b/ext/node/polyfills/02_init.js @@ -16,38 +16,32 @@ function initialize( runningOnMainThread, workerId, maybeWorkerMetadata, + warmup = false, ) { - if (initialized) { - throw Error("Node runtime already initialized"); - } - initialized = true; - if (usesLocalNodeModulesDir) { - requireImpl.setUsesLocalNodeModulesDir(); - } - const nativeModuleExports = requireImpl.nativeModuleExports; - nodeGlobals.Buffer = nativeModuleExports["buffer"].Buffer; - nodeGlobals.clearImmediate = nativeModuleExports["timers"].clearImmediate; - nodeGlobals.clearInterval = nativeModuleExports["timers"].clearInterval; - nodeGlobals.clearTimeout = nativeModuleExports["timers"].clearTimeout; - nodeGlobals.console = nativeModuleExports["console"]; - nodeGlobals.global = globalThis; - nodeGlobals.process = nativeModuleExports["process"]; - nodeGlobals.setImmediate = nativeModuleExports["timers"].setImmediate; - nodeGlobals.setInterval = nativeModuleExports["timers"].setInterval; - nodeGlobals.setTimeout = nativeModuleExports["timers"].setTimeout; - nodeGlobals.performance = nativeModuleExports["perf_hooks"].performance; + if (!warmup) { + if (initialized) { + throw Error("Node runtime already initialized"); + } + initialized = true; + if (usesLocalNodeModulesDir) { + requireImpl.setUsesLocalNodeModulesDir(); + } - // FIXME(bartlomieju): not nice to depend on `Deno` namespace here - // but it's the only way to get `args` and `version` and this point. - internals.__bootstrapNodeProcess(argv0, Deno.args, Deno.version); - internals.__initWorkerThreads( - runningOnMainThread, - workerId, - maybeWorkerMetadata, - ); - internals.__setupChildProcessIpcChannel(); - // `Deno[Deno.internal].requireImpl` will be unreachable after this line. - delete internals.requireImpl; + // FIXME(bartlomieju): not nice to depend on `Deno` namespace here + // but it's the only way to get `args` and `version` and this point. + internals.__bootstrapNodeProcess(argv0, Deno.args, Deno.version); + internals.__initWorkerThreads( + runningOnMainThread, + workerId, + maybeWorkerMetadata, + ); + internals.__setupChildProcessIpcChannel(); + // `Deno[Deno.internal].requireImpl` will be unreachable after this line. + delete internals.requireImpl; + } else { + // Warm up the process module + internals.__bootstrapNodeProcess(undefined, undefined, undefined, true); + } } function loadCjsModule(moduleName, isMain, inspectBrk) { @@ -63,3 +57,16 @@ internals.node = { initialize, loadCjsModule, }; + +const nativeModuleExports = requireImpl.nativeModuleExports; +nodeGlobals.Buffer = nativeModuleExports["buffer"].Buffer; +nodeGlobals.clearImmediate = nativeModuleExports["timers"].clearImmediate; +nodeGlobals.clearInterval = nativeModuleExports["timers"].clearInterval; +nodeGlobals.clearTimeout = nativeModuleExports["timers"].clearTimeout; +nodeGlobals.console = nativeModuleExports["console"]; +nodeGlobals.global = globalThis; +nodeGlobals.process = nativeModuleExports["process"]; +nodeGlobals.setImmediate = nativeModuleExports["timers"].setImmediate; +nodeGlobals.setInterval = nativeModuleExports["timers"].setInterval; +nodeGlobals.setTimeout = nativeModuleExports["timers"].setTimeout; +nodeGlobals.performance = nativeModuleExports["perf_hooks"].performance; diff --git a/ext/node/polyfills/_process/streams.mjs b/ext/node/polyfills/_process/streams.mjs index 09c53eb9a..f50e20588 100644 --- a/ext/node/polyfills/_process/streams.mjs +++ b/ext/node/polyfills/_process/streams.mjs @@ -16,7 +16,7 @@ import * as io from "ext:deno_io/12_io.js"; import { guessHandleType } from "ext:deno_node/internal_binding/util.ts"; // https://github.com/nodejs/node/blob/00738314828074243c9a52a228ab4c68b04259ef/lib/internal/bootstrap/switches/is_main_thread.js#L41 -export function createWritableStdioStream(writer, name) { +export function createWritableStdioStream(writer, name, warmup = false) { const stream = new Writable({ emitClose: false, write(buf, enc, cb) { @@ -73,7 +73,9 @@ export function createWritableStdioStream(writer, name) { }, }); - if (writer?.isTerminal()) { + // If we're warming up, create a stdout/stderr stream that assumes a terminal (the most likely case). + // If we're wrong at boot time, we'll recreate it. + if (warmup || writer?.isTerminal()) { // These belong on tty.WriteStream(), but the TTY streams currently have // following problems: // 1. Using them here introduces a circular dependency. @@ -123,10 +125,11 @@ export function setReadStream(s) { /** https://nodejs.org/api/process.html#process_process_stdin */ // https://github.com/nodejs/node/blob/v18.12.1/lib/internal/bootstrap/switches/is_main_thread.js#L189 /** Create process.stdin */ -export const initStdin = () => { +export const initStdin = (warmup = false) => { const fd = io.stdin ? io.STDIN_RID : undefined; let stdin; - const stdinType = _guessStdinType(fd); + // Warmup assumes a TTY for all stdio + const stdinType = warmup ? "TTY" : _guessStdinType(fd); switch (stdinType) { case "FILE": { @@ -142,6 +145,11 @@ export const initStdin = () => { break; } case "TTY": { + // If it's a TTY, we know that the stdin we created during warmup is the correct one and + // just return null to re-use it. + if (!warmup) { + return null; + } stdin = new readStream(fd); break; } diff --git a/ext/node/polyfills/process.ts b/ext/node/polyfills/process.ts index 44207e2f4..70c516c96 100644 --- a/ext/node/polyfills/process.ts +++ b/ext/node/polyfills/process.ts @@ -68,7 +68,7 @@ const notImplementedEvents = [ "worker", ]; -export const argv: string[] = []; +export const argv: string[] = ["", ""]; let globalProcessExitCode: number | undefined = undefined; /** https://nodejs.org/api/process.html#process_process_exit_code */ @@ -868,69 +868,91 @@ function synchronizeListeners() { } } +// Overwrites the 1st and 2nd items with getters. +Object.defineProperty(argv, "0", { get: () => argv0 }); +Object.defineProperty(argv, "1", { + get: () => { + if (Deno.mainModule?.startsWith("file:")) { + return pathFromURL(new URL(Deno.mainModule)); + } else { + return join(Deno.cwd(), "$deno$node.js"); + } + }, +}); + // Should be called only once, in `runtime/js/99_main.js` when the runtime is // bootstrapped. internals.__bootstrapNodeProcess = function ( argv0Val: string | undefined, args: string[], denoVersions: Record<string, string>, + warmup = false, ) { - // Overwrites the 1st item with getter. - if (typeof argv0Val === "string") { - argv0 = argv0Val; - Object.defineProperty(argv, "0", { - get: () => { - return argv0Val; - }, - }); - } else { - Object.defineProperty(argv, "0", { get: () => argv0 }); - } + if (!warmup) { + argv0 = argv0Val || ""; + // Manually concatenate these arrays to avoid triggering the getter + for (let i = 0; i < args.length; i++) { + argv[i + 2] = args[i]; + } - // Overwrites the 2st item with getter. - Object.defineProperty(argv, "1", { - get: () => { - if (Deno.mainModule?.startsWith("file:")) { - return pathFromURL(new URL(Deno.mainModule)); - } else { - return join(Deno.cwd(), "$deno$node.js"); - } - }, - }); - for (let i = 0; i < args.length; i++) { - argv[i + 2] = args[i]; - } + for (const [key, value] of Object.entries(denoVersions)) { + versions[key] = value; + } - for (const [key, value] of Object.entries(denoVersions)) { - versions[key] = value; - } + core.setNextTickCallback(processTicksAndRejections); + core.setMacrotaskCallback(runNextTicks); + enableNextTick(); - core.setNextTickCallback(processTicksAndRejections); - core.setMacrotaskCallback(runNextTicks); - enableNextTick(); + // Replace stdin if it is not a terminal + const newStdin = initStdin(); + if (newStdin) { + stdin = process.stdin = newStdin; + } - stdin = process.stdin = initStdin(); - /** https://nodejs.org/api/process.html#process_process_stdout */ - stdout = process.stdout = createWritableStdioStream( - io.stdout, - "stdout", - ); + // Replace stdout/stderr if they are not terminals + if (!io.stdout.isTerminal()) { + /** https://nodejs.org/api/process.html#process_process_stdout */ + stdout = process.stdout = createWritableStdioStream( + io.stdout, + "stdout", + ); + } - /** https://nodejs.org/api/process.html#process_process_stderr */ - stderr = process.stderr = createWritableStdioStream( - io.stderr, - "stderr", - ); + if (!io.stderr.isTerminal()) { + /** https://nodejs.org/api/process.html#process_process_stderr */ + stderr = process.stderr = createWritableStdioStream( + io.stderr, + "stderr", + ); + } - process.setStartTime(Date.now()); + process.setStartTime(Date.now()); - arch = arch_(); - platform = isWindows ? "win32" : Deno.build.os; - pid = Deno.pid; + arch = arch_(); + platform = isWindows ? "win32" : Deno.build.os; + pid = Deno.pid; - // @ts-ignore Remove setStartTime and #startTime is not modifiable - delete process.setStartTime; - delete internals.__bootstrapNodeProcess; + // @ts-ignore Remove setStartTime and #startTime is not modifiable + delete process.setStartTime; + delete internals.__bootstrapNodeProcess; + } else { + // Warmup, assuming stdin/stdout/stderr are all terminals + stdin = process.stdin = initStdin(true); + + /** https://nodejs.org/api/process.html#process_process_stdout */ + stdout = process.stdout = createWritableStdioStream( + io.stdout, + "stdout", + true, + ); + + /** https://nodejs.org/api/process.html#process_process_stderr */ + stderr = process.stderr = createWritableStdioStream( + io.stderr, + "stderr", + true, + ); + } }; export default process; |