diff options
Diffstat (limited to 'runtime')
-rw-r--r-- | runtime/Cargo.toml | 1 | ||||
-rw-r--r-- | runtime/js/40_process.js | 4 | ||||
-rw-r--r-- | runtime/js/99_main.js | 1 | ||||
-rw-r--r-- | runtime/ops/process.rs | 106 | ||||
-rw-r--r-- | runtime/snapshot.rs | 2 | ||||
-rw-r--r-- | runtime/web_worker.rs | 6 | ||||
-rw-r--r-- | runtime/worker.rs | 7 |
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(), |