summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatt Mastracci <matthew@mastracci.com>2023-08-15 13:36:36 -0600
committerGitHub <noreply@github.com>2023-08-16 04:36:36 +0900
commit4380a09a0598c73aa434e2f0f3a34555e0bd55cb (patch)
tree671bba82325653ed188efb49e099c4d61ec318cc
parent41cad2179fb36c2371ab84ce587d3460af64b5fb (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.rs10
-rw-r--r--cli/npm/resolvers/mod.rs5
-rw-r--r--cli/standalone/mod.rs10
-rw-r--r--cli/tools/repl/session.rs14
-rw-r--r--cli/worker.rs78
-rw-r--r--ext/node/global.rs6
-rw-r--r--ext/node/lib.rs24
-rw-r--r--ext/node/polyfills/02_init.js4
-rw-r--r--ext/node/polyfills/process.ts266
-rw-r--r--ext/node/resolution.rs7
-rw-r--r--runtime/js/99_main.js19
-rw-r--r--runtime/ops/worker_host.rs25
-rw-r--r--runtime/web_worker.rs36
-rw-r--r--runtime/worker.rs14
-rw-r--r--runtime/worker_bootstrap.rs204
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()
}
}