diff options
author | Matt Mastracci <matthew@mastracci.com> | 2023-08-15 13:36:36 -0600 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-08-16 04:36:36 +0900 |
commit | 4380a09a0598c73aa434e2f0f3a34555e0bd55cb (patch) | |
tree | 671bba82325653ed188efb49e099c4d61ec318cc | |
parent | 41cad2179fb36c2371ab84ce587d3460af64b5fb (diff) |
feat(ext/node): eagerly bootstrap node (#20153)
To fix bugs around detection of when node emulation is required, we will
just eagerly initialize it. The improvements we make to reduce the
impact of the startup time:
- [x] Process stdin/stdout/stderr are lazily created
- [x] node.js global proxy no longer allocates on each access check
- [x] Process checks for `beforeExit` listeners before doing expensive
shutdown work
- [x] Process should avoid adding global event handlers until listeners
are added
Benchmarking this PR (`89de7e1ff`) vs main (`41cad2179`)
```
12:36 $ third_party/prebuilt/mac/hyperfine --warmup 100 -S none './deno-41cad2179 run ./empty.js' './deno-89de7e1ff run ./empty.js'
Benchmark 1: ./deno-41cad2179 run ./empty.js
Time (mean ± σ): 24.3 ms ± 1.6 ms [User: 16.2 ms, System: 6.0 ms]
Range (min … max): 21.1 ms … 29.1 ms 115 runs
Benchmark 2: ./deno-89de7e1ff run ./empty.js
Time (mean ± σ): 24.0 ms ± 1.4 ms [User: 16.3 ms, System: 5.6 ms]
Range (min … max): 21.3 ms … 28.6 ms 126 runs
```
Fixes https://github.com/denoland/deno/issues/20142
Fixes https://github.com/denoland/deno/issues/15826
Fixes https://github.com/denoland/deno/issues/20028
-rw-r--r-- | cli/factory.rs | 10 | ||||
-rw-r--r-- | cli/npm/resolvers/mod.rs | 5 | ||||
-rw-r--r-- | cli/standalone/mod.rs | 10 | ||||
-rw-r--r-- | cli/tools/repl/session.rs | 14 | ||||
-rw-r--r-- | cli/worker.rs | 78 | ||||
-rw-r--r-- | ext/node/global.rs | 6 | ||||
-rw-r--r-- | ext/node/lib.rs | 24 | ||||
-rw-r--r-- | ext/node/polyfills/02_init.js | 4 | ||||
-rw-r--r-- | ext/node/polyfills/process.ts | 266 | ||||
-rw-r--r-- | ext/node/resolution.rs | 7 | ||||
-rw-r--r-- | runtime/js/99_main.js | 19 | ||||
-rw-r--r-- | runtime/ops/worker_host.rs | 25 | ||||
-rw-r--r-- | runtime/web_worker.rs | 36 | ||||
-rw-r--r-- | runtime/worker.rs | 14 | ||||
-rw-r--r-- | runtime/worker_bootstrap.rs | 204 |
15 files changed, 323 insertions, 399 deletions
diff --git a/cli/factory.rs b/cli/factory.rs index 9c553620d..6a99bb2da 100644 --- a/cli/factory.rs +++ b/cli/factory.rs @@ -44,7 +44,6 @@ use crate::util::progress_bar::ProgressBar; use crate::util::progress_bar::ProgressBarStyle; use crate::worker::CliMainWorkerFactory; use crate::worker::CliMainWorkerOptions; -use crate::worker::HasNodeSpecifierChecker; use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; @@ -623,7 +622,6 @@ impl CliFactory { StorageKeyResolver::from_options(&self.options), self.npm_resolver().await?.clone(), node_resolver.clone(), - Box::new(CliHasNodeSpecifierChecker(self.graph_container().clone())), self.blob_store().clone(), Box::new(CliModuleLoaderFactory::new( &self.options, @@ -683,11 +681,3 @@ impl CliFactory { }) } } - -struct CliHasNodeSpecifierChecker(Arc<ModuleGraphContainer>); - -impl HasNodeSpecifierChecker for CliHasNodeSpecifierChecker { - fn has_node_specifier(&self) -> bool { - self.0.graph().has_node_specifier - } -} diff --git a/cli/npm/resolvers/mod.rs b/cli/npm/resolvers/mod.rs index 9ae84d7f9..1b3f57c9a 100644 --- a/cli/npm/resolvers/mod.rs +++ b/cli/npm/resolvers/mod.rs @@ -179,11 +179,6 @@ impl CliNpmResolver { specifier.as_ref().starts_with(root_dir_url.as_str()) } - /// If the resolver has resolved any npm packages. - pub fn has_packages(&self) -> bool { - self.resolution.has_packages() - } - /// Adds package requirements to the resolver and ensures everything is setup. pub async fn add_package_reqs( &self, diff --git a/cli/standalone/mod.rs b/cli/standalone/mod.rs index 92aba6d29..f6c489487 100644 --- a/cli/standalone/mod.rs +++ b/cli/standalone/mod.rs @@ -26,7 +26,6 @@ use crate::util::progress_bar::ProgressBarStyle; use crate::util::v8::construct_v8_flags; use crate::worker::CliMainWorkerFactory; use crate::worker::CliMainWorkerOptions; -use crate::worker::HasNodeSpecifierChecker; use crate::worker::ModuleLoaderFactory; use deno_ast::MediaType; use deno_core::anyhow::Context; @@ -266,14 +265,6 @@ impl ModuleLoaderFactory for StandaloneModuleLoaderFactory { } } -struct StandaloneHasNodeSpecifierChecker; - -impl HasNodeSpecifierChecker for StandaloneHasNodeSpecifierChecker { - fn has_node_specifier(&self) -> bool { - false - } -} - struct StandaloneRootCertStoreProvider { ca_stores: Option<Vec<String>>, ca_data: Option<CaData>, @@ -438,7 +429,6 @@ pub async fn run( StorageKeyResolver::empty(), npm_resolver.clone(), node_resolver, - Box::new(StandaloneHasNodeSpecifierChecker), Default::default(), Box::new(module_loader_factory), root_cert_store_provider, diff --git a/cli/tools/repl/session.rs b/cli/tools/repl/session.rs index 4a30c93c4..9261299df 100644 --- a/cli/tools/repl/session.rs +++ b/cli/tools/repl/session.rs @@ -25,7 +25,6 @@ use deno_core::serde_json; use deno_core::serde_json::Value; use deno_core::LocalInspectorSession; use deno_graph::source::Resolver; -use deno_runtime::deno_node; use deno_runtime::worker::MainWorker; use deno_semver::npm::NpmPackageReqReference; use once_cell::sync::Lazy; @@ -123,7 +122,6 @@ struct TsEvaluateResponse { } pub struct ReplSession { - has_node_modules_dir: bool, npm_resolver: Arc<CliNpmResolver>, resolver: Arc<CliGraphResolver>, pub worker: MainWorker, @@ -131,7 +129,6 @@ pub struct ReplSession { pub context_id: u64, pub language_server: ReplLanguageServer, pub notifications: Rc<RefCell<UnboundedReceiver<Value>>>, - has_initialized_node_runtime: bool, referrer: ModuleSpecifier, } @@ -183,14 +180,12 @@ impl ReplSession { .unwrap(); let mut repl_session = ReplSession { - has_node_modules_dir: cli_options.has_node_modules_dir(), npm_resolver, resolver, worker, session, context_id, language_server, - has_initialized_node_runtime: false, referrer, notifications: Rc::new(RefCell::new(notification_rx)), }; @@ -515,15 +510,6 @@ impl ReplSession { let has_node_specifier = resolved_imports.iter().any(|url| url.scheme() == "node"); if !npm_imports.is_empty() || has_node_specifier { - if !self.has_initialized_node_runtime { - deno_node::initialize_runtime( - &mut self.worker.js_runtime, - self.has_node_modules_dir, - None, - )?; - self.has_initialized_node_runtime = true; - } - self.npm_resolver.add_package_reqs(&npm_imports).await?; // prevent messages in the repl about @types/node not being cached diff --git a/cli/worker.rs b/cli/worker.rs index a712dc9c6..b451cdbed 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -8,7 +8,6 @@ use deno_ast::ModuleSpecifier; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; -use deno_core::futures::task::LocalFutureObj; use deno_core::futures::FutureExt; use deno_core::located_script_name; use deno_core::parking_lot::Mutex; @@ -32,7 +31,6 @@ use deno_runtime::deno_web::BlobStore; use deno_runtime::fmt_errors::format_js_error; use deno_runtime::inspector_server::InspectorServer; use deno_runtime::ops::worker_host::CreateWebWorkerCb; -use deno_runtime::ops::worker_host::WorkerEventCb; use deno_runtime::permissions::PermissionsContainer; use deno_runtime::web_worker::WebWorker; use deno_runtime::web_worker::WebWorkerOptions; @@ -97,7 +95,6 @@ struct SharedWorkerState { storage_key_resolver: StorageKeyResolver, npm_resolver: Arc<CliNpmResolver>, node_resolver: Arc<NodeResolver>, - has_node_specifier_checker: Box<dyn HasNodeSpecifierChecker>, blob_store: Arc<BlobStore>, broadcast_channel: InMemoryBroadcastChannel, shared_array_buffer_store: SharedArrayBufferStore, @@ -110,11 +107,7 @@ struct SharedWorkerState { } impl SharedWorkerState { - pub fn should_initialize_node_runtime(&self) -> bool { - self.npm_resolver.has_packages() - || self.has_node_specifier_checker.has_node_specifier() - || self.options.is_npm_main - } + // Currently empty } pub struct CliMainWorker { @@ -140,7 +133,6 @@ impl CliMainWorker { log::debug!("main_module {}", self.main_module); if self.is_main_cjs { - self.initialize_main_module_for_node()?; deno_node::load_cjs_module( &mut self.worker.js_runtime, &self.main_module.to_file_path().unwrap().to_string_lossy(), @@ -266,22 +258,9 @@ impl CliMainWorker { &mut self, id: ModuleId, ) -> Result<(), AnyError> { - if self.shared.should_initialize_node_runtime() { - self.initialize_main_module_for_node()?; - } self.worker.evaluate_module(id).await } - fn initialize_main_module_for_node(&mut self) -> Result<(), AnyError> { - deno_node::initialize_runtime( - &mut self.worker.js_runtime, - self.shared.options.has_node_modules_dir, - self.shared.options.maybe_binary_npm_command_name.as_deref(), - )?; - - Ok(()) - } - pub async fn maybe_setup_coverage_collector( &mut self, ) -> Result<Option<CoverageCollector>, AnyError> { @@ -312,7 +291,6 @@ impl CliMainWorkerFactory { storage_key_resolver: StorageKeyResolver, npm_resolver: Arc<CliNpmResolver>, node_resolver: Arc<NodeResolver>, - has_node_specifier_checker: Box<dyn HasNodeSpecifierChecker>, blob_store: Arc<BlobStore>, module_loader_factory: Box<dyn ModuleLoaderFactory>, root_cert_store_provider: Arc<dyn RootCertStoreProvider>, @@ -327,7 +305,6 @@ impl CliMainWorkerFactory { storage_key_resolver, npm_resolver, node_resolver, - has_node_specifier_checker, blob_store, broadcast_channel: Default::default(), shared_array_buffer_store: Default::default(), @@ -404,10 +381,6 @@ impl CliMainWorkerFactory { let create_web_worker_cb = create_web_worker_callback(shared.clone(), stdio.clone()); - let web_worker_preload_module_cb = - create_web_worker_preload_module_callback(shared); - let web_worker_pre_execute_module_cb = - create_web_worker_pre_execute_module_callback(shared.clone()); let maybe_storage_key = shared .storage_key_resolver @@ -448,6 +421,11 @@ impl CliMainWorkerFactory { unstable: shared.options.unstable, user_agent: version::get_user_agent().to_string(), inspect: shared.options.is_inspecting, + has_node_modules_dir: shared.options.has_node_modules_dir, + maybe_binary_npm_command_name: shared + .options + .maybe_binary_npm_command_name + .clone(), }, extensions, startup_snapshot: crate::js::deno_isolate_init(), @@ -461,8 +439,6 @@ impl CliMainWorkerFactory { source_map_getter: maybe_source_map_getter, format_js_error_fn: Some(Arc::new(format_js_error)), create_web_worker_cb, - web_worker_preload_module_cb, - web_worker_pre_execute_module_cb, maybe_inspector_server, should_break_on_first_statement: shared.options.inspect_brk, should_wait_for_inspector_session: shared.options.inspect_wait, @@ -555,38 +531,6 @@ impl CliMainWorkerFactory { } } -// TODO(bartlomieju): this callback could have default value -// and not be required -fn create_web_worker_preload_module_callback( - _shared: &Arc<SharedWorkerState>, -) -> Arc<WorkerEventCb> { - Arc::new(move |worker| { - let fut = async move { Ok(worker) }; - LocalFutureObj::new(Box::new(fut)) - }) -} - -fn create_web_worker_pre_execute_module_callback( - shared: Arc<SharedWorkerState>, -) -> Arc<WorkerEventCb> { - Arc::new(move |mut worker| { - let shared = shared.clone(); - let fut = async move { - // this will be up to date after pre-load - if shared.should_initialize_node_runtime() { - deno_node::initialize_runtime( - &mut worker.js_runtime, - shared.options.has_node_modules_dir, - None, - )?; - } - - Ok(worker) - }; - LocalFutureObj::new(Box::new(fut)) - }) -} - fn create_web_worker_callback( shared: Arc<SharedWorkerState>, stdio: deno_runtime::deno_io::Stdio, @@ -602,9 +546,6 @@ fn create_web_worker_callback( shared.module_loader_factory.create_source_map_getter(); let create_web_worker_cb = create_web_worker_callback(shared.clone(), stdio.clone()); - let preload_module_cb = create_web_worker_preload_module_callback(&shared); - let pre_execute_module_cb = - create_web_worker_pre_execute_module_callback(shared.clone()); let extensions = ops::cli_exts(shared.npm_resolver.clone()); @@ -636,6 +577,11 @@ fn create_web_worker_callback( unstable: shared.options.unstable, user_agent: version::get_user_agent().to_string(), inspect: shared.options.is_inspecting, + has_node_modules_dir: shared.options.has_node_modules_dir, + maybe_binary_npm_command_name: shared + .options + .maybe_binary_npm_command_name + .clone(), }, extensions, startup_snapshot: crate::js::deno_isolate_init(), @@ -646,8 +592,6 @@ fn create_web_worker_callback( root_cert_store_provider: Some(shared.root_cert_store_provider.clone()), seed: shared.options.seed, create_web_worker_cb, - preload_module_cb, - pre_execute_module_cb, format_js_error_fn: Some(Arc::new(format_js_error)), source_map_getter: maybe_source_map_getter, module_loader, diff --git a/ext/node/global.rs b/ext/node/global.rs index 78e009971..52c1b6bb9 100644 --- a/ext/node/global.rs +++ b/ext/node/global.rs @@ -1,5 +1,6 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use std::mem::MaybeUninit; use std::rc::Rc; use deno_core::v8; @@ -266,13 +267,14 @@ fn current_mode(scope: &mut v8::HandleScope) -> Mode { let Some(v8_string) = v8::StackTrace::current_script_name_or_source_url(scope) else { return Mode::Deno; }; - let string = v8_string.to_rust_string_lossy(scope); let op_state = deno_core::JsRuntime::op_state_from(scope); let op_state = op_state.borrow(); let Some(node_resolver) = op_state.try_borrow::<Rc<NodeResolver>>() else { return Mode::Deno; }; - if node_resolver.in_npm_package_with_cache(string) { + let mut buffer = [MaybeUninit::uninit(); 2048]; + let str = v8_string.to_rust_cow_lossy(scope, &mut buffer); + if node_resolver.in_npm_package_with_cache(str) { Mode::Node } else { Mode::Deno diff --git a/ext/node/lib.rs b/ext/node/lib.rs index e2643a84f..c7d617666 100644 --- a/ext/node/lib.rs +++ b/ext/node/lib.rs @@ -8,7 +8,6 @@ use std::rc::Rc; use deno_core::error::AnyError; use deno_core::located_script_name; use deno_core::op; -use deno_core::serde_json; use deno_core::serde_v8; use deno_core::url::Url; #[allow(unused_imports)] @@ -558,29 +557,6 @@ deno_core::extension!(deno_node, }, ); -pub fn initialize_runtime( - js_runtime: &mut JsRuntime, - uses_local_node_modules_dir: bool, - maybe_binary_command_name: Option<&str>, -) -> Result<(), AnyError> { - let argv0 = if let Some(binary_command_name) = maybe_binary_command_name { - serde_json::to_string(binary_command_name)? - } else { - "undefined".to_string() - }; - let source_code = format!( - r#"(function loadBuiltinNodeModules(usesLocalNodeModulesDir, argv0) {{ - Deno[Deno.internal].node.initialize( - usesLocalNodeModulesDir, - argv0 - ); - }})({uses_local_node_modules_dir}, {argv0});"#, - ); - - js_runtime.execute_script(located_script_name!(), source_code.into())?; - Ok(()) -} - pub fn load_cjs_module( js_runtime: &mut JsRuntime, module: &str, diff --git a/ext/node/polyfills/02_init.js b/ext/node/polyfills/02_init.js index d73d5d822..e3061c95d 100644 --- a/ext/node/polyfills/02_init.js +++ b/ext/node/polyfills/02_init.js @@ -7,6 +7,10 @@ const requireImpl = internals.requireImpl; import { nodeGlobals } from "ext:deno_node/00_globals.js"; import "node:module"; +globalThis.nodeBootstrap = function (usesLocalNodeModulesDir, argv0) { + initialize(usesLocalNodeModulesDir, argv0); +}; + let initialized = false; function initialize( diff --git a/ext/node/polyfills/process.ts b/ext/node/polyfills/process.ts index 4c375760d..c7c22b562 100644 --- a/ext/node/polyfills/process.ts +++ b/ext/node/polyfills/process.ts @@ -33,6 +33,8 @@ export { _nextTick as nextTick, chdir, cwd, env, version, versions }; import { createWritableStdioStream, initStdin, + Readable, + Writable, } from "ext:deno_node/_process/streams.mjs"; import { enableNextTick, @@ -52,15 +54,42 @@ export let platform = ""; // TODO(kt3k): This should be set at start up time export let pid = 0; -// TODO(kt3k): Give better types to stdio objects -// deno-lint-ignore no-explicit-any -let stderr = null as any; -// deno-lint-ignore no-explicit-any -let stdin = null as any; -// deno-lint-ignore no-explicit-any -let stdout = null as any; +// We want streams to be as lazy as possible, but we cannot export a getter in a module. To +// work around this we make these proxies that eagerly instantiate the underlying object on +// first access of any property/method. +function makeLazyStream<T>(objectFactory: () => T): T { + return new Proxy({}, { + get: function (_, prop, receiver) { + // deno-lint-ignore no-explicit-any + return Reflect.get(objectFactory() as any, prop, receiver); + }, + has: function (_, prop) { + // deno-lint-ignore no-explicit-any + return Reflect.has(objectFactory() as any, prop); + }, + ownKeys: function (_) { + // deno-lint-ignore no-explicit-any + return Reflect.ownKeys(objectFactory() as any); + }, + set: function (_, prop, value, receiver) { + // deno-lint-ignore no-explicit-any + return Reflect.set(objectFactory() as any, prop, value, receiver); + }, + getPrototypeOf: function (_) { + // deno-lint-ignore no-explicit-any + return Reflect.getPrototypeOf(objectFactory() as any); + }, + getOwnPropertyDescriptor(_, prop) { + // deno-lint-ignore no-explicit-any + return Reflect.getOwnPropertyDescriptor(objectFactory() as any, prop); + }, + }) as T; +} + +export let stderr = makeLazyStream(getStderr); +export let stdin = makeLazyStream(getStdin); +export let stdout = makeLazyStream(getStdout); -export { stderr, stdin, stdout }; import { getBinding } from "ext:deno_node/internal_binding/mod.ts"; import * as constants from "ext:deno_node/internal_binding/constants.ts"; import * as uv from "ext:deno_node/internal_binding/uv.ts"; @@ -605,13 +634,19 @@ class Process extends EventEmitter { memoryUsage = memoryUsage; /** https://nodejs.org/api/process.html#process_process_stderr */ - stderr = stderr; + get stderr(): Writable { + return getStderr(); + } /** https://nodejs.org/api/process.html#process_process_stdin */ - stdin = stdin; + get stdin(): Readable { + return getStdin(); + } /** https://nodejs.org/api/process.html#process_process_stdout */ - stdout = stdout; + get stdout(): Writable { + return getStdout(); + } /** https://nodejs.org/api/process.html#process_process_version */ version = version; @@ -704,6 +739,115 @@ addReadOnlyProcessAlias("throwDeprecation", "--throw-deprecation"); export const removeListener = process.removeListener; export const removeAllListeners = process.removeAllListeners; +let unhandledRejectionListenerCount = 0; +let uncaughtExceptionListenerCount = 0; +let beforeExitListenerCount = 0; +let exitListenerCount = 0; + +process.on("newListener", (event: string) => { + switch (event) { + case "unhandledRejection": + unhandledRejectionListenerCount++; + break; + case "uncaughtException": + uncaughtExceptionListenerCount++; + break; + case "beforeExit": + beforeExitListenerCount++; + break; + case "exit": + exitListenerCount++; + break; + default: + return; + } + synchronizeListeners(); +}); + +process.on("removeListener", (event: string) => { + switch (event) { + case "unhandledRejection": + unhandledRejectionListenerCount--; + break; + case "uncaughtException": + uncaughtExceptionListenerCount--; + break; + case "beforeExit": + beforeExitListenerCount--; + break; + case "exit": + exitListenerCount--; + break; + default: + return; + } + synchronizeListeners(); +}); + +function processOnError(event: ErrorEvent) { + if (process.listenerCount("uncaughtException") > 0) { + event.preventDefault(); + } + + uncaughtExceptionHandler(event.error, "uncaughtException"); +} + +function processOnBeforeUnload(event: Event) { + process.emit("beforeExit", process.exitCode || 0); + processTicksAndRejections(); + if (core.eventLoopHasMoreWork()) { + event.preventDefault(); + } +} + +function processOnUnload() { + if (!process._exiting) { + process._exiting = true; + process.emit("exit", process.exitCode || 0); + } +} + +function synchronizeListeners() { + // Install special "unhandledrejection" handler, that will be called + // last. + if ( + unhandledRejectionListenerCount > 0 || uncaughtExceptionListenerCount > 0 + ) { + internals.nodeProcessUnhandledRejectionCallback = (event) => { + if (process.listenerCount("unhandledRejection") === 0) { + // The Node.js default behavior is to raise an uncaught exception if + // an unhandled rejection occurs and there are no unhandledRejection + // listeners. + + event.preventDefault(); + uncaughtExceptionHandler(event.reason, "unhandledRejection"); + return; + } + + event.preventDefault(); + process.emit("unhandledRejection", event.reason, event.promise); + }; + } else { + internals.nodeProcessUnhandledRejectionCallback = undefined; + } + + if (uncaughtExceptionListenerCount > 0) { + globalThis.addEventListener("error", processOnError); + } else { + globalThis.removeEventListener("error", processOnError); + } + if (beforeExitListenerCount > 0) { + globalThis.addEventListener("beforeunload", processOnBeforeUnload); + } else { + globalThis.removeEventListener("beforeunload", processOnBeforeUnload); + } + if (exitListenerCount > 0) { + globalThis.addEventListener("unload", processOnUnload); + } else { + globalThis.removeEventListener("unload", processOnUnload); + } +} + // Should be called only once, in `runtime/js/99_main.js` when the runtime is // bootstrapped. internals.__bootstrapNodeProcess = function ( @@ -748,68 +892,52 @@ internals.__bootstrapNodeProcess = function ( core.setMacrotaskCallback(runNextTicks); enableNextTick(); - // Install special "unhandledrejection" handler, that will be called - // last. - internals.nodeProcessUnhandledRejectionCallback = (event) => { - if (process.listenerCount("unhandledRejection") === 0) { - // The Node.js default behavior is to raise an uncaught exception if - // an unhandled rejection occurs and there are no unhandledRejection - // listeners. - if (process.listenerCount("uncaughtException") === 0) { - throw event.reason; - } - - event.preventDefault(); - uncaughtExceptionHandler(event.reason, "unhandledRejection"); - return; - } - - event.preventDefault(); - process.emit("unhandledRejection", event.reason, event.promise); - }; - - globalThis.addEventListener("error", (event) => { - if (process.listenerCount("uncaughtException") > 0) { - event.preventDefault(); - } - - uncaughtExceptionHandler(event.error, "uncaughtException"); - }); - - globalThis.addEventListener("beforeunload", (e) => { - process.emit("beforeExit", process.exitCode || 0); - processTicksAndRejections(); - if (core.eventLoopHasMoreWork()) { - e.preventDefault(); - } - }); - - globalThis.addEventListener("unload", () => { - if (!process._exiting) { - process._exiting = true; - process.emit("exit", process.exitCode || 0); - } - }); - - // Initializes stdin - stdin = process.stdin = initStdin(); - - /** https://nodejs.org/api/process.html#process_process_stderr */ - stderr = process.stderr = createWritableStdioStream( - io.stderr, - "stderr", - ); - - /** https://nodejs.org/api/process.html#process_process_stdout */ - stdout = process.stdout = createWritableStdioStream( - io.stdout, - "stdout", - ); - process.setStartTime(Date.now()); // @ts-ignore Remove setStartTime and #startTime is not modifiable delete process.setStartTime; delete internals.__bootstrapNodeProcess; }; +// deno-lint-ignore no-explicit-any +let stderr_ = null as any; +// deno-lint-ignore no-explicit-any +let stdin_ = null as any; +// deno-lint-ignore no-explicit-any +let stdout_ = null as any; + +function getStdin(): Readable { + if (!stdin_) { + stdin_ = initStdin(); + stdin = stdin_; + Object.defineProperty(process, "stdin", { get: () => stdin_ }); + } + return stdin_; +} + +/** https://nodejs.org/api/process.html#process_process_stdout */ +function getStdout(): Writable { + if (!stdout_) { + stdout_ = createWritableStdioStream( + io.stdout, + "stdout", + ); + stdout = stdout_; + Object.defineProperty(process, "stdout", { get: () => stdout_ }); + } + return stdout_; +} + +/** https://nodejs.org/api/process.html#process_process_stderr */ +function getStderr(): Writable { + if (!stderr_) { + stderr_ = createWritableStdioStream( + io.stderr, + "stderr", + ); + stderr = stderr_; + Object.defineProperty(process, "stderr", { get: () => stderr_ }); + } + return stderr_; +} + export default process; diff --git a/ext/node/resolution.rs b/ext/node/resolution.rs index 4c43fcbad..20501b0f1 100644 --- a/ext/node/resolution.rs +++ b/ext/node/resolution.rs @@ -1,5 +1,6 @@ // Copyright 2018-2023 the Deno authors. All rights reserved. MIT license. +use std::borrow::Cow; use std::collections::HashMap; use std::path::Path; use std::path::PathBuf; @@ -128,10 +129,10 @@ impl NodeResolver { self.npm_resolver.in_npm_package(specifier) } - pub fn in_npm_package_with_cache(&self, specifier: String) -> bool { + pub fn in_npm_package_with_cache(&self, specifier: Cow<str>) -> bool { let mut cache = self.in_npm_package_cache.lock(); - if let Some(result) = cache.get(&specifier) { + if let Some(result) = cache.get(specifier.as_ref()) { return *result; } @@ -141,7 +142,7 @@ impl NodeResolver { } else { false }; - cache.insert(specifier, result); + cache.insert(specifier.into_owned(), result); result } diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index c8fdabc25..fdd82862c 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -438,6 +438,7 @@ function bootstrapMainRuntime(runtimeOptions) { if (hasBootstrapped) { throw new Error("Worker runtime already bootstrapped"); } + const nodeBootstrap = globalThis.nodeBootstrap; const { 0: args, @@ -456,6 +457,8 @@ function bootstrapMainRuntime(runtimeOptions) { 13: userAgent, 14: inspectFlag, // 15: enableTestingFeaturesFlag + 16: hasNodeModulesDir, + 17: maybeBinaryNpmCommandName, } = runtimeOptions; performance.setTimeOrigin(DateNow()); @@ -464,12 +467,13 @@ function bootstrapMainRuntime(runtimeOptions) { // Remove bootstrapping data from the global scope delete globalThis.__bootstrap; delete globalThis.bootstrap; + delete globalThis.nodeBootstrap; hasBootstrapped = true; // If the `--location` flag isn't set, make `globalThis.location` `undefined` and // writable, so that they can mock it themselves if they like. If the flag was // set, define `globalThis.location`, using the provided value. - if (location_ === undefined) { + if (location_ == null) { mainRuntimeGlobalProperties.location = { writable: true, }; @@ -542,6 +546,10 @@ function bootstrapMainRuntime(runtimeOptions) { ObjectDefineProperty(globalThis, "Deno", util.readOnly(finalDenoNs)); util.log("args", args); + + if (nodeBootstrap) { + nodeBootstrap(hasNodeModulesDir, maybeBinaryNpmCommandName); + } } function bootstrapWorkerRuntime( @@ -553,6 +561,8 @@ function bootstrapWorkerRuntime( throw new Error("Worker runtime already bootstrapped"); } + const nodeBootstrap = globalThis.nodeBootstrap; + const { 0: args, 1: cpuCount, @@ -570,6 +580,8 @@ function bootstrapWorkerRuntime( 13: userAgent, // 14: inspectFlag, 15: enableTestingFeaturesFlag, + 16: hasNodeModulesDir, + 17: maybeBinaryNpmCommandName, } = runtimeOptions; performance.setTimeOrigin(DateNow()); @@ -580,6 +592,7 @@ function bootstrapWorkerRuntime( // Remove bootstrapping data from the global scope delete globalThis.__bootstrap; delete globalThis.bootstrap; + delete globalThis.nodeBootstrap; hasBootstrapped = true; if (unstableFlag) { @@ -649,6 +662,10 @@ function bootstrapWorkerRuntime( // Setup `Deno` global - we're actually overriding already // existing global `Deno` with `Deno` namespace from "./deno.ts". ObjectDefineProperty(globalThis, "Deno", util.readOnly(finalDenoNs)); + + if (nodeBootstrap) { + nodeBootstrap(hasNodeModulesDir, maybeBinaryNpmCommandName); + } } globalThis.bootstrap = { diff --git a/runtime/ops/worker_host.rs b/runtime/ops/worker_host.rs index f96ae38e8..9bfbd9d10 100644 --- a/runtime/ops/worker_host.rs +++ b/runtime/ops/worker_host.rs @@ -13,7 +13,6 @@ use crate::web_worker::WorkerControlEvent; use crate::web_worker::WorkerId; use crate::worker::FormatJsErrorFn; use deno_core::error::AnyError; -use deno_core::futures::future::LocalFutureObj; use deno_core::op; use deno_core::serde::Deserialize; use deno_core::CancelFuture; @@ -40,10 +39,6 @@ pub type CreateWebWorkerCb = dyn Fn(CreateWebWorkerArgs) -> (WebWorker, Sendable + Sync + Send; -pub type WorkerEventCb = dyn Fn(WebWorker) -> LocalFutureObj<'static, Result<WebWorker, AnyError>> - + Sync - + Send; - /// A holder for callback that is used to create a new /// WebWorker. It's a struct instead of a type alias /// because `GothamState` used in `OpState` overrides @@ -54,12 +49,6 @@ struct CreateWebWorkerCbHolder(Arc<CreateWebWorkerCb>); #[derive(Clone)] struct FormatJsErrorFnHolder(Option<Arc<FormatJsErrorFn>>); -#[derive(Clone)] -struct PreloadModuleCbHolder(Arc<WorkerEventCb>); - -#[derive(Clone)] -struct PreExecuteModuleCbHolder(Arc<WorkerEventCb>); - pub struct WorkerThread { worker_handle: WebWorkerHandle, cancel_handle: Rc<CancelHandle>, @@ -98,8 +87,6 @@ deno_core::extension!( ], options = { create_web_worker_cb: Arc<CreateWebWorkerCb>, - preload_module_cb: Arc<WorkerEventCb>, - pre_execute_module_cb: Arc<WorkerEventCb>, format_js_error_fn: Option<Arc<FormatJsErrorFn>>, }, state = |state, options| { @@ -109,12 +96,6 @@ deno_core::extension!( let create_web_worker_cb_holder = CreateWebWorkerCbHolder(options.create_web_worker_cb); state.put::<CreateWebWorkerCbHolder>(create_web_worker_cb_holder); - let preload_module_cb_holder = - PreloadModuleCbHolder(options.preload_module_cb); - state.put::<PreloadModuleCbHolder>(preload_module_cb_holder); - let pre_execute_module_cb_holder = - PreExecuteModuleCbHolder(options.pre_execute_module_cb); - state.put::<PreExecuteModuleCbHolder>(pre_execute_module_cb_holder); let format_js_error_fn_holder = FormatJsErrorFnHolder(options.format_js_error_fn); state.put::<FormatJsErrorFnHolder>(format_js_error_fn_holder); @@ -174,10 +155,6 @@ fn op_create_worker( let worker_id = state.take::<WorkerId>(); let create_web_worker_cb = state.take::<CreateWebWorkerCbHolder>(); state.put::<CreateWebWorkerCbHolder>(create_web_worker_cb.clone()); - let preload_module_cb = state.take::<PreloadModuleCbHolder>(); - state.put::<PreloadModuleCbHolder>(preload_module_cb.clone()); - let pre_execute_module_cb = state.take::<PreExecuteModuleCbHolder>(); - state.put::<PreExecuteModuleCbHolder>(pre_execute_module_cb.clone()); let format_js_error_fn = state.take::<FormatJsErrorFnHolder>(); state.put::<FormatJsErrorFnHolder>(format_js_error_fn.clone()); state.put::<WorkerId>(worker_id.next().unwrap()); @@ -221,8 +198,6 @@ fn op_create_worker( worker, module_specifier, maybe_source_code, - preload_module_cb.0, - pre_execute_module_cb.0, format_js_error_fn.0, ) })?; diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs index a3b93836c..0c4e95140 100644 --- a/runtime/web_worker.rs +++ b/runtime/web_worker.rs @@ -338,8 +338,6 @@ pub struct WebWorkerOptions { pub module_loader: Rc<dyn ModuleLoader>, pub npm_resolver: Option<Arc<dyn deno_node::NpmResolver>>, pub create_web_worker_cb: Arc<ops::worker_host::CreateWebWorkerCb>, - pub preload_module_cb: Arc<ops::worker_host::WorkerEventCb>, - pub pre_execute_module_cb: Arc<ops::worker_host::WorkerEventCb>, pub format_js_error_fn: Option<Arc<FormatJsErrorFn>>, pub source_map_getter: Option<Box<dyn SourceMapGetter>>, pub worker_type: WebWorkerType, @@ -460,8 +458,6 @@ impl WebWorker { ops::runtime::deno_runtime::init_ops_and_esm(main_module.clone()), ops::worker_host::deno_worker_host::init_ops_and_esm( options.create_web_worker_cb.clone(), - options.preload_module_cb.clone(), - options.pre_execute_module_cb.clone(), options.format_js_error_fn.clone(), ), ops::fs_events::deno_fs_events::init_ops_and_esm(), @@ -600,7 +596,7 @@ impl WebWorker { .unwrap() .into(); bootstrap_fn - .call(scope, undefined.into(), &[args.into(), name_str, id_str]) + .call(scope, undefined.into(), &[args, name_str, id_str]) .unwrap(); } // TODO(bartlomieju): this could be done using V8 API, without calling `execute_script`. @@ -782,11 +778,9 @@ fn print_worker_error( /// This function should be called from a thread dedicated to this worker. // TODO(bartlomieju): check if order of actions is aligned to Worker spec pub fn run_web_worker( - worker: WebWorker, + mut worker: WebWorker, specifier: ModuleSpecifier, mut maybe_source_code: Option<String>, - preload_module_cb: Arc<ops::worker_host::WorkerEventCb>, - pre_execute_module_cb: Arc<ops::worker_host::WorkerEventCb>, format_js_error_fn: Option<Arc<FormatJsErrorFn>>, ) -> Result<(), AnyError> { let name = worker.name.to_string(); @@ -796,20 +790,6 @@ pub fn run_web_worker( let fut = async move { let internal_handle = worker.internal_handle.clone(); - let result = (preload_module_cb)(worker).await; - - let mut worker = match result { - Ok(worker) => worker, - Err(e) => { - print_worker_error(&e, &name, format_js_error_fn.as_deref()); - internal_handle - .post_event(WorkerControlEvent::TerminalError(e)) - .expect("Failed to post message to host"); - - // Failure to execute script is a terminal error, bye, bye. - return Ok(()); - } - }; // Execute provided source code immediately let result = if let Some(source_code) = maybe_source_code.take() { @@ -821,18 +801,6 @@ pub fn run_web_worker( // script instead of module match worker.preload_main_module(&specifier).await { Ok(id) => { - worker = match (pre_execute_module_cb)(worker).await { - Ok(worker) => worker, - Err(e) => { - print_worker_error(&e, &name, format_js_error_fn.as_deref()); - internal_handle - .post_event(WorkerControlEvent::TerminalError(e)) - .expect("Failed to post message to host"); - - // Failure to execute script is a terminal error, bye, bye. - return Ok(()); - } - }; worker.start_polling_for_messages(); worker.execute_main_module(id).await } diff --git a/runtime/worker.rs b/runtime/worker.rs index 060f5537b..5eefd5fa8 100644 --- a/runtime/worker.rs +++ b/runtime/worker.rs @@ -178,8 +178,6 @@ pub struct WorkerOptions { pub npm_resolver: Option<Arc<dyn deno_node::NpmResolver>>, // Callbacks invoked when creating new instance of WebWorker pub create_web_worker_cb: Arc<ops::worker_host::CreateWebWorkerCb>, - pub web_worker_preload_module_cb: Arc<ops::worker_host::WorkerEventCb>, - pub web_worker_pre_execute_module_cb: Arc<ops::worker_host::WorkerEventCb>, pub format_js_error_fn: Option<Arc<FormatJsErrorFn>>, /// Source map reference for errors. @@ -221,12 +219,6 @@ pub struct WorkerOptions { impl Default for WorkerOptions { fn default() -> Self { Self { - web_worker_preload_module_cb: Arc::new(|_| { - unimplemented!("web workers are not supported") - }), - web_worker_pre_execute_module_cb: Arc::new(|_| { - unimplemented!("web workers are not supported") - }), create_web_worker_cb: Arc::new(|_| { unimplemented!("web workers are not supported") }), @@ -362,8 +354,6 @@ impl MainWorker { ops::runtime::deno_runtime::init_ops_and_esm(main_module.clone()), ops::worker_host::deno_worker_host::init_ops_and_esm( options.create_web_worker_cb.clone(), - options.web_worker_preload_module_cb.clone(), - options.web_worker_pre_execute_module_cb.clone(), options.format_js_error_fn.clone(), ), ops::fs_events::deno_fs_events::init_ops_and_esm(), @@ -478,9 +468,7 @@ impl MainWorker { let bootstrap_fn = self.bootstrap_fn_global.take().unwrap(); let bootstrap_fn = v8::Local::new(scope, bootstrap_fn); let undefined = v8::undefined(scope); - bootstrap_fn - .call(scope, undefined.into(), &[args.into()]) - .unwrap(); + bootstrap_fn.call(scope, undefined.into(), &[args]).unwrap(); } /// See [JsRuntime::execute_script](deno_core::JsRuntime::execute_script) diff --git a/runtime/worker_bootstrap.rs b/runtime/worker_bootstrap.rs index 9627281a6..0f533344f 100644 --- a/runtime/worker_bootstrap.rs +++ b/runtime/worker_bootstrap.rs @@ -2,6 +2,8 @@ use deno_core::v8; use deno_core::ModuleSpecifier; +use serde::Serialize; +use std::cell::RefCell; use std::thread; use crate::colors; @@ -55,6 +57,8 @@ pub struct BootstrapOptions { pub unstable: bool, pub user_agent: String, pub inspect: bool, + pub has_node_modules_dir: bool, + pub maybe_binary_npm_command_name: Option<String>, } impl Default for BootstrapOptions { @@ -80,135 +84,91 @@ impl Default for BootstrapOptions { unstable: Default::default(), inspect: Default::default(), args: Default::default(), + has_node_modules_dir: Default::default(), + maybe_binary_npm_command_name: None, } } } +/// This is a struct that we use to serialize the contents of the `BootstrapOptions` +/// struct above to a V8 form. While `serde_v8` is not as fast as hand-coding this, +/// it's "fast enough" while serializing a large tuple like this that it doesn't appear +/// on flamegraphs. +/// +/// Note that a few fields in here are derived from the process and environment and +/// are not sourced from the underlying `BootstrapOptions`. +/// +/// Keep this in sync with `99_main.js`. +#[derive(Serialize)] +struct BootstrapV8<'a>( + // args + &'a Vec<String>, + // cpu_count + i32, + // log_level + i32, + // runtime_version + &'a str, + // locale + &'a str, + // location + Option<&'a str>, + // no_color + bool, + // is_tty + bool, + // ts_version + &'a str, + // unstable + bool, + // process_id + i32, + // env!("TARGET") + &'a str, + // v8_version + &'a str, + // user_agent + &'a str, + // inspect + bool, + // enable_testing_features + bool, + // has_node_modules_dir + bool, + // maybe_binary_npm_command_name + Option<&'a str>, +); + impl BootstrapOptions { + /// Return the v8 equivalent of this structure. pub fn as_v8<'s>( &self, scope: &mut v8::HandleScope<'s>, - ) -> v8::Local<'s, v8::Array> { - let array = v8::Array::new(scope, 16); - - { - let args = v8::Array::new(scope, self.args.len() as i32); - for (idx, arg) in self.args.iter().enumerate() { - let arg_str = v8::String::new(scope, arg).unwrap(); - args.set_index(scope, idx as u32, arg_str.into()); - } - array.set_index(scope, 0, args.into()); - } - - { - let val = v8::Integer::new(scope, self.cpu_count as i32); - array.set_index(scope, 1, val.into()); - } - - { - let val = v8::Integer::new(scope, self.log_level as i32); - array.set_index(scope, 2, val.into()); - } - - { - let val = v8::String::new_from_one_byte( - scope, - self.runtime_version.as_bytes(), - v8::NewStringType::Internalized, - ) - .unwrap(); - array.set_index(scope, 3, val.into()); - } - - { - let val = v8::String::new_from_one_byte( - scope, - self.locale.as_bytes(), - v8::NewStringType::Normal, - ) - .unwrap(); - array.set_index(scope, 4, val.into()); - } - - { - let val: v8::Local<v8::Value> = if let Some(location) = &self.location { - v8::String::new(scope, location.as_str()).unwrap().into() - } else { - v8::undefined(scope).into() - }; - - array.set_index(scope, 5, val); - } - - { - let val = v8::Boolean::new(scope, self.no_color); - array.set_index(scope, 6, val.into()); - } - - { - let val = v8::Boolean::new(scope, self.is_tty); - array.set_index(scope, 7, val.into()); - } - - { - let val = v8::String::new_from_one_byte( - scope, - self.ts_version.as_bytes(), - v8::NewStringType::Normal, - ) - .unwrap(); - array.set_index(scope, 8, val.into()); - } - - { - let val = v8::Boolean::new(scope, self.unstable); - array.set_index(scope, 9, val.into()); - } - - { - let val = v8::Integer::new(scope, std::process::id() as i32); - array.set_index(scope, 10, val.into()); - } - - { - let val = v8::String::new_external_onebyte_static( - scope, - env!("TARGET").as_bytes(), - ) - .unwrap(); - array.set_index(scope, 11, val.into()); - } - - { - let val = v8::String::new_from_one_byte( - scope, - deno_core::v8_version().as_bytes(), - v8::NewStringType::Normal, - ) - .unwrap(); - array.set_index(scope, 12, val.into()); - } - - { - let val = v8::String::new_from_one_byte( - scope, - self.user_agent.as_bytes(), - v8::NewStringType::Normal, - ) - .unwrap(); - array.set_index(scope, 13, val.into()); - } - - { - let val = v8::Boolean::new(scope, self.inspect); - array.set_index(scope, 14, val.into()); - } - - { - let val = v8::Boolean::new(scope, self.enable_testing_features); - array.set_index(scope, 15, val.into()); - } - - array + ) -> v8::Local<'s, v8::Value> { + let scope = RefCell::new(scope); + let ser = deno_core::serde_v8::Serializer::new(&scope); + + let bootstrap = BootstrapV8( + &self.args, + self.cpu_count as _, + self.log_level as _, + &self.runtime_version, + &self.locale, + self.location.as_ref().map(|l| l.as_str()), + self.no_color, + self.is_tty, + &self.ts_version, + self.unstable, + std::process::id() as _, + env!("TARGET"), + deno_core::v8_version(), + &self.user_agent, + self.inspect, + self.enable_testing_features, + self.has_node_modules_dir, + self.maybe_binary_npm_command_name.as_deref(), + ); + + bootstrap.serialize(ser).unwrap() } } |