summaryrefslogtreecommitdiff
path: root/runtime
diff options
context:
space:
mode:
authorNathan Whitaker <17734409+nathanwhit@users.noreply.github.com>2024-09-27 12:35:37 -0700
committerGitHub <noreply@github.com>2024-09-27 12:35:37 -0700
commitfbddd5a2ebfb11dd376a751e9fc4cf09a6286ada (patch)
tree75c13ee9f26f61fe8c1d6f80df2580a523177c1b /runtime
parenta8d1ab52761516b7f9b6069d6e433254794ed48c (diff)
fix(node): Pass NPM_PROCESS_STATE to subprocesses via temp file instead of env var (#25896)
Fixes https://github.com/denoland/deno/issues/25401. Fixes https://github.com/denoland/deno/issues/25841. Fixes https://github.com/denoland/deno/issues/25891.
Diffstat (limited to 'runtime')
-rw-r--r--runtime/Cargo.toml1
-rw-r--r--runtime/js/40_process.js4
-rw-r--r--runtime/js/99_main.js1
-rw-r--r--runtime/ops/process.rs106
-rw-r--r--runtime/snapshot.rs2
-rw-r--r--runtime/web_worker.rs6
-rw-r--r--runtime/worker.rs7
7 files changed, 105 insertions, 22 deletions
diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml
index 6d24781cf..ea2978c49 100644
--- a/runtime/Cargo.toml
+++ b/runtime/Cargo.toml
@@ -118,6 +118,7 @@ rustyline = { workspace = true, features = ["custom-bindings"] }
serde.workspace = true
signal-hook = "0.3.17"
signal-hook-registry = "1.4.0"
+tempfile.workspace = true
tokio.workspace = true
tokio-metrics.workspace = true
twox-hash.workspace = true
diff --git a/runtime/js/40_process.js b/runtime/js/40_process.js
index 2592c5c46..e2cb1d95b 100644
--- a/runtime/js/40_process.js
+++ b/runtime/js/40_process.js
@@ -160,6 +160,7 @@ function run({
export const kExtraStdio = Symbol("extraStdio");
export const kIpc = Symbol("ipc");
export const kDetached = Symbol("detached");
+export const kNeedsNpmProcessState = Symbol("needsNpmProcessState");
const illegalConstructorKey = Symbol("illegalConstructorKey");
@@ -178,6 +179,7 @@ function spawnChildInner(command, apiName, {
[kDetached]: detached = false,
[kExtraStdio]: extraStdio = [],
[kIpc]: ipc = -1,
+ [kNeedsNpmProcessState]: needsNpmProcessState = false,
} = { __proto__: null }) {
const child = op_spawn_child({
cmd: pathFromURL(command),
@@ -194,6 +196,7 @@ function spawnChildInner(command, apiName, {
ipc,
extraStdio,
detached,
+ needsNpmProcessState,
}, apiName);
return new ChildProcess(illegalConstructorKey, {
...child,
@@ -421,6 +424,7 @@ function spawnSync(command, {
windowsRawArguments,
extraStdio: [],
detached: false,
+ needsNpmProcessState: false,
});
return {
success: result.status.success,
diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js
index 9134ac48a..0da2072b8 100644
--- a/runtime/js/99_main.js
+++ b/runtime/js/99_main.js
@@ -487,7 +487,6 @@ const NOT_IMPORTED_OPS = [
// to not depend on them.
"op_set_exit_code",
"op_napi_open",
- "op_npm_process_state",
];
function removeImportedOps() {
diff --git a/runtime/ops/process.rs b/runtime/ops/process.rs
index a39bb5f04..530dcf49b 100644
--- a/runtime/ops/process.rs
+++ b/runtime/ops/process.rs
@@ -16,6 +16,7 @@ use deno_io::fs::FileResource;
use deno_io::ChildStderrResource;
use deno_io::ChildStdinResource;
use deno_io::ChildStdoutResource;
+use deno_io::IntoRawIoHandle;
use deno_permissions::PermissionsContainer;
use deno_permissions::RunQueryDescriptor;
use serde::Deserialize;
@@ -24,6 +25,7 @@ use std::borrow::Cow;
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::OsString;
+use std::io::Write;
use std::path::Path;
use std::path::PathBuf;
use std::process::ExitStatus;
@@ -117,6 +119,27 @@ impl StdioOrRid {
}
}
+#[allow(clippy::disallowed_types)]
+pub type NpmProcessStateProviderRc =
+ deno_fs::sync::MaybeArc<dyn NpmProcessStateProvider>;
+
+pub trait NpmProcessStateProvider:
+ std::fmt::Debug + deno_fs::sync::MaybeSend + deno_fs::sync::MaybeSync
+{
+ /// Gets a string containing the serialized npm state of the process.
+ ///
+ /// This will be set on the `DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE` environment
+ /// variable when doing a `child_process.fork`. The implementor can then check this environment
+ /// variable on startup to repopulate the internal npm state.
+ fn get_npm_process_state(&self) -> String {
+ // This method is only used in the CLI.
+ String::new()
+ }
+}
+
+#[derive(Debug)]
+pub struct EmptyNpmProcessStateProvider;
+impl NpmProcessStateProvider for EmptyNpmProcessStateProvider {}
deno_core::extension!(
deno_process,
ops = [
@@ -128,6 +151,10 @@ deno_core::extension!(
deprecated::op_run_status,
deprecated::op_kill,
],
+ options = { get_npm_process_state: Option<NpmProcessStateProviderRc> },
+ state = |state, options| {
+ state.put::<NpmProcessStateProviderRc>(options.get_npm_process_state.unwrap_or(deno_fs::sync::MaybeArc::new(EmptyNpmProcessStateProvider)));
+ },
);
/// Second member stores the pid separately from the RefCell. It's needed for
@@ -161,6 +188,7 @@ pub struct SpawnArgs {
extra_stdio: Vec<Stdio>,
detached: bool,
+ needs_npm_process_state: bool,
}
#[derive(Deserialize)]
@@ -229,11 +257,64 @@ type CreateCommand = (
Vec<deno_io::RawBiPipeHandle>,
);
+pub fn npm_process_state_tempfile(
+ contents: &[u8],
+) -> Result<deno_io::RawIoHandle, AnyError> {
+ let mut temp_file = tempfile::tempfile()?;
+ temp_file.write_all(contents)?;
+ let handle = temp_file.into_raw_io_handle();
+ #[cfg(windows)]
+ {
+ use windows_sys::Win32::Foundation::HANDLE_FLAG_INHERIT;
+ // make the handle inheritable
+ // SAFETY: winapi call, handle is valid
+ unsafe {
+ windows_sys::Win32::Foundation::SetHandleInformation(
+ handle as _,
+ HANDLE_FLAG_INHERIT,
+ HANDLE_FLAG_INHERIT,
+ );
+ }
+ Ok(handle)
+ }
+ #[cfg(unix)]
+ {
+ // SAFETY: libc call, fd is valid
+ let inheritable = unsafe {
+ // duplicate the FD to get a new one that doesn't have the CLOEXEC flag set
+ // so it can be inherited by the child process
+ libc::dup(handle)
+ };
+ // SAFETY: libc call, fd is valid
+ unsafe {
+ // close the old one
+ libc::close(handle);
+ }
+ Ok(inheritable)
+ }
+}
+
+pub const NPM_RESOLUTION_STATE_FD_ENV_VAR_NAME: &str =
+ "DENO_DONT_USE_INTERNAL_NODE_COMPAT_STATE_FD";
+
fn create_command(
state: &mut OpState,
mut args: SpawnArgs,
api_name: &str,
) -> Result<CreateCommand, AnyError> {
+ let maybe_npm_process_state = if args.needs_npm_process_state {
+ let provider = state.borrow::<NpmProcessStateProviderRc>();
+ let process_state = provider.get_npm_process_state();
+ let fd = npm_process_state_tempfile(process_state.as_bytes())?;
+ args.env.push((
+ NPM_RESOLUTION_STATE_FD_ENV_VAR_NAME.to_string(),
+ (fd as usize).to_string(),
+ ));
+ Some(fd)
+ } else {
+ None
+ };
+
let (cmd, run_env) = compute_run_cmd_and_check_permissions(
&args.cmd,
args.cwd.as_deref(),
@@ -301,6 +382,9 @@ fn create_command(
let mut fds_to_dup = Vec::new();
let mut fds_to_close = Vec::new();
let mut ipc_rid = None;
+ if let Some(fd) = maybe_npm_process_state {
+ fds_to_close.push(fd);
+ }
if let Some(ipc) = args.ipc {
if ipc >= 0 {
let (ipc_fd1, ipc_fd2) = deno_io::bi_pipe_pair_raw()?;
@@ -369,6 +453,9 @@ fn create_command(
{
let mut ipc_rid = None;
let mut handles_to_close = Vec::with_capacity(1);
+ if let Some(handle) = maybe_npm_process_state {
+ handles_to_close.push(handle);
+ }
if let Some(ipc) = args.ipc {
if ipc >= 0 {
let (hd1, hd2) = deno_io::bi_pipe_pair_raw()?;
@@ -506,23 +593,6 @@ fn spawn_child(
})
}
-fn close_raw_handle(handle: deno_io::RawBiPipeHandle) {
- #[cfg(unix)]
- {
- // SAFETY: libc call
- unsafe {
- libc::close(handle);
- }
- }
- #[cfg(windows)]
- {
- // SAFETY: win32 call
- unsafe {
- windows_sys::Win32::Foundation::CloseHandle(handle as _);
- }
- }
-}
-
fn compute_run_cmd_and_check_permissions(
arg_cmd: &str,
arg_cwd: Option<&str>,
@@ -690,7 +760,7 @@ fn op_spawn_child(
create_command(state, args, &api_name)?;
let child = spawn_child(state, command, pipe_rid, extra_pipe_rids, detached);
for handle in handles_to_close {
- close_raw_handle(handle);
+ deno_io::close_raw_handle(handle);
}
child
}
diff --git a/runtime/snapshot.rs b/runtime/snapshot.rs
index db6688b46..f20c04f08 100644
--- a/runtime/snapshot.rs
+++ b/runtime/snapshot.rs
@@ -302,7 +302,7 @@ pub fn create_runtime_snapshot(
ops::permissions::deno_permissions::init_ops(Arc::new(
RuntimePermissionDescriptorParser::new(fs),
)),
- ops::process::deno_process::init_ops(),
+ ops::process::deno_process::init_ops(None),
ops::signal::deno_signal::init_ops(),
ops::tty::deno_tty::init_ops(),
ops::http::deno_http_runtime::init_ops(),
diff --git a/runtime/web_worker.rs b/runtime/web_worker.rs
index 7b69a9798..8892d5bc6 100644
--- a/runtime/web_worker.rs
+++ b/runtime/web_worker.rs
@@ -1,6 +1,7 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use crate::inspector_server::InspectorServer;
use crate::ops;
+use crate::ops::process::NpmProcessStateProviderRc;
use crate::ops::worker_host::WorkersTable;
use crate::shared::maybe_transpile_source;
use crate::shared::runtime;
@@ -383,6 +384,7 @@ pub struct WebWorkerOptions {
pub strace_ops: Option<Vec<String>>,
pub close_on_idle: bool,
pub maybe_worker_metadata: Option<WorkerMetadata>,
+ pub npm_process_state_provider: Option<NpmProcessStateProviderRc>,
}
impl WebWorker {
@@ -507,7 +509,9 @@ impl WebWorker {
ops::permissions::deno_permissions::init_ops_and_esm(
options.permission_desc_parser.clone(),
),
- ops::process::deno_process::init_ops_and_esm(),
+ ops::process::deno_process::init_ops_and_esm(
+ options.npm_process_state_provider,
+ ),
ops::signal::deno_signal::init_ops_and_esm(),
ops::tty::deno_tty::init_ops_and_esm(),
ops::http::deno_http_runtime::init_ops_and_esm(),
diff --git a/runtime/worker.rs b/runtime/worker.rs
index 3d8c8a0b9..f72e6d7c0 100644
--- a/runtime/worker.rs
+++ b/runtime/worker.rs
@@ -49,6 +49,7 @@ use crate::code_cache::CodeCache;
use crate::code_cache::CodeCacheType;
use crate::inspector_server::InspectorServer;
use crate::ops;
+use crate::ops::process::NpmProcessStateProviderRc;
use crate::permissions::RuntimePermissionDescriptorParser;
use crate::shared::maybe_transpile_source;
use crate::shared::runtime;
@@ -158,6 +159,7 @@ pub struct WorkerOptions {
/// executed tries to load modules.
pub module_loader: Rc<dyn ModuleLoader>,
pub node_services: Option<NodeExtInitServices>,
+ pub npm_process_state_provider: Option<NpmProcessStateProviderRc>,
pub permission_desc_parser:
Arc<dyn deno_permissions::PermissionDescriptorParser>,
// Callbacks invoked when creating new instance of WebWorker
@@ -235,6 +237,7 @@ impl Default for WorkerOptions {
extensions: Default::default(),
startup_snapshot: Default::default(),
create_params: Default::default(),
+ npm_process_state_provider: Default::default(),
bootstrap: Default::default(),
stdio: Default::default(),
feature_checker: Default::default(),
@@ -437,7 +440,9 @@ impl MainWorker {
ops::permissions::deno_permissions::init_ops_and_esm(
options.permission_desc_parser,
),
- ops::process::deno_process::init_ops_and_esm(),
+ ops::process::deno_process::init_ops_and_esm(
+ options.npm_process_state_provider,
+ ),
ops::signal::deno_signal::init_ops_and_esm(),
ops::tty::deno_tty::init_ops_and_esm(),
ops::http::deno_http_runtime::init_ops_and_esm(),