diff options
Diffstat (limited to 'runtime/ops')
-rw-r--r-- | runtime/ops/fs_events.rs | 121 | ||||
-rw-r--r-- | runtime/ops/http.rs | 43 | ||||
-rw-r--r-- | runtime/ops/mod.rs | 2 | ||||
-rw-r--r-- | runtime/ops/os/mod.rs | 99 | ||||
-rw-r--r-- | runtime/ops/os/sys_info.rs | 425 | ||||
-rw-r--r-- | runtime/ops/otel.rs | 855 | ||||
-rw-r--r-- | runtime/ops/permissions.rs | 43 | ||||
-rw-r--r-- | runtime/ops/process.rs | 171 | ||||
-rw-r--r-- | runtime/ops/runtime.rs | 6 | ||||
-rw-r--r-- | runtime/ops/signal.rs | 663 | ||||
-rw-r--r-- | runtime/ops/tty.rs | 54 | ||||
-rw-r--r-- | runtime/ops/utils.rs | 12 | ||||
-rw-r--r-- | runtime/ops/web_worker.rs | 10 | ||||
-rw-r--r-- | runtime/ops/web_worker/sync_fetch.rs | 95 | ||||
-rw-r--r-- | runtime/ops/worker_host.rs | 57 |
15 files changed, 1549 insertions, 1107 deletions
diff --git a/runtime/ops/fs_events.rs b/runtime/ops/fs_events.rs index d88a32d91..c8e0228bc 100644 --- a/runtime/ops/fs_events.rs +++ b/runtime/ops/fs_events.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::AnyError; use deno_core::parking_lot::Mutex; use deno_core::AsyncRefCell; use deno_core::CancelFuture; @@ -20,13 +19,14 @@ use notify::EventKind; use notify::RecommendedWatcher; use notify::RecursiveMode; use notify::Watcher; -use serde::Deserialize; use serde::Serialize; use std::borrow::Cow; use std::cell::RefCell; use std::convert::From; +use std::path::Path; use std::path::PathBuf; use std::rc::Rc; +use std::sync::Arc; use tokio::sync::mpsc; deno_core::extension!( @@ -35,9 +35,7 @@ deno_core::extension!( ); struct FsEventsResource { - #[allow(unused)] - watcher: RecommendedWatcher, - receiver: AsyncRefCell<mpsc::Receiver<Result<FsEvent, AnyError>>>, + receiver: AsyncRefCell<mpsc::Receiver<Result<FsEvent, NotifyError>>>, cancel: CancelHandle, } @@ -59,7 +57,7 @@ impl Resource for FsEventsResource { /// /// Feel free to expand this struct as long as you can add tests to demonstrate /// the complexity. -#[derive(Serialize, Debug)] +#[derive(Serialize, Debug, Clone)] struct FsEvent { kind: &'static str, paths: Vec<PathBuf>, @@ -93,43 +91,102 @@ impl From<NotifyEvent> for FsEvent { } } -#[derive(Deserialize)] -pub struct OpenArgs { - recursive: bool, - paths: Vec<String>, +type WatchSender = (Vec<String>, mpsc::Sender<Result<FsEvent, NotifyError>>); + +struct WatcherState { + senders: Arc<Mutex<Vec<WatchSender>>>, + watcher: RecommendedWatcher, } -#[op2] -#[smi] -fn op_fs_events_open( +fn starts_with_canonicalized(path: &Path, prefix: &str) -> bool { + #[allow(clippy::disallowed_methods)] + let path = path.canonicalize().ok(); + #[allow(clippy::disallowed_methods)] + let prefix = std::fs::canonicalize(prefix).ok(); + match (path, prefix) { + (Some(path), Some(prefix)) => path.starts_with(prefix), + _ => false, + } +} + +#[derive(Debug, thiserror::Error)] +pub enum FsEventsError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error(transparent)] + Permission(#[from] deno_permissions::PermissionCheckError), + #[error(transparent)] + Notify(#[from] NotifyError), + #[error(transparent)] + Canceled(#[from] deno_core::Canceled), +} + +fn start_watcher( state: &mut OpState, - #[serde] args: OpenArgs, -) -> Result<ResourceId, AnyError> { - let (sender, receiver) = mpsc::channel::<Result<FsEvent, AnyError>>(16); - let sender = Mutex::new(sender); - let mut watcher: RecommendedWatcher = Watcher::new( + paths: Vec<String>, + sender: mpsc::Sender<Result<FsEvent, NotifyError>>, +) -> Result<(), FsEventsError> { + if let Some(watcher) = state.try_borrow_mut::<WatcherState>() { + watcher.senders.lock().push((paths, sender)); + return Ok(()); + } + + let senders = Arc::new(Mutex::new(vec![(paths, sender)])); + + let sender_clone = senders.clone(); + let watcher: RecommendedWatcher = Watcher::new( move |res: Result<NotifyEvent, NotifyError>| { - let res2 = res.map(FsEvent::from).map_err(AnyError::from); - let sender = sender.lock(); - // Ignore result, if send failed it means that watcher was already closed, - // but not all messages have been flushed. - let _ = sender.try_send(res2); + let res2 = res.map(FsEvent::from).map_err(FsEventsError::Notify); + for (paths, sender) in sender_clone.lock().iter() { + // Ignore result, if send failed it means that watcher was already closed, + // but not all messages have been flushed. + + // Only send the event if the path matches one of the paths that the user is watching + if let Ok(event) = &res2 { + if paths.iter().any(|path| { + event.paths.iter().any(|event_path| { + same_file::is_same_file(event_path, path).unwrap_or(false) + || starts_with_canonicalized(event_path, path) + }) + }) { + let _ = sender.try_send(Ok(event.clone())); + } + } + } }, Default::default(), )?; - let recursive_mode = if args.recursive { + + state.put::<WatcherState>(WatcherState { watcher, senders }); + + Ok(()) +} + +#[op2] +#[smi] +fn op_fs_events_open( + state: &mut OpState, + recursive: bool, + #[serde] paths: Vec<String>, +) -> Result<ResourceId, FsEventsError> { + let (sender, receiver) = mpsc::channel::<Result<FsEvent, NotifyError>>(16); + + start_watcher(state, paths.clone(), sender)?; + + let recursive_mode = if recursive { RecursiveMode::Recursive } else { RecursiveMode::NonRecursive }; - for path in &args.paths { + for path in &paths { let path = state .borrow_mut::<PermissionsContainer>() .check_read(path, "Deno.watchFs()")?; - watcher.watch(&path, recursive_mode)?; + + let watcher = state.borrow_mut::<WatcherState>(); + watcher.watcher.watch(&path, recursive_mode)?; } let resource = FsEventsResource { - watcher, receiver: AsyncRefCell::new(receiver), cancel: Default::default(), }; @@ -142,14 +199,18 @@ fn op_fs_events_open( async fn op_fs_events_poll( state: Rc<RefCell<OpState>>, #[smi] rid: ResourceId, -) -> Result<Option<FsEvent>, AnyError> { - let resource = state.borrow().resource_table.get::<FsEventsResource>(rid)?; +) -> Result<Option<FsEvent>, FsEventsError> { + let resource = state + .borrow() + .resource_table + .get::<FsEventsResource>(rid) + .map_err(FsEventsError::Resource)?; let mut receiver = RcRef::map(&resource, |r| &r.receiver).borrow_mut().await; let cancel = RcRef::map(resource, |r| &r.cancel); let maybe_result = receiver.recv().or_cancel(cancel).await?; match maybe_result { Some(Ok(value)) => Ok(Some(value)), - Some(Err(err)) => Err(err), + Some(Err(err)) => Err(FsEventsError::Notify(err)), None => Ok(None), } } diff --git a/runtime/ops/http.rs b/runtime/ops/http.rs index cec8b0ef8..6e3157668 100644 --- a/runtime/ops/http.rs +++ b/runtime/ops/http.rs @@ -2,9 +2,6 @@ use std::rc::Rc; -use deno_core::error::bad_resource_id; -use deno_core::error::custom_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use deno_core::ResourceId; @@ -16,12 +13,31 @@ pub const UNSTABLE_FEATURE_NAME: &str = "http"; deno_core::extension!(deno_http_runtime, ops = [op_http_start],); +#[derive(Debug, thiserror::Error)] +pub enum HttpStartError { + #[error("TCP stream is currently in use")] + TcpStreamInUse, + #[error("TLS stream is currently in use")] + TlsStreamInUse, + #[error("Unix socket is currently in use")] + UnixSocketInUse, + #[error(transparent)] + ReuniteTcp(#[from] tokio::net::tcp::ReuniteError), + #[cfg(unix)] + #[error(transparent)] + ReuniteUnix(#[from] tokio::net::unix::ReuniteError), + #[error("{0}")] + Io(#[from] std::io::Error), + #[error(transparent)] + Other(deno_core::error::AnyError), +} + #[op2(fast)] #[smi] fn op_http_start( state: &mut OpState, #[smi] tcp_stream_rid: ResourceId, -) -> Result<ResourceId, AnyError> { +) -> Result<ResourceId, HttpStartError> { if let Ok(resource_rc) = state .resource_table .take::<TcpStreamResource>(tcp_stream_rid) @@ -30,11 +46,11 @@ fn op_http_start( // process of starting a HTTP server on top of this TCP connection, so we just return a Busy error. // See also: https://github.com/denoland/deno/pull/16242 let resource = Rc::try_unwrap(resource_rc) - .map_err(|_| custom_error("Busy", "TCP stream is currently in use"))?; + .map_err(|_| HttpStartError::TcpStreamInUse)?; let (read_half, write_half) = resource.into_inner(); let tcp_stream = read_half.reunite(write_half)?; let addr = tcp_stream.local_addr()?; - return http_create_conn_resource(state, tcp_stream, addr, "http"); + return Ok(http_create_conn_resource(state, tcp_stream, addr, "http")); } if let Ok(resource_rc) = state @@ -45,11 +61,11 @@ fn op_http_start( // process of starting a HTTP server on top of this TLS connection, so we just return a Busy error. // See also: https://github.com/denoland/deno/pull/16242 let resource = Rc::try_unwrap(resource_rc) - .map_err(|_| custom_error("Busy", "TLS stream is currently in use"))?; + .map_err(|_| HttpStartError::TlsStreamInUse)?; let (read_half, write_half) = resource.into_inner(); let tls_stream = read_half.unsplit(write_half); let addr = tls_stream.local_addr()?; - return http_create_conn_resource(state, tls_stream, addr, "https"); + return Ok(http_create_conn_resource(state, tls_stream, addr, "https")); } #[cfg(unix)] @@ -61,12 +77,17 @@ fn op_http_start( // process of starting a HTTP server on top of this UNIX socket, so we just return a Busy error. // See also: https://github.com/denoland/deno/pull/16242 let resource = Rc::try_unwrap(resource_rc) - .map_err(|_| custom_error("Busy", "Unix socket is currently in use"))?; + .map_err(|_| HttpStartError::UnixSocketInUse)?; let (read_half, write_half) = resource.into_inner(); let unix_stream = read_half.reunite(write_half)?; let addr = unix_stream.local_addr()?; - return http_create_conn_resource(state, unix_stream, addr, "http+unix"); + return Ok(http_create_conn_resource( + state, + unix_stream, + addr, + "http+unix", + )); } - Err(bad_resource_id()) + Err(HttpStartError::Other(deno_core::error::bad_resource_id())) } diff --git a/runtime/ops/mod.rs b/runtime/ops/mod.rs index feed5052b..c2e402f33 100644 --- a/runtime/ops/mod.rs +++ b/runtime/ops/mod.rs @@ -4,12 +4,12 @@ pub mod bootstrap; pub mod fs_events; pub mod http; pub mod os; +pub mod otel; pub mod permissions; pub mod process; pub mod runtime; pub mod signal; pub mod tty; -mod utils; pub mod web_worker; pub mod worker_host; diff --git a/runtime/ops/os/mod.rs b/runtime/ops/os/mod.rs index bd9260e97..74c708c53 100644 --- a/runtime/ops/os/mod.rs +++ b/runtime/ops/os/mod.rs @@ -1,9 +1,7 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use super::utils::into_string; +use crate::sys_info; use crate::worker::ExitCode; -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::v8; use deno_core::OpState; @@ -14,8 +12,6 @@ use serde::Serialize; use std::collections::HashMap; use std::env; -mod sys_info; - deno_core::extension!( deno_os, ops = [ @@ -73,9 +69,27 @@ deno_core::extension!( }, ); +#[derive(Debug, thiserror::Error)] +pub enum OsError { + #[error(transparent)] + Permission(#[from] deno_permissions::PermissionCheckError), + #[error("File name or path {0:?} is not valid UTF-8")] + InvalidUtf8(std::ffi::OsString), + #[error("Key is an empty string.")] + EnvEmptyKey, + #[error("Key contains invalid characters: {0:?}")] + EnvInvalidKey(String), + #[error("Value contains invalid characters: {0:?}")] + EnvInvalidValue(String), + #[error(transparent)] + Var(#[from] env::VarError), + #[error("{0}")] + Io(#[from] std::io::Error), +} + #[op2] #[string] -fn op_exec_path(state: &mut OpState) -> Result<String, AnyError> { +fn op_exec_path(state: &mut OpState) -> Result<String, OsError> { let current_exe = env::current_exe().unwrap(); state .borrow_mut::<PermissionsContainer>() @@ -83,7 +97,10 @@ fn op_exec_path(state: &mut OpState) -> Result<String, AnyError> { // normalize path so it doesn't include '.' or '..' components let path = normalize_path(current_exe); - into_string(path.into_os_string()) + path + .into_os_string() + .into_string() + .map_err(OsError::InvalidUtf8) } #[op2(fast)] @@ -91,20 +108,16 @@ fn op_set_env( state: &mut OpState, #[string] key: &str, #[string] value: &str, -) -> Result<(), AnyError> { +) -> Result<(), OsError> { state.borrow_mut::<PermissionsContainer>().check_env(key)?; if key.is_empty() { - return Err(type_error("Key is an empty string.")); + return Err(OsError::EnvEmptyKey); } if key.contains(&['=', '\0'] as &[char]) { - return Err(type_error(format!( - "Key contains invalid characters: {key:?}" - ))); + return Err(OsError::EnvInvalidKey(key.to_string())); } if value.contains('\0') { - return Err(type_error(format!( - "Value contains invalid characters: {value:?}" - ))); + return Err(OsError::EnvInvalidValue(value.to_string())); } env::set_var(key, value); Ok(()) @@ -112,7 +125,9 @@ fn op_set_env( #[op2] #[serde] -fn op_env(state: &mut OpState) -> Result<HashMap<String, String>, AnyError> { +fn op_env( + state: &mut OpState, +) -> Result<HashMap<String, String>, deno_core::error::AnyError> { state.borrow_mut::<PermissionsContainer>().check_env_all()?; Ok(env::vars().collect()) } @@ -122,7 +137,7 @@ fn op_env(state: &mut OpState) -> Result<HashMap<String, String>, AnyError> { fn op_get_env( state: &mut OpState, #[string] key: String, -) -> Result<Option<String>, AnyError> { +) -> Result<Option<String>, OsError> { let skip_permission_check = NODE_ENV_VAR_ALLOWLIST.contains(&key); if !skip_permission_check { @@ -130,13 +145,11 @@ fn op_get_env( } if key.is_empty() { - return Err(type_error("Key is an empty string.")); + return Err(OsError::EnvEmptyKey); } if key.contains(&['=', '\0'] as &[char]) { - return Err(type_error(format!( - "Key contains invalid characters: {key:?}" - ))); + return Err(OsError::EnvInvalidKey(key.to_string())); } let r = match env::var(key) { @@ -150,10 +163,10 @@ fn op_get_env( fn op_delete_env( state: &mut OpState, #[string] key: String, -) -> Result<(), AnyError> { +) -> Result<(), OsError> { state.borrow_mut::<PermissionsContainer>().check_env(&key)?; if key.is_empty() || key.contains(&['=', '\0'] as &[char]) { - return Err(type_error("Key contains invalid characters.")); + return Err(OsError::EnvInvalidKey(key.to_string())); } env::remove_var(key); Ok(()) @@ -173,12 +186,14 @@ fn op_get_exit_code(state: &mut OpState) -> i32 { #[op2(fast)] fn op_exit(state: &mut OpState) { let code = state.borrow::<ExitCode>().get(); - std::process::exit(code) + crate::exit(code) } #[op2] #[serde] -fn op_loadavg(state: &mut OpState) -> Result<(f64, f64, f64), AnyError> { +fn op_loadavg( + state: &mut OpState, +) -> Result<(f64, f64, f64), deno_core::error::AnyError> { state .borrow_mut::<PermissionsContainer>() .check_sys("loadavg", "Deno.loadavg()")?; @@ -187,7 +202,9 @@ fn op_loadavg(state: &mut OpState) -> Result<(f64, f64, f64), AnyError> { #[op2] #[string] -fn op_hostname(state: &mut OpState) -> Result<String, AnyError> { +fn op_hostname( + state: &mut OpState, +) -> Result<String, deno_core::error::AnyError> { state .borrow_mut::<PermissionsContainer>() .check_sys("hostname", "Deno.hostname()")?; @@ -196,7 +213,9 @@ fn op_hostname(state: &mut OpState) -> Result<String, AnyError> { #[op2] #[string] -fn op_os_release(state: &mut OpState) -> Result<String, AnyError> { +fn op_os_release( + state: &mut OpState, +) -> Result<String, deno_core::error::AnyError> { state .borrow_mut::<PermissionsContainer>() .check_sys("osRelease", "Deno.osRelease()")?; @@ -207,7 +226,7 @@ fn op_os_release(state: &mut OpState) -> Result<String, AnyError> { #[serde] fn op_network_interfaces( state: &mut OpState, -) -> Result<Vec<NetworkInterface>, AnyError> { +) -> Result<Vec<NetworkInterface>, OsError> { state .borrow_mut::<PermissionsContainer>() .check_sys("networkInterfaces", "Deno.networkInterfaces()")?; @@ -259,7 +278,7 @@ impl From<netif::Interface> for NetworkInterface { #[serde] fn op_system_memory_info( state: &mut OpState, -) -> Result<Option<sys_info::MemInfo>, AnyError> { +) -> Result<Option<sys_info::MemInfo>, deno_core::error::AnyError> { state .borrow_mut::<PermissionsContainer>() .check_sys("systemMemoryInfo", "Deno.systemMemoryInfo()")?; @@ -269,7 +288,9 @@ fn op_system_memory_info( #[cfg(not(windows))] #[op2] #[smi] -fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> { +fn op_gid( + state: &mut OpState, +) -> Result<Option<u32>, deno_core::error::AnyError> { state .borrow_mut::<PermissionsContainer>() .check_sys("gid", "Deno.gid()")?; @@ -283,7 +304,9 @@ fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> { #[cfg(windows)] #[op2] #[smi] -fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> { +fn op_gid( + state: &mut OpState, +) -> Result<Option<u32>, deno_core::error::AnyError> { state .borrow_mut::<PermissionsContainer>() .check_sys("gid", "Deno.gid()")?; @@ -293,7 +316,9 @@ fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> { #[cfg(not(windows))] #[op2] #[smi] -fn op_uid(state: &mut OpState) -> Result<Option<u32>, AnyError> { +fn op_uid( + state: &mut OpState, +) -> Result<Option<u32>, deno_core::error::AnyError> { state .borrow_mut::<PermissionsContainer>() .check_sys("uid", "Deno.uid()")?; @@ -307,7 +332,9 @@ fn op_uid(state: &mut OpState) -> Result<Option<u32>, AnyError> { #[cfg(windows)] #[op2] #[smi] -fn op_uid(state: &mut OpState) -> Result<Option<u32>, AnyError> { +fn op_uid( + state: &mut OpState, +) -> Result<Option<u32>, deno_core::error::AnyError> { state .borrow_mut::<PermissionsContainer>() .check_sys("uid", "Deno.uid()")?; @@ -485,7 +512,7 @@ fn rss() -> usize { } } -fn os_uptime(state: &mut OpState) -> Result<u64, AnyError> { +fn os_uptime(state: &mut OpState) -> Result<u64, deno_core::error::AnyError> { state .borrow_mut::<PermissionsContainer>() .check_sys("osUptime", "Deno.osUptime()")?; @@ -494,6 +521,8 @@ fn os_uptime(state: &mut OpState) -> Result<u64, AnyError> { #[op2(fast)] #[number] -fn op_os_uptime(state: &mut OpState) -> Result<u64, AnyError> { +fn op_os_uptime( + state: &mut OpState, +) -> Result<u64, deno_core::error::AnyError> { os_uptime(state) } diff --git a/runtime/ops/os/sys_info.rs b/runtime/ops/os/sys_info.rs deleted file mode 100644 index cffc90e9d..000000000 --- a/runtime/ops/os/sys_info.rs +++ /dev/null @@ -1,425 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -#[cfg(target_family = "windows")] -use std::sync::Once; - -type LoadAvg = (f64, f64, f64); -const DEFAULT_LOADAVG: LoadAvg = (0.0, 0.0, 0.0); - -pub fn loadavg() -> LoadAvg { - #[cfg(any(target_os = "android", target_os = "linux"))] - { - use libc::SI_LOAD_SHIFT; - - let mut info = std::mem::MaybeUninit::uninit(); - // SAFETY: `info` is a valid pointer to a `libc::sysinfo` struct. - let res = unsafe { libc::sysinfo(info.as_mut_ptr()) }; - if res == 0 { - // SAFETY: `sysinfo` returns 0 on success, and `info` is initialized. - let info = unsafe { info.assume_init() }; - ( - info.loads[0] as f64 / (1 << SI_LOAD_SHIFT) as f64, - info.loads[1] as f64 / (1 << SI_LOAD_SHIFT) as f64, - info.loads[2] as f64 / (1 << SI_LOAD_SHIFT) as f64, - ) - } else { - DEFAULT_LOADAVG - } - } - #[cfg(any( - target_vendor = "apple", - target_os = "freebsd", - target_os = "openbsd" - ))] - { - let mut l: [f64; 3] = [0.; 3]; - // SAFETY: `&mut l` is a valid pointer to an array of 3 doubles - if unsafe { libc::getloadavg(&mut l as *mut f64, l.len() as _) } < 3 { - DEFAULT_LOADAVG - } else { - (l[0], l[1], l[2]) - } - } - #[cfg(target_os = "windows")] - { - DEFAULT_LOADAVG - } -} - -pub fn os_release() -> String { - #[cfg(target_os = "linux")] - { - #[allow(clippy::disallowed_methods)] - match std::fs::read_to_string("/proc/sys/kernel/osrelease") { - Ok(mut s) => { - s.pop(); // pop '\n' - s - } - _ => String::from(""), - } - } - #[cfg(target_os = "android")] - { - let mut info = std::mem::MaybeUninit::uninit(); - // SAFETY: `info` is a valid pointer to a `libc::utsname` struct. - let res = unsafe { libc::uname(info.as_mut_ptr()) }; - if res != 0 { - return String::from(""); - } - // SAFETY: `uname` returns 0 on success, and `info` is initialized. - let mut info = unsafe { info.assume_init() }; - let len = info.release.len(); - info.release[len - 1] = 0; - // SAFETY: `info.release` is a valid pointer and NUL-terminated. - let c_str = unsafe { std::ffi::CStr::from_ptr(info.release.as_ptr()) }; - c_str.to_string_lossy().into_owned() - } - #[cfg(any( - target_vendor = "apple", - target_os = "freebsd", - target_os = "openbsd" - ))] - { - let mut s = [0u8; 256]; - let mut mib = [libc::CTL_KERN, libc::KERN_OSRELEASE]; - // 256 is enough. - let mut len = s.len(); - // SAFETY: `sysctl` is thread-safe. - // `s` is only accessed if sysctl() succeeds and agrees with the `len` set - // by sysctl(). - if unsafe { - libc::sysctl( - mib.as_mut_ptr(), - mib.len() as _, - s.as_mut_ptr() as _, - &mut len, - std::ptr::null_mut(), - 0, - ) - } == -1 - { - return String::from("Unknown"); - } - - // without the NUL terminator - return String::from_utf8_lossy(&s[..len - 1]).to_string(); - } - #[cfg(target_family = "windows")] - { - use ntapi::ntrtl::RtlGetVersion; - use winapi::shared::ntdef::NT_SUCCESS; - use winapi::um::winnt::RTL_OSVERSIONINFOEXW; - - let mut version_info = - std::mem::MaybeUninit::<RTL_OSVERSIONINFOEXW>::uninit(); - // SAFETY: we need to initialize dwOSVersionInfoSize. - unsafe { - (*version_info.as_mut_ptr()).dwOSVersionInfoSize = - std::mem::size_of::<RTL_OSVERSIONINFOEXW>() as u32; - } - // SAFETY: `version_info` is pointer to a valid `RTL_OSVERSIONINFOEXW` struct and - // dwOSVersionInfoSize is set to the size of RTL_OSVERSIONINFOEXW. - if !NT_SUCCESS(unsafe { - RtlGetVersion(version_info.as_mut_ptr() as *mut _) - }) { - String::from("") - } else { - // SAFETY: we assume that RtlGetVersion() initializes the fields. - let version_info = unsafe { version_info.assume_init() }; - format!( - "{}.{}.{}", - version_info.dwMajorVersion, - version_info.dwMinorVersion, - version_info.dwBuildNumber - ) - } - } -} - -#[cfg(target_family = "windows")] -static WINSOCKET_INIT: Once = Once::new(); - -pub fn hostname() -> String { - #[cfg(target_family = "unix")] - // SAFETY: `sysconf` returns a system constant. - unsafe { - let buf_size = libc::sysconf(libc::_SC_HOST_NAME_MAX) as usize; - let mut buf = vec![0u8; buf_size + 1]; - let len = buf.len(); - if libc::gethostname(buf.as_mut_ptr() as *mut libc::c_char, len) < 0 { - return String::from(""); - } - // ensure NUL termination - buf[len - 1] = 0; - std::ffi::CStr::from_ptr(buf.as_ptr() as *const libc::c_char) - .to_string_lossy() - .to_string() - } - #[cfg(target_family = "windows")] - { - use std::ffi::OsString; - use std::mem; - use std::os::windows::ffi::OsStringExt; - use winapi::shared::minwindef::MAKEWORD; - use winapi::um::winsock2::GetHostNameW; - use winapi::um::winsock2::WSAStartup; - - let namelen = 256; - let mut name: Vec<u16> = vec![0u16; namelen]; - // Start winsock to make `GetHostNameW` work correctly - // https://github.com/retep998/winapi-rs/issues/296 - // SAFETY: winapi call - WINSOCKET_INIT.call_once(|| unsafe { - let mut data = mem::zeroed(); - let wsa_startup_result = WSAStartup(MAKEWORD(2, 2), &mut data); - if wsa_startup_result != 0 { - panic!("Failed to start winsocket"); - } - }); - let err = - // SAFETY: length of wide string is 256 chars or less. - // https://learn.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-gethostnamew - unsafe { GetHostNameW(name.as_mut_ptr(), namelen as libc::c_int) }; - - if err == 0 { - // TODO(@littledivy): Probably not the most efficient way. - let len = name.iter().take_while(|&&c| c != 0).count(); - OsString::from_wide(&name[..len]) - .to_string_lossy() - .into_owned() - } else { - String::from("") - } - } -} - -#[derive(serde::Serialize)] -#[serde(rename_all = "camelCase")] -pub struct MemInfo { - pub total: u64, - pub free: u64, - pub available: u64, - pub buffers: u64, - pub cached: u64, - pub swap_total: u64, - pub swap_free: u64, -} - -pub fn mem_info() -> Option<MemInfo> { - let mut mem_info = MemInfo { - total: 0, - free: 0, - available: 0, - buffers: 0, - cached: 0, - swap_total: 0, - swap_free: 0, - }; - #[cfg(any(target_os = "android", target_os = "linux"))] - { - let mut info = std::mem::MaybeUninit::uninit(); - // SAFETY: `info` is a valid pointer to a `libc::sysinfo` struct. - let res = unsafe { libc::sysinfo(info.as_mut_ptr()) }; - if res == 0 { - // SAFETY: `sysinfo` initializes the struct. - let info = unsafe { info.assume_init() }; - let mem_unit = info.mem_unit as u64; - mem_info.swap_total = info.totalswap * mem_unit; - mem_info.swap_free = info.freeswap * mem_unit; - mem_info.total = info.totalram * mem_unit; - mem_info.free = info.freeram * mem_unit; - mem_info.available = mem_info.free; - mem_info.buffers = info.bufferram * mem_unit; - } - - // Gets the available memory from /proc/meminfo in linux for compatibility - #[allow(clippy::disallowed_methods)] - if let Ok(meminfo) = std::fs::read_to_string("/proc/meminfo") { - let line = meminfo.lines().find(|l| l.starts_with("MemAvailable:")); - if let Some(line) = line { - let mem = line.split_whitespace().nth(1); - let mem = mem.and_then(|v| v.parse::<u64>().ok()); - mem_info.available = mem.unwrap_or(0) * 1024; - } - } - } - #[cfg(target_vendor = "apple")] - { - let mut mib: [i32; 2] = [0, 0]; - mib[0] = libc::CTL_HW; - mib[1] = libc::HW_MEMSIZE; - // SAFETY: - // - We assume that `mach_host_self` always returns a valid value. - // - sysconf returns a system constant. - unsafe { - let mut size = std::mem::size_of::<u64>(); - libc::sysctl( - mib.as_mut_ptr(), - mib.len() as _, - &mut mem_info.total as *mut _ as *mut libc::c_void, - &mut size, - std::ptr::null_mut(), - 0, - ); - - let mut xs: libc::xsw_usage = std::mem::zeroed::<libc::xsw_usage>(); - mib[0] = libc::CTL_VM; - mib[1] = libc::VM_SWAPUSAGE; - - let mut size = std::mem::size_of::<libc::xsw_usage>(); - libc::sysctl( - mib.as_mut_ptr(), - mib.len() as _, - &mut xs as *mut _ as *mut libc::c_void, - &mut size, - std::ptr::null_mut(), - 0, - ); - - mem_info.swap_total = xs.xsu_total; - mem_info.swap_free = xs.xsu_avail; - - let mut count: u32 = libc::HOST_VM_INFO64_COUNT as _; - let mut stat = std::mem::zeroed::<libc::vm_statistics64>(); - if libc::host_statistics64( - // TODO(@littledivy): Put this in a once_cell. - libc::mach_host_self(), - libc::HOST_VM_INFO64, - &mut stat as *mut libc::vm_statistics64 as *mut _, - &mut count, - ) == libc::KERN_SUCCESS - { - // TODO(@littledivy): Put this in a once_cell - let page_size = libc::sysconf(libc::_SC_PAGESIZE) as u64; - mem_info.available = - (stat.free_count as u64 + stat.inactive_count as u64) * page_size - / 1024; - mem_info.free = - (stat.free_count as u64 - stat.speculative_count as u64) * page_size - / 1024; - } - } - } - #[cfg(target_family = "windows")] - // SAFETY: - // - `mem_status` is a valid pointer to a `libc::MEMORYSTATUSEX` struct. - // - `dwLength` is set to the size of the struct. - unsafe { - use std::mem; - use winapi::shared::minwindef; - use winapi::um::psapi::GetPerformanceInfo; - use winapi::um::psapi::PERFORMANCE_INFORMATION; - use winapi::um::sysinfoapi; - - let mut mem_status = - mem::MaybeUninit::<sysinfoapi::MEMORYSTATUSEX>::uninit(); - let length = - mem::size_of::<sysinfoapi::MEMORYSTATUSEX>() as minwindef::DWORD; - (*mem_status.as_mut_ptr()).dwLength = length; - - let result = sysinfoapi::GlobalMemoryStatusEx(mem_status.as_mut_ptr()); - if result != 0 { - let stat = mem_status.assume_init(); - mem_info.total = stat.ullTotalPhys; - mem_info.available = 0; - mem_info.free = stat.ullAvailPhys; - mem_info.cached = 0; - mem_info.buffers = 0; - - // `stat.ullTotalPageFile` is reliable only from GetPerformanceInfo() - // - // See https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/ns-sysinfoapi-memorystatusex - // and https://github.com/GuillaumeGomez/sysinfo/issues/534 - - let mut perf_info = mem::MaybeUninit::<PERFORMANCE_INFORMATION>::uninit(); - let result = GetPerformanceInfo( - perf_info.as_mut_ptr(), - mem::size_of::<PERFORMANCE_INFORMATION>() as minwindef::DWORD, - ); - if result == minwindef::TRUE { - let perf_info = perf_info.assume_init(); - let swap_total = perf_info.PageSize - * perf_info - .CommitLimit - .saturating_sub(perf_info.PhysicalTotal); - let swap_free = perf_info.PageSize - * perf_info - .CommitLimit - .saturating_sub(perf_info.PhysicalTotal) - .saturating_sub(perf_info.PhysicalAvailable); - mem_info.swap_total = (swap_total / 1000) as u64; - mem_info.swap_free = (swap_free / 1000) as u64; - } - } - } - - Some(mem_info) -} - -pub fn os_uptime() -> u64 { - let uptime: u64; - - #[cfg(any(target_os = "android", target_os = "linux"))] - { - let mut info = std::mem::MaybeUninit::uninit(); - // SAFETY: `info` is a valid pointer to a `libc::sysinfo` struct. - let res = unsafe { libc::sysinfo(info.as_mut_ptr()) }; - uptime = if res == 0 { - // SAFETY: `sysinfo` initializes the struct. - let info = unsafe { info.assume_init() }; - info.uptime as u64 - } else { - 0 - } - } - - #[cfg(any( - target_vendor = "apple", - target_os = "freebsd", - target_os = "openbsd" - ))] - { - use std::mem; - use std::time::Duration; - use std::time::SystemTime; - let mut request = [libc::CTL_KERN, libc::KERN_BOOTTIME]; - // SAFETY: `boottime` is only accessed if sysctl() succeeds - // and agrees with the `size` set by sysctl(). - let mut boottime: libc::timeval = unsafe { mem::zeroed() }; - let mut size: libc::size_t = mem::size_of_val(&boottime) as libc::size_t; - // SAFETY: `sysctl` is thread-safe. - let res = unsafe { - libc::sysctl( - &mut request[0], - 2, - &mut boottime as *mut libc::timeval as *mut libc::c_void, - &mut size, - std::ptr::null_mut(), - 0, - ) - }; - uptime = if res == 0 { - SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .map(|d| { - (d - Duration::new( - boottime.tv_sec as u64, - boottime.tv_usec as u32 * 1000, - )) - .as_secs() - }) - .unwrap_or_default() - } else { - 0 - } - } - - #[cfg(target_family = "windows")] - // SAFETY: windows API usage - unsafe { - // Windows is the only one that returns `uptime` in millisecond precision, - // so we need to get the seconds out of it to be in sync with other envs. - uptime = winapi::um::sysinfoapi::GetTickCount64() / 1000; - } - - uptime -} diff --git a/runtime/ops/otel.rs b/runtime/ops/otel.rs new file mode 100644 index 000000000..61a7b0ef0 --- /dev/null +++ b/runtime/ops/otel.rs @@ -0,0 +1,855 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use crate::tokio_util::create_basic_runtime; +use deno_core::anyhow; +use deno_core::anyhow::anyhow; +use deno_core::futures::channel::mpsc; +use deno_core::futures::channel::mpsc::UnboundedSender; +use deno_core::futures::future::BoxFuture; +use deno_core::futures::stream; +use deno_core::futures::Stream; +use deno_core::futures::StreamExt; +use deno_core::op2; +use deno_core::v8; +use deno_core::OpState; +use once_cell::sync::Lazy; +use once_cell::sync::OnceCell; +use opentelemetry::logs::AnyValue; +use opentelemetry::logs::LogRecord as LogRecordTrait; +use opentelemetry::logs::Severity; +use opentelemetry::trace::SpanContext; +use opentelemetry::trace::SpanId; +use opentelemetry::trace::SpanKind; +use opentelemetry::trace::Status as SpanStatus; +use opentelemetry::trace::TraceFlags; +use opentelemetry::trace::TraceId; +use opentelemetry::Key; +use opentelemetry::KeyValue; +use opentelemetry::StringValue; +use opentelemetry::Value; +use opentelemetry_otlp::HttpExporterBuilder; +use opentelemetry_otlp::Protocol; +use opentelemetry_otlp::WithExportConfig; +use opentelemetry_otlp::WithHttpConfig; +use opentelemetry_sdk::export::trace::SpanData; +use opentelemetry_sdk::logs::BatchLogProcessor; +use opentelemetry_sdk::logs::LogProcessor as LogProcessorTrait; +use opentelemetry_sdk::logs::LogRecord; +use opentelemetry_sdk::trace::BatchSpanProcessor; +use opentelemetry_sdk::trace::SpanProcessor as SpanProcessorTrait; +use opentelemetry_sdk::Resource; +use opentelemetry_semantic_conventions::resource::PROCESS_RUNTIME_NAME; +use opentelemetry_semantic_conventions::resource::PROCESS_RUNTIME_VERSION; +use opentelemetry_semantic_conventions::resource::TELEMETRY_SDK_LANGUAGE; +use opentelemetry_semantic_conventions::resource::TELEMETRY_SDK_NAME; +use opentelemetry_semantic_conventions::resource::TELEMETRY_SDK_VERSION; +use serde::Deserialize; +use serde::Serialize; +use std::borrow::Cow; +use std::env; +use std::fmt::Debug; +use std::pin::Pin; +use std::task::Context; +use std::task::Poll; +use std::thread; +use std::time::Duration; +use std::time::SystemTime; + +type SpanProcessor = BatchSpanProcessor<OtelSharedRuntime>; +type LogProcessor = BatchLogProcessor<OtelSharedRuntime>; + +deno_core::extension!( + deno_otel, + ops = [ + op_otel_log, + op_otel_instrumentation_scope_create_and_enter, + op_otel_instrumentation_scope_enter, + op_otel_instrumentation_scope_enter_builtin, + op_otel_span_start, + op_otel_span_continue, + op_otel_span_attribute, + op_otel_span_attribute2, + op_otel_span_attribute3, + op_otel_span_set_dropped, + op_otel_span_flush, + ], +); + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OtelConfig { + pub runtime_name: Cow<'static, str>, + pub runtime_version: Cow<'static, str>, + pub console: OtelConsoleConfig, + pub deterministic: bool, +} + +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[repr(u8)] +pub enum OtelConsoleConfig { + Ignore = 0, + Capture = 1, + Replace = 2, +} + +impl Default for OtelConfig { + fn default() -> Self { + Self { + runtime_name: Cow::Borrowed(env!("CARGO_PKG_NAME")), + runtime_version: Cow::Borrowed(env!("CARGO_PKG_VERSION")), + console: OtelConsoleConfig::Capture, + deterministic: false, + } + } +} + +static OTEL_SHARED_RUNTIME_SPAWN_TASK_TX: Lazy< + UnboundedSender<BoxFuture<'static, ()>>, +> = Lazy::new(otel_create_shared_runtime); + +fn otel_create_shared_runtime() -> UnboundedSender<BoxFuture<'static, ()>> { + let (spawn_task_tx, mut spawn_task_rx) = + mpsc::unbounded::<BoxFuture<'static, ()>>(); + + thread::spawn(move || { + let rt = create_basic_runtime(); + rt.block_on(async move { + while let Some(task) = spawn_task_rx.next().await { + tokio::spawn(task); + } + }); + }); + + spawn_task_tx +} + +#[derive(Clone, Copy)] +struct OtelSharedRuntime; + +impl hyper::rt::Executor<BoxFuture<'static, ()>> for OtelSharedRuntime { + fn execute(&self, fut: BoxFuture<'static, ()>) { + (*OTEL_SHARED_RUNTIME_SPAWN_TASK_TX) + .unbounded_send(fut) + .expect("failed to send task to shared OpenTelemetry runtime"); + } +} + +impl opentelemetry_sdk::runtime::Runtime for OtelSharedRuntime { + type Interval = Pin<Box<dyn Stream<Item = ()> + Send + 'static>>; + type Delay = Pin<Box<tokio::time::Sleep>>; + + fn interval(&self, period: Duration) -> Self::Interval { + stream::repeat(()) + .then(move |_| tokio::time::sleep(period)) + .boxed() + } + + fn spawn(&self, future: BoxFuture<'static, ()>) { + (*OTEL_SHARED_RUNTIME_SPAWN_TASK_TX) + .unbounded_send(future) + .expect("failed to send task to shared OpenTelemetry runtime"); + } + + fn delay(&self, duration: Duration) -> Self::Delay { + Box::pin(tokio::time::sleep(duration)) + } +} + +impl opentelemetry_sdk::runtime::RuntimeChannel for OtelSharedRuntime { + type Receiver<T: Debug + Send> = BatchMessageChannelReceiver<T>; + type Sender<T: Debug + Send> = BatchMessageChannelSender<T>; + + fn batch_message_channel<T: Debug + Send>( + &self, + capacity: usize, + ) -> (Self::Sender<T>, Self::Receiver<T>) { + let (batch_tx, batch_rx) = tokio::sync::mpsc::channel::<T>(capacity); + (batch_tx.into(), batch_rx.into()) + } +} + +#[derive(Debug)] +pub struct BatchMessageChannelSender<T: Send> { + sender: tokio::sync::mpsc::Sender<T>, +} + +impl<T: Send> From<tokio::sync::mpsc::Sender<T>> + for BatchMessageChannelSender<T> +{ + fn from(sender: tokio::sync::mpsc::Sender<T>) -> Self { + Self { sender } + } +} + +impl<T: Send> opentelemetry_sdk::runtime::TrySend + for BatchMessageChannelSender<T> +{ + type Message = T; + + fn try_send( + &self, + item: Self::Message, + ) -> Result<(), opentelemetry_sdk::runtime::TrySendError> { + self.sender.try_send(item).map_err(|err| match err { + tokio::sync::mpsc::error::TrySendError::Full(_) => { + opentelemetry_sdk::runtime::TrySendError::ChannelFull + } + tokio::sync::mpsc::error::TrySendError::Closed(_) => { + opentelemetry_sdk::runtime::TrySendError::ChannelClosed + } + }) + } +} + +pub struct BatchMessageChannelReceiver<T> { + receiver: tokio::sync::mpsc::Receiver<T>, +} + +impl<T> From<tokio::sync::mpsc::Receiver<T>> + for BatchMessageChannelReceiver<T> +{ + fn from(receiver: tokio::sync::mpsc::Receiver<T>) -> Self { + Self { receiver } + } +} + +impl<T> Stream for BatchMessageChannelReceiver<T> { + type Item = T; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll<Option<Self::Item>> { + self.receiver.poll_recv(cx) + } +} + +mod hyper_client { + use http_body_util::BodyExt; + use http_body_util::Full; + use hyper::body::Body as HttpBody; + use hyper::body::Frame; + use hyper_util::client::legacy::connect::HttpConnector; + use hyper_util::client::legacy::Client; + use opentelemetry_http::Bytes; + use opentelemetry_http::HttpError; + use opentelemetry_http::Request; + use opentelemetry_http::Response; + use opentelemetry_http::ResponseExt; + use std::fmt::Debug; + use std::pin::Pin; + use std::task::Poll; + use std::task::{self}; + + use super::OtelSharedRuntime; + + // same as opentelemetry_http::HyperClient except it uses OtelSharedRuntime + #[derive(Debug, Clone)] + pub struct HyperClient { + inner: Client<HttpConnector, Body>, + } + + impl HyperClient { + pub fn new() -> Self { + Self { + inner: Client::builder(OtelSharedRuntime).build(HttpConnector::new()), + } + } + } + + #[async_trait::async_trait] + impl opentelemetry_http::HttpClient for HyperClient { + async fn send( + &self, + request: Request<Vec<u8>>, + ) -> Result<Response<Bytes>, HttpError> { + let (parts, body) = request.into_parts(); + let request = Request::from_parts(parts, Body(Full::from(body))); + let mut response = self.inner.request(request).await?; + let headers = std::mem::take(response.headers_mut()); + + let mut http_response = Response::builder() + .status(response.status()) + .body(response.into_body().collect().await?.to_bytes())?; + *http_response.headers_mut() = headers; + + Ok(http_response.error_for_status()?) + } + } + + #[pin_project::pin_project] + pub struct Body(#[pin] Full<Bytes>); + + impl HttpBody for Body { + type Data = Bytes; + type Error = Box<dyn std::error::Error + Send + Sync + 'static>; + + #[inline] + fn poll_frame( + self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + ) -> Poll<Option<Result<Frame<Self::Data>, Self::Error>>> { + self.project().0.poll_frame(cx).map_err(Into::into) + } + + #[inline] + fn is_end_stream(&self) -> bool { + self.0.is_end_stream() + } + + #[inline] + fn size_hint(&self) -> hyper::body::SizeHint { + self.0.size_hint() + } + } +} + +static OTEL_PROCESSORS: OnceCell<(SpanProcessor, LogProcessor)> = + OnceCell::new(); + +static BUILT_IN_INSTRUMENTATION_SCOPE: OnceCell< + opentelemetry::InstrumentationScope, +> = OnceCell::new(); + +pub fn init(config: OtelConfig) -> anyhow::Result<()> { + // Parse the `OTEL_EXPORTER_OTLP_PROTOCOL` variable. The opentelemetry_* + // crates don't do this automatically. + // TODO(piscisaureus): enable GRPC support. + let protocol = match env::var("OTEL_EXPORTER_OTLP_PROTOCOL").as_deref() { + Ok("http/protobuf") => Protocol::HttpBinary, + Ok("http/json") => Protocol::HttpJson, + Ok("") | Err(env::VarError::NotPresent) => { + return Ok(()); + } + Ok(protocol) => { + return Err(anyhow!( + "Env var OTEL_EXPORTER_OTLP_PROTOCOL specifies an unsupported protocol: {}", + protocol + )); + } + Err(err) => { + return Err(anyhow!( + "Failed to read env var OTEL_EXPORTER_OTLP_PROTOCOL: {}", + err + )); + } + }; + + // Define the resource attributes that will be attached to all log records. + // These attributes are sourced as follows (in order of precedence): + // * The `service.name` attribute from the `OTEL_SERVICE_NAME` env var. + // * Additional attributes from the `OTEL_RESOURCE_ATTRIBUTES` env var. + // * Default attribute values defined here. + // TODO(piscisaureus): add more default attributes (e.g. script path). + let mut resource = Resource::default(); + + // Add the runtime name and version to the resource attributes. Also override + // the `telemetry.sdk` attributes to include the Deno runtime. + resource = resource.merge(&Resource::new(vec![ + KeyValue::new(PROCESS_RUNTIME_NAME, config.runtime_name), + KeyValue::new(PROCESS_RUNTIME_VERSION, config.runtime_version.clone()), + KeyValue::new( + TELEMETRY_SDK_LANGUAGE, + format!( + "deno-{}", + resource.get(Key::new(TELEMETRY_SDK_LANGUAGE)).unwrap() + ), + ), + KeyValue::new( + TELEMETRY_SDK_NAME, + format!( + "deno-{}", + resource.get(Key::new(TELEMETRY_SDK_NAME)).unwrap() + ), + ), + KeyValue::new( + TELEMETRY_SDK_VERSION, + format!( + "{}-{}", + config.runtime_version, + resource.get(Key::new(TELEMETRY_SDK_VERSION)).unwrap() + ), + ), + ])); + + // The OTLP endpoint is automatically picked up from the + // `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable. Additional headers can + // be specified using `OTEL_EXPORTER_OTLP_HEADERS`. + + let client = hyper_client::HyperClient::new(); + + let span_exporter = HttpExporterBuilder::default() + .with_http_client(client.clone()) + .with_protocol(protocol) + .build_span_exporter()?; + let mut span_processor = + BatchSpanProcessor::builder(span_exporter, OtelSharedRuntime).build(); + span_processor.set_resource(&resource); + + let log_exporter = HttpExporterBuilder::default() + .with_http_client(client) + .with_protocol(protocol) + .build_log_exporter()?; + let log_processor = + BatchLogProcessor::builder(log_exporter, OtelSharedRuntime).build(); + log_processor.set_resource(&resource); + + OTEL_PROCESSORS + .set((span_processor, log_processor)) + .map_err(|_| anyhow!("failed to init otel"))?; + + let builtin_instrumentation_scope = + opentelemetry::InstrumentationScope::builder("deno") + .with_version(config.runtime_version.clone()) + .build(); + BUILT_IN_INSTRUMENTATION_SCOPE + .set(builtin_instrumentation_scope) + .map_err(|_| anyhow!("failed to init otel"))?; + + Ok(()) +} + +/// This function is called by the runtime whenever it is about to call +/// `process::exit()`, to ensure that all OpenTelemetry logs are properly +/// flushed before the process terminates. +pub fn flush() { + if let Some((span_processor, log_processor)) = OTEL_PROCESSORS.get() { + let _ = span_processor.force_flush(); + let _ = log_processor.force_flush(); + } +} + +pub fn handle_log(record: &log::Record) { + use log::Level; + + let Some((_, log_processor)) = OTEL_PROCESSORS.get() else { + return; + }; + + let mut log_record = LogRecord::default(); + + log_record.set_observed_timestamp(SystemTime::now()); + log_record.set_severity_number(match record.level() { + Level::Error => Severity::Error, + Level::Warn => Severity::Warn, + Level::Info => Severity::Info, + Level::Debug => Severity::Debug, + Level::Trace => Severity::Trace, + }); + log_record.set_severity_text(record.level().as_str()); + log_record.set_body(record.args().to_string().into()); + log_record.set_target(record.metadata().target().to_string()); + + struct Visitor<'s>(&'s mut LogRecord); + + impl<'s, 'kvs> log::kv::VisitSource<'kvs> for Visitor<'s> { + fn visit_pair( + &mut self, + key: log::kv::Key<'kvs>, + value: log::kv::Value<'kvs>, + ) -> Result<(), log::kv::Error> { + #[allow(clippy::manual_map)] + let value = if let Some(v) = value.to_bool() { + Some(AnyValue::Boolean(v)) + } else if let Some(v) = value.to_borrowed_str() { + Some(AnyValue::String(v.to_owned().into())) + } else if let Some(v) = value.to_f64() { + Some(AnyValue::Double(v)) + } else if let Some(v) = value.to_i64() { + Some(AnyValue::Int(v)) + } else { + None + }; + + if let Some(value) = value { + let key = Key::from(key.as_str().to_owned()); + self.0.add_attribute(key, value); + } + + Ok(()) + } + } + + let _ = record.key_values().visit(&mut Visitor(&mut log_record)); + + log_processor.emit( + &mut log_record, + BUILT_IN_INSTRUMENTATION_SCOPE.get().unwrap(), + ); +} + +fn parse_trace_id( + scope: &mut v8::HandleScope<'_>, + trace_id: v8::Local<'_, v8::Value>, +) -> TraceId { + if let Ok(string) = trace_id.try_cast() { + let value_view = v8::ValueView::new(scope, string); + match value_view.data() { + v8::ValueViewData::OneByte(bytes) => { + TraceId::from_hex(&String::from_utf8_lossy(bytes)) + .unwrap_or(TraceId::INVALID) + } + + _ => TraceId::INVALID, + } + } else if let Ok(uint8array) = trace_id.try_cast::<v8::Uint8Array>() { + let data = uint8array.data(); + let byte_length = uint8array.byte_length(); + if byte_length != 16 { + return TraceId::INVALID; + } + // SAFETY: We have ensured that the byte length is 16, so it is safe to + // cast the data to an array of 16 bytes. + let bytes = unsafe { &*(data as *const u8 as *const [u8; 16]) }; + TraceId::from_bytes(*bytes) + } else { + TraceId::INVALID + } +} + +fn parse_span_id( + scope: &mut v8::HandleScope<'_>, + span_id: v8::Local<'_, v8::Value>, +) -> SpanId { + if let Ok(string) = span_id.try_cast() { + let value_view = v8::ValueView::new(scope, string); + match value_view.data() { + v8::ValueViewData::OneByte(bytes) => { + SpanId::from_hex(&String::from_utf8_lossy(bytes)) + .unwrap_or(SpanId::INVALID) + } + _ => SpanId::INVALID, + } + } else if let Ok(uint8array) = span_id.try_cast::<v8::Uint8Array>() { + let data = uint8array.data(); + let byte_length = uint8array.byte_length(); + if byte_length != 8 { + return SpanId::INVALID; + } + // SAFETY: We have ensured that the byte length is 8, so it is safe to + // cast the data to an array of 8 bytes. + let bytes = unsafe { &*(data as *const u8 as *const [u8; 8]) }; + SpanId::from_bytes(*bytes) + } else { + SpanId::INVALID + } +} + +macro_rules! attr { + ($scope:ident, $attributes:expr $(=> $dropped_attributes_count:expr)?, $name:expr, $value:expr) => { + let name = if let Ok(name) = $name.try_cast() { + let view = v8::ValueView::new($scope, name); + match view.data() { + v8::ValueViewData::OneByte(bytes) => { + Some(String::from_utf8_lossy(bytes).into_owned()) + } + v8::ValueViewData::TwoByte(bytes) => { + Some(String::from_utf16_lossy(bytes)) + } + } + } else { + None + }; + let value = if let Ok(string) = $value.try_cast::<v8::String>() { + Some(Value::String(StringValue::from({ + let x = v8::ValueView::new($scope, string); + match x.data() { + v8::ValueViewData::OneByte(bytes) => { + String::from_utf8_lossy(bytes).into_owned() + } + v8::ValueViewData::TwoByte(bytes) => String::from_utf16_lossy(bytes), + } + }))) + } else if let Ok(number) = $value.try_cast::<v8::Number>() { + Some(Value::F64(number.value())) + } else if let Ok(boolean) = $value.try_cast::<v8::Boolean>() { + Some(Value::Bool(boolean.is_true())) + } else if let Ok(bigint) = $value.try_cast::<v8::BigInt>() { + let (i64_value, _lossless) = bigint.i64_value(); + Some(Value::I64(i64_value)) + } else { + None + }; + if let (Some(name), Some(value)) = (name, value) { + $attributes.push(KeyValue::new(name, value)); + } + $( + else { + $dropped_attributes_count += 1; + } + )? + }; +} + +#[derive(Debug, Clone)] +struct InstrumentationScope(opentelemetry::InstrumentationScope); + +impl deno_core::GarbageCollected for InstrumentationScope {} + +#[op2] +#[cppgc] +fn op_otel_instrumentation_scope_create_and_enter( + state: &mut OpState, + #[string] name: String, + #[string] version: Option<String>, + #[string] schema_url: Option<String>, +) -> InstrumentationScope { + let mut builder = opentelemetry::InstrumentationScope::builder(name); + if let Some(version) = version { + builder = builder.with_version(version); + } + if let Some(schema_url) = schema_url { + builder = builder.with_schema_url(schema_url); + } + let scope = InstrumentationScope(builder.build()); + state.put(scope.clone()); + scope +} + +#[op2(fast)] +fn op_otel_instrumentation_scope_enter( + state: &mut OpState, + #[cppgc] scope: &InstrumentationScope, +) { + state.put(scope.clone()); +} + +#[op2(fast)] +fn op_otel_instrumentation_scope_enter_builtin(state: &mut OpState) { + state.put(InstrumentationScope( + BUILT_IN_INSTRUMENTATION_SCOPE.get().unwrap().clone(), + )); +} + +#[op2(fast)] +fn op_otel_log( + scope: &mut v8::HandleScope<'_>, + #[string] message: String, + #[smi] level: i32, + trace_id: v8::Local<'_, v8::Value>, + span_id: v8::Local<'_, v8::Value>, + #[smi] trace_flags: u8, +) { + let Some((_, log_processor)) = OTEL_PROCESSORS.get() else { + return; + }; + + // Convert the integer log level that ext/console uses to the corresponding + // OpenTelemetry log severity. + let severity = match level { + ..=0 => Severity::Debug, + 1 => Severity::Info, + 2 => Severity::Warn, + 3.. => Severity::Error, + }; + + let trace_id = parse_trace_id(scope, trace_id); + let span_id = parse_span_id(scope, span_id); + + let mut log_record = LogRecord::default(); + + log_record.set_observed_timestamp(SystemTime::now()); + log_record.set_body(message.into()); + log_record.set_severity_number(severity); + log_record.set_severity_text(severity.name()); + if trace_id != TraceId::INVALID && span_id != SpanId::INVALID { + log_record.set_trace_context( + trace_id, + span_id, + Some(TraceFlags::new(trace_flags)), + ); + } + + log_processor.emit( + &mut log_record, + BUILT_IN_INSTRUMENTATION_SCOPE.get().unwrap(), + ); +} + +struct TemporarySpan(SpanData); + +#[allow(clippy::too_many_arguments)] +#[op2(fast)] +fn op_otel_span_start<'s>( + scope: &mut v8::HandleScope<'s>, + state: &mut OpState, + trace_id: v8::Local<'s, v8::Value>, + span_id: v8::Local<'s, v8::Value>, + parent_span_id: v8::Local<'s, v8::Value>, + #[smi] span_kind: u8, + name: v8::Local<'s, v8::Value>, + start_time: f64, + end_time: f64, +) -> Result<(), anyhow::Error> { + if let Some(temporary_span) = state.try_take::<TemporarySpan>() { + let Some((span_processor, _)) = OTEL_PROCESSORS.get() else { + return Ok(()); + }; + span_processor.on_end(temporary_span.0); + }; + + let Some(InstrumentationScope(instrumentation_scope)) = + state.try_borrow::<InstrumentationScope>() + else { + return Err(anyhow!("instrumentation scope not available")); + }; + + let trace_id = parse_trace_id(scope, trace_id); + if trace_id == TraceId::INVALID { + return Err(anyhow!("invalid trace_id")); + } + + let span_id = parse_span_id(scope, span_id); + if span_id == SpanId::INVALID { + return Err(anyhow!("invalid span_id")); + } + + let parent_span_id = parse_span_id(scope, parent_span_id); + + let name = { + let x = v8::ValueView::new(scope, name.try_cast()?); + match x.data() { + v8::ValueViewData::OneByte(bytes) => { + String::from_utf8_lossy(bytes).into_owned() + } + v8::ValueViewData::TwoByte(bytes) => String::from_utf16_lossy(bytes), + } + }; + + let temporary_span = TemporarySpan(SpanData { + span_context: SpanContext::new( + trace_id, + span_id, + TraceFlags::SAMPLED, + false, + Default::default(), + ), + parent_span_id, + span_kind: match span_kind { + 0 => SpanKind::Internal, + 1 => SpanKind::Server, + 2 => SpanKind::Client, + 3 => SpanKind::Producer, + 4 => SpanKind::Consumer, + _ => return Err(anyhow!("invalid span kind")), + }, + name: Cow::Owned(name), + start_time: SystemTime::UNIX_EPOCH + .checked_add(std::time::Duration::from_secs_f64(start_time)) + .ok_or_else(|| anyhow!("invalid start time"))?, + end_time: SystemTime::UNIX_EPOCH + .checked_add(std::time::Duration::from_secs_f64(end_time)) + .ok_or_else(|| anyhow!("invalid start time"))?, + attributes: Vec::new(), + dropped_attributes_count: 0, + events: Default::default(), + links: Default::default(), + status: SpanStatus::Unset, + instrumentation_scope: instrumentation_scope.clone(), + }); + state.put(temporary_span); + + Ok(()) +} + +#[op2(fast)] +fn op_otel_span_continue( + state: &mut OpState, + #[smi] status: u8, + #[string] error_description: Cow<'_, str>, +) { + if let Some(temporary_span) = state.try_borrow_mut::<TemporarySpan>() { + temporary_span.0.status = match status { + 0 => SpanStatus::Unset, + 1 => SpanStatus::Ok, + 2 => SpanStatus::Error { + description: Cow::Owned(error_description.into_owned()), + }, + _ => return, + }; + } +} + +#[op2(fast)] +fn op_otel_span_attribute<'s>( + scope: &mut v8::HandleScope<'s>, + state: &mut OpState, + #[smi] capacity: u32, + key: v8::Local<'s, v8::Value>, + value: v8::Local<'s, v8::Value>, +) { + if let Some(temporary_span) = state.try_borrow_mut::<TemporarySpan>() { + temporary_span.0.attributes.reserve_exact( + (capacity as usize) - temporary_span.0.attributes.capacity(), + ); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key, value); + } +} + +#[op2(fast)] +fn op_otel_span_attribute2<'s>( + scope: &mut v8::HandleScope<'s>, + state: &mut OpState, + #[smi] capacity: u32, + key1: v8::Local<'s, v8::Value>, + value1: v8::Local<'s, v8::Value>, + key2: v8::Local<'s, v8::Value>, + value2: v8::Local<'s, v8::Value>, +) { + if let Some(temporary_span) = state.try_borrow_mut::<TemporarySpan>() { + temporary_span.0.attributes.reserve_exact( + (capacity as usize) - temporary_span.0.attributes.capacity(), + ); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key1, value1); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key2, value2); + } +} + +#[allow(clippy::too_many_arguments)] +#[op2(fast)] +fn op_otel_span_attribute3<'s>( + scope: &mut v8::HandleScope<'s>, + state: &mut OpState, + #[smi] capacity: u32, + key1: v8::Local<'s, v8::Value>, + value1: v8::Local<'s, v8::Value>, + key2: v8::Local<'s, v8::Value>, + value2: v8::Local<'s, v8::Value>, + key3: v8::Local<'s, v8::Value>, + value3: v8::Local<'s, v8::Value>, +) { + if let Some(temporary_span) = state.try_borrow_mut::<TemporarySpan>() { + temporary_span.0.attributes.reserve_exact( + (capacity as usize) - temporary_span.0.attributes.capacity(), + ); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key1, value1); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key2, value2); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key3, value3); + } +} + +#[op2(fast)] +fn op_otel_span_set_dropped( + state: &mut OpState, + #[smi] dropped_attributes_count: u32, + #[smi] dropped_links_count: u32, + #[smi] dropped_events_count: u32, +) { + if let Some(temporary_span) = state.try_borrow_mut::<TemporarySpan>() { + temporary_span.0.dropped_attributes_count = dropped_attributes_count; + temporary_span.0.links.dropped_count = dropped_links_count; + temporary_span.0.events.dropped_count = dropped_events_count; + } +} + +#[op2(fast)] +fn op_otel_span_flush(state: &mut OpState) { + let Some(temporary_span) = state.try_take::<TemporarySpan>() else { + return; + }; + + let Some((span_processor, _)) = OTEL_PROCESSORS.get() else { + return; + }; + + span_processor.on_end(temporary_span.0); +} diff --git a/runtime/ops/permissions.rs b/runtime/ops/permissions.rs index 1dbc85259..9ad963f3b 100644 --- a/runtime/ops/permissions.rs +++ b/runtime/ops/permissions.rs @@ -2,8 +2,6 @@ use ::deno_permissions::PermissionState; use ::deno_permissions::PermissionsContainer; -use deno_core::error::custom_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use serde::Deserialize; @@ -47,12 +45,26 @@ impl From<PermissionState> for PermissionStatus { } } +#[derive(Debug, thiserror::Error)] +pub enum PermissionError { + #[error("No such permission name: {0}")] + InvalidPermissionName(String), + #[error("{0}")] + PathResolve(#[from] ::deno_permissions::PathResolveError), + #[error("{0}")] + NetDescriptorParse(#[from] ::deno_permissions::NetDescriptorParseError), + #[error("{0}")] + SysDescriptorParse(#[from] ::deno_permissions::SysDescriptorParseError), + #[error("{0}")] + RunDescriptorParse(#[from] ::deno_permissions::RunDescriptorParseError), +} + #[op2] #[serde] pub fn op_query_permission( state: &mut OpState, #[serde] args: PermissionArgs, -) -> Result<PermissionStatus, AnyError> { +) -> Result<PermissionStatus, PermissionError> { let permissions = state.borrow::<PermissionsContainer>(); let perm = match args.name.as_ref() { "read" => permissions.query_read(args.path.as_deref())?, @@ -62,12 +74,7 @@ pub fn op_query_permission( "sys" => permissions.query_sys(args.kind.as_deref())?, "run" => permissions.query_run(args.command.as_deref())?, "ffi" => permissions.query_ffi(args.path.as_deref())?, - n => { - return Err(custom_error( - "ReferenceError", - format!("No such permission name: {n}"), - )) - } + _ => return Err(PermissionError::InvalidPermissionName(args.name)), }; Ok(PermissionStatus::from(perm)) } @@ -77,7 +84,7 @@ pub fn op_query_permission( pub fn op_revoke_permission( state: &mut OpState, #[serde] args: PermissionArgs, -) -> Result<PermissionStatus, AnyError> { +) -> Result<PermissionStatus, PermissionError> { let permissions = state.borrow::<PermissionsContainer>(); let perm = match args.name.as_ref() { "read" => permissions.revoke_read(args.path.as_deref())?, @@ -87,12 +94,7 @@ pub fn op_revoke_permission( "sys" => permissions.revoke_sys(args.kind.as_deref())?, "run" => permissions.revoke_run(args.command.as_deref())?, "ffi" => permissions.revoke_ffi(args.path.as_deref())?, - n => { - return Err(custom_error( - "ReferenceError", - format!("No such permission name: {n}"), - )) - } + _ => return Err(PermissionError::InvalidPermissionName(args.name)), }; Ok(PermissionStatus::from(perm)) } @@ -102,7 +104,7 @@ pub fn op_revoke_permission( pub fn op_request_permission( state: &mut OpState, #[serde] args: PermissionArgs, -) -> Result<PermissionStatus, AnyError> { +) -> Result<PermissionStatus, PermissionError> { let permissions = state.borrow::<PermissionsContainer>(); let perm = match args.name.as_ref() { "read" => permissions.request_read(args.path.as_deref())?, @@ -112,12 +114,7 @@ pub fn op_request_permission( "sys" => permissions.request_sys(args.kind.as_deref())?, "run" => permissions.request_run(args.command.as_deref())?, "ffi" => permissions.request_ffi(args.path.as_deref())?, - n => { - return Err(custom_error( - "ReferenceError", - format!("No such permission name: {n}"), - )) - } + _ => return Err(PermissionError::InvalidPermissionName(args.name)), }; Ok(PermissionStatus::from(perm)) } diff --git a/runtime/ops/process.rs b/runtime/ops/process.rs index f6555e932..ee2f660dc 100644 --- a/runtime/ops/process.rs +++ b/runtime/ops/process.rs @@ -1,8 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::anyhow::Context; -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::serde_json; use deno_core::AsyncMutFuture; @@ -35,6 +32,7 @@ use tokio::process::Command; #[cfg(windows)] use std::os::windows::process::CommandExt; +use crate::ops::signal::SignalError; #[cfg(unix)] use std::os::unix::prelude::ExitStatusExt; #[cfg(unix)] @@ -105,11 +103,12 @@ impl StdioOrRid { pub fn as_stdio( &self, state: &mut OpState, - ) -> Result<std::process::Stdio, AnyError> { + ) -> Result<std::process::Stdio, ProcessError> { match &self { StdioOrRid::Stdio(val) => Ok(val.as_stdio()), StdioOrRid::Rid(rid) => { FileResource::with_file(state, *rid, |file| Ok(file.as_stdio()?)) + .map_err(ProcessError::Resource) } } } @@ -191,6 +190,41 @@ pub struct SpawnArgs { needs_npm_process_state: bool, } +#[derive(Debug, thiserror::Error)] +pub enum ProcessError { + #[error("Failed to spawn '{command}': {error}")] + SpawnFailed { + command: String, + #[source] + error: Box<ProcessError>, + }, + #[error("{0}")] + Io(#[from] std::io::Error), + #[cfg(unix)] + #[error(transparent)] + Nix(nix::Error), + #[error("failed resolving cwd: {0}")] + FailedResolvingCwd(#[source] std::io::Error), + #[error(transparent)] + Permission(#[from] deno_permissions::PermissionCheckError), + #[error(transparent)] + RunPermission(#[from] CheckRunPermissionError), + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error(transparent)] + BorrowMut(std::cell::BorrowMutError), + #[error(transparent)] + Which(which::Error), + #[error("Child process has already terminated.")] + ChildProcessAlreadyTerminated, + #[error("Invalid pid")] + InvalidPid, + #[error(transparent)] + Signal(#[from] SignalError), + #[error("Missing cmd")] + MissingCmd, // only for Deno.run +} + #[derive(Deserialize)] #[serde(rename_all = "camelCase")] pub struct ChildStdio { @@ -208,7 +242,7 @@ pub struct ChildStatus { } impl TryFrom<ExitStatus> for ChildStatus { - type Error = AnyError; + type Error = SignalError; fn try_from(status: ExitStatus) -> Result<Self, Self::Error> { let code = status.code(); @@ -259,7 +293,7 @@ type CreateCommand = ( pub fn npm_process_state_tempfile( contents: &[u8], -) -> Result<deno_io::RawIoHandle, AnyError> { +) -> Result<deno_io::RawIoHandle, std::io::Error> { let mut temp_file = tempfile::tempfile()?; temp_file.write_all(contents)?; let handle = temp_file.into_raw_io_handle(); @@ -301,7 +335,7 @@ fn create_command( state: &mut OpState, mut args: SpawnArgs, api_name: &str, -) -> Result<CreateCommand, AnyError> { +) -> Result<CreateCommand, ProcessError> { let maybe_npm_process_state = if args.needs_npm_process_state { let provider = state.borrow::<NpmProcessStateProviderRc>(); let process_state = provider.get_npm_process_state(); @@ -505,7 +539,7 @@ fn spawn_child( ipc_pipe_rid: Option<ResourceId>, extra_pipe_rids: Vec<Option<ResourceId>>, detached: bool, -) -> Result<Child, AnyError> { +) -> Result<Child, ProcessError> { let mut command = tokio::process::Command::from(command); // TODO(@crowlkats): allow detaching processes. // currently deno will orphan a process when exiting with an error or Deno.exit() @@ -554,10 +588,10 @@ fn spawn_child( } } - return Err(AnyError::from(err).context(format!( - "Failed to spawn '{}'", - command.get_program().to_string_lossy() - ))); + return Err(ProcessError::SpawnFailed { + command: command.get_program().to_string_lossy().to_string(), + error: Box::new(err.into()), + }); } }; @@ -600,11 +634,19 @@ fn compute_run_cmd_and_check_permissions( arg_clear_env: bool, state: &mut OpState, api_name: &str, -) -> Result<(PathBuf, RunEnv), AnyError> { - let run_env = compute_run_env(arg_cwd, arg_envs, arg_clear_env) - .with_context(|| format!("Failed to spawn '{}'", arg_cmd))?; - let cmd = resolve_cmd(arg_cmd, &run_env) - .with_context(|| format!("Failed to spawn '{}'", arg_cmd))?; +) -> Result<(PathBuf, RunEnv), ProcessError> { + let run_env = + compute_run_env(arg_cwd, arg_envs, arg_clear_env).map_err(|e| { + ProcessError::SpawnFailed { + command: arg_cmd.to_string(), + error: Box::new(e), + } + })?; + let cmd = + resolve_cmd(arg_cmd, &run_env).map_err(|e| ProcessError::SpawnFailed { + command: arg_cmd.to_string(), + error: Box::new(e), + })?; check_run_permission( state, &RunQueryDescriptor::Path { @@ -631,9 +673,10 @@ fn compute_run_env( arg_cwd: Option<&str>, arg_envs: &[(String, String)], arg_clear_env: bool, -) -> Result<RunEnv, AnyError> { +) -> Result<RunEnv, ProcessError> { #[allow(clippy::disallowed_methods)] - let cwd = std::env::current_dir().context("failed resolving cwd")?; + let cwd = + std::env::current_dir().map_err(ProcessError::FailedResolvingCwd)?; let cwd = arg_cwd .map(|cwd_arg| resolve_path(cwd_arg, &cwd)) .unwrap_or(cwd); @@ -670,7 +713,7 @@ fn compute_run_env( Ok(RunEnv { envs, cwd }) } -fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result<PathBuf, AnyError> { +fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result<PathBuf, ProcessError> { let is_path = cmd.contains('/'); #[cfg(windows)] let is_path = is_path || cmd.contains('\\') || Path::new(&cmd).is_absolute(); @@ -683,7 +726,7 @@ fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result<PathBuf, AnyError> { Err(which::Error::CannotFindBinaryPath) => { Err(std::io::Error::from(std::io::ErrorKind::NotFound).into()) } - Err(err) => Err(err.into()), + Err(err) => Err(ProcessError::Which(err)), } } } @@ -692,12 +735,20 @@ fn resolve_path(path: &str, cwd: &Path) -> PathBuf { deno_path_util::normalize_path(cwd.join(path)) } +#[derive(Debug, thiserror::Error)] +pub enum CheckRunPermissionError { + #[error(transparent)] + Permission(#[from] deno_permissions::PermissionCheckError), + #[error("{0}")] + Other(deno_core::error::AnyError), +} + fn check_run_permission( state: &mut OpState, cmd: &RunQueryDescriptor, run_env: &RunEnv, api_name: &str, -) -> Result<(), AnyError> { +) -> Result<(), CheckRunPermissionError> { let permissions = state.borrow_mut::<PermissionsContainer>(); if !permissions.query_run_all(api_name) { // error the same on all platforms @@ -705,13 +756,16 @@ fn check_run_permission( if !env_var_names.is_empty() { // we don't allow users to launch subprocesses with any LD_ or DYLD_* // env vars set because this allows executing code (ex. LD_PRELOAD) - return Err(deno_core::error::custom_error( - "NotCapable", - format!( - "Requires --allow-all permissions to spawn subprocess with {} environment variable{}.", - env_var_names.join(", "), - if env_var_names.len() != 1 { "s" } else { "" } - ) + return Err(CheckRunPermissionError::Other( + deno_core::error::custom_error( + "NotCapable", + format!( + "Requires --allow-run permissions to spawn subprocess with {0} environment variable{1}. Alternatively, spawn with {2} environment variable{1} unset.", + env_var_names.join(", "), + if env_var_names.len() != 1 { "s" } else { "" }, + if env_var_names.len() != 1 { "these" } else { "the" } + ), + ), )); } permissions.check_run(cmd, api_name)?; @@ -754,7 +808,7 @@ fn op_spawn_child( state: &mut OpState, #[serde] args: SpawnArgs, #[string] api_name: String, -) -> Result<Child, AnyError> { +) -> Result<Child, ProcessError> { let detached = args.detached; let (command, pipe_rid, extra_pipe_rids, handles_to_close) = create_command(state, args, &api_name)?; @@ -771,16 +825,23 @@ fn op_spawn_child( async fn op_spawn_wait( state: Rc<RefCell<OpState>>, #[smi] rid: ResourceId, -) -> Result<ChildStatus, AnyError> { +) -> Result<ChildStatus, ProcessError> { let resource = state .borrow_mut() .resource_table - .get::<ChildResource>(rid)?; - let result = resource.0.try_borrow_mut()?.wait().await?.try_into(); + .get::<ChildResource>(rid) + .map_err(ProcessError::Resource)?; + let result = resource + .0 + .try_borrow_mut() + .map_err(ProcessError::BorrowMut)? + .wait() + .await? + .try_into()?; if let Ok(resource) = state.borrow_mut().resource_table.take_any(rid) { resource.close(); } - result + Ok(result) } #[op2] @@ -788,16 +849,14 @@ async fn op_spawn_wait( fn op_spawn_sync( state: &mut OpState, #[serde] args: SpawnArgs, -) -> Result<SpawnOutput, AnyError> { +) -> Result<SpawnOutput, ProcessError> { let stdout = matches!(args.stdio.stdout, StdioOrRid::Stdio(Stdio::Piped)); let stderr = matches!(args.stdio.stderr, StdioOrRid::Stdio(Stdio::Piped)); let (mut command, _, _, _) = create_command(state, args, "Deno.Command().outputSync()")?; - let output = command.output().with_context(|| { - format!( - "Failed to spawn '{}'", - command.get_program().to_string_lossy() - ) + let output = command.output().map_err(|e| ProcessError::SpawnFailed { + command: command.get_program().to_string_lossy().to_string(), + error: Box::new(e.into()), })?; Ok(SpawnOutput { @@ -820,17 +879,15 @@ fn op_spawn_kill( state: &mut OpState, #[smi] rid: ResourceId, #[string] signal: String, -) -> Result<(), AnyError> { +) -> Result<(), ProcessError> { if let Ok(child_resource) = state.resource_table.get::<ChildResource>(rid) { deprecated::kill(child_resource.1 as i32, &signal)?; return Ok(()); } - Err(type_error("Child process has already terminated.")) + Err(ProcessError::ChildProcessAlreadyTerminated) } mod deprecated { - use deno_core::anyhow; - use super::*; #[derive(Deserialize)] @@ -876,9 +933,9 @@ mod deprecated { pub fn op_run( state: &mut OpState, #[serde] run_args: RunArgs, - ) -> Result<RunInfo, AnyError> { + ) -> Result<RunInfo, ProcessError> { let args = run_args.cmd; - let cmd = args.first().ok_or_else(|| anyhow::anyhow!("Missing cmd"))?; + let cmd = args.first().ok_or(ProcessError::MissingCmd)?; let (cmd, run_env) = compute_run_cmd_and_check_permissions( cmd, run_args.cwd.as_deref(), @@ -990,11 +1047,12 @@ mod deprecated { pub async fn op_run_status( state: Rc<RefCell<OpState>>, #[smi] rid: ResourceId, - ) -> Result<ProcessStatus, AnyError> { + ) -> Result<ProcessStatus, ProcessError> { let resource = state .borrow_mut() .resource_table - .get::<ChildResource>(rid)?; + .get::<ChildResource>(rid) + .map_err(ProcessError::Resource)?; let mut child = resource.borrow_mut().await; let run_status = child.wait().await?; let code = run_status.code(); @@ -1017,17 +1075,17 @@ mod deprecated { } #[cfg(unix)] - pub fn kill(pid: i32, signal: &str) -> Result<(), AnyError> { + pub fn kill(pid: i32, signal: &str) -> Result<(), ProcessError> { let signo = super::super::signal::signal_str_to_int(signal)?; use nix::sys::signal::kill as unix_kill; use nix::sys::signal::Signal; use nix::unistd::Pid; - let sig = Signal::try_from(signo)?; - unix_kill(Pid::from_raw(pid), Option::Some(sig)).map_err(AnyError::from) + let sig = Signal::try_from(signo).map_err(ProcessError::Nix)?; + unix_kill(Pid::from_raw(pid), Some(sig)).map_err(ProcessError::Nix) } #[cfg(not(unix))] - pub fn kill(pid: i32, signal: &str) -> Result<(), AnyError> { + pub fn kill(pid: i32, signal: &str) -> Result<(), ProcessError> { use std::io::Error; use std::io::ErrorKind::NotFound; use winapi::shared::minwindef::DWORD; @@ -1041,9 +1099,9 @@ mod deprecated { use winapi::um::winnt::PROCESS_TERMINATE; if !matches!(signal, "SIGKILL" | "SIGTERM") { - Err(type_error(format!("Invalid signal: {signal}"))) + Err(SignalError::InvalidSignalStr(signal.to_string()).into()) } else if pid <= 0 { - Err(type_error("Invalid pid")) + Err(ProcessError::InvalidPid) } else { let handle = // SAFETY: winapi call @@ -1077,11 +1135,10 @@ mod deprecated { #[smi] pid: i32, #[string] signal: String, #[string] api_name: String, - ) -> Result<(), AnyError> { + ) -> Result<(), ProcessError> { state .borrow_mut::<PermissionsContainer>() .check_run_all(&api_name)?; - kill(pid, &signal)?; - Ok(()) + kill(pid, &signal) } } diff --git a/runtime/ops/runtime.rs b/runtime/ops/runtime.rs index 419274ebd..8d54783fc 100644 --- a/runtime/ops/runtime.rs +++ b/runtime/ops/runtime.rs @@ -1,6 +1,5 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::AnyError; use deno_core::op2; use deno_core::ModuleSpecifier; use deno_core::OpState; @@ -16,10 +15,9 @@ deno_core::extension!( #[op2] #[string] -fn op_main_module(state: &mut OpState) -> Result<String, AnyError> { +fn op_main_module(state: &mut OpState) -> String { let main_url = state.borrow::<ModuleSpecifier>(); - let main_path = main_url.to_string(); - Ok(main_path) + main_url.to_string() } /// This is an op instead of being done at initialization time because diff --git a/runtime/ops/signal.rs b/runtime/ops/signal.rs index ebc6db6d1..e1e4ab68b 100644 --- a/runtime/ops/signal.rs +++ b/runtime/ops/signal.rs @@ -1,6 +1,4 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::AsyncRefCell; use deno_core::CancelFuture; @@ -46,6 +44,42 @@ deno_core::extension!( } ); +#[derive(Debug, thiserror::Error)] +pub enum SignalError { + #[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "openbsd", + target_os = "openbsd", + target_os = "macos", + target_os = "solaris", + target_os = "illumos" + ))] + #[error("Invalid signal: {0}")] + InvalidSignalStr(String), + #[cfg(any( + target_os = "android", + target_os = "linux", + target_os = "openbsd", + target_os = "openbsd", + target_os = "macos", + target_os = "solaris", + target_os = "illumos" + ))] + #[error("Invalid signal: {0}")] + InvalidSignalInt(libc::c_int), + #[cfg(target_os = "windows")] + #[error("Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK), but got {0}")] + InvalidSignalStr(String), + #[cfg(target_os = "windows")] + #[error("Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK), but got {0}")] + InvalidSignalInt(libc::c_int), + #[error("Binding to signal '{0}' is not allowed")] + SignalNotAllowed(String), + #[error("{0}")] + Io(#[from] std::io::Error), +} + #[cfg(unix)] #[derive(Default)] struct SignalState { @@ -147,438 +181,217 @@ impl Resource for SignalStreamResource { } } -#[cfg(target_os = "freebsd")] -pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> { - match s { - "SIGHUP" => Ok(1), - "SIGINT" => Ok(2), - "SIGQUIT" => Ok(3), - "SIGILL" => Ok(4), - "SIGTRAP" => Ok(5), - "SIGIOT" => Ok(6), - "SIGABRT" => Ok(6), - "SIGEMT" => Ok(7), - "SIGFPE" => Ok(8), - "SIGKILL" => Ok(9), - "SIGBUS" => Ok(10), - "SIGSEGV" => Ok(11), - "SIGSYS" => Ok(12), - "SIGPIPE" => Ok(13), - "SIGALRM" => Ok(14), - "SIGTERM" => Ok(15), - "SIGURG" => Ok(16), - "SIGSTOP" => Ok(17), - "SIGTSTP" => Ok(18), - "SIGCONT" => Ok(19), - "SIGCHLD" => Ok(20), - "SIGTTIN" => Ok(21), - "SIGTTOU" => Ok(22), - "SIGIO" => Ok(23), - "SIGXCPU" => Ok(24), - "SIGXFSZ" => Ok(25), - "SIGVTALRM" => Ok(26), - "SIGPROF" => Ok(27), - "SIGWINCH" => Ok(28), - "SIGINFO" => Ok(29), - "SIGUSR1" => Ok(30), - "SIGUSR2" => Ok(31), - "SIGTHR" => Ok(32), - "SIGLIBRT" => Ok(33), - _ => Err(type_error(format!("Invalid signal : {}", s))), - } +macro_rules! first_literal { + ($head:literal $(, $tail:literal)*) => { + $head + }; } +macro_rules! signal_dict { + ($(($number:literal, $($name:literal)|+)),*) => { + pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, SignalError> { + match s { + $($($name)|* => Ok($number),)* + _ => Err(SignalError::InvalidSignalStr(s.to_string())), + } + } -#[cfg(target_os = "freebsd")] -pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { - match s { - 1 => Ok("SIGHUP"), - 2 => Ok("SIGINT"), - 3 => Ok("SIGQUIT"), - 4 => Ok("SIGILL"), - 5 => Ok("SIGTRAP"), - 6 => Ok("SIGABRT"), - 7 => Ok("SIGEMT"), - 8 => Ok("SIGFPE"), - 9 => Ok("SIGKILL"), - 10 => Ok("SIGBUS"), - 11 => Ok("SIGSEGV"), - 12 => Ok("SIGSYS"), - 13 => Ok("SIGPIPE"), - 14 => Ok("SIGALRM"), - 15 => Ok("SIGTERM"), - 16 => Ok("SIGURG"), - 17 => Ok("SIGSTOP"), - 18 => Ok("SIGTSTP"), - 19 => Ok("SIGCONT"), - 20 => Ok("SIGCHLD"), - 21 => Ok("SIGTTIN"), - 22 => Ok("SIGTTOU"), - 23 => Ok("SIGIO"), - 24 => Ok("SIGXCPU"), - 25 => Ok("SIGXFSZ"), - 26 => Ok("SIGVTALRM"), - 27 => Ok("SIGPROF"), - 28 => Ok("SIGWINCH"), - 29 => Ok("SIGINFO"), - 30 => Ok("SIGUSR1"), - 31 => Ok("SIGUSR2"), - 32 => Ok("SIGTHR"), - 33 => Ok("SIGLIBRT"), - _ => Err(type_error(format!("Invalid signal : {}", s))), + pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, SignalError> { + match s { + $($number => Ok(first_literal!($($name),+)),)* + _ => Err(SignalError::InvalidSignalInt(s)), + } + } } } -#[cfg(target_os = "openbsd")] -pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> { - match s { - "SIGHUP" => Ok(1), - "SIGINT" => Ok(2), - "SIGQUIT" => Ok(3), - "SIGILL" => Ok(4), - "SIGTRAP" => Ok(5), - "SIGIOT" => Ok(6), - "SIGABRT" => Ok(6), - "SIGEMT" => Ok(7), - "SIGFPE" => Ok(8), - "SIGKILL" => Ok(9), - "SIGBUS" => Ok(10), - "SIGSEGV" => Ok(11), - "SIGSYS" => Ok(12), - "SIGPIPE" => Ok(13), - "SIGALRM" => Ok(14), - "SIGTERM" => Ok(15), - "SIGURG" => Ok(16), - "SIGSTOP" => Ok(17), - "SIGTSTP" => Ok(18), - "SIGCONT" => Ok(19), - "SIGCHLD" => Ok(20), - "SIGTTIN" => Ok(21), - "SIGTTOU" => Ok(22), - "SIGIO" => Ok(23), - "SIGXCPU" => Ok(24), - "SIGXFSZ" => Ok(25), - "SIGVTALRM" => Ok(26), - "SIGPROF" => Ok(27), - "SIGWINCH" => Ok(28), - "SIGINFO" => Ok(29), - "SIGUSR1" => Ok(30), - "SIGUSR2" => Ok(31), - "SIGTHR" => Ok(32), - _ => Err(type_error(format!("Invalid signal : {}", s))), - } -} +#[cfg(target_os = "freebsd")] +signal_dict!( + (1, "SIGHUP"), + (2, "SIGINT"), + (3, "SIGQUIT"), + (4, "SIGILL"), + (5, "SIGTRAP"), + (6, "SIGABRT" | "SIGIOT"), + (7, "SIGEMT"), + (8, "SIGFPE"), + (9, "SIGKILL"), + (10, "SIGBUS"), + (11, "SIGSEGV"), + (12, "SIGSYS"), + (13, "SIGPIPE"), + (14, "SIGALRM"), + (15, "SIGTERM"), + (16, "SIGURG"), + (17, "SIGSTOP"), + (18, "SIGTSTP"), + (19, "SIGCONT"), + (20, "SIGCHLD"), + (21, "SIGTTIN"), + (22, "SIGTTOU"), + (23, "SIGIO"), + (24, "SIGXCPU"), + (25, "SIGXFSZ"), + (26, "SIGVTALRM"), + (27, "SIGPROF"), + (28, "SIGWINCH"), + (29, "SIGINFO"), + (30, "SIGUSR1"), + (31, "SIGUSR2"), + (32, "SIGTHR"), + (33, "SIGLIBRT") +); #[cfg(target_os = "openbsd")] -pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { - match s { - 1 => Ok("SIGHUP"), - 2 => Ok("SIGINT"), - 3 => Ok("SIGQUIT"), - 4 => Ok("SIGILL"), - 5 => Ok("SIGTRAP"), - 6 => Ok("SIGABRT"), - 7 => Ok("SIGEMT"), - 8 => Ok("SIGFPE"), - 9 => Ok("SIGKILL"), - 10 => Ok("SIGBUS"), - 11 => Ok("SIGSEGV"), - 12 => Ok("SIGSYS"), - 13 => Ok("SIGPIPE"), - 14 => Ok("SIGALRM"), - 15 => Ok("SIGTERM"), - 16 => Ok("SIGURG"), - 17 => Ok("SIGSTOP"), - 18 => Ok("SIGTSTP"), - 19 => Ok("SIGCONT"), - 20 => Ok("SIGCHLD"), - 21 => Ok("SIGTTIN"), - 22 => Ok("SIGTTOU"), - 23 => Ok("SIGIO"), - 24 => Ok("SIGXCPU"), - 25 => Ok("SIGXFSZ"), - 26 => Ok("SIGVTALRM"), - 27 => Ok("SIGPROF"), - 28 => Ok("SIGWINCH"), - 29 => Ok("SIGINFO"), - 30 => Ok("SIGUSR1"), - 31 => Ok("SIGUSR2"), - 32 => Ok("SIGTHR"), - _ => Err(type_error(format!("Invalid signal : {}", s))), - } -} - -#[cfg(any(target_os = "android", target_os = "linux"))] -pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> { - match s { - "SIGHUP" => Ok(1), - "SIGINT" => Ok(2), - "SIGQUIT" => Ok(3), - "SIGILL" => Ok(4), - "SIGTRAP" => Ok(5), - "SIGIOT" => Ok(6), - "SIGABRT" => Ok(6), - "SIGBUS" => Ok(7), - "SIGFPE" => Ok(8), - "SIGKILL" => Ok(9), - "SIGUSR1" => Ok(10), - "SIGSEGV" => Ok(11), - "SIGUSR2" => Ok(12), - "SIGPIPE" => Ok(13), - "SIGALRM" => Ok(14), - "SIGTERM" => Ok(15), - "SIGSTKFLT" => Ok(16), - "SIGCHLD" => Ok(17), - "SIGCONT" => Ok(18), - "SIGSTOP" => Ok(19), - "SIGTSTP" => Ok(20), - "SIGTTIN" => Ok(21), - "SIGTTOU" => Ok(22), - "SIGURG" => Ok(23), - "SIGXCPU" => Ok(24), - "SIGXFSZ" => Ok(25), - "SIGVTALRM" => Ok(26), - "SIGPROF" => Ok(27), - "SIGWINCH" => Ok(28), - "SIGIO" | "SIGPOLL" => Ok(29), - "SIGPWR" => Ok(30), - "SIGSYS" | "SIGUNUSED" => Ok(31), - _ => Err(type_error(format!("Invalid signal : {s}"))), - } -} +signal_dict!( + (1, "SIGHUP"), + (2, "SIGINT"), + (3, "SIGQUIT"), + (4, "SIGILL"), + (5, "SIGTRAP"), + (6, "SIGABRT" | "SIGIOT"), + (7, "SIGEMT"), + (8, "SIGKILL"), + (10, "SIGBUS"), + (11, "SIGSEGV"), + (12, "SIGSYS"), + (13, "SIGPIPE"), + (14, "SIGALRM"), + (15, "SIGTERM"), + (16, "SIGURG"), + (17, "SIGSTOP"), + (18, "SIGTSTP"), + (19, "SIGCONT"), + (20, "SIGCHLD"), + (21, "SIGTTIN"), + (22, "SIGTTOU"), + (23, "SIGIO"), + (24, "SIGXCPU"), + (25, "SIGXFSZ"), + (26, "SIGVTALRM"), + (27, "SIGPROF"), + (28, "SIGWINCH"), + (29, "SIGINFO"), + (30, "SIGUSR1"), + (31, "SIGUSR2"), + (32, "SIGTHR") +); #[cfg(any(target_os = "android", target_os = "linux"))] -pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { - match s { - 1 => Ok("SIGHUP"), - 2 => Ok("SIGINT"), - 3 => Ok("SIGQUIT"), - 4 => Ok("SIGILL"), - 5 => Ok("SIGTRAP"), - 6 => Ok("SIGABRT"), - 7 => Ok("SIGBUS"), - 8 => Ok("SIGFPE"), - 9 => Ok("SIGKILL"), - 10 => Ok("SIGUSR1"), - 11 => Ok("SIGSEGV"), - 12 => Ok("SIGUSR2"), - 13 => Ok("SIGPIPE"), - 14 => Ok("SIGALRM"), - 15 => Ok("SIGTERM"), - 16 => Ok("SIGSTKFLT"), - 17 => Ok("SIGCHLD"), - 18 => Ok("SIGCONT"), - 19 => Ok("SIGSTOP"), - 20 => Ok("SIGTSTP"), - 21 => Ok("SIGTTIN"), - 22 => Ok("SIGTTOU"), - 23 => Ok("SIGURG"), - 24 => Ok("SIGXCPU"), - 25 => Ok("SIGXFSZ"), - 26 => Ok("SIGVTALRM"), - 27 => Ok("SIGPROF"), - 28 => Ok("SIGWINCH"), - 29 => Ok("SIGIO"), - 30 => Ok("SIGPWR"), - 31 => Ok("SIGSYS"), - _ => Err(type_error(format!("Invalid signal : {s}"))), - } -} - -#[cfg(target_os = "macos")] -pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> { - match s { - "SIGHUP" => Ok(1), - "SIGINT" => Ok(2), - "SIGQUIT" => Ok(3), - "SIGILL" => Ok(4), - "SIGTRAP" => Ok(5), - "SIGIOT" => Ok(6), - "SIGABRT" => Ok(6), - "SIGEMT" => Ok(7), - "SIGFPE" => Ok(8), - "SIGKILL" => Ok(9), - "SIGBUS" => Ok(10), - "SIGSEGV" => Ok(11), - "SIGSYS" => Ok(12), - "SIGPIPE" => Ok(13), - "SIGALRM" => Ok(14), - "SIGTERM" => Ok(15), - "SIGURG" => Ok(16), - "SIGSTOP" => Ok(17), - "SIGTSTP" => Ok(18), - "SIGCONT" => Ok(19), - "SIGCHLD" => Ok(20), - "SIGTTIN" => Ok(21), - "SIGTTOU" => Ok(22), - "SIGIO" => Ok(23), - "SIGXCPU" => Ok(24), - "SIGXFSZ" => Ok(25), - "SIGVTALRM" => Ok(26), - "SIGPROF" => Ok(27), - "SIGWINCH" => Ok(28), - "SIGINFO" => Ok(29), - "SIGUSR1" => Ok(30), - "SIGUSR2" => Ok(31), - _ => Err(type_error(format!("Invalid signal: {s}"))), - } -} +signal_dict!( + (1, "SIGHUP"), + (2, "SIGINT"), + (3, "SIGQUIT"), + (4, "SIGILL"), + (5, "SIGTRAP"), + (6, "SIGABRT" | "SIGIOT"), + (7, "SIGBUS"), + (8, "SIGFPE"), + (9, "SIGKILL"), + (10, "SIGUSR1"), + (11, "SIGSEGV"), + (12, "SIGUSR2"), + (13, "SIGPIPE"), + (14, "SIGALRM"), + (15, "SIGTERM"), + (16, "SIGSTKFLT"), + (17, "SIGCHLD"), + (18, "SIGCONT"), + (19, "SIGSTOP"), + (20, "SIGTSTP"), + (21, "SIGTTIN"), + (22, "SIGTTOU"), + (23, "SIGURG"), + (24, "SIGXCPU"), + (25, "SIGXFSZ"), + (26, "SIGVTALRM"), + (27, "SIGPROF"), + (28, "SIGWINCH"), + (29, "SIGIO" | "SIGPOLL"), + (30, "SIGPWR"), + (31, "SIGSYS" | "SIGUNUSED") +); #[cfg(target_os = "macos")] -pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { - match s { - 1 => Ok("SIGHUP"), - 2 => Ok("SIGINT"), - 3 => Ok("SIGQUIT"), - 4 => Ok("SIGILL"), - 5 => Ok("SIGTRAP"), - 6 => Ok("SIGABRT"), - 7 => Ok("SIGEMT"), - 8 => Ok("SIGFPE"), - 9 => Ok("SIGKILL"), - 10 => Ok("SIGBUS"), - 11 => Ok("SIGSEGV"), - 12 => Ok("SIGSYS"), - 13 => Ok("SIGPIPE"), - 14 => Ok("SIGALRM"), - 15 => Ok("SIGTERM"), - 16 => Ok("SIGURG"), - 17 => Ok("SIGSTOP"), - 18 => Ok("SIGTSTP"), - 19 => Ok("SIGCONT"), - 20 => Ok("SIGCHLD"), - 21 => Ok("SIGTTIN"), - 22 => Ok("SIGTTOU"), - 23 => Ok("SIGIO"), - 24 => Ok("SIGXCPU"), - 25 => Ok("SIGXFSZ"), - 26 => Ok("SIGVTALRM"), - 27 => Ok("SIGPROF"), - 28 => Ok("SIGWINCH"), - 29 => Ok("SIGINFO"), - 30 => Ok("SIGUSR1"), - 31 => Ok("SIGUSR2"), - _ => Err(type_error(format!("Invalid signal: {s}"))), - } -} - -#[cfg(any(target_os = "solaris", target_os = "illumos"))] -pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> { - match s { - "SIGHUP" => Ok(1), - "SIGINT" => Ok(2), - "SIGQUIT" => Ok(3), - "SIGILL" => Ok(4), - "SIGTRAP" => Ok(5), - "SIGIOT" => Ok(6), - "SIGABRT" => Ok(6), - "SIGEMT" => Ok(7), - "SIGFPE" => Ok(8), - "SIGKILL" => Ok(9), - "SIGBUS" => Ok(10), - "SIGSEGV" => Ok(11), - "SIGSYS" => Ok(12), - "SIGPIPE" => Ok(13), - "SIGALRM" => Ok(14), - "SIGTERM" => Ok(15), - "SIGUSR1" => Ok(16), - "SIGUSR2" => Ok(17), - "SIGCLD" => Ok(18), - "SIGCHLD" => Ok(18), - "SIGPWR" => Ok(19), - "SIGWINCH" => Ok(20), - "SIGURG" => Ok(21), - "SIGPOLL" => Ok(22), - "SIGIO" => Ok(22), - "SIGSTOP" => Ok(23), - "SIGTSTP" => Ok(24), - "SIGCONT" => Ok(25), - "SIGTTIN" => Ok(26), - "SIGTTOU" => Ok(27), - "SIGVTALRM" => Ok(28), - "SIGPROF" => Ok(29), - "SIGXCPU" => Ok(30), - "SIGXFSZ" => Ok(31), - "SIGWAITING" => Ok(32), - "SIGLWP" => Ok(33), - "SIGFREEZE" => Ok(34), - "SIGTHAW" => Ok(35), - "SIGCANCEL" => Ok(36), - "SIGLOST" => Ok(37), - "SIGXRES" => Ok(38), - "SIGJVM1" => Ok(39), - "SIGJVM2" => Ok(40), - _ => Err(type_error(format!("Invalid signal : {}", s))), - } -} +signal_dict!( + (1, "SIGHUP"), + (2, "SIGINT"), + (3, "SIGQUIT"), + (4, "SIGILL"), + (5, "SIGTRAP"), + (6, "SIGABRT" | "SIGIOT"), + (7, "SIGEMT"), + (8, "SIGFPE"), + (9, "SIGKILL"), + (10, "SIGBUS"), + (11, "SIGSEGV"), + (12, "SIGSYS"), + (13, "SIGPIPE"), + (14, "SIGALRM"), + (15, "SIGTERM"), + (16, "SIGURG"), + (17, "SIGSTOP"), + (18, "SIGTSTP"), + (19, "SIGCONT"), + (20, "SIGCHLD"), + (21, "SIGTTIN"), + (22, "SIGTTOU"), + (23, "SIGIO"), + (24, "SIGXCPU"), + (25, "SIGXFSZ"), + (26, "SIGVTALRM"), + (27, "SIGPROF"), + (28, "SIGWINCH"), + (29, "SIGINFO"), + (30, "SIGUSR1"), + (31, "SIGUSR2") +); #[cfg(any(target_os = "solaris", target_os = "illumos"))] -pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { - match s { - 1 => Ok("SIGHUP"), - 2 => Ok("SIGINT"), - 3 => Ok("SIGQUIT"), - 4 => Ok("SIGILL"), - 5 => Ok("SIGTRAP"), - 6 => Ok("SIGABRT"), - 7 => Ok("SIGEMT"), - 8 => Ok("SIGFPE"), - 9 => Ok("SIGKILL"), - 10 => Ok("SIGBUS"), - 11 => Ok("SIGSEGV"), - 12 => Ok("SIGSYS"), - 13 => Ok("SIGPIPE"), - 14 => Ok("SIGALRM"), - 15 => Ok("SIGTERM"), - 16 => Ok("SIGUSR1"), - 17 => Ok("SIGUSR2"), - 18 => Ok("SIGCHLD"), - 19 => Ok("SIGPWR"), - 20 => Ok("SIGWINCH"), - 21 => Ok("SIGURG"), - 22 => Ok("SIGPOLL"), - 23 => Ok("SIGSTOP"), - 24 => Ok("SIGTSTP"), - 25 => Ok("SIGCONT"), - 26 => Ok("SIGTTIN"), - 27 => Ok("SIGTTOU"), - 28 => Ok("SIGVTALRM"), - 29 => Ok("SIGPROF"), - 30 => Ok("SIGXCPU"), - 31 => Ok("SIGXFSZ"), - 32 => Ok("SIGWAITING"), - 33 => Ok("SIGLWP"), - 34 => Ok("SIGFREEZE"), - 35 => Ok("SIGTHAW"), - 36 => Ok("SIGCANCEL"), - 37 => Ok("SIGLOST"), - 38 => Ok("SIGXRES"), - 39 => Ok("SIGJVM1"), - 40 => Ok("SIGJVM2"), - _ => Err(type_error(format!("Invalid signal : {}", s))), - } -} - -#[cfg(target_os = "windows")] -pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> { - match s { - "SIGINT" => Ok(2), - "SIGBREAK" => Ok(21), - _ => Err(type_error( - "Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK).", - )), - } -} +signal_dict!( + (1, "SIGHUP"), + (2, "SIGINT"), + (3, "SIGQUIT"), + (4, "SIGILL"), + (5, "SIGTRAP"), + (6, "SIGABRT" | "SIGIOT"), + (7, "SIGEMT"), + (8, "SIGFPE"), + (9, "SIGKILL"), + (10, "SIGBUS"), + (11, "SIGSEGV"), + (12, "SIGSYS"), + (13, "SIGPIPE"), + (14, "SIGALRM"), + (15, "SIGTERM"), + (16, "SIGUSR1"), + (17, "SIGUSR2"), + (18, "SIGCHLD"), + (19, "SIGPWR"), + (20, "SIGWINCH"), + (21, "SIGURG"), + (22, "SIGPOLL"), + (23, "SIGSTOP"), + (24, "SIGTSTP"), + (25, "SIGCONT"), + (26, "SIGTTIN"), + (27, "SIGTTOU"), + (28, "SIGVTALRM"), + (29, "SIGPROF"), + (30, "SIGXCPU"), + (31, "SIGXFSZ"), + (32, "SIGWAITING"), + (33, "SIGLWP"), + (34, "SIGFREEZE"), + (35, "SIGTHAW"), + (36, "SIGCANCEL"), + (37, "SIGLOST"), + (38, "SIGXRES"), + (39, "SIGJVM1"), + (40, "SIGJVM2") +); #[cfg(target_os = "windows")] -pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { - match s { - 2 => Ok("SIGINT"), - 21 => Ok("SIGBREAK"), - _ => Err(type_error( - "Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK).", - )), - } -} +signal_dict!((2, "SIGINT"), (21, "SIGBREAK")); #[cfg(unix)] #[op2(fast)] @@ -586,12 +399,10 @@ pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { fn op_signal_bind( state: &mut OpState, #[string] sig: &str, -) -> Result<ResourceId, AnyError> { +) -> Result<ResourceId, SignalError> { let signo = signal_str_to_int(sig)?; if signal_hook_registry::FORBIDDEN.contains(&signo) { - return Err(type_error(format!( - "Binding to signal '{sig}' is not allowed", - ))); + return Err(SignalError::SignalNotAllowed(sig.to_string())); } let signal = AsyncRefCell::new(signal(SignalKind::from_raw(signo))?); @@ -625,7 +436,7 @@ fn op_signal_bind( fn op_signal_bind( state: &mut OpState, #[string] sig: &str, -) -> Result<ResourceId, AnyError> { +) -> Result<ResourceId, SignalError> { let signo = signal_str_to_int(sig)?; let resource = SignalStreamResource { signal: AsyncRefCell::new(match signo { @@ -649,7 +460,7 @@ fn op_signal_bind( async fn op_signal_poll( state: Rc<RefCell<OpState>>, #[smi] rid: ResourceId, -) -> Result<bool, AnyError> { +) -> Result<bool, deno_core::error::AnyError> { let resource = state .borrow_mut() .resource_table @@ -668,7 +479,7 @@ async fn op_signal_poll( pub fn op_signal_unbind( state: &mut OpState, #[smi] rid: ResourceId, -) -> Result<(), AnyError> { +) -> Result<(), deno_core::error::AnyError> { let resource = state.resource_table.take::<SignalStreamResource>(rid)?; #[cfg(unix)] diff --git a/runtime/ops/tty.rs b/runtime/ops/tty.rs index 5b49e3a24..7849185fa 100644 --- a/runtime/ops/tty.rs +++ b/runtime/ops/tty.rs @@ -2,7 +2,6 @@ use std::io::Error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::OpState; use rustyline::config::Configurer; @@ -64,6 +63,19 @@ deno_core::extension!( }, ); +#[derive(Debug, thiserror::Error)] +pub enum TtyError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error("{0}")] + Io(#[from] std::io::Error), + #[cfg(unix)] + #[error(transparent)] + Nix(nix::Error), + #[error(transparent)] + Other(deno_core::error::AnyError), +} + // ref: <https://learn.microsoft.com/en-us/windows/console/setconsolemode> #[cfg(windows)] const COOKED_MODE: DWORD = @@ -90,8 +102,11 @@ fn op_set_raw( rid: u32, is_raw: bool, cbreak: bool, -) -> Result<(), AnyError> { - let handle_or_fd = state.resource_table.get_fd(rid)?; +) -> Result<(), TtyError> { + let handle_or_fd = state + .resource_table + .get_fd(rid) + .map_err(TtyError::Resource)?; // From https://github.com/kkawakam/rustyline/blob/master/src/tty/windows.rs // and https://github.com/kkawakam/rustyline/blob/master/src/tty/unix.rs @@ -107,7 +122,7 @@ fn op_set_raw( let handle = handle_or_fd; if cbreak { - return Err(deno_core::error::not_supported()); + return Err(TtyError::Other(deno_core::error::not_supported())); } let mut original_mode: DWORD = 0; @@ -115,7 +130,7 @@ fn op_set_raw( if unsafe { consoleapi::GetConsoleMode(handle, &mut original_mode) } == FALSE { - return Err(Error::last_os_error().into()); + return Err(TtyError::Io(Error::last_os_error())); } let new_mode = if is_raw { @@ -185,7 +200,7 @@ fn op_set_raw( winapi::um::wincon::WriteConsoleInputW(handle, &record, 1, &mut 0) } == FALSE { - return Err(Error::last_os_error().into()); + return Err(TtyError::Io(Error::last_os_error())); } /* Wait for read thread to acknowledge the cancellation to ensure that nothing @@ -199,7 +214,7 @@ fn op_set_raw( // SAFETY: winapi call if unsafe { consoleapi::SetConsoleMode(handle, new_mode) } == FALSE { - return Err(Error::last_os_error().into()); + return Err(TtyError::Io(Error::last_os_error())); } Ok(()) @@ -244,14 +259,16 @@ fn op_set_raw( let tty_mode_store = state.borrow::<TtyModeStore>().clone(); let previous_mode = tty_mode_store.get(rid); - let raw_fd = handle_or_fd; + // SAFETY: Nix crate requires value to implement the AsFd trait + let raw_fd = unsafe { std::os::fd::BorrowedFd::borrow_raw(handle_or_fd) }; if is_raw { let mut raw = match previous_mode { Some(mode) => mode, None => { // Save original mode. - let original_mode = termios::tcgetattr(raw_fd)?; + let original_mode = + termios::tcgetattr(raw_fd).map_err(TtyError::Nix)?; tty_mode_store.set(rid, original_mode.clone()); original_mode } @@ -273,11 +290,13 @@ fn op_set_raw( } raw.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1; raw.control_chars[termios::SpecialCharacterIndices::VTIME as usize] = 0; - termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &raw)?; + termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &raw) + .map_err(TtyError::Nix)?; } else { // Try restore saved mode. if let Some(mode) = tty_mode_store.take(rid) { - termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &mode)?; + termios::tcsetattr(raw_fd, termios::SetArg::TCSADRAIN, &mode) + .map_err(TtyError::Nix)?; } } @@ -289,13 +308,16 @@ fn op_set_raw( fn op_console_size( state: &mut OpState, #[buffer] result: &mut [u32], -) -> Result<(), AnyError> { +) -> Result<(), TtyError> { fn check_console_size( state: &mut OpState, result: &mut [u32], rid: u32, - ) -> Result<(), AnyError> { - let fd = state.resource_table.get_fd(rid)?; + ) -> Result<(), TtyError> { + let fd = state + .resource_table + .get_fd(rid) + .map_err(TtyError::Resource)?; let size = console_size_from_fd(fd)?; result[0] = size.cols; result[1] = size.rows; @@ -418,7 +440,7 @@ mod tests { pub fn op_read_line_prompt( #[string] prompt_text: &str, #[string] default_value: &str, -) -> Result<Option<String>, AnyError> { +) -> Result<Option<String>, ReadlineError> { let mut editor = Editor::<(), rustyline::history::DefaultHistory>::new() .expect("Failed to create editor."); @@ -438,6 +460,6 @@ pub fn op_read_line_prompt( Ok(None) } Err(ReadlineError::Eof) => Ok(None), - Err(err) => Err(err.into()), + Err(err) => Err(err), } } diff --git a/runtime/ops/utils.rs b/runtime/ops/utils.rs deleted file mode 100644 index d5ce61c1f..000000000 --- a/runtime/ops/utils.rs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. - -use deno_core::error::custom_error; -use deno_core::error::AnyError; - -/// A utility function to map OsStrings to Strings -pub fn into_string(s: std::ffi::OsString) -> Result<String, AnyError> { - s.into_string().map_err(|s| { - let message = format!("File name or path {s:?} is not valid UTF-8"); - custom_error("InvalidData", message) - }) -} diff --git a/runtime/ops/web_worker.rs b/runtime/ops/web_worker.rs index 0ed76ebd5..d0c3eea66 100644 --- a/runtime/ops/web_worker.rs +++ b/runtime/ops/web_worker.rs @@ -4,15 +4,16 @@ mod sync_fetch; use crate::web_worker::WebWorkerInternalHandle; use crate::web_worker::WebWorkerType; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::CancelFuture; use deno_core::OpState; use deno_web::JsMessageData; +use deno_web::MessagePortError; use std::cell::RefCell; use std::rc::Rc; use self::sync_fetch::op_worker_sync_fetch; +pub use sync_fetch::SyncFetchError; deno_core::extension!( deno_web_worker, @@ -30,17 +31,16 @@ deno_core::extension!( fn op_worker_post_message( state: &mut OpState, #[serde] data: JsMessageData, -) -> Result<(), AnyError> { +) -> Result<(), MessagePortError> { let handle = state.borrow::<WebWorkerInternalHandle>().clone(); - handle.port.send(state, data)?; - Ok(()) + handle.port.send(state, data) } #[op2(async(lazy), fast)] #[serde] async fn op_worker_recv_message( state: Rc<RefCell<OpState>>, -) -> Result<Option<JsMessageData>, AnyError> { +) -> Result<Option<JsMessageData>, MessagePortError> { let handle = { let state = state.borrow(); state.borrow::<WebWorkerInternalHandle>().clone() diff --git a/runtime/ops/web_worker/sync_fetch.rs b/runtime/ops/web_worker/sync_fetch.rs index cdb151a86..d1f133d3d 100644 --- a/runtime/ops/web_worker/sync_fetch.rs +++ b/runtime/ops/web_worker/sync_fetch.rs @@ -4,15 +4,13 @@ use std::sync::Arc; use crate::web_worker::WebWorkerInternalHandle; use crate::web_worker::WebWorkerType; -use deno_core::error::type_error; -use deno_core::error::AnyError; use deno_core::futures::StreamExt; use deno_core::op2; use deno_core::url::Url; use deno_core::OpState; use deno_fetch::data_url::DataUrl; +use deno_fetch::FetchError; use deno_web::BlobStore; -use deno_websocket::DomExceptionNetworkError; use http_body_util::BodyExt; use hyper::body::Bytes; use serde::Deserialize; @@ -27,6 +25,32 @@ fn mime_type_essence(mime_type: &str) -> String { essence.trim().to_ascii_lowercase() } +#[derive(Debug, thiserror::Error)] +pub enum SyncFetchError { + #[error("Blob URLs are not supported in this context.")] + BlobUrlsNotSupportedInContext, + #[error("{0}")] + Io(#[from] std::io::Error), + #[error("Invalid script URL")] + InvalidScriptUrl, + #[error("http status error: {0}")] + InvalidStatusCode(http::StatusCode), + #[error("Classic scripts with scheme {0}: are not supported in workers")] + ClassicScriptSchemeUnsupportedInWorkers(String), + #[error("{0}")] + InvalidUri(#[from] http::uri::InvalidUri), + #[error("Invalid MIME type {0:?}.")] + InvalidMimeType(String), + #[error("Missing MIME type.")] + MissingMimeType, + #[error(transparent)] + Fetch(#[from] FetchError), + #[error(transparent)] + Join(#[from] tokio::task::JoinError), + #[error(transparent)] + Other(deno_core::error::AnyError), +} + #[derive(Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SyncFetchScript { @@ -40,21 +64,22 @@ pub fn op_worker_sync_fetch( state: &mut OpState, #[serde] scripts: Vec<String>, loose_mime_checks: bool, -) -> Result<Vec<SyncFetchScript>, AnyError> { +) -> Result<Vec<SyncFetchScript>, SyncFetchError> { let handle = state.borrow::<WebWorkerInternalHandle>().clone(); assert_eq!(handle.worker_type, WebWorkerType::Classic); // it's not safe to share a client across tokio runtimes, so create a fresh one // https://github.com/seanmonstar/reqwest/issues/1148#issuecomment-910868788 let options = state.borrow::<deno_fetch::Options>().clone(); - let client = deno_fetch::create_client_from_options(&options)?; + let client = deno_fetch::create_client_from_options(&options) + .map_err(FetchError::ClientCreate)?; // TODO(andreubotella) It's not good to throw an exception related to blob // URLs when none of the script URLs use the blob scheme. // Also, in which contexts are blob URLs not supported? let blob_store = state .try_borrow::<Arc<BlobStore>>() - .ok_or_else(|| type_error("Blob URLs are not supported in this context."))? + .ok_or(SyncFetchError::BlobUrlsNotSupportedInContext)? .clone(); // TODO(andreubotella): make the below thread into a resource that can be @@ -74,7 +99,7 @@ pub fn op_worker_sync_fetch( let blob_store = blob_store.clone(); deno_core::unsync::spawn(async move { let script_url = Url::parse(&script) - .map_err(|_| type_error("Invalid script URL"))?; + .map_err(|_| SyncFetchError::InvalidScriptUrl)?; let mut loose_mime_checks = loose_mime_checks; let (body, mime_type, res_url) = match script_url.scheme() { @@ -86,15 +111,13 @@ pub fn op_worker_sync_fetch( ); *req.uri_mut() = script_url.as_str().parse()?; - let resp = client.send(req).await?; + let resp = + client.send(req).await.map_err(FetchError::ClientSend)?; if resp.status().is_client_error() || resp.status().is_server_error() { - return Err(type_error(format!( - "http status error: {}", - resp.status() - ))); + return Err(SyncFetchError::InvalidStatusCode(resp.status())); } // TODO(andreubotella) Properly run fetch's "extract a MIME type". @@ -107,42 +130,45 @@ pub fn op_worker_sync_fetch( // Always check the MIME type with HTTP(S). loose_mime_checks = false; - let body = resp.collect().await?.to_bytes(); + let body = resp + .collect() + .await + .map_err(SyncFetchError::Other)? + .to_bytes(); (body, mime_type, script) } "data" => { - let data_url = DataUrl::process(&script) - .map_err(|e| type_error(format!("{e:?}")))?; + let data_url = + DataUrl::process(&script).map_err(FetchError::DataUrl)?; let mime_type = { let mime = data_url.mime_type(); format!("{}/{}", mime.type_, mime.subtype) }; - let (body, _) = data_url - .decode_to_vec() - .map_err(|e| type_error(format!("{e:?}")))?; + let (body, _) = + data_url.decode_to_vec().map_err(FetchError::Base64)?; (Bytes::from(body), Some(mime_type), script) } "blob" => { - let blob = - blob_store.get_object_url(script_url).ok_or_else(|| { - type_error("Blob for the given URL not found.") - })?; + let blob = blob_store + .get_object_url(script_url) + .ok_or(FetchError::BlobNotFound)?; let mime_type = mime_type_essence(&blob.media_type); - let body = blob.read_all().await?; + let body = blob.read_all().await; (Bytes::from(body), Some(mime_type), script) } _ => { - return Err(type_error(format!( - "Classic scripts with scheme {}: are not supported in workers.", - script_url.scheme() - ))) + return Err( + SyncFetchError::ClassicScriptSchemeUnsupportedInWorkers( + script_url.scheme().to_string(), + ), + ) } }; @@ -151,18 +177,11 @@ pub fn op_worker_sync_fetch( match mime_type.as_deref() { Some("application/javascript" | "text/javascript") => {} Some(mime_type) => { - return Err( - DomExceptionNetworkError { - msg: format!("Invalid MIME type {mime_type:?}."), - } - .into(), - ) - } - None => { - return Err( - DomExceptionNetworkError::new("Missing MIME type.").into(), - ) + return Err(SyncFetchError::InvalidMimeType( + mime_type.to_string(), + )) } + None => return Err(SyncFetchError::MissingMimeType), } } diff --git a/runtime/ops/worker_host.rs b/runtime/ops/worker_host.rs index b9fd06654..521284a6a 100644 --- a/runtime/ops/worker_host.rs +++ b/runtime/ops/worker_host.rs @@ -10,7 +10,6 @@ use crate::web_worker::WorkerControlEvent; use crate::web_worker::WorkerId; use crate::web_worker::WorkerMetadata; use crate::worker::FormatJsErrorFn; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::serde::Deserialize; use deno_core::CancelFuture; @@ -21,6 +20,7 @@ use deno_permissions::ChildPermissionsArg; use deno_permissions::PermissionsContainer; use deno_web::deserialize_js_transferables; use deno_web::JsMessageData; +use deno_web::MessagePortError; use log::debug; use std::cell::RefCell; use std::collections::HashMap; @@ -118,6 +118,20 @@ pub struct CreateWorkerArgs { close_on_idle: bool, } +#[derive(Debug, thiserror::Error)] +pub enum CreateWorkerError { + #[error("Classic workers are not supported.")] + ClassicWorkers, + #[error(transparent)] + Permission(deno_permissions::ChildPermissionError), + #[error(transparent)] + ModuleResolution(#[from] deno_core::ModuleResolutionError), + #[error(transparent)] + MessagePort(#[from] MessagePortError), + #[error("{0}")] + Io(#[from] std::io::Error), +} + /// Create worker as the host #[op2] #[serde] @@ -125,7 +139,7 @@ fn op_create_worker( state: &mut OpState, #[serde] args: CreateWorkerArgs, #[serde] maybe_worker_metadata: Option<JsMessageData>, -) -> Result<WorkerId, AnyError> { +) -> Result<WorkerId, CreateWorkerError> { let specifier = args.specifier.clone(); let maybe_source_code = if args.has_source_code { Some(args.source_code.clone()) @@ -136,12 +150,7 @@ fn op_create_worker( let worker_type = args.worker_type; if let WebWorkerType::Classic = worker_type { if let TestingFeaturesEnabled(false) = state.borrow() { - return Err( - deno_webstorage::DomExceptionNotSupportedError::new( - "Classic workers are not supported.", - ) - .into(), - ); + return Err(CreateWorkerError::ClassicWorkers); } } @@ -155,7 +164,9 @@ fn op_create_worker( let parent_permissions = state.borrow_mut::<PermissionsContainer>(); let worker_permissions = if let Some(child_permissions_arg) = args.permissions { - parent_permissions.create_child_permissions(child_permissions_arg)? + parent_permissions + .create_child_permissions(child_permissions_arg) + .map_err(CreateWorkerError::Permission)? } else { parent_permissions.clone() }; @@ -167,9 +178,8 @@ fn op_create_worker( let module_specifier = deno_core::resolve_url(&specifier)?; let worker_name = args_name.unwrap_or_default(); - let (handle_sender, handle_receiver) = std::sync::mpsc::sync_channel::< - Result<SendableWebWorkerHandle, AnyError>, - >(1); + let (handle_sender, handle_receiver) = + std::sync::mpsc::sync_channel::<SendableWebWorkerHandle>(1); // Setup new thread let thread_builder = std::thread::Builder::new().name(format!("{worker_id}")); @@ -203,7 +213,7 @@ fn op_create_worker( }); // Send thread safe handle from newly created worker to host thread - handle_sender.send(Ok(external_handle)).unwrap(); + handle_sender.send(external_handle).unwrap(); drop(handle_sender); // At this point the only method of communication with host @@ -219,7 +229,7 @@ fn op_create_worker( })?; // Receive WebWorkerHandle from newly created worker - let worker_handle = handle_receiver.recv().unwrap()?; + let worker_handle = handle_receiver.recv().unwrap(); let worker_thread = WorkerThread { worker_handle: worker_handle.into(), @@ -292,7 +302,7 @@ fn close_channel( async fn op_host_recv_ctrl( state: Rc<RefCell<OpState>>, #[serde] id: WorkerId, -) -> Result<WorkerControlEvent, AnyError> { +) -> WorkerControlEvent { let (worker_handle, cancel_handle) = { let state = state.borrow(); let workers_table = state.borrow::<WorkersTable>(); @@ -301,7 +311,7 @@ async fn op_host_recv_ctrl( (handle.worker_handle.clone(), handle.cancel_handle.clone()) } else { // If handle was not found it means worker has already shutdown - return Ok(WorkerControlEvent::Close); + return WorkerControlEvent::Close; } }; @@ -310,22 +320,21 @@ async fn op_host_recv_ctrl( .or_cancel(cancel_handle) .await; match maybe_event { - Ok(Ok(Some(event))) => { + Ok(Some(event)) => { // Terminal error means that worker should be removed from worker table. if let WorkerControlEvent::TerminalError(_) = &event { close_channel(state, id, WorkerChannel::Ctrl); } - Ok(event) + event } - Ok(Ok(None)) => { + Ok(None) => { // If there was no event from worker it means it has already been closed. close_channel(state, id, WorkerChannel::Ctrl); - Ok(WorkerControlEvent::Close) + WorkerControlEvent::Close } - Ok(Err(err)) => Err(err), Err(_) => { // The worker was terminated. - Ok(WorkerControlEvent::Close) + WorkerControlEvent::Close } } } @@ -335,7 +344,7 @@ async fn op_host_recv_ctrl( async fn op_host_recv_message( state: Rc<RefCell<OpState>>, #[serde] id: WorkerId, -) -> Result<Option<JsMessageData>, AnyError> { +) -> Result<Option<JsMessageData>, MessagePortError> { let (worker_handle, cancel_handle) = { let s = state.borrow(); let workers_table = s.borrow::<WorkersTable>(); @@ -374,7 +383,7 @@ fn op_host_post_message( state: &mut OpState, #[serde] id: WorkerId, #[serde] data: JsMessageData, -) -> Result<(), AnyError> { +) -> Result<(), MessagePortError> { if let Some(worker_thread) = state.borrow::<WorkersTable>().get(&id) { debug!("post message to worker {}", id); let worker_handle = worker_thread.worker_handle.clone(); |