From cb385d9e4acbd81235c3784d7e56b49c3fa41dd3 Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Mon, 14 Oct 2024 13:53:17 -0700 Subject: refactor(ext/webstorage): use concrete error types (#26173) --- runtime/ops/worker_host.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) (limited to 'runtime/ops') diff --git a/runtime/ops/worker_host.rs b/runtime/ops/worker_host.rs index b9fd06654..61e5ef3e0 100644 --- a/runtime/ops/worker_host.rs +++ b/runtime/ops/worker_host.rs @@ -10,6 +10,7 @@ use crate::web_worker::WorkerControlEvent; use crate::web_worker::WorkerId; use crate::web_worker::WorkerMetadata; use crate::worker::FormatJsErrorFn; +use deno_core::error::custom_error; use deno_core::error::AnyError; use deno_core::op2; use deno_core::serde::Deserialize; @@ -136,12 +137,10 @@ 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(custom_error( + "DOMExceptionNotSupportedError", + "Classic workers are not supported.", + )); } } -- cgit v1.2.3 From e61e2e4b5cc10bd3e1d92472e035edd794ab0311 Mon Sep 17 00:00:00 2001 From: Patrick Uftring <14282874+pjuftring@users.noreply.github.com> Date: Thu, 17 Oct 2024 13:35:57 +0200 Subject: refactor: use macros for signal table (#26214) --- runtime/ops/signal.rs | 622 +++++++++++++++++--------------------------------- 1 file changed, 205 insertions(+), 417 deletions(-) (limited to 'runtime/ops') diff --git a/runtime/ops/signal.rs b/runtime/ops/signal.rs index ebc6db6d1..3bf43d6e1 100644 --- a/runtime/ops/signal.rs +++ b/runtime/ops/signal.rs @@ -147,438 +147,226 @@ impl Resource for SignalStreamResource { } } -#[cfg(target_os = "freebsd")] -pub fn signal_str_to_int(s: &str) -> Result { - 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 { + ($error_msg:expr, $(($number:literal, $($name:literal)|+)),*) => { + pub fn signal_str_to_int(s: &str) -> Result { + match s { + $($($name)|* => Ok($number),)* + _ => Err(type_error($error_msg(s))), + } + } -#[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, AnyError> { + match s { + $($number => Ok(first_literal!($($name),+)),)* + _ => Err(type_error($error_msg(s))), + } + } } } -#[cfg(target_os = "openbsd")] -pub fn signal_str_to_int(s: &str) -> Result { - 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!( + |s| { format!("Invalid signal : {}", s) }, + (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 { - 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!( + |s| { format!("Invalid signal : {}", s) }, + (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 { - 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!( + |s| { format!("Invalid signal : {s}") }, + (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 { - 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!( + |s| { format!("Invalid signal : {s}") }, + (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 { - match s { - "SIGINT" => Ok(2), - "SIGBREAK" => Ok(21), - _ => Err(type_error( - "Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK).", - )), - } -} +signal_dict!( + |s| { format!("Invalid signal : {s}") }, + (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!( + |_| { "Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK)." }, + (2, "SIGINT"), + (21, "SIGBREAK") +); #[cfg(unix)] #[op2(fast)] -- cgit v1.2.3 From eca83fc9b45ab1e5a73bd7b13b05ee42ab1a4dcc Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Thu, 17 Oct 2024 12:05:38 -0700 Subject: refactor(ext/web): use concrete error types (#26185) --- runtime/ops/web_worker.rs | 1 + runtime/ops/web_worker/sync_fetch.rs | 2 +- runtime/ops/worker_host.rs | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) (limited to 'runtime/ops') diff --git a/runtime/ops/web_worker.rs b/runtime/ops/web_worker.rs index 0ed76ebd5..e28bf2192 100644 --- a/runtime/ops/web_worker.rs +++ b/runtime/ops/web_worker.rs @@ -50,6 +50,7 @@ async fn op_worker_recv_message( .recv(state.clone()) .or_cancel(handle.cancel) .await? + .map_err(|e| e.into()) } #[op2(fast)] diff --git a/runtime/ops/web_worker/sync_fetch.rs b/runtime/ops/web_worker/sync_fetch.rs index cdb151a86..87fc55840 100644 --- a/runtime/ops/web_worker/sync_fetch.rs +++ b/runtime/ops/web_worker/sync_fetch.rs @@ -134,7 +134,7 @@ pub fn op_worker_sync_fetch( 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) } diff --git a/runtime/ops/worker_host.rs b/runtime/ops/worker_host.rs index 61e5ef3e0..d85541d51 100644 --- a/runtime/ops/worker_host.rs +++ b/runtime/ops/worker_host.rs @@ -359,7 +359,7 @@ async fn op_host_recv_message( } Ok(ret) } - Ok(Err(err)) => Err(err), + Ok(Err(err)) => Err(err.into()), Err(_) => { // The worker was terminated. Ok(None) -- cgit v1.2.3 From d047cab14b754d20a43c7119e327b451440aaed9 Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Fri, 18 Oct 2024 12:30:46 -0700 Subject: refactor(ext/websocket): use concrete error type (#26226) --- runtime/ops/web_worker/sync_fetch.rs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) (limited to 'runtime/ops') diff --git a/runtime/ops/web_worker/sync_fetch.rs b/runtime/ops/web_worker/sync_fetch.rs index 87fc55840..bd55a5fc8 100644 --- a/runtime/ops/web_worker/sync_fetch.rs +++ b/runtime/ops/web_worker/sync_fetch.rs @@ -4,6 +4,7 @@ use std::sync::Arc; use crate::web_worker::WebWorkerInternalHandle; use crate::web_worker::WebWorkerType; +use deno_core::error::custom_error; use deno_core::error::type_error; use deno_core::error::AnyError; use deno_core::futures::StreamExt; @@ -12,7 +13,6 @@ use deno_core::url::Url; use deno_core::OpState; use deno_fetch::data_url::DataUrl; use deno_web::BlobStore; -use deno_websocket::DomExceptionNetworkError; use http_body_util::BodyExt; use hyper::body::Bytes; use serde::Deserialize; @@ -151,17 +151,16 @@ 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(), - ) + return Err(custom_error( + "DOMExceptionNetworkError", + format!("Invalid MIME type {mime_type:?}."), + )) } None => { - return Err( - DomExceptionNetworkError::new("Missing MIME type.").into(), - ) + return Err(custom_error( + "DOMExceptionNetworkError", + "Missing MIME type.", + )) } } } -- cgit v1.2.3 From 2c3900370ac3e0b62f1e0dfb86a883c75952146d Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Fri, 18 Oct 2024 15:57:12 -0700 Subject: refactor(ext/http): use concrete error types (#26377) --- runtime/ops/http.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) (limited to 'runtime/ops') diff --git a/runtime/ops/http.rs b/runtime/ops/http.rs index cec8b0ef8..cbabbe22c 100644 --- a/runtime/ops/http.rs +++ b/runtime/ops/http.rs @@ -34,7 +34,7 @@ fn op_http_start( 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 @@ -49,7 +49,7 @@ fn op_http_start( 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)] @@ -65,7 +65,12 @@ fn op_http_start( 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()) -- cgit v1.2.3 From 473e3069de4bf5877a6f1140aa0462e05f745536 Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Sat, 19 Oct 2024 14:59:39 -0700 Subject: chore: update nix crate (#26422) Dedupes nix dependency, since `rustyline` depends on a newer version that what we currently use --- runtime/ops/tty.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'runtime/ops') diff --git a/runtime/ops/tty.rs b/runtime/ops/tty.rs index 5b49e3a24..77e1330b5 100644 --- a/runtime/ops/tty.rs +++ b/runtime/ops/tty.rs @@ -244,7 +244,8 @@ fn op_set_raw( let tty_mode_store = state.borrow::().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 { -- cgit v1.2.3 From f26c8bcf3167069ccd8ac3beb9185d1bf480a83f Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Tue, 22 Oct 2024 01:41:08 -0700 Subject: refactor(runtime/ops): use concrete error types (#26409) --- runtime/ops/fs_events.rs | 34 +++++--- runtime/ops/http.rs | 32 ++++++-- runtime/ops/mod.rs | 1 - runtime/ops/os/mod.rs | 115 ++++++++++++++++++--------- runtime/ops/process.rs | 150 +++++++++++++++++++++++------------ runtime/ops/runtime.rs | 6 +- runtime/ops/signal.rs | 71 +++++++++++------ runtime/ops/tty.rs | 51 ++++++++---- runtime/ops/utils.rs | 12 --- runtime/ops/web_worker.rs | 11 ++- runtime/ops/web_worker/sync_fetch.rs | 90 +++++++++++++-------- runtime/ops/worker_host.rs | 58 ++++++++------ 12 files changed, 404 insertions(+), 227 deletions(-) delete mode 100644 runtime/ops/utils.rs (limited to 'runtime/ops') diff --git a/runtime/ops/fs_events.rs b/runtime/ops/fs_events.rs index d88a32d91..89a0606db 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; @@ -37,7 +36,7 @@ deno_core::extension!( struct FsEventsResource { #[allow(unused)] watcher: RecommendedWatcher, - receiver: AsyncRefCell>>, + receiver: AsyncRefCell>>, cancel: CancelHandle, } @@ -93,6 +92,18 @@ impl From for FsEvent { } } +#[derive(Debug, thiserror::Error)] +pub enum FsEventsError { + #[error(transparent)] + Resource(deno_core::error::AnyError), + #[error(transparent)] + Permission(deno_core::error::AnyError), + #[error(transparent)] + Notify(#[from] NotifyError), + #[error(transparent)] + Canceled(#[from] deno_core::Canceled), +} + #[derive(Deserialize)] pub struct OpenArgs { recursive: bool, @@ -104,12 +115,12 @@ pub struct OpenArgs { fn op_fs_events_open( state: &mut OpState, #[serde] args: OpenArgs, -) -> Result { - let (sender, receiver) = mpsc::channel::>(16); +) -> Result { + let (sender, receiver) = mpsc::channel::>(16); let sender = Mutex::new(sender); let mut watcher: RecommendedWatcher = Watcher::new( move |res: Result| { - let res2 = res.map(FsEvent::from).map_err(AnyError::from); + let res2 = res.map(FsEvent::from); let sender = sender.lock(); // Ignore result, if send failed it means that watcher was already closed, // but not all messages have been flushed. @@ -125,7 +136,8 @@ fn op_fs_events_open( for path in &args.paths { let path = state .borrow_mut::() - .check_read(path, "Deno.watchFs()")?; + .check_read(path, "Deno.watchFs()") + .map_err(FsEventsError::Permission)?; watcher.watch(&path, recursive_mode)?; } let resource = FsEventsResource { @@ -142,14 +154,18 @@ fn op_fs_events_open( async fn op_fs_events_poll( state: Rc>, #[smi] rid: ResourceId, -) -> Result, AnyError> { - let resource = state.borrow().resource_table.get::(rid)?; +) -> Result, FsEventsError> { + let resource = state + .borrow() + .resource_table + .get::(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 cbabbe22c..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 { +) -> Result { if let Ok(resource_rc) = state .resource_table .take::(tcp_stream_rid) @@ -30,7 +46,7 @@ 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()?; @@ -45,7 +61,7 @@ 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()?; @@ -61,7 +77,7 @@ 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()?; @@ -73,5 +89,5 @@ fn op_http_start( )); } - 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..67065b901 100644 --- a/runtime/ops/mod.rs +++ b/runtime/ops/mod.rs @@ -9,7 +9,6 @@ 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..24b0389e1 100644 --- a/runtime/ops/os/mod.rs +++ b/runtime/ops/os/mod.rs @@ -1,9 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. -use super::utils::into_string; 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; @@ -73,17 +70,39 @@ deno_core::extension!( }, ); +#[derive(Debug, thiserror::Error)] +pub enum OsError { + #[error(transparent)] + Permission(deno_core::error::AnyError), + #[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 { +fn op_exec_path(state: &mut OpState) -> Result { let current_exe = env::current_exe().unwrap(); state .borrow_mut::() - .check_read_blind(¤t_exe, "exec_path", "Deno.execPath()")?; + .check_read_blind(¤t_exe, "exec_path", "Deno.execPath()") + .map_err(OsError::Permission)?; // 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 +110,19 @@ fn op_set_env( state: &mut OpState, #[string] key: &str, #[string] value: &str, -) -> Result<(), AnyError> { - state.borrow_mut::().check_env(key)?; +) -> Result<(), OsError> { + state + .borrow_mut::() + .check_env(key) + .map_err(OsError::Permission)?; 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 +130,9 @@ fn op_set_env( #[op2] #[serde] -fn op_env(state: &mut OpState) -> Result, AnyError> { +fn op_env( + state: &mut OpState, +) -> Result, deno_core::error::AnyError> { state.borrow_mut::().check_env_all()?; Ok(env::vars().collect()) } @@ -122,21 +142,22 @@ fn op_env(state: &mut OpState) -> Result, AnyError> { fn op_get_env( state: &mut OpState, #[string] key: String, -) -> Result, AnyError> { +) -> Result, OsError> { let skip_permission_check = NODE_ENV_VAR_ALLOWLIST.contains(&key); if !skip_permission_check { - state.borrow_mut::().check_env(&key)?; + state + .borrow_mut::() + .check_env(&key) + .map_err(OsError::Permission)?; } 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 +171,13 @@ fn op_get_env( fn op_delete_env( state: &mut OpState, #[string] key: String, -) -> Result<(), AnyError> { - state.borrow_mut::().check_env(&key)?; +) -> Result<(), OsError> { + state + .borrow_mut::() + .check_env(&key) + .map_err(OsError::Permission)?; 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(()) @@ -178,7 +202,9 @@ fn op_exit(state: &mut OpState) { #[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::() .check_sys("loadavg", "Deno.loadavg()")?; @@ -187,7 +213,9 @@ fn op_loadavg(state: &mut OpState) -> Result<(f64, f64, f64), AnyError> { #[op2] #[string] -fn op_hostname(state: &mut OpState) -> Result { +fn op_hostname( + state: &mut OpState, +) -> Result { state .borrow_mut::() .check_sys("hostname", "Deno.hostname()")?; @@ -196,7 +224,9 @@ fn op_hostname(state: &mut OpState) -> Result { #[op2] #[string] -fn op_os_release(state: &mut OpState) -> Result { +fn op_os_release( + state: &mut OpState, +) -> Result { state .borrow_mut::() .check_sys("osRelease", "Deno.osRelease()")?; @@ -207,10 +237,11 @@ fn op_os_release(state: &mut OpState) -> Result { #[serde] fn op_network_interfaces( state: &mut OpState, -) -> Result, AnyError> { +) -> Result, OsError> { state .borrow_mut::() - .check_sys("networkInterfaces", "Deno.networkInterfaces()")?; + .check_sys("networkInterfaces", "Deno.networkInterfaces()") + .map_err(OsError::Permission)?; Ok(netif::up()?.map(NetworkInterface::from).collect()) } @@ -259,7 +290,7 @@ impl From for NetworkInterface { #[serde] fn op_system_memory_info( state: &mut OpState, -) -> Result, AnyError> { +) -> Result, deno_core::error::AnyError> { state .borrow_mut::() .check_sys("systemMemoryInfo", "Deno.systemMemoryInfo()")?; @@ -269,7 +300,9 @@ fn op_system_memory_info( #[cfg(not(windows))] #[op2] #[smi] -fn op_gid(state: &mut OpState) -> Result, AnyError> { +fn op_gid( + state: &mut OpState, +) -> Result, deno_core::error::AnyError> { state .borrow_mut::() .check_sys("gid", "Deno.gid()")?; @@ -283,7 +316,9 @@ fn op_gid(state: &mut OpState) -> Result, AnyError> { #[cfg(windows)] #[op2] #[smi] -fn op_gid(state: &mut OpState) -> Result, AnyError> { +fn op_gid( + state: &mut OpState, +) -> Result, deno_core::error::AnyError> { state .borrow_mut::() .check_sys("gid", "Deno.gid()")?; @@ -293,7 +328,9 @@ fn op_gid(state: &mut OpState) -> Result, AnyError> { #[cfg(not(windows))] #[op2] #[smi] -fn op_uid(state: &mut OpState) -> Result, AnyError> { +fn op_uid( + state: &mut OpState, +) -> Result, deno_core::error::AnyError> { state .borrow_mut::() .check_sys("uid", "Deno.uid()")?; @@ -307,7 +344,9 @@ fn op_uid(state: &mut OpState) -> Result, AnyError> { #[cfg(windows)] #[op2] #[smi] -fn op_uid(state: &mut OpState) -> Result, AnyError> { +fn op_uid( + state: &mut OpState, +) -> Result, deno_core::error::AnyError> { state .borrow_mut::() .check_sys("uid", "Deno.uid()")?; @@ -485,7 +524,7 @@ fn rss() -> usize { } } -fn os_uptime(state: &mut OpState) -> Result { +fn os_uptime(state: &mut OpState) -> Result { state .borrow_mut::() .check_sys("osUptime", "Deno.osUptime()")?; @@ -494,6 +533,8 @@ fn os_uptime(state: &mut OpState) -> Result { #[op2(fast)] #[number] -fn op_os_uptime(state: &mut OpState) -> Result { +fn op_os_uptime( + state: &mut OpState, +) -> Result { os_uptime(state) } diff --git a/runtime/ops/process.rs b/runtime/ops/process.rs index f6555e932..28913f7c1 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 { + ) -> Result { 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,39 @@ 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, + }, + #[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(deno_core::error::AnyError), + #[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 +240,7 @@ pub struct ChildStatus { } impl TryFrom for ChildStatus { - type Error = AnyError; + type Error = SignalError; fn try_from(status: ExitStatus) -> Result { let code = status.code(); @@ -259,7 +291,7 @@ type CreateCommand = ( pub fn npm_process_state_tempfile( contents: &[u8], -) -> Result { +) -> Result { let mut temp_file = tempfile::tempfile()?; temp_file.write_all(contents)?; let handle = temp_file.into_raw_io_handle(); @@ -301,7 +333,7 @@ fn create_command( state: &mut OpState, mut args: SpawnArgs, api_name: &str, -) -> Result { +) -> Result { let maybe_npm_process_state = if args.needs_npm_process_state { let provider = state.borrow::(); let process_state = provider.get_npm_process_state(); @@ -505,7 +537,7 @@ fn spawn_child( ipc_pipe_rid: Option, extra_pipe_rids: Vec>, detached: bool, -) -> Result { +) -> Result { 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 +586,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 +632,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 { @@ -613,7 +653,8 @@ fn compute_run_cmd_and_check_permissions( }, &run_env, api_name, - )?; + ) + .map_err(ProcessError::Permission)?; Ok((cmd, run_env)) } @@ -631,9 +672,10 @@ fn compute_run_env( arg_cwd: Option<&str>, arg_envs: &[(String, String)], arg_clear_env: bool, -) -> Result { +) -> Result { #[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 +712,7 @@ fn compute_run_env( Ok(RunEnv { envs, cwd }) } -fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result { +fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result { let is_path = cmd.contains('/'); #[cfg(windows)] let is_path = is_path || cmd.contains('\\') || Path::new(&cmd).is_absolute(); @@ -683,7 +725,7 @@ fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result { 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)), } } } @@ -697,7 +739,7 @@ fn check_run_permission( cmd: &RunQueryDescriptor, run_env: &RunEnv, api_name: &str, -) -> Result<(), AnyError> { +) -> Result<(), deno_core::error::AnyError> { let permissions = state.borrow_mut::(); if !permissions.query_run_all(api_name) { // error the same on all platforms @@ -754,7 +796,7 @@ fn op_spawn_child( state: &mut OpState, #[serde] args: SpawnArgs, #[string] api_name: String, -) -> Result { +) -> Result { let detached = args.detached; let (command, pipe_rid, extra_pipe_rids, handles_to_close) = create_command(state, args, &api_name)?; @@ -771,16 +813,23 @@ fn op_spawn_child( async fn op_spawn_wait( state: Rc>, #[smi] rid: ResourceId, -) -> Result { +) -> Result { let resource = state .borrow_mut() .resource_table - .get::(rid)?; - let result = resource.0.try_borrow_mut()?.wait().await?.try_into(); + .get::(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 +837,14 @@ async fn op_spawn_wait( fn op_spawn_sync( state: &mut OpState, #[serde] args: SpawnArgs, -) -> Result { +) -> Result { 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 +867,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::(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 +921,9 @@ mod deprecated { pub fn op_run( state: &mut OpState, #[serde] run_args: RunArgs, - ) -> Result { + ) -> Result { 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 +1035,12 @@ mod deprecated { pub async fn op_run_status( state: Rc>, #[smi] rid: ResourceId, - ) -> Result { + ) -> Result { let resource = state .borrow_mut() .resource_table - .get::(rid)?; + .get::(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 +1063,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 +1087,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 +1123,11 @@ mod deprecated { #[smi] pid: i32, #[string] signal: String, #[string] api_name: String, - ) -> Result<(), AnyError> { + ) -> Result<(), ProcessError> { state .borrow_mut::() - .check_run_all(&api_name)?; - kill(pid, &signal)?; - Ok(()) + .check_run_all(&api_name) + .map_err(ProcessError::Permission)?; + 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 { +fn op_main_module(state: &mut OpState) -> String { let main_url = state.borrow::(); - 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 3bf43d6e1..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 { @@ -153,18 +187,18 @@ macro_rules! first_literal { }; } macro_rules! signal_dict { - ($error_msg:expr, $(($number:literal, $($name:literal)|+)),*) => { - pub fn signal_str_to_int(s: &str) -> Result { + ($(($number:literal, $($name:literal)|+)),*) => { + pub fn signal_str_to_int(s: &str) -> Result { match s { $($($name)|* => Ok($number),)* - _ => Err(type_error($error_msg(s))), + _ => Err(SignalError::InvalidSignalStr(s.to_string())), } } - pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, AnyError> { + pub fn signal_int_to_str(s: libc::c_int) -> Result<&'static str, SignalError> { match s { $($number => Ok(first_literal!($($name),+)),)* - _ => Err(type_error($error_msg(s))), + _ => Err(SignalError::InvalidSignalInt(s)), } } } @@ -172,7 +206,6 @@ macro_rules! signal_dict { #[cfg(target_os = "freebsd")] signal_dict!( - |s| { format!("Invalid signal : {}", s) }, (1, "SIGHUP"), (2, "SIGINT"), (3, "SIGQUIT"), @@ -210,7 +243,6 @@ signal_dict!( #[cfg(target_os = "openbsd")] signal_dict!( - |s| { format!("Invalid signal : {}", s) }, (1, "SIGHUP"), (2, "SIGINT"), (3, "SIGQUIT"), @@ -246,7 +278,6 @@ signal_dict!( #[cfg(any(target_os = "android", target_os = "linux"))] signal_dict!( - |s| { format!("Invalid signal : {s}") }, (1, "SIGHUP"), (2, "SIGINT"), (3, "SIGQUIT"), @@ -282,7 +313,6 @@ signal_dict!( #[cfg(target_os = "macos")] signal_dict!( - |s| { format!("Invalid signal : {s}") }, (1, "SIGHUP"), (2, "SIGINT"), (3, "SIGQUIT"), @@ -318,7 +348,6 @@ signal_dict!( #[cfg(any(target_os = "solaris", target_os = "illumos"))] signal_dict!( - |s| { format!("Invalid signal : {s}") }, (1, "SIGHUP"), (2, "SIGINT"), (3, "SIGQUIT"), @@ -362,11 +391,7 @@ signal_dict!( ); #[cfg(target_os = "windows")] -signal_dict!( - |_| { "Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK)." }, - (2, "SIGINT"), - (21, "SIGBREAK") -); +signal_dict!((2, "SIGINT"), (21, "SIGBREAK")); #[cfg(unix)] #[op2(fast)] @@ -374,12 +399,10 @@ signal_dict!( fn op_signal_bind( state: &mut OpState, #[string] sig: &str, -) -> Result { +) -> Result { 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))?); @@ -413,7 +436,7 @@ fn op_signal_bind( fn op_signal_bind( state: &mut OpState, #[string] sig: &str, -) -> Result { +) -> Result { let signo = signal_str_to_int(sig)?; let resource = SignalStreamResource { signal: AsyncRefCell::new(match signo { @@ -437,7 +460,7 @@ fn op_signal_bind( async fn op_signal_poll( state: Rc>, #[smi] rid: ResourceId, -) -> Result { +) -> Result { let resource = state .borrow_mut() .resource_table @@ -456,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::(rid)?; #[cfg(unix)] diff --git a/runtime/ops/tty.rs b/runtime/ops/tty.rs index 77e1330b5..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: #[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(()) @@ -252,7 +267,8 @@ fn op_set_raw( 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 } @@ -274,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)?; } } @@ -290,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; @@ -419,7 +440,7 @@ mod tests { pub fn op_read_line_prompt( #[string] prompt_text: &str, #[string] default_value: &str, -) -> Result, AnyError> { +) -> Result, ReadlineError> { let mut editor = Editor::<(), rustyline::history::DefaultHistory>::new() .expect("Failed to create editor."); @@ -439,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 { - 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 e28bf2192..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::().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>, -) -> Result, AnyError> { +) -> Result, MessagePortError> { let handle = { let state = state.borrow(); state.borrow::().clone() @@ -50,7 +50,6 @@ async fn op_worker_recv_message( .recv(state.clone()) .or_cancel(handle.cancel) .await? - .map_err(|e| e.into()) } #[op2(fast)] diff --git a/runtime/ops/web_worker/sync_fetch.rs b/runtime/ops/web_worker/sync_fetch.rs index bd55a5fc8..d1f133d3d 100644 --- a/runtime/ops/web_worker/sync_fetch.rs +++ b/runtime/ops/web_worker/sync_fetch.rs @@ -4,14 +4,12 @@ use std::sync::Arc; use crate::web_worker::WebWorkerInternalHandle; use crate::web_worker::WebWorkerType; -use deno_core::error::custom_error; -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 http_body_util::BodyExt; use hyper::body::Bytes; @@ -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, loose_mime_checks: bool, -) -> Result, AnyError> { +) -> Result, SyncFetchError> { let handle = state.borrow::().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::().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::>() - .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,30 +130,32 @@ 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); @@ -139,10 +164,11 @@ pub fn op_worker_sync_fetch( (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,17 +177,11 @@ pub fn op_worker_sync_fetch( match mime_type.as_deref() { Some("application/javascript" | "text/javascript") => {} Some(mime_type) => { - return Err(custom_error( - "DOMExceptionNetworkError", - format!("Invalid MIME type {mime_type:?}."), - )) - } - None => { - return Err(custom_error( - "DOMExceptionNetworkError", - "Missing MIME type.", + 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 d85541d51..82cc94924 100644 --- a/runtime/ops/worker_host.rs +++ b/runtime/ops/worker_host.rs @@ -10,8 +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::custom_error; -use deno_core::error::AnyError; use deno_core::op2; use deno_core::serde::Deserialize; use deno_core::CancelFuture; @@ -22,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; @@ -119,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_core::error::AnyError), + #[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] @@ -126,7 +139,7 @@ fn op_create_worker( state: &mut OpState, #[serde] args: CreateWorkerArgs, #[serde] maybe_worker_metadata: Option, -) -> Result { +) -> Result { let specifier = args.specifier.clone(); let maybe_source_code = if args.has_source_code { Some(args.source_code.clone()) @@ -137,10 +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(custom_error( - "DOMExceptionNotSupportedError", - "Classic workers are not supported.", - )); + return Err(CreateWorkerError::ClassicWorkers); } } @@ -154,7 +164,9 @@ fn op_create_worker( let parent_permissions = state.borrow_mut::(); 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() }; @@ -166,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, - >(1); + let (handle_sender, handle_receiver) = + std::sync::mpsc::sync_channel::(1); // Setup new thread let thread_builder = std::thread::Builder::new().name(format!("{worker_id}")); @@ -202,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 @@ -218,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(), @@ -291,7 +302,7 @@ fn close_channel( async fn op_host_recv_ctrl( state: Rc>, #[serde] id: WorkerId, -) -> Result { +) -> WorkerControlEvent { let (worker_handle, cancel_handle) = { let state = state.borrow(); let workers_table = state.borrow::(); @@ -300,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; } }; @@ -309,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 } } } @@ -334,7 +344,7 @@ async fn op_host_recv_ctrl( async fn op_host_recv_message( state: Rc>, #[serde] id: WorkerId, -) -> Result, AnyError> { +) -> Result, MessagePortError> { let (worker_handle, cancel_handle) = { let s = state.borrow(); let workers_table = s.borrow::(); @@ -359,7 +369,7 @@ async fn op_host_recv_message( } Ok(ret) } - Ok(Err(err)) => Err(err.into()), + Ok(Err(err)) => Err(err), Err(_) => { // The worker was terminated. Ok(None) @@ -373,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::().get(&id) { debug!("post message to worker {}", id); let worker_handle = worker_thread.worker_handle.clone(); -- cgit v1.2.3 From be969cb5328bb83c26022ea5307467b40647cb64 Mon Sep 17 00:00:00 2001 From: Divy Srivastava Date: Wed, 23 Oct 2024 09:22:58 +0530 Subject: fix: share inotify fd across watchers (#26200) Fixes https://github.com/denoland/deno/issues/26104 Fixes https://github.com/denoland/deno/issues/26071 Fixes https://github.com/denoland/deno/issues/17757 --- runtime/ops/fs_events.rs | 92 ++++++++++++++++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 23 deletions(-) (limited to 'runtime/ops') diff --git a/runtime/ops/fs_events.rs b/runtime/ops/fs_events.rs index 89a0606db..648553376 100644 --- a/runtime/ops/fs_events.rs +++ b/runtime/ops/fs_events.rs @@ -19,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!( @@ -34,8 +35,6 @@ deno_core::extension!( ); struct FsEventsResource { - #[allow(unused)] - watcher: RecommendedWatcher, receiver: AsyncRefCell>>, cancel: CancelHandle, } @@ -58,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, @@ -92,6 +91,24 @@ impl From for FsEvent { } } +type WatchSender = (Vec, mpsc::Sender>); + +struct WatcherState { + senders: Arc>>, + watcher: RecommendedWatcher, +} + +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)] @@ -104,44 +121,73 @@ pub enum FsEventsError { Canceled(#[from] deno_core::Canceled), } -#[derive(Deserialize)] -pub struct OpenArgs { - recursive: bool, +fn start_watcher( + state: &mut OpState, paths: Vec, + sender: mpsc::Sender>, +) -> Result<(), FsEventsError> { + if let Some(watcher) = state.try_borrow_mut::() { + 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| { + 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(), + )?; + + state.put::(WatcherState { watcher, senders }); + + Ok(()) } #[op2] #[smi] fn op_fs_events_open( state: &mut OpState, - #[serde] args: OpenArgs, + recursive: bool, + #[serde] paths: Vec, ) -> Result { let (sender, receiver) = mpsc::channel::>(16); - let sender = Mutex::new(sender); - let mut watcher: RecommendedWatcher = Watcher::new( - move |res: Result| { - let res2 = res.map(FsEvent::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); - }, - Default::default(), - )?; - let recursive_mode = if args.recursive { + + 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::() .check_read(path, "Deno.watchFs()") .map_err(FsEventsError::Permission)?; - watcher.watch(&path, recursive_mode)?; + + let watcher = state.borrow_mut::(); + watcher.watcher.watch(&path, recursive_mode)?; } let resource = FsEventsResource { - watcher, receiver: AsyncRefCell::new(receiver), cancel: Default::default(), }; -- cgit v1.2.3 From fe9f0ee5934871175758857899fe64e56c397fd5 Mon Sep 17 00:00:00 2001 From: Leo Kettmeir Date: Mon, 4 Nov 2024 09:17:21 -0800 Subject: refactor(runtime/permissions): use concrete error types (#26464) --- runtime/ops/fs_events.rs | 5 ++--- runtime/ops/os/mod.rs | 23 ++++++----------------- runtime/ops/permissions.rs | 43 ++++++++++++++++++++----------------------- runtime/ops/process.rs | 24 ++++++++++++++++-------- runtime/ops/worker_host.rs | 2 +- 5 files changed, 45 insertions(+), 52 deletions(-) (limited to 'runtime/ops') diff --git a/runtime/ops/fs_events.rs b/runtime/ops/fs_events.rs index 648553376..c8e0228bc 100644 --- a/runtime/ops/fs_events.rs +++ b/runtime/ops/fs_events.rs @@ -114,7 +114,7 @@ pub enum FsEventsError { #[error(transparent)] Resource(deno_core::error::AnyError), #[error(transparent)] - Permission(deno_core::error::AnyError), + Permission(#[from] deno_permissions::PermissionCheckError), #[error(transparent)] Notify(#[from] NotifyError), #[error(transparent)] @@ -181,8 +181,7 @@ fn op_fs_events_open( for path in &paths { let path = state .borrow_mut::() - .check_read(path, "Deno.watchFs()") - .map_err(FsEventsError::Permission)?; + .check_read(path, "Deno.watchFs()")?; let watcher = state.borrow_mut::(); watcher.watcher.watch(&path, recursive_mode)?; diff --git a/runtime/ops/os/mod.rs b/runtime/ops/os/mod.rs index 24b0389e1..9bee9d823 100644 --- a/runtime/ops/os/mod.rs +++ b/runtime/ops/os/mod.rs @@ -73,7 +73,7 @@ deno_core::extension!( #[derive(Debug, thiserror::Error)] pub enum OsError { #[error(transparent)] - Permission(deno_core::error::AnyError), + 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.")] @@ -94,8 +94,7 @@ fn op_exec_path(state: &mut OpState) -> Result { let current_exe = env::current_exe().unwrap(); state .borrow_mut::() - .check_read_blind(¤t_exe, "exec_path", "Deno.execPath()") - .map_err(OsError::Permission)?; + .check_read_blind(¤t_exe, "exec_path", "Deno.execPath()")?; // normalize path so it doesn't include '.' or '..' components let path = normalize_path(current_exe); @@ -111,10 +110,7 @@ fn op_set_env( #[string] key: &str, #[string] value: &str, ) -> Result<(), OsError> { - state - .borrow_mut::() - .check_env(key) - .map_err(OsError::Permission)?; + state.borrow_mut::().check_env(key)?; if key.is_empty() { return Err(OsError::EnvEmptyKey); } @@ -146,10 +142,7 @@ fn op_get_env( let skip_permission_check = NODE_ENV_VAR_ALLOWLIST.contains(&key); if !skip_permission_check { - state - .borrow_mut::() - .check_env(&key) - .map_err(OsError::Permission)?; + state.borrow_mut::().check_env(&key)?; } if key.is_empty() { @@ -172,10 +165,7 @@ fn op_delete_env( state: &mut OpState, #[string] key: String, ) -> Result<(), OsError> { - state - .borrow_mut::() - .check_env(&key) - .map_err(OsError::Permission)?; + state.borrow_mut::().check_env(&key)?; if key.is_empty() || key.contains(&['=', '\0'] as &[char]) { return Err(OsError::EnvInvalidKey(key.to_string())); } @@ -240,8 +230,7 @@ fn op_network_interfaces( ) -> Result, OsError> { state .borrow_mut::() - .check_sys("networkInterfaces", "Deno.networkInterfaces()") - .map_err(OsError::Permission)?; + .check_sys("networkInterfaces", "Deno.networkInterfaces()")?; Ok(netif::up()?.map(NetworkInterface::from).collect()) } 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 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 { +) -> Result { let permissions = state.borrow::(); 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 { +) -> Result { let permissions = state.borrow::(); 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 { +) -> Result { let permissions = state.borrow::(); 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 28913f7c1..de3141f1f 100644 --- a/runtime/ops/process.rs +++ b/runtime/ops/process.rs @@ -206,7 +206,9 @@ pub enum ProcessError { #[error("failed resolving cwd: {0}")] FailedResolvingCwd(#[source] std::io::Error), #[error(transparent)] - Permission(deno_core::error::AnyError), + Permission(#[from] deno_permissions::PermissionCheckError), + #[error(transparent)] + RunPermission(#[from] CheckRunPermissionError), #[error(transparent)] Resource(deno_core::error::AnyError), #[error(transparent)] @@ -653,8 +655,7 @@ fn compute_run_cmd_and_check_permissions( }, &run_env, api_name, - ) - .map_err(ProcessError::Permission)?; + )?; Ok((cmd, run_env)) } @@ -734,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<(), deno_core::error::AnyError> { +) -> Result<(), CheckRunPermissionError> { let permissions = state.borrow_mut::(); if !permissions.query_run_all(api_name) { // error the same on all platforms @@ -747,14 +756,14 @@ 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( + return Err(CheckRunPermissionError::Other(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 { "" } ) - )); + ))); } permissions.check_run(cmd, api_name)?; } @@ -1126,8 +1135,7 @@ mod deprecated { ) -> Result<(), ProcessError> { state .borrow_mut::() - .check_run_all(&api_name) - .map_err(ProcessError::Permission)?; + .check_run_all(&api_name)?; kill(pid, &signal) } } diff --git a/runtime/ops/worker_host.rs b/runtime/ops/worker_host.rs index 82cc94924..521284a6a 100644 --- a/runtime/ops/worker_host.rs +++ b/runtime/ops/worker_host.rs @@ -123,7 +123,7 @@ pub enum CreateWorkerError { #[error("Classic workers are not supported.")] ClassicWorkers, #[error(transparent)] - Permission(deno_core::error::AnyError), + Permission(deno_permissions::ChildPermissionError), #[error(transparent)] ModuleResolution(#[from] deno_core::ModuleResolutionError), #[error(transparent)] -- cgit v1.2.3 From 119910f3395cf073b7acf6a31c207daf597917f1 Mon Sep 17 00:00:00 2001 From: David Sherret Date: Tue, 12 Nov 2024 17:14:19 -0500 Subject: fix(permissions): say to use --allow-run instead of --allow-all (#26842) For https://github.com/denoland/deno/issues/26839 --- runtime/ops/process.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) (limited to 'runtime/ops') diff --git a/runtime/ops/process.rs b/runtime/ops/process.rs index de3141f1f..ee2f660dc 100644 --- a/runtime/ops/process.rs +++ b/runtime/ops/process.rs @@ -756,14 +756,17 @@ 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(CheckRunPermissionError::Other(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)?; } -- cgit v1.2.3 From aa546189be730163ee5370029e4dfdb3b454ab96 Mon Sep 17 00:00:00 2001 From: snek Date: Wed, 13 Nov 2024 11:38:46 +0100 Subject: feat: OpenTelemetry Tracing API and Exporting (#26710) Initial import of OTEL code supporting tracing. Metrics soon to come. Implements APIs for https://jsr.io/@deno/otel so that code using OpenTelemetry.js just works tm. There is still a lot of work to do with configuration and adding built-in tracing to core APIs, which will come in followup PRs. --------- Co-authored-by: Luca Casonato --- runtime/ops/mod.rs | 1 + runtime/ops/os/mod.rs | 2 + runtime/ops/otel.rs | 686 ++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 689 insertions(+) create mode 100644 runtime/ops/otel.rs (limited to 'runtime/ops') diff --git a/runtime/ops/mod.rs b/runtime/ops/mod.rs index 67065b901..c2e402f33 100644 --- a/runtime/ops/mod.rs +++ b/runtime/ops/mod.rs @@ -4,6 +4,7 @@ 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; diff --git a/runtime/ops/os/mod.rs b/runtime/ops/os/mod.rs index 9bee9d823..790962f38 100644 --- a/runtime/ops/os/mod.rs +++ b/runtime/ops/os/mod.rs @@ -186,6 +186,8 @@ fn op_get_exit_code(state: &mut OpState) -> i32 { #[op2(fast)] fn op_exit(state: &mut OpState) { + crate::ops::otel::otel_drop_state(state); + let code = state.borrow::().get(); std::process::exit(code) } diff --git a/runtime/ops/otel.rs b/runtime/ops/otel.rs new file mode 100644 index 000000000..6a4750acc --- /dev/null +++ b/runtime/ops/otel.rs @@ -0,0 +1,686 @@ +// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. + +use crate::tokio_util::create_basic_runtime; +use deno_core::anyhow::anyhow; +use deno_core::anyhow::{self}; +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 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::InstrumentationScope; +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; +type LogProcessor = BatchLogProcessor; + +deno_core::extension!( + deno_otel, + ops = [op_otel_log, op_otel_span_start, op_otel_span_continue, op_otel_span_attribute, op_otel_span_attribute2, op_otel_span_attribute3, op_otel_span_flush], + options = { + otel_config: Option, // `None` means OpenTelemetry is disabled. + }, + state = |state, options| { + if let Some(otel_config) = options.otel_config { + otel_create_globals(otel_config, state).unwrap(); + } + } +); + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OtelConfig { + pub runtime_name: Cow<'static, str>, + pub runtime_version: Cow<'static, str>, + pub console: OtelConsoleConfig, +} + +#[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, + } + } +} + +static OTEL_SHARED_RUNTIME_SPAWN_TASK_TX: Lazy< + UnboundedSender>, +> = Lazy::new(otel_create_shared_runtime); + +fn otel_create_shared_runtime() -> UnboundedSender> { + let (spawn_task_tx, mut spawn_task_rx) = + mpsc::unbounded::>(); + + 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> 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 + Send + 'static>>; + type Delay = Pin>; + + 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 = BatchMessageChannelReceiver; + type Sender = BatchMessageChannelSender; + + fn batch_message_channel( + &self, + capacity: usize, + ) -> (Self::Sender, Self::Receiver) { + let (batch_tx, batch_rx) = tokio::sync::mpsc::channel::(capacity); + (batch_tx.into(), batch_rx.into()) + } +} + +#[derive(Debug)] +pub struct BatchMessageChannelSender { + sender: tokio::sync::mpsc::Sender, +} + +impl From> + for BatchMessageChannelSender +{ + fn from(sender: tokio::sync::mpsc::Sender) -> Self { + Self { sender } + } +} + +impl opentelemetry_sdk::runtime::TrySend + for BatchMessageChannelSender +{ + 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 { + receiver: tokio::sync::mpsc::Receiver, +} + +impl From> + for BatchMessageChannelReceiver +{ + fn from(receiver: tokio::sync::mpsc::Receiver) -> Self { + Self { receiver } + } +} + +impl Stream for BatchMessageChannelReceiver { + type Item = T; + + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + 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, + } + + 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>, + ) -> Result, 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); + + impl HttpBody for Body { + type Data = Bytes; + type Error = Box; + + #[inline] + fn poll_frame( + self: Pin<&mut Self>, + cx: &mut task::Context<'_>, + ) -> Poll, 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() + } + } +} + +fn otel_create_globals( + config: OtelConfig, + op_state: &mut OpState, +) -> 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); + op_state.put::(span_processor); + + 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); + op_state.put::(log_processor); + + Ok(()) +} + +/// This function is called by the runtime whenever it is about to call +/// `os::process::exit()`, to ensure that all OpenTelemetry logs are properly +/// flushed before the process terminates. +pub fn otel_drop_state(state: &mut OpState) { + if let Some(processor) = state.try_take::() { + let _ = processor.force_flush(); + drop(processor); + } + if let Some(processor) = state.try_take::() { + let _ = processor.force_flush(); + drop(processor); + } +} + +#[op2(fast)] +fn op_otel_log( + state: &mut OpState, + #[string] message: String, + #[smi] level: i32, + #[string] trace_id: &str, + #[string] span_id: &str, + #[smi] trace_flags: u8, +) { + let Some(logger) = state.try_borrow::() else { + log::error!("op_otel_log: OpenTelemetry Logger not available"); + 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 mut log_record = LogRecord::default(); + + log_record.observed_timestamp = Some(SystemTime::now()); + log_record.body = Some(message.into()); + log_record.severity_number = Some(severity); + log_record.severity_text = Some(severity.name()); + if let (Ok(trace_id), Ok(span_id)) = + (TraceId::from_hex(trace_id), SpanId::from_hex(span_id)) + { + let span_context = SpanContext::new( + trace_id, + span_id, + TraceFlags::new(trace_flags), + false, + Default::default(), + ); + log_record.trace_context = Some((&span_context).into()); + } + logger.emit( + &mut log_record, + &InstrumentationScope::builder("deno").build(), + ); +} + +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::() { + let Some(span_processor) = state.try_borrow::() else { + return Ok(()); + }; + span_processor.on_end(temporary_span.0); + }; + + let trace_id = { + let x = v8::ValueView::new(scope, trace_id.try_cast()?); + match x.data() { + v8::ValueViewData::OneByte(bytes) => { + TraceId::from_hex(&String::from_utf8_lossy(bytes))? + } + _ => return Err(anyhow!("invalid trace_id")), + } + }; + + let span_id = { + let x = v8::ValueView::new(scope, span_id.try_cast()?); + match x.data() { + v8::ValueViewData::OneByte(bytes) => { + SpanId::from_hex(&String::from_utf8_lossy(bytes))? + } + _ => return Err(anyhow!("invalid span_id")), + } + }; + + let parent_span_id = { + let x = v8::ValueView::new(scope, parent_span_id.try_cast()?); + match x.data() { + v8::ValueViewData::OneByte(bytes) => { + let s = String::from_utf8_lossy(bytes); + if s.is_empty() { + SpanId::INVALID + } else { + SpanId::from_hex(&s)? + } + } + _ => return Err(anyhow!("invalid 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: InstrumentationScope::builder("deno").build(), + }); + 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::() { + temporary_span.0.status = match status { + 0 => SpanStatus::Unset, + 1 => SpanStatus::Ok, + 2 => SpanStatus::Error { + description: Cow::Owned(error_description.into_owned()), + }, + _ => return, + }; + } +} + +macro_rules! attr { + ($scope:ident, $temporary_span:ident, $name:ident, $value:ident) => { + 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::() { + 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::() { + Some(Value::F64(number.value())) + } else if let Ok(boolean) = $value.try_cast::() { + Some(Value::Bool(boolean.is_true())) + } else if let Ok(bigint) = $value.try_cast::() { + let (i64_value, _lossless) = bigint.i64_value(); + Some(Value::I64(i64_value)) + } else { + None + }; + if let (Some(name), Some(value)) = (name, value) { + $temporary_span + .0 + .attributes + .push(KeyValue::new(name, value)); + } else { + $temporary_span.0.dropped_attributes_count += 1; + } + }; +} + +#[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::() { + temporary_span.0.attributes.reserve_exact( + (capacity as usize) - temporary_span.0.attributes.capacity(), + ); + attr!(scope, temporary_span, 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::() { + temporary_span.0.attributes.reserve_exact( + (capacity as usize) - temporary_span.0.attributes.capacity(), + ); + attr!(scope, temporary_span, key1, value1); + attr!(scope, temporary_span, 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::() { + temporary_span.0.attributes.reserve_exact( + (capacity as usize) - temporary_span.0.attributes.capacity(), + ); + attr!(scope, temporary_span, key1, value1); + attr!(scope, temporary_span, key2, value2); + attr!(scope, temporary_span, key3, value3); + } +} + +#[op2(fast)] +fn op_otel_span_flush(state: &mut OpState) { + let Some(temporary_span) = state.try_take::() else { + return; + }; + + let Some(span_processor) = state.try_borrow::() else { + return; + }; + + span_processor.on_end(temporary_span.0); +} -- cgit v1.2.3 From 4e899d48cffa95617266dd8f9aef54603a87ad82 Mon Sep 17 00:00:00 2001 From: snek Date: Thu, 14 Nov 2024 13:16:28 +0100 Subject: fix: otel resiliency (#26857) Improving the breadth of collected data, and ensuring that the collected data is more likely to be successfully reported. - Use `log` crate in more places - Hook up `log` crate to otel - Switch to process-wide otel processors - Handle places that use `process::exit` Also adds a more robust testing framework, with a deterministic tracing setting. Refs: https://github.com/denoland/deno/issues/26852 --- runtime/ops/os/mod.rs | 4 +- runtime/ops/otel.rs | 136 +++++++++++++++++++++++++++++++++++--------------- 2 files changed, 98 insertions(+), 42 deletions(-) (limited to 'runtime/ops') diff --git a/runtime/ops/os/mod.rs b/runtime/ops/os/mod.rs index 790962f38..b10a2939e 100644 --- a/runtime/ops/os/mod.rs +++ b/runtime/ops/os/mod.rs @@ -186,10 +186,8 @@ fn op_get_exit_code(state: &mut OpState) -> i32 { #[op2(fast)] fn op_exit(state: &mut OpState) { - crate::ops::otel::otel_drop_state(state); - let code = state.borrow::().get(); - std::process::exit(code) + crate::exit(code) } #[op2] diff --git a/runtime/ops/otel.rs b/runtime/ops/otel.rs index 6a4750acc..b32764d7f 100644 --- a/runtime/ops/otel.rs +++ b/runtime/ops/otel.rs @@ -13,6 +13,9 @@ 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; @@ -58,15 +61,15 @@ type LogProcessor = BatchLogProcessor; deno_core::extension!( deno_otel, - ops = [op_otel_log, op_otel_span_start, op_otel_span_continue, op_otel_span_attribute, op_otel_span_attribute2, op_otel_span_attribute3, op_otel_span_flush], - options = { - otel_config: Option, // `None` means OpenTelemetry is disabled. - }, - state = |state, options| { - if let Some(otel_config) = options.otel_config { - otel_create_globals(otel_config, state).unwrap(); - } - } + ops = [ + op_otel_log, + op_otel_span_start, + op_otel_span_continue, + op_otel_span_attribute, + op_otel_span_attribute2, + op_otel_span_attribute3, + op_otel_span_flush, + ], ); #[derive(Debug, Clone, Serialize, Deserialize)] @@ -74,6 +77,7 @@ 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)] @@ -90,6 +94,7 @@ impl Default for OtelConfig { runtime_name: Cow::Borrowed(env!("CARGO_PKG_NAME")), runtime_version: Cow::Borrowed(env!("CARGO_PKG_VERSION")), console: OtelConsoleConfig::Capture, + deterministic: false, } } } @@ -295,10 +300,10 @@ mod hyper_client { } } -fn otel_create_globals( - config: OtelConfig, - op_state: &mut OpState, -) -> anyhow::Result<()> { +static OTEL_PROCESSORS: OnceCell<(SpanProcessor, LogProcessor)> = + 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. @@ -318,7 +323,7 @@ fn otel_create_globals( return Err(anyhow!( "Failed to read env var OTEL_EXPORTER_OTLP_PROTOCOL: {}", err - )) + )); } }; @@ -372,7 +377,6 @@ fn otel_create_globals( let mut span_processor = BatchSpanProcessor::builder(span_exporter, OtelSharedRuntime).build(); span_processor.set_resource(&resource); - op_state.put::(span_processor); let log_exporter = HttpExporterBuilder::default() .with_http_client(client) @@ -381,36 +385,92 @@ fn otel_create_globals( let log_processor = BatchLogProcessor::builder(log_exporter, OtelSharedRuntime).build(); log_processor.set_resource(&resource); - op_state.put::(log_processor); + + OTEL_PROCESSORS + .set((span_processor, log_processor)) + .map_err(|_| anyhow!("failed to init otel"))?; Ok(()) } /// This function is called by the runtime whenever it is about to call -/// `os::process::exit()`, to ensure that all OpenTelemetry logs are properly +/// `process::exit()`, to ensure that all OpenTelemetry logs are properly /// flushed before the process terminates. -pub fn otel_drop_state(state: &mut OpState) { - if let Some(processor) = state.try_take::() { - let _ = processor.force_flush(); - drop(processor); +pub fn flush() { + if let Some((span_processor, log_processor)) = OTEL_PROCESSORS.get() { + let _ = span_processor.force_flush(); + let _ = log_processor.force_flush(); } - if let Some(processor) = state.try_take::() { - let _ = processor.force_flush(); - drop(processor); +} + +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, + &InstrumentationScope::builder("deno").build(), + ); } #[op2(fast)] fn op_otel_log( - state: &mut OpState, #[string] message: String, #[smi] level: i32, #[string] trace_id: &str, #[string] span_id: &str, #[smi] trace_flags: u8, ) { - let Some(logger) = state.try_borrow::() else { - log::error!("op_otel_log: OpenTelemetry Logger not available"); + let Some((_, log_processor)) = OTEL_PROCESSORS.get() else { return; }; @@ -425,23 +485,21 @@ fn op_otel_log( let mut log_record = LogRecord::default(); - log_record.observed_timestamp = Some(SystemTime::now()); - log_record.body = Some(message.into()); - log_record.severity_number = Some(severity); - log_record.severity_text = Some(severity.name()); + 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 let (Ok(trace_id), Ok(span_id)) = (TraceId::from_hex(trace_id), SpanId::from_hex(span_id)) { - let span_context = SpanContext::new( + log_record.set_trace_context( trace_id, span_id, - TraceFlags::new(trace_flags), - false, - Default::default(), + Some(TraceFlags::new(trace_flags)), ); - log_record.trace_context = Some((&span_context).into()); } - logger.emit( + + log_processor.emit( &mut log_record, &InstrumentationScope::builder("deno").build(), ); @@ -463,7 +521,7 @@ fn op_otel_span_start<'s>( end_time: f64, ) -> Result<(), anyhow::Error> { if let Some(temporary_span) = state.try_take::() { - let Some(span_processor) = state.try_borrow::() else { + let Some((span_processor, _)) = OTEL_PROCESSORS.get() else { return Ok(()); }; span_processor.on_end(temporary_span.0); @@ -678,7 +736,7 @@ fn op_otel_span_flush(state: &mut OpState) { return; }; - let Some(span_processor) = state.try_borrow::() else { + let Some((span_processor, _)) = OTEL_PROCESSORS.get() else { return; }; -- cgit v1.2.3 From c9baf3849fdbe161a9251a712a71e2b91eeabf3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartek=20Iwa=C5=84czuk?= Date: Fri, 15 Nov 2024 09:33:03 +0000 Subject: perf: use available system memory for v8 isolate memory limit (#26868) Instead of using the default 1.4Gb limit (which was meant for browser tabs) configure V8 to set the heap limit to the amount of memory available in the system. Closes https://github.com/denoland/deno/issues/23424 Closes https://github.com/denoland/deno/issues/26435 Closes https://github.com/denoland/deno/issues/21226 --- runtime/ops/os/mod.rs | 3 +- runtime/ops/os/sys_info.rs | 425 --------------------------------------------- 2 files changed, 1 insertion(+), 427 deletions(-) delete mode 100644 runtime/ops/os/sys_info.rs (limited to 'runtime/ops') diff --git a/runtime/ops/os/mod.rs b/runtime/ops/os/mod.rs index b10a2939e..74c708c53 100644 --- a/runtime/ops/os/mod.rs +++ b/runtime/ops/os/mod.rs @@ -1,5 +1,6 @@ // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. +use crate::sys_info; use crate::worker::ExitCode; use deno_core::op2; use deno_core::v8; @@ -11,8 +12,6 @@ use serde::Serialize; use std::collections::HashMap; use std::env; -mod sys_info; - deno_core::extension!( deno_os, ops = [ 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::::uninit(); - // SAFETY: we need to initialize dwOSVersionInfoSize. - unsafe { - (*version_info.as_mut_ptr()).dwOSVersionInfoSize = - std::mem::size_of::() 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 = 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 { - 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::().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::(); - 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::(); - mib[0] = libc::CTL_VM; - mib[1] = libc::VM_SWAPUSAGE; - - let mut size = std::mem::size_of::(); - 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::(); - 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::::uninit(); - let length = - mem::size_of::() 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::::uninit(); - let result = GetPerformanceInfo( - perf_info.as_mut_ptr(), - mem::size_of::() 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 -} -- cgit v1.2.3 From 594a99817cbe44553b2c288578fbba8e1e9c1907 Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Tue, 19 Nov 2024 00:55:22 +0100 Subject: feat(runtime): remove public OTEL trace API (#26854) This PR removes the public Deno.tracing.Span API. We are not confident we can ship an API that is better than the `@opentelemetry/api` API, because V8 CPED does not support us using `using` to manage span context. If this changes, we can revisit this decision. For now, users wanting custom spans can instrument their code using the `@opentelemetry/api` API and `@deno/otel`. This PR also speeds up the OTEL trace generation by a 30% by using Uint8Array instead of strings for the trace ID and span ID. --- runtime/ops/otel.rs | 297 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 204 insertions(+), 93 deletions(-) (limited to 'runtime/ops') diff --git a/runtime/ops/otel.rs b/runtime/ops/otel.rs index b32764d7f..61a7b0ef0 100644 --- a/runtime/ops/otel.rs +++ b/runtime/ops/otel.rs @@ -1,8 +1,8 @@ // 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::anyhow::{self}; use deno_core::futures::channel::mpsc; use deno_core::futures::channel::mpsc::UnboundedSender; use deno_core::futures::future::BoxFuture; @@ -23,7 +23,6 @@ use opentelemetry::trace::SpanKind; use opentelemetry::trace::Status as SpanStatus; use opentelemetry::trace::TraceFlags; use opentelemetry::trace::TraceId; -use opentelemetry::InstrumentationScope; use opentelemetry::Key; use opentelemetry::KeyValue; use opentelemetry::StringValue; @@ -63,11 +62,15 @@ 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, ], ); @@ -303,6 +306,10 @@ mod hyper_client { 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. @@ -390,6 +397,14 @@ pub fn init(config: OtelConfig) -> anyhow::Result<()> { .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(()) } @@ -458,16 +473,160 @@ pub fn handle_log(record: &log::Record) { log_processor.emit( &mut log_record, - &InstrumentationScope::builder("deno").build(), + 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::() { + 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::() { + 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::() { + 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::() { + Some(Value::F64(number.value())) + } else if let Ok(boolean) = $value.try_cast::() { + Some(Value::Bool(boolean.is_true())) + } else if let Ok(bigint) = $value.try_cast::() { + 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] schema_url: Option, +) -> 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, - #[string] trace_id: &str, - #[string] span_id: &str, + trace_id: v8::Local<'_, v8::Value>, + span_id: v8::Local<'_, v8::Value>, #[smi] trace_flags: u8, ) { let Some((_, log_processor)) = OTEL_PROCESSORS.get() else { @@ -483,15 +642,16 @@ fn op_otel_log( 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 let (Ok(trace_id), Ok(span_id)) = - (TraceId::from_hex(trace_id), SpanId::from_hex(span_id)) - { + if trace_id != TraceId::INVALID && span_id != SpanId::INVALID { log_record.set_trace_context( trace_id, span_id, @@ -501,7 +661,7 @@ fn op_otel_log( log_processor.emit( &mut log_record, - &InstrumentationScope::builder("deno").build(), + BUILT_IN_INSTRUMENTATION_SCOPE.get().unwrap(), ); } @@ -527,40 +687,23 @@ fn op_otel_span_start<'s>( span_processor.on_end(temporary_span.0); }; - let trace_id = { - let x = v8::ValueView::new(scope, trace_id.try_cast()?); - match x.data() { - v8::ValueViewData::OneByte(bytes) => { - TraceId::from_hex(&String::from_utf8_lossy(bytes))? - } - _ => return Err(anyhow!("invalid trace_id")), - } + let Some(InstrumentationScope(instrumentation_scope)) = + state.try_borrow::() + else { + return Err(anyhow!("instrumentation scope not available")); }; - let span_id = { - let x = v8::ValueView::new(scope, span_id.try_cast()?); - match x.data() { - v8::ValueViewData::OneByte(bytes) => { - SpanId::from_hex(&String::from_utf8_lossy(bytes))? - } - _ => return Err(anyhow!("invalid span_id")), - } - }; + let trace_id = parse_trace_id(scope, trace_id); + if trace_id == TraceId::INVALID { + return Err(anyhow!("invalid trace_id")); + } - let parent_span_id = { - let x = v8::ValueView::new(scope, parent_span_id.try_cast()?); - match x.data() { - v8::ValueViewData::OneByte(bytes) => { - let s = String::from_utf8_lossy(bytes); - if s.is_empty() { - SpanId::INVALID - } else { - SpanId::from_hex(&s)? - } - } - _ => return Err(anyhow!("invalid parent_span_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()?); @@ -601,7 +744,7 @@ fn op_otel_span_start<'s>( events: Default::default(), links: Default::default(), status: SpanStatus::Unset, - instrumentation_scope: InstrumentationScope::builder("deno").build(), + instrumentation_scope: instrumentation_scope.clone(), }); state.put(temporary_span); @@ -626,52 +769,6 @@ fn op_otel_span_continue( } } -macro_rules! attr { - ($scope:ident, $temporary_span:ident, $name:ident, $value:ident) => { - 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::() { - 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::() { - Some(Value::F64(number.value())) - } else if let Ok(boolean) = $value.try_cast::() { - Some(Value::Bool(boolean.is_true())) - } else if let Ok(bigint) = $value.try_cast::() { - let (i64_value, _lossless) = bigint.i64_value(); - Some(Value::I64(i64_value)) - } else { - None - }; - if let (Some(name), Some(value)) = (name, value) { - $temporary_span - .0 - .attributes - .push(KeyValue::new(name, value)); - } else { - $temporary_span.0.dropped_attributes_count += 1; - } - }; -} - #[op2(fast)] fn op_otel_span_attribute<'s>( scope: &mut v8::HandleScope<'s>, @@ -684,7 +781,7 @@ fn op_otel_span_attribute<'s>( temporary_span.0.attributes.reserve_exact( (capacity as usize) - temporary_span.0.attributes.capacity(), ); - attr!(scope, temporary_span, key, value); + attr!(scope, temporary_span.0.attributes => temporary_span.0.dropped_attributes_count, key, value); } } @@ -702,8 +799,8 @@ fn op_otel_span_attribute2<'s>( temporary_span.0.attributes.reserve_exact( (capacity as usize) - temporary_span.0.attributes.capacity(), ); - attr!(scope, temporary_span, key1, value1); - attr!(scope, temporary_span, key2, value2); + 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); } } @@ -724,9 +821,23 @@ fn op_otel_span_attribute3<'s>( temporary_span.0.attributes.reserve_exact( (capacity as usize) - temporary_span.0.attributes.capacity(), ); - attr!(scope, temporary_span, key1, value1); - attr!(scope, temporary_span, key2, value2); - attr!(scope, temporary_span, key3, value3); + 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::() { + 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; } } -- cgit v1.2.3