1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2024-11-21 15:04:11 -05:00

refactor(runtime/ops): use concrete error types (#26409)

This commit is contained in:
Leo Kettmeir 2024-10-22 01:41:08 -07:00 committed by GitHub
parent 67280f8b55
commit f26c8bcf31
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 574 additions and 250 deletions

1
Cargo.lock generated
View file

@ -2047,6 +2047,7 @@ dependencies = [
"signal-hook-registry", "signal-hook-registry",
"tempfile", "tempfile",
"test_server", "test_server",
"thiserror",
"tokio", "tokio",
"tokio-metrics", "tokio-metrics",
"twox-hash", "twox-hash",

View file

@ -121,6 +121,7 @@ serde.workspace = true
signal-hook = "0.3.17" signal-hook = "0.3.17"
signal-hook-registry = "1.4.0" signal-hook-registry = "1.4.0"
tempfile.workspace = true tempfile.workspace = true
thiserror.workspace = true
tokio.workspace = true tokio.workspace = true
tokio-metrics.workspace = true tokio-metrics.workspace = true
twox-hash.workspace = true twox-hash.workspace = true

View file

@ -9,6 +9,14 @@
//! Diagnostics are compile-time type errors, whereas JsErrors are runtime //! Diagnostics are compile-time type errors, whereas JsErrors are runtime
//! exceptions. //! exceptions.
use crate::ops::fs_events::FsEventsError;
use crate::ops::http::HttpStartError;
use crate::ops::os::OsError;
use crate::ops::process::ProcessError;
use crate::ops::signal::SignalError;
use crate::ops::tty::TtyError;
use crate::ops::web_worker::SyncFetchError;
use crate::ops::worker_host::CreateWorkerError;
use deno_broadcast_channel::BroadcastChannelError; use deno_broadcast_channel::BroadcastChannelError;
use deno_cache::CacheError; use deno_cache::CacheError;
use deno_canvas::CanvasError; use deno_canvas::CanvasError;
@ -49,6 +57,7 @@ use deno_web::WebError;
use deno_websocket::HandshakeError; use deno_websocket::HandshakeError;
use deno_websocket::WebsocketError; use deno_websocket::WebsocketError;
use deno_webstorage::WebStorageError; use deno_webstorage::WebStorageError;
use rustyline::error::ReadlineError;
use std::env; use std::env;
use std::error::Error; use std::error::Error;
use std::io; use std::io;
@ -806,6 +815,102 @@ fn get_net_map_error(error: &deno_net::io::MapError) -> &'static str {
} }
} }
fn get_create_worker_error(error: &CreateWorkerError) -> &'static str {
match error {
CreateWorkerError::ClassicWorkers => "DOMExceptionNotSupportedError",
CreateWorkerError::Permission(e) => {
get_error_class_name(e).unwrap_or("Error")
}
CreateWorkerError::ModuleResolution(e) => {
get_module_resolution_error_class(e)
}
CreateWorkerError::Io(e) => get_io_error_class(e),
CreateWorkerError::MessagePort(e) => get_web_message_port_error_class(e),
}
}
fn get_tty_error(error: &TtyError) -> &'static str {
match error {
TtyError::Resource(e) | TtyError::Other(e) => {
get_error_class_name(e).unwrap_or("Error")
}
TtyError::Io(e) => get_io_error_class(e),
#[cfg(unix)]
TtyError::Nix(e) => get_nix_error_class(e),
}
}
fn get_readline_error(error: &ReadlineError) -> &'static str {
match error {
ReadlineError::Io(e) => get_io_error_class(e),
ReadlineError::Eof => "Error",
ReadlineError::Interrupted => "Error",
#[cfg(unix)]
ReadlineError::Errno(e) => get_nix_error_class(e),
ReadlineError::WindowResized => "Error",
#[cfg(windows)]
ReadlineError::Decode(_) => "Error",
#[cfg(windows)]
ReadlineError::SystemError(_) => "Error",
_ => "Error",
}
}
fn get_signal_error(error: &SignalError) -> &'static str {
match error {
SignalError::InvalidSignalStr(_) => "TypeError",
SignalError::InvalidSignalInt(_) => "TypeError",
SignalError::SignalNotAllowed(_) => "TypeError",
SignalError::Io(e) => get_io_error_class(e),
}
}
fn get_fs_events_error(error: &FsEventsError) -> &'static str {
match error {
FsEventsError::Resource(e) | FsEventsError::Permission(e) => {
get_error_class_name(e).unwrap_or("Error")
}
FsEventsError::Notify(e) => get_notify_error_class(e),
FsEventsError::Canceled(e) => {
let io_err: io::Error = e.to_owned().into();
get_io_error_class(&io_err)
}
}
}
fn get_http_start_error(error: &HttpStartError) -> &'static str {
match error {
HttpStartError::TcpStreamInUse => "Busy",
HttpStartError::TlsStreamInUse => "Busy",
HttpStartError::UnixSocketInUse => "Busy",
HttpStartError::ReuniteTcp(_) => "Error",
#[cfg(unix)]
HttpStartError::ReuniteUnix(_) => "Error",
HttpStartError::Io(e) => get_io_error_class(e),
HttpStartError::Other(e) => get_error_class_name(e).unwrap_or("Error"),
}
}
fn get_process_error(error: &ProcessError) -> &'static str {
match error {
ProcessError::SpawnFailed { error, .. } => get_process_error(error),
ProcessError::FailedResolvingCwd(e) | ProcessError::Io(e) => {
get_io_error_class(e)
}
ProcessError::Permission(e) | ProcessError::Resource(e) => {
get_error_class_name(e).unwrap_or("Error")
}
ProcessError::BorrowMut(_) => "Error",
ProcessError::Which(_) => "Error",
ProcessError::ChildProcessAlreadyTerminated => "TypeError",
ProcessError::Signal(e) => get_signal_error(e),
ProcessError::MissingCmd => "Error",
ProcessError::InvalidPid => "TypeError",
#[cfg(unix)]
ProcessError::Nix(e) => get_nix_error_class(e),
}
}
fn get_http_error(error: &HttpError) -> &'static str { fn get_http_error(error: &HttpError) -> &'static str {
match error { match error {
HttpError::Canceled(e) => { HttpError::Canceled(e) => {
@ -859,10 +964,50 @@ fn get_websocket_upgrade_error(error: &WebSocketUpgradeError) -> &'static str {
} }
} }
fn get_os_error(error: &OsError) -> &'static str {
match error {
OsError::Permission(e) => get_error_class_name(e).unwrap_or("Error"),
OsError::InvalidUtf8(_) => "InvalidData",
OsError::EnvEmptyKey => "TypeError",
OsError::EnvInvalidKey(_) => "TypeError",
OsError::EnvInvalidValue(_) => "TypeError",
OsError::Io(e) => get_io_error_class(e),
OsError::Var(e) => get_env_var_error_class(e),
}
}
fn get_sync_fetch_error(error: &SyncFetchError) -> &'static str {
match error {
SyncFetchError::BlobUrlsNotSupportedInContext => "TypeError",
SyncFetchError::Io(e) => get_io_error_class(e),
SyncFetchError::InvalidScriptUrl => "TypeError",
SyncFetchError::InvalidStatusCode(_) => "TypeError",
SyncFetchError::ClassicScriptSchemeUnsupportedInWorkers(_) => "TypeError",
SyncFetchError::InvalidUri(_) => "Error",
SyncFetchError::InvalidMimeType(_) => "DOMExceptionNetworkError",
SyncFetchError::MissingMimeType => "DOMExceptionNetworkError",
SyncFetchError::Fetch(e) => get_fetch_error(e),
SyncFetchError::Join(_) => "Error",
SyncFetchError::Other(e) => get_error_class_name(e).unwrap_or("Error"),
}
}
pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> { pub fn get_error_class_name(e: &AnyError) -> Option<&'static str> {
deno_core::error::get_custom_error_class(e) deno_core::error::get_custom_error_class(e)
.or_else(|| e.downcast_ref::<NApiError>().map(get_napi_error_class)) .or_else(|| e.downcast_ref::<NApiError>().map(get_napi_error_class))
.or_else(|| e.downcast_ref::<WebError>().map(get_web_error_class)) .or_else(|| e.downcast_ref::<WebError>().map(get_web_error_class))
.or_else(|| {
e.downcast_ref::<CreateWorkerError>()
.map(get_create_worker_error)
})
.or_else(|| e.downcast_ref::<TtyError>().map(get_tty_error))
.or_else(|| e.downcast_ref::<ReadlineError>().map(get_readline_error))
.or_else(|| e.downcast_ref::<SignalError>().map(get_signal_error))
.or_else(|| e.downcast_ref::<FsEventsError>().map(get_fs_events_error))
.or_else(|| e.downcast_ref::<HttpStartError>().map(get_http_start_error))
.or_else(|| e.downcast_ref::<ProcessError>().map(get_process_error))
.or_else(|| e.downcast_ref::<OsError>().map(get_os_error))
.or_else(|| e.downcast_ref::<SyncFetchError>().map(get_sync_fetch_error))
.or_else(|| { .or_else(|| {
e.downcast_ref::<CompressionError>() e.downcast_ref::<CompressionError>()
.map(get_web_compression_error_class) .map(get_web_compression_error_class)

View file

@ -1,6 +1,5 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // 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::parking_lot::Mutex;
use deno_core::AsyncRefCell; use deno_core::AsyncRefCell;
use deno_core::CancelFuture; use deno_core::CancelFuture;
@ -37,7 +36,7 @@ deno_core::extension!(
struct FsEventsResource { struct FsEventsResource {
#[allow(unused)] #[allow(unused)]
watcher: RecommendedWatcher, watcher: RecommendedWatcher,
receiver: AsyncRefCell<mpsc::Receiver<Result<FsEvent, AnyError>>>, receiver: AsyncRefCell<mpsc::Receiver<Result<FsEvent, NotifyError>>>,
cancel: CancelHandle, cancel: CancelHandle,
} }
@ -93,6 +92,18 @@ impl From<NotifyEvent> 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)] #[derive(Deserialize)]
pub struct OpenArgs { pub struct OpenArgs {
recursive: bool, recursive: bool,
@ -104,12 +115,12 @@ pub struct OpenArgs {
fn op_fs_events_open( fn op_fs_events_open(
state: &mut OpState, state: &mut OpState,
#[serde] args: OpenArgs, #[serde] args: OpenArgs,
) -> Result<ResourceId, AnyError> { ) -> Result<ResourceId, FsEventsError> {
let (sender, receiver) = mpsc::channel::<Result<FsEvent, AnyError>>(16); let (sender, receiver) = mpsc::channel::<Result<FsEvent, NotifyError>>(16);
let sender = Mutex::new(sender); let sender = Mutex::new(sender);
let mut watcher: RecommendedWatcher = Watcher::new( let mut watcher: RecommendedWatcher = Watcher::new(
move |res: Result<NotifyEvent, NotifyError>| { move |res: Result<NotifyEvent, NotifyError>| {
let res2 = res.map(FsEvent::from).map_err(AnyError::from); let res2 = res.map(FsEvent::from);
let sender = sender.lock(); let sender = sender.lock();
// Ignore result, if send failed it means that watcher was already closed, // Ignore result, if send failed it means that watcher was already closed,
// but not all messages have been flushed. // but not all messages have been flushed.
@ -125,7 +136,8 @@ fn op_fs_events_open(
for path in &args.paths { for path in &args.paths {
let path = state let path = state
.borrow_mut::<PermissionsContainer>() .borrow_mut::<PermissionsContainer>()
.check_read(path, "Deno.watchFs()")?; .check_read(path, "Deno.watchFs()")
.map_err(FsEventsError::Permission)?;
watcher.watch(&path, recursive_mode)?; watcher.watch(&path, recursive_mode)?;
} }
let resource = FsEventsResource { let resource = FsEventsResource {
@ -142,14 +154,18 @@ fn op_fs_events_open(
async fn op_fs_events_poll( async fn op_fs_events_poll(
state: Rc<RefCell<OpState>>, state: Rc<RefCell<OpState>>,
#[smi] rid: ResourceId, #[smi] rid: ResourceId,
) -> Result<Option<FsEvent>, AnyError> { ) -> Result<Option<FsEvent>, FsEventsError> {
let resource = state.borrow().resource_table.get::<FsEventsResource>(rid)?; let resource = state
.borrow()
.resource_table
.get::<FsEventsResource>(rid)
.map_err(FsEventsError::Resource)?;
let mut receiver = RcRef::map(&resource, |r| &r.receiver).borrow_mut().await; let mut receiver = RcRef::map(&resource, |r| &r.receiver).borrow_mut().await;
let cancel = RcRef::map(resource, |r| &r.cancel); let cancel = RcRef::map(resource, |r| &r.cancel);
let maybe_result = receiver.recv().or_cancel(cancel).await?; let maybe_result = receiver.recv().or_cancel(cancel).await?;
match maybe_result { match maybe_result {
Some(Ok(value)) => Ok(Some(value)), Some(Ok(value)) => Ok(Some(value)),
Some(Err(err)) => Err(err), Some(Err(err)) => Err(FsEventsError::Notify(err)),
None => Ok(None), None => Ok(None),
} }
} }

View file

@ -2,9 +2,6 @@
use std::rc::Rc; 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::op2;
use deno_core::OpState; use deno_core::OpState;
use deno_core::ResourceId; 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],); 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)] #[op2(fast)]
#[smi] #[smi]
fn op_http_start( fn op_http_start(
state: &mut OpState, state: &mut OpState,
#[smi] tcp_stream_rid: ResourceId, #[smi] tcp_stream_rid: ResourceId,
) -> Result<ResourceId, AnyError> { ) -> Result<ResourceId, HttpStartError> {
if let Ok(resource_rc) = state if let Ok(resource_rc) = state
.resource_table .resource_table
.take::<TcpStreamResource>(tcp_stream_rid) .take::<TcpStreamResource>(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. // 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 // See also: https://github.com/denoland/deno/pull/16242
let resource = Rc::try_unwrap(resource_rc) 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 (read_half, write_half) = resource.into_inner();
let tcp_stream = read_half.reunite(write_half)?; let tcp_stream = read_half.reunite(write_half)?;
let addr = tcp_stream.local_addr()?; 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. // 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 // See also: https://github.com/denoland/deno/pull/16242
let resource = Rc::try_unwrap(resource_rc) 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 (read_half, write_half) = resource.into_inner();
let tls_stream = read_half.unsplit(write_half); let tls_stream = read_half.unsplit(write_half);
let addr = tls_stream.local_addr()?; 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. // 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 // See also: https://github.com/denoland/deno/pull/16242
let resource = Rc::try_unwrap(resource_rc) 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 (read_half, write_half) = resource.into_inner();
let unix_stream = read_half.reunite(write_half)?; let unix_stream = read_half.reunite(write_half)?;
let addr = unix_stream.local_addr()?; 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()))
} }

View file

@ -9,7 +9,6 @@ pub mod process;
pub mod runtime; pub mod runtime;
pub mod signal; pub mod signal;
pub mod tty; pub mod tty;
mod utils;
pub mod web_worker; pub mod web_worker;
pub mod worker_host; pub mod worker_host;

View file

@ -1,9 +1,6 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use super::utils::into_string;
use crate::worker::ExitCode; use crate::worker::ExitCode;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::op2; use deno_core::op2;
use deno_core::v8; use deno_core::v8;
use deno_core::OpState; 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] #[op2]
#[string] #[string]
fn op_exec_path(state: &mut OpState) -> Result<String, AnyError> { fn op_exec_path(state: &mut OpState) -> Result<String, OsError> {
let current_exe = env::current_exe().unwrap(); let current_exe = env::current_exe().unwrap();
state state
.borrow_mut::<PermissionsContainer>() .borrow_mut::<PermissionsContainer>()
.check_read_blind(&current_exe, "exec_path", "Deno.execPath()")?; .check_read_blind(&current_exe, "exec_path", "Deno.execPath()")
.map_err(OsError::Permission)?;
// normalize path so it doesn't include '.' or '..' components // normalize path so it doesn't include '.' or '..' components
let path = normalize_path(current_exe); let path = normalize_path(current_exe);
into_string(path.into_os_string()) path
.into_os_string()
.into_string()
.map_err(OsError::InvalidUtf8)
} }
#[op2(fast)] #[op2(fast)]
@ -91,20 +110,19 @@ fn op_set_env(
state: &mut OpState, state: &mut OpState,
#[string] key: &str, #[string] key: &str,
#[string] value: &str, #[string] value: &str,
) -> Result<(), AnyError> { ) -> Result<(), OsError> {
state.borrow_mut::<PermissionsContainer>().check_env(key)?; state
.borrow_mut::<PermissionsContainer>()
.check_env(key)
.map_err(OsError::Permission)?;
if key.is_empty() { if key.is_empty() {
return Err(type_error("Key is an empty string.")); return Err(OsError::EnvEmptyKey);
} }
if key.contains(&['=', '\0'] as &[char]) { if key.contains(&['=', '\0'] as &[char]) {
return Err(type_error(format!( return Err(OsError::EnvInvalidKey(key.to_string()));
"Key contains invalid characters: {key:?}"
)));
} }
if value.contains('\0') { if value.contains('\0') {
return Err(type_error(format!( return Err(OsError::EnvInvalidValue(value.to_string()));
"Value contains invalid characters: {value:?}"
)));
} }
env::set_var(key, value); env::set_var(key, value);
Ok(()) Ok(())
@ -112,7 +130,9 @@ fn op_set_env(
#[op2] #[op2]
#[serde] #[serde]
fn op_env(state: &mut OpState) -> Result<HashMap<String, String>, AnyError> { fn op_env(
state: &mut OpState,
) -> Result<HashMap<String, String>, deno_core::error::AnyError> {
state.borrow_mut::<PermissionsContainer>().check_env_all()?; state.borrow_mut::<PermissionsContainer>().check_env_all()?;
Ok(env::vars().collect()) Ok(env::vars().collect())
} }
@ -122,21 +142,22 @@ fn op_env(state: &mut OpState) -> Result<HashMap<String, String>, AnyError> {
fn op_get_env( fn op_get_env(
state: &mut OpState, state: &mut OpState,
#[string] key: String, #[string] key: String,
) -> Result<Option<String>, AnyError> { ) -> Result<Option<String>, OsError> {
let skip_permission_check = NODE_ENV_VAR_ALLOWLIST.contains(&key); let skip_permission_check = NODE_ENV_VAR_ALLOWLIST.contains(&key);
if !skip_permission_check { if !skip_permission_check {
state.borrow_mut::<PermissionsContainer>().check_env(&key)?; state
.borrow_mut::<PermissionsContainer>()
.check_env(&key)
.map_err(OsError::Permission)?;
} }
if key.is_empty() { if key.is_empty() {
return Err(type_error("Key is an empty string.")); return Err(OsError::EnvEmptyKey);
} }
if key.contains(&['=', '\0'] as &[char]) { if key.contains(&['=', '\0'] as &[char]) {
return Err(type_error(format!( return Err(OsError::EnvInvalidKey(key.to_string()));
"Key contains invalid characters: {key:?}"
)));
} }
let r = match env::var(key) { let r = match env::var(key) {
@ -150,10 +171,13 @@ fn op_get_env(
fn op_delete_env( fn op_delete_env(
state: &mut OpState, state: &mut OpState,
#[string] key: String, #[string] key: String,
) -> Result<(), AnyError> { ) -> Result<(), OsError> {
state.borrow_mut::<PermissionsContainer>().check_env(&key)?; state
.borrow_mut::<PermissionsContainer>()
.check_env(&key)
.map_err(OsError::Permission)?;
if key.is_empty() || key.contains(&['=', '\0'] as &[char]) { 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); env::remove_var(key);
Ok(()) Ok(())
@ -178,7 +202,9 @@ fn op_exit(state: &mut OpState) {
#[op2] #[op2]
#[serde] #[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 state
.borrow_mut::<PermissionsContainer>() .borrow_mut::<PermissionsContainer>()
.check_sys("loadavg", "Deno.loadavg()")?; .check_sys("loadavg", "Deno.loadavg()")?;
@ -187,7 +213,9 @@ fn op_loadavg(state: &mut OpState) -> Result<(f64, f64, f64), AnyError> {
#[op2] #[op2]
#[string] #[string]
fn op_hostname(state: &mut OpState) -> Result<String, AnyError> { fn op_hostname(
state: &mut OpState,
) -> Result<String, deno_core::error::AnyError> {
state state
.borrow_mut::<PermissionsContainer>() .borrow_mut::<PermissionsContainer>()
.check_sys("hostname", "Deno.hostname()")?; .check_sys("hostname", "Deno.hostname()")?;
@ -196,7 +224,9 @@ fn op_hostname(state: &mut OpState) -> Result<String, AnyError> {
#[op2] #[op2]
#[string] #[string]
fn op_os_release(state: &mut OpState) -> Result<String, AnyError> { fn op_os_release(
state: &mut OpState,
) -> Result<String, deno_core::error::AnyError> {
state state
.borrow_mut::<PermissionsContainer>() .borrow_mut::<PermissionsContainer>()
.check_sys("osRelease", "Deno.osRelease()")?; .check_sys("osRelease", "Deno.osRelease()")?;
@ -207,10 +237,11 @@ fn op_os_release(state: &mut OpState) -> Result<String, AnyError> {
#[serde] #[serde]
fn op_network_interfaces( fn op_network_interfaces(
state: &mut OpState, state: &mut OpState,
) -> Result<Vec<NetworkInterface>, AnyError> { ) -> Result<Vec<NetworkInterface>, OsError> {
state state
.borrow_mut::<PermissionsContainer>() .borrow_mut::<PermissionsContainer>()
.check_sys("networkInterfaces", "Deno.networkInterfaces()")?; .check_sys("networkInterfaces", "Deno.networkInterfaces()")
.map_err(OsError::Permission)?;
Ok(netif::up()?.map(NetworkInterface::from).collect()) Ok(netif::up()?.map(NetworkInterface::from).collect())
} }
@ -259,7 +290,7 @@ impl From<netif::Interface> for NetworkInterface {
#[serde] #[serde]
fn op_system_memory_info( fn op_system_memory_info(
state: &mut OpState, state: &mut OpState,
) -> Result<Option<sys_info::MemInfo>, AnyError> { ) -> Result<Option<sys_info::MemInfo>, deno_core::error::AnyError> {
state state
.borrow_mut::<PermissionsContainer>() .borrow_mut::<PermissionsContainer>()
.check_sys("systemMemoryInfo", "Deno.systemMemoryInfo()")?; .check_sys("systemMemoryInfo", "Deno.systemMemoryInfo()")?;
@ -269,7 +300,9 @@ fn op_system_memory_info(
#[cfg(not(windows))] #[cfg(not(windows))]
#[op2] #[op2]
#[smi] #[smi]
fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> { fn op_gid(
state: &mut OpState,
) -> Result<Option<u32>, deno_core::error::AnyError> {
state state
.borrow_mut::<PermissionsContainer>() .borrow_mut::<PermissionsContainer>()
.check_sys("gid", "Deno.gid()")?; .check_sys("gid", "Deno.gid()")?;
@ -283,7 +316,9 @@ fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
#[cfg(windows)] #[cfg(windows)]
#[op2] #[op2]
#[smi] #[smi]
fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> { fn op_gid(
state: &mut OpState,
) -> Result<Option<u32>, deno_core::error::AnyError> {
state state
.borrow_mut::<PermissionsContainer>() .borrow_mut::<PermissionsContainer>()
.check_sys("gid", "Deno.gid()")?; .check_sys("gid", "Deno.gid()")?;
@ -293,7 +328,9 @@ fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
#[cfg(not(windows))] #[cfg(not(windows))]
#[op2] #[op2]
#[smi] #[smi]
fn op_uid(state: &mut OpState) -> Result<Option<u32>, AnyError> { fn op_uid(
state: &mut OpState,
) -> Result<Option<u32>, deno_core::error::AnyError> {
state state
.borrow_mut::<PermissionsContainer>() .borrow_mut::<PermissionsContainer>()
.check_sys("uid", "Deno.uid()")?; .check_sys("uid", "Deno.uid()")?;
@ -307,7 +344,9 @@ fn op_uid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
#[cfg(windows)] #[cfg(windows)]
#[op2] #[op2]
#[smi] #[smi]
fn op_uid(state: &mut OpState) -> Result<Option<u32>, AnyError> { fn op_uid(
state: &mut OpState,
) -> Result<Option<u32>, deno_core::error::AnyError> {
state state
.borrow_mut::<PermissionsContainer>() .borrow_mut::<PermissionsContainer>()
.check_sys("uid", "Deno.uid()")?; .check_sys("uid", "Deno.uid()")?;
@ -485,7 +524,7 @@ fn rss() -> usize {
} }
} }
fn os_uptime(state: &mut OpState) -> Result<u64, AnyError> { fn os_uptime(state: &mut OpState) -> Result<u64, deno_core::error::AnyError> {
state state
.borrow_mut::<PermissionsContainer>() .borrow_mut::<PermissionsContainer>()
.check_sys("osUptime", "Deno.osUptime()")?; .check_sys("osUptime", "Deno.osUptime()")?;
@ -494,6 +533,8 @@ fn os_uptime(state: &mut OpState) -> Result<u64, AnyError> {
#[op2(fast)] #[op2(fast)]
#[number] #[number]
fn op_os_uptime(state: &mut OpState) -> Result<u64, AnyError> { fn op_os_uptime(
state: &mut OpState,
) -> Result<u64, deno_core::error::AnyError> {
os_uptime(state) os_uptime(state)
} }

View file

@ -1,8 +1,5 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // 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::op2;
use deno_core::serde_json; use deno_core::serde_json;
use deno_core::AsyncMutFuture; use deno_core::AsyncMutFuture;
@ -35,6 +32,7 @@ use tokio::process::Command;
#[cfg(windows)] #[cfg(windows)]
use std::os::windows::process::CommandExt; use std::os::windows::process::CommandExt;
use crate::ops::signal::SignalError;
#[cfg(unix)] #[cfg(unix)]
use std::os::unix::prelude::ExitStatusExt; use std::os::unix::prelude::ExitStatusExt;
#[cfg(unix)] #[cfg(unix)]
@ -105,11 +103,12 @@ impl StdioOrRid {
pub fn as_stdio( pub fn as_stdio(
&self, &self,
state: &mut OpState, state: &mut OpState,
) -> Result<std::process::Stdio, AnyError> { ) -> Result<std::process::Stdio, ProcessError> {
match &self { match &self {
StdioOrRid::Stdio(val) => Ok(val.as_stdio()), StdioOrRid::Stdio(val) => Ok(val.as_stdio()),
StdioOrRid::Rid(rid) => { StdioOrRid::Rid(rid) => {
FileResource::with_file(state, *rid, |file| Ok(file.as_stdio()?)) 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, needs_npm_process_state: bool,
} }
#[derive(Debug, thiserror::Error)]
pub enum ProcessError {
#[error("Failed to spawn '{command}': {error}")]
SpawnFailed {
command: String,
#[source]
error: Box<ProcessError>,
},
#[error("{0}")]
Io(#[from] std::io::Error),
#[cfg(unix)]
#[error(transparent)]
Nix(nix::Error),
#[error("failed resolving cwd: {0}")]
FailedResolvingCwd(#[source] std::io::Error),
#[error(transparent)]
Permission(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)] #[derive(Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct ChildStdio { pub struct ChildStdio {
@ -208,7 +240,7 @@ pub struct ChildStatus {
} }
impl TryFrom<ExitStatus> for ChildStatus { impl TryFrom<ExitStatus> for ChildStatus {
type Error = AnyError; type Error = SignalError;
fn try_from(status: ExitStatus) -> Result<Self, Self::Error> { fn try_from(status: ExitStatus) -> Result<Self, Self::Error> {
let code = status.code(); let code = status.code();
@ -259,7 +291,7 @@ type CreateCommand = (
pub fn npm_process_state_tempfile( pub fn npm_process_state_tempfile(
contents: &[u8], contents: &[u8],
) -> Result<deno_io::RawIoHandle, AnyError> { ) -> Result<deno_io::RawIoHandle, std::io::Error> {
let mut temp_file = tempfile::tempfile()?; let mut temp_file = tempfile::tempfile()?;
temp_file.write_all(contents)?; temp_file.write_all(contents)?;
let handle = temp_file.into_raw_io_handle(); let handle = temp_file.into_raw_io_handle();
@ -301,7 +333,7 @@ fn create_command(
state: &mut OpState, state: &mut OpState,
mut args: SpawnArgs, mut args: SpawnArgs,
api_name: &str, api_name: &str,
) -> Result<CreateCommand, AnyError> { ) -> Result<CreateCommand, ProcessError> {
let maybe_npm_process_state = if args.needs_npm_process_state { let maybe_npm_process_state = if args.needs_npm_process_state {
let provider = state.borrow::<NpmProcessStateProviderRc>(); let provider = state.borrow::<NpmProcessStateProviderRc>();
let process_state = provider.get_npm_process_state(); let process_state = provider.get_npm_process_state();
@ -505,7 +537,7 @@ fn spawn_child(
ipc_pipe_rid: Option<ResourceId>, ipc_pipe_rid: Option<ResourceId>,
extra_pipe_rids: Vec<Option<ResourceId>>, extra_pipe_rids: Vec<Option<ResourceId>>,
detached: bool, detached: bool,
) -> Result<Child, AnyError> { ) -> Result<Child, ProcessError> {
let mut command = tokio::process::Command::from(command); let mut command = tokio::process::Command::from(command);
// TODO(@crowlkats): allow detaching processes. // TODO(@crowlkats): allow detaching processes.
// currently deno will orphan a process when exiting with an error or Deno.exit() // 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!( return Err(ProcessError::SpawnFailed {
"Failed to spawn '{}'", command: command.get_program().to_string_lossy().to_string(),
command.get_program().to_string_lossy() error: Box::new(err.into()),
))); });
} }
}; };
@ -600,11 +632,19 @@ fn compute_run_cmd_and_check_permissions(
arg_clear_env: bool, arg_clear_env: bool,
state: &mut OpState, state: &mut OpState,
api_name: &str, api_name: &str,
) -> Result<(PathBuf, RunEnv), AnyError> { ) -> Result<(PathBuf, RunEnv), ProcessError> {
let run_env = compute_run_env(arg_cwd, arg_envs, arg_clear_env) let run_env =
.with_context(|| format!("Failed to spawn '{}'", arg_cmd))?; compute_run_env(arg_cwd, arg_envs, arg_clear_env).map_err(|e| {
let cmd = resolve_cmd(arg_cmd, &run_env) ProcessError::SpawnFailed {
.with_context(|| format!("Failed to spawn '{}'", arg_cmd))?; 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( check_run_permission(
state, state,
&RunQueryDescriptor::Path { &RunQueryDescriptor::Path {
@ -613,7 +653,8 @@ fn compute_run_cmd_and_check_permissions(
}, },
&run_env, &run_env,
api_name, api_name,
)?; )
.map_err(ProcessError::Permission)?;
Ok((cmd, run_env)) Ok((cmd, run_env))
} }
@ -631,9 +672,10 @@ fn compute_run_env(
arg_cwd: Option<&str>, arg_cwd: Option<&str>,
arg_envs: &[(String, String)], arg_envs: &[(String, String)],
arg_clear_env: bool, arg_clear_env: bool,
) -> Result<RunEnv, AnyError> { ) -> Result<RunEnv, ProcessError> {
#[allow(clippy::disallowed_methods)] #[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 let cwd = arg_cwd
.map(|cwd_arg| resolve_path(cwd_arg, &cwd)) .map(|cwd_arg| resolve_path(cwd_arg, &cwd))
.unwrap_or(cwd); .unwrap_or(cwd);
@ -670,7 +712,7 @@ fn compute_run_env(
Ok(RunEnv { envs, cwd }) Ok(RunEnv { envs, cwd })
} }
fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result<PathBuf, AnyError> { fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result<PathBuf, ProcessError> {
let is_path = cmd.contains('/'); let is_path = cmd.contains('/');
#[cfg(windows)] #[cfg(windows)]
let is_path = is_path || cmd.contains('\\') || Path::new(&cmd).is_absolute(); let is_path = is_path || cmd.contains('\\') || Path::new(&cmd).is_absolute();
@ -683,7 +725,7 @@ fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result<PathBuf, AnyError> {
Err(which::Error::CannotFindBinaryPath) => { Err(which::Error::CannotFindBinaryPath) => {
Err(std::io::Error::from(std::io::ErrorKind::NotFound).into()) 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, cmd: &RunQueryDescriptor,
run_env: &RunEnv, run_env: &RunEnv,
api_name: &str, api_name: &str,
) -> Result<(), AnyError> { ) -> Result<(), deno_core::error::AnyError> {
let permissions = state.borrow_mut::<PermissionsContainer>(); let permissions = state.borrow_mut::<PermissionsContainer>();
if !permissions.query_run_all(api_name) { if !permissions.query_run_all(api_name) {
// error the same on all platforms // error the same on all platforms
@ -754,7 +796,7 @@ fn op_spawn_child(
state: &mut OpState, state: &mut OpState,
#[serde] args: SpawnArgs, #[serde] args: SpawnArgs,
#[string] api_name: String, #[string] api_name: String,
) -> Result<Child, AnyError> { ) -> Result<Child, ProcessError> {
let detached = args.detached; let detached = args.detached;
let (command, pipe_rid, extra_pipe_rids, handles_to_close) = let (command, pipe_rid, extra_pipe_rids, handles_to_close) =
create_command(state, args, &api_name)?; create_command(state, args, &api_name)?;
@ -771,16 +813,23 @@ fn op_spawn_child(
async fn op_spawn_wait( async fn op_spawn_wait(
state: Rc<RefCell<OpState>>, state: Rc<RefCell<OpState>>,
#[smi] rid: ResourceId, #[smi] rid: ResourceId,
) -> Result<ChildStatus, AnyError> { ) -> Result<ChildStatus, ProcessError> {
let resource = state let resource = state
.borrow_mut() .borrow_mut()
.resource_table .resource_table
.get::<ChildResource>(rid)?; .get::<ChildResource>(rid)
let result = resource.0.try_borrow_mut()?.wait().await?.try_into(); .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) { if let Ok(resource) = state.borrow_mut().resource_table.take_any(rid) {
resource.close(); resource.close();
} }
result Ok(result)
} }
#[op2] #[op2]
@ -788,16 +837,14 @@ async fn op_spawn_wait(
fn op_spawn_sync( fn op_spawn_sync(
state: &mut OpState, state: &mut OpState,
#[serde] args: SpawnArgs, #[serde] args: SpawnArgs,
) -> Result<SpawnOutput, AnyError> { ) -> Result<SpawnOutput, ProcessError> {
let stdout = matches!(args.stdio.stdout, StdioOrRid::Stdio(Stdio::Piped)); let stdout = matches!(args.stdio.stdout, StdioOrRid::Stdio(Stdio::Piped));
let stderr = matches!(args.stdio.stderr, StdioOrRid::Stdio(Stdio::Piped)); let stderr = matches!(args.stdio.stderr, StdioOrRid::Stdio(Stdio::Piped));
let (mut command, _, _, _) = let (mut command, _, _, _) =
create_command(state, args, "Deno.Command().outputSync()")?; create_command(state, args, "Deno.Command().outputSync()")?;
let output = command.output().with_context(|| { let output = command.output().map_err(|e| ProcessError::SpawnFailed {
format!( command: command.get_program().to_string_lossy().to_string(),
"Failed to spawn '{}'", error: Box::new(e.into()),
command.get_program().to_string_lossy()
)
})?; })?;
Ok(SpawnOutput { Ok(SpawnOutput {
@ -820,17 +867,15 @@ fn op_spawn_kill(
state: &mut OpState, state: &mut OpState,
#[smi] rid: ResourceId, #[smi] rid: ResourceId,
#[string] signal: String, #[string] signal: String,
) -> Result<(), AnyError> { ) -> Result<(), ProcessError> {
if let Ok(child_resource) = state.resource_table.get::<ChildResource>(rid) { if let Ok(child_resource) = state.resource_table.get::<ChildResource>(rid) {
deprecated::kill(child_resource.1 as i32, &signal)?; deprecated::kill(child_resource.1 as i32, &signal)?;
return Ok(()); return Ok(());
} }
Err(type_error("Child process has already terminated.")) Err(ProcessError::ChildProcessAlreadyTerminated)
} }
mod deprecated { mod deprecated {
use deno_core::anyhow;
use super::*; use super::*;
#[derive(Deserialize)] #[derive(Deserialize)]
@ -876,9 +921,9 @@ mod deprecated {
pub fn op_run( pub fn op_run(
state: &mut OpState, state: &mut OpState,
#[serde] run_args: RunArgs, #[serde] run_args: RunArgs,
) -> Result<RunInfo, AnyError> { ) -> Result<RunInfo, ProcessError> {
let args = run_args.cmd; 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( let (cmd, run_env) = compute_run_cmd_and_check_permissions(
cmd, cmd,
run_args.cwd.as_deref(), run_args.cwd.as_deref(),
@ -990,11 +1035,12 @@ mod deprecated {
pub async fn op_run_status( pub async fn op_run_status(
state: Rc<RefCell<OpState>>, state: Rc<RefCell<OpState>>,
#[smi] rid: ResourceId, #[smi] rid: ResourceId,
) -> Result<ProcessStatus, AnyError> { ) -> Result<ProcessStatus, ProcessError> {
let resource = state let resource = state
.borrow_mut() .borrow_mut()
.resource_table .resource_table
.get::<ChildResource>(rid)?; .get::<ChildResource>(rid)
.map_err(ProcessError::Resource)?;
let mut child = resource.borrow_mut().await; let mut child = resource.borrow_mut().await;
let run_status = child.wait().await?; let run_status = child.wait().await?;
let code = run_status.code(); let code = run_status.code();
@ -1017,17 +1063,17 @@ mod deprecated {
} }
#[cfg(unix)] #[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)?; let signo = super::super::signal::signal_str_to_int(signal)?;
use nix::sys::signal::kill as unix_kill; use nix::sys::signal::kill as unix_kill;
use nix::sys::signal::Signal; use nix::sys::signal::Signal;
use nix::unistd::Pid; use nix::unistd::Pid;
let sig = Signal::try_from(signo)?; let sig = Signal::try_from(signo).map_err(ProcessError::Nix)?;
unix_kill(Pid::from_raw(pid), Option::Some(sig)).map_err(AnyError::from) unix_kill(Pid::from_raw(pid), Some(sig)).map_err(ProcessError::Nix)
} }
#[cfg(not(unix))] #[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::Error;
use std::io::ErrorKind::NotFound; use std::io::ErrorKind::NotFound;
use winapi::shared::minwindef::DWORD; use winapi::shared::minwindef::DWORD;
@ -1041,9 +1087,9 @@ mod deprecated {
use winapi::um::winnt::PROCESS_TERMINATE; use winapi::um::winnt::PROCESS_TERMINATE;
if !matches!(signal, "SIGKILL" | "SIGTERM") { if !matches!(signal, "SIGKILL" | "SIGTERM") {
Err(type_error(format!("Invalid signal: {signal}"))) Err(SignalError::InvalidSignalStr(signal.to_string()).into())
} else if pid <= 0 { } else if pid <= 0 {
Err(type_error("Invalid pid")) Err(ProcessError::InvalidPid)
} else { } else {
let handle = let handle =
// SAFETY: winapi call // SAFETY: winapi call
@ -1077,11 +1123,11 @@ mod deprecated {
#[smi] pid: i32, #[smi] pid: i32,
#[string] signal: String, #[string] signal: String,
#[string] api_name: String, #[string] api_name: String,
) -> Result<(), AnyError> { ) -> Result<(), ProcessError> {
state state
.borrow_mut::<PermissionsContainer>() .borrow_mut::<PermissionsContainer>()
.check_run_all(&api_name)?; .check_run_all(&api_name)
kill(pid, &signal)?; .map_err(ProcessError::Permission)?;
Ok(()) kill(pid, &signal)
} }
} }

View file

@ -1,6 +1,5 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use deno_core::error::AnyError;
use deno_core::op2; use deno_core::op2;
use deno_core::ModuleSpecifier; use deno_core::ModuleSpecifier;
use deno_core::OpState; use deno_core::OpState;
@ -16,10 +15,9 @@ deno_core::extension!(
#[op2] #[op2]
#[string] #[string]
fn op_main_module(state: &mut OpState) -> Result<String, AnyError> { fn op_main_module(state: &mut OpState) -> String {
let main_url = state.borrow::<ModuleSpecifier>(); let main_url = state.borrow::<ModuleSpecifier>();
let main_path = main_url.to_string(); main_url.to_string()
Ok(main_path)
} }
/// This is an op instead of being done at initialization time because /// This is an op instead of being done at initialization time because

View file

@ -1,6 +1,4 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license. // 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::op2;
use deno_core::AsyncRefCell; use deno_core::AsyncRefCell;
use deno_core::CancelFuture; 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)] #[cfg(unix)]
#[derive(Default)] #[derive(Default)]
struct SignalState { struct SignalState {
@ -153,18 +187,18 @@ macro_rules! first_literal {
}; };
} }
macro_rules! signal_dict { macro_rules! signal_dict {
($error_msg:expr, $(($number:literal, $($name:literal)|+)),*) => { ($(($number:literal, $($name:literal)|+)),*) => {
pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, AnyError> { pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, SignalError> {
match s { match s {
$($($name)|* => Ok($number),)* $($($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 { match s {
$($number => Ok(first_literal!($($name),+)),)* $($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")] #[cfg(target_os = "freebsd")]
signal_dict!( signal_dict!(
|s| { format!("Invalid signal : {}", s) },
(1, "SIGHUP"), (1, "SIGHUP"),
(2, "SIGINT"), (2, "SIGINT"),
(3, "SIGQUIT"), (3, "SIGQUIT"),
@ -210,7 +243,6 @@ signal_dict!(
#[cfg(target_os = "openbsd")] #[cfg(target_os = "openbsd")]
signal_dict!( signal_dict!(
|s| { format!("Invalid signal : {}", s) },
(1, "SIGHUP"), (1, "SIGHUP"),
(2, "SIGINT"), (2, "SIGINT"),
(3, "SIGQUIT"), (3, "SIGQUIT"),
@ -246,7 +278,6 @@ signal_dict!(
#[cfg(any(target_os = "android", target_os = "linux"))] #[cfg(any(target_os = "android", target_os = "linux"))]
signal_dict!( signal_dict!(
|s| { format!("Invalid signal : {s}") },
(1, "SIGHUP"), (1, "SIGHUP"),
(2, "SIGINT"), (2, "SIGINT"),
(3, "SIGQUIT"), (3, "SIGQUIT"),
@ -282,7 +313,6 @@ signal_dict!(
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
signal_dict!( signal_dict!(
|s| { format!("Invalid signal : {s}") },
(1, "SIGHUP"), (1, "SIGHUP"),
(2, "SIGINT"), (2, "SIGINT"),
(3, "SIGQUIT"), (3, "SIGQUIT"),
@ -318,7 +348,6 @@ signal_dict!(
#[cfg(any(target_os = "solaris", target_os = "illumos"))] #[cfg(any(target_os = "solaris", target_os = "illumos"))]
signal_dict!( signal_dict!(
|s| { format!("Invalid signal : {s}") },
(1, "SIGHUP"), (1, "SIGHUP"),
(2, "SIGINT"), (2, "SIGINT"),
(3, "SIGQUIT"), (3, "SIGQUIT"),
@ -362,11 +391,7 @@ signal_dict!(
); );
#[cfg(target_os = "windows")] #[cfg(target_os = "windows")]
signal_dict!( signal_dict!((2, "SIGINT"), (21, "SIGBREAK"));
|_| { "Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK)." },
(2, "SIGINT"),
(21, "SIGBREAK")
);
#[cfg(unix)] #[cfg(unix)]
#[op2(fast)] #[op2(fast)]
@ -374,12 +399,10 @@ signal_dict!(
fn op_signal_bind( fn op_signal_bind(
state: &mut OpState, state: &mut OpState,
#[string] sig: &str, #[string] sig: &str,
) -> Result<ResourceId, AnyError> { ) -> Result<ResourceId, SignalError> {
let signo = signal_str_to_int(sig)?; let signo = signal_str_to_int(sig)?;
if signal_hook_registry::FORBIDDEN.contains(&signo) { if signal_hook_registry::FORBIDDEN.contains(&signo) {
return Err(type_error(format!( return Err(SignalError::SignalNotAllowed(sig.to_string()));
"Binding to signal '{sig}' is not allowed",
)));
} }
let signal = AsyncRefCell::new(signal(SignalKind::from_raw(signo))?); let signal = AsyncRefCell::new(signal(SignalKind::from_raw(signo))?);
@ -413,7 +436,7 @@ fn op_signal_bind(
fn op_signal_bind( fn op_signal_bind(
state: &mut OpState, state: &mut OpState,
#[string] sig: &str, #[string] sig: &str,
) -> Result<ResourceId, AnyError> { ) -> Result<ResourceId, SignalError> {
let signo = signal_str_to_int(sig)?; let signo = signal_str_to_int(sig)?;
let resource = SignalStreamResource { let resource = SignalStreamResource {
signal: AsyncRefCell::new(match signo { signal: AsyncRefCell::new(match signo {
@ -437,7 +460,7 @@ fn op_signal_bind(
async fn op_signal_poll( async fn op_signal_poll(
state: Rc<RefCell<OpState>>, state: Rc<RefCell<OpState>>,
#[smi] rid: ResourceId, #[smi] rid: ResourceId,
) -> Result<bool, AnyError> { ) -> Result<bool, deno_core::error::AnyError> {
let resource = state let resource = state
.borrow_mut() .borrow_mut()
.resource_table .resource_table
@ -456,7 +479,7 @@ async fn op_signal_poll(
pub fn op_signal_unbind( pub fn op_signal_unbind(
state: &mut OpState, state: &mut OpState,
#[smi] rid: ResourceId, #[smi] rid: ResourceId,
) -> Result<(), AnyError> { ) -> Result<(), deno_core::error::AnyError> {
let resource = state.resource_table.take::<SignalStreamResource>(rid)?; let resource = state.resource_table.take::<SignalStreamResource>(rid)?;
#[cfg(unix)] #[cfg(unix)]

View file

@ -2,7 +2,6 @@
use std::io::Error; use std::io::Error;
use deno_core::error::AnyError;
use deno_core::op2; use deno_core::op2;
use deno_core::OpState; use deno_core::OpState;
use rustyline::config::Configurer; use rustyline::config::Configurer;
@ -64,6 +63,19 @@ deno_core::extension!(
}, },
); );
#[derive(Debug, thiserror::Error)]
pub enum TtyError {
#[error(transparent)]
Resource(deno_core::error::AnyError),
#[error("{0}")]
Io(#[from] std::io::Error),
#[cfg(unix)]
#[error(transparent)]
Nix(nix::Error),
#[error(transparent)]
Other(deno_core::error::AnyError),
}
// ref: <https://learn.microsoft.com/en-us/windows/console/setconsolemode> // ref: <https://learn.microsoft.com/en-us/windows/console/setconsolemode>
#[cfg(windows)] #[cfg(windows)]
const COOKED_MODE: DWORD = const COOKED_MODE: DWORD =
@ -90,8 +102,11 @@ fn op_set_raw(
rid: u32, rid: u32,
is_raw: bool, is_raw: bool,
cbreak: bool, cbreak: bool,
) -> Result<(), AnyError> { ) -> Result<(), TtyError> {
let handle_or_fd = state.resource_table.get_fd(rid)?; 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 // From https://github.com/kkawakam/rustyline/blob/master/src/tty/windows.rs
// and https://github.com/kkawakam/rustyline/blob/master/src/tty/unix.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; let handle = handle_or_fd;
if cbreak { if cbreak {
return Err(deno_core::error::not_supported()); return Err(TtyError::Other(deno_core::error::not_supported()));
} }
let mut original_mode: DWORD = 0; let mut original_mode: DWORD = 0;
@ -115,7 +130,7 @@ fn op_set_raw(
if unsafe { consoleapi::GetConsoleMode(handle, &mut original_mode) } if unsafe { consoleapi::GetConsoleMode(handle, &mut original_mode) }
== FALSE == FALSE
{ {
return Err(Error::last_os_error().into()); return Err(TtyError::Io(Error::last_os_error()));
} }
let new_mode = if is_raw { let new_mode = if is_raw {
@ -185,7 +200,7 @@ fn op_set_raw(
winapi::um::wincon::WriteConsoleInputW(handle, &record, 1, &mut 0) winapi::um::wincon::WriteConsoleInputW(handle, &record, 1, &mut 0)
} == FALSE } == 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 /* Wait for read thread to acknowledge the cancellation to ensure that nothing
@ -199,7 +214,7 @@ fn op_set_raw(
// SAFETY: winapi call // SAFETY: winapi call
if unsafe { consoleapi::SetConsoleMode(handle, new_mode) } == FALSE { if unsafe { consoleapi::SetConsoleMode(handle, new_mode) } == FALSE {
return Err(Error::last_os_error().into()); return Err(TtyError::Io(Error::last_os_error()));
} }
Ok(()) Ok(())
@ -252,7 +267,8 @@ fn op_set_raw(
Some(mode) => mode, Some(mode) => mode,
None => { None => {
// Save original mode. // 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()); tty_mode_store.set(rid, original_mode.clone());
original_mode original_mode
} }
@ -274,11 +290,13 @@ fn op_set_raw(
} }
raw.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1; raw.control_chars[termios::SpecialCharacterIndices::VMIN as usize] = 1;
raw.control_chars[termios::SpecialCharacterIndices::VTIME as usize] = 0; 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 { } else {
// Try restore saved mode. // Try restore saved mode.
if let Some(mode) = tty_mode_store.take(rid) { 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( fn op_console_size(
state: &mut OpState, state: &mut OpState,
#[buffer] result: &mut [u32], #[buffer] result: &mut [u32],
) -> Result<(), AnyError> { ) -> Result<(), TtyError> {
fn check_console_size( fn check_console_size(
state: &mut OpState, state: &mut OpState,
result: &mut [u32], result: &mut [u32],
rid: u32, rid: u32,
) -> Result<(), AnyError> { ) -> Result<(), TtyError> {
let fd = state.resource_table.get_fd(rid)?; let fd = state
.resource_table
.get_fd(rid)
.map_err(TtyError::Resource)?;
let size = console_size_from_fd(fd)?; let size = console_size_from_fd(fd)?;
result[0] = size.cols; result[0] = size.cols;
result[1] = size.rows; result[1] = size.rows;
@ -419,7 +440,7 @@ mod tests {
pub fn op_read_line_prompt( pub fn op_read_line_prompt(
#[string] prompt_text: &str, #[string] prompt_text: &str,
#[string] default_value: &str, #[string] default_value: &str,
) -> Result<Option<String>, AnyError> { ) -> Result<Option<String>, ReadlineError> {
let mut editor = Editor::<(), rustyline::history::DefaultHistory>::new() let mut editor = Editor::<(), rustyline::history::DefaultHistory>::new()
.expect("Failed to create editor."); .expect("Failed to create editor.");
@ -439,6 +460,6 @@ pub fn op_read_line_prompt(
Ok(None) Ok(None)
} }
Err(ReadlineError::Eof) => Ok(None), Err(ReadlineError::Eof) => Ok(None),
Err(err) => Err(err.into()), Err(err) => Err(err),
} }
} }

View file

@ -1,12 +0,0 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use deno_core::error::custom_error;
use deno_core::error::AnyError;
/// A utility function to map OsStrings to Strings
pub fn into_string(s: std::ffi::OsString) -> Result<String, AnyError> {
s.into_string().map_err(|s| {
let message = format!("File name or path {s:?} is not valid UTF-8");
custom_error("InvalidData", message)
})
}

View file

@ -4,15 +4,16 @@ mod sync_fetch;
use crate::web_worker::WebWorkerInternalHandle; use crate::web_worker::WebWorkerInternalHandle;
use crate::web_worker::WebWorkerType; use crate::web_worker::WebWorkerType;
use deno_core::error::AnyError;
use deno_core::op2; use deno_core::op2;
use deno_core::CancelFuture; use deno_core::CancelFuture;
use deno_core::OpState; use deno_core::OpState;
use deno_web::JsMessageData; use deno_web::JsMessageData;
use deno_web::MessagePortError;
use std::cell::RefCell; use std::cell::RefCell;
use std::rc::Rc; use std::rc::Rc;
use self::sync_fetch::op_worker_sync_fetch; use self::sync_fetch::op_worker_sync_fetch;
pub use sync_fetch::SyncFetchError;
deno_core::extension!( deno_core::extension!(
deno_web_worker, deno_web_worker,
@ -30,17 +31,16 @@ deno_core::extension!(
fn op_worker_post_message( fn op_worker_post_message(
state: &mut OpState, state: &mut OpState,
#[serde] data: JsMessageData, #[serde] data: JsMessageData,
) -> Result<(), AnyError> { ) -> Result<(), MessagePortError> {
let handle = state.borrow::<WebWorkerInternalHandle>().clone(); let handle = state.borrow::<WebWorkerInternalHandle>().clone();
handle.port.send(state, data)?; handle.port.send(state, data)
Ok(())
} }
#[op2(async(lazy), fast)] #[op2(async(lazy), fast)]
#[serde] #[serde]
async fn op_worker_recv_message( async fn op_worker_recv_message(
state: Rc<RefCell<OpState>>, state: Rc<RefCell<OpState>>,
) -> Result<Option<JsMessageData>, AnyError> { ) -> Result<Option<JsMessageData>, MessagePortError> {
let handle = { let handle = {
let state = state.borrow(); let state = state.borrow();
state.borrow::<WebWorkerInternalHandle>().clone() state.borrow::<WebWorkerInternalHandle>().clone()
@ -50,7 +50,6 @@ async fn op_worker_recv_message(
.recv(state.clone()) .recv(state.clone())
.or_cancel(handle.cancel) .or_cancel(handle.cancel)
.await? .await?
.map_err(|e| e.into())
} }
#[op2(fast)] #[op2(fast)]

View file

@ -4,14 +4,12 @@ use std::sync::Arc;
use crate::web_worker::WebWorkerInternalHandle; use crate::web_worker::WebWorkerInternalHandle;
use crate::web_worker::WebWorkerType; 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::futures::StreamExt;
use deno_core::op2; use deno_core::op2;
use deno_core::url::Url; use deno_core::url::Url;
use deno_core::OpState; use deno_core::OpState;
use deno_fetch::data_url::DataUrl; use deno_fetch::data_url::DataUrl;
use deno_fetch::FetchError;
use deno_web::BlobStore; use deno_web::BlobStore;
use http_body_util::BodyExt; use http_body_util::BodyExt;
use hyper::body::Bytes; use hyper::body::Bytes;
@ -27,6 +25,32 @@ fn mime_type_essence(mime_type: &str) -> String {
essence.trim().to_ascii_lowercase() 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)] #[derive(Serialize, Deserialize)]
#[serde(rename_all = "camelCase")] #[serde(rename_all = "camelCase")]
pub struct SyncFetchScript { pub struct SyncFetchScript {
@ -40,21 +64,22 @@ pub fn op_worker_sync_fetch(
state: &mut OpState, state: &mut OpState,
#[serde] scripts: Vec<String>, #[serde] scripts: Vec<String>,
loose_mime_checks: bool, loose_mime_checks: bool,
) -> Result<Vec<SyncFetchScript>, AnyError> { ) -> Result<Vec<SyncFetchScript>, SyncFetchError> {
let handle = state.borrow::<WebWorkerInternalHandle>().clone(); let handle = state.borrow::<WebWorkerInternalHandle>().clone();
assert_eq!(handle.worker_type, WebWorkerType::Classic); assert_eq!(handle.worker_type, WebWorkerType::Classic);
// it's not safe to share a client across tokio runtimes, so create a fresh one // 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 // https://github.com/seanmonstar/reqwest/issues/1148#issuecomment-910868788
let options = state.borrow::<deno_fetch::Options>().clone(); let options = state.borrow::<deno_fetch::Options>().clone();
let client = deno_fetch::create_client_from_options(&options)?; let client = deno_fetch::create_client_from_options(&options)
.map_err(FetchError::ClientCreate)?;
// TODO(andreubotella) It's not good to throw an exception related to blob // TODO(andreubotella) It's not good to throw an exception related to blob
// URLs when none of the script URLs use the blob scheme. // URLs when none of the script URLs use the blob scheme.
// Also, in which contexts are blob URLs not supported? // Also, in which contexts are blob URLs not supported?
let blob_store = state let blob_store = state
.try_borrow::<Arc<BlobStore>>() .try_borrow::<Arc<BlobStore>>()
.ok_or_else(|| type_error("Blob URLs are not supported in this context."))? .ok_or(SyncFetchError::BlobUrlsNotSupportedInContext)?
.clone(); .clone();
// TODO(andreubotella): make the below thread into a resource that can be // 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(); let blob_store = blob_store.clone();
deno_core::unsync::spawn(async move { deno_core::unsync::spawn(async move {
let script_url = Url::parse(&script) 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 mut loose_mime_checks = loose_mime_checks;
let (body, mime_type, res_url) = match script_url.scheme() { 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()?; *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() if resp.status().is_client_error()
|| resp.status().is_server_error() || resp.status().is_server_error()
{ {
return Err(type_error(format!( return Err(SyncFetchError::InvalidStatusCode(resp.status()));
"http status error: {}",
resp.status()
)));
} }
// TODO(andreubotella) Properly run fetch's "extract a MIME type". // 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). // Always check the MIME type with HTTP(S).
loose_mime_checks = false; 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) (body, mime_type, script)
} }
"data" => { "data" => {
let data_url = DataUrl::process(&script) let data_url =
.map_err(|e| type_error(format!("{e:?}")))?; DataUrl::process(&script).map_err(FetchError::DataUrl)?;
let mime_type = { let mime_type = {
let mime = data_url.mime_type(); let mime = data_url.mime_type();
format!("{}/{}", mime.type_, mime.subtype) format!("{}/{}", mime.type_, mime.subtype)
}; };
let (body, _) = data_url let (body, _) =
.decode_to_vec() data_url.decode_to_vec().map_err(FetchError::Base64)?;
.map_err(|e| type_error(format!("{e:?}")))?;
(Bytes::from(body), Some(mime_type), script) (Bytes::from(body), Some(mime_type), script)
} }
"blob" => { "blob" => {
let blob = let blob = blob_store
blob_store.get_object_url(script_url).ok_or_else(|| { .get_object_url(script_url)
type_error("Blob for the given URL not found.") .ok_or(FetchError::BlobNotFound)?;
})?;
let mime_type = mime_type_essence(&blob.media_type); 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) (Bytes::from(body), Some(mime_type), script)
} }
_ => { _ => {
return Err(type_error(format!( return Err(
"Classic scripts with scheme {}: are not supported in workers.", SyncFetchError::ClassicScriptSchemeUnsupportedInWorkers(
script_url.scheme() script_url.scheme().to_string(),
))) ),
)
} }
}; };
@ -151,17 +177,11 @@ pub fn op_worker_sync_fetch(
match mime_type.as_deref() { match mime_type.as_deref() {
Some("application/javascript" | "text/javascript") => {} Some("application/javascript" | "text/javascript") => {}
Some(mime_type) => { Some(mime_type) => {
return Err(custom_error( return Err(SyncFetchError::InvalidMimeType(
"DOMExceptionNetworkError", mime_type.to_string(),
format!("Invalid MIME type {mime_type:?}."),
))
}
None => {
return Err(custom_error(
"DOMExceptionNetworkError",
"Missing MIME type.",
)) ))
} }
None => return Err(SyncFetchError::MissingMimeType),
} }
} }

View file

@ -10,8 +10,6 @@ use crate::web_worker::WorkerControlEvent;
use crate::web_worker::WorkerId; use crate::web_worker::WorkerId;
use crate::web_worker::WorkerMetadata; use crate::web_worker::WorkerMetadata;
use crate::worker::FormatJsErrorFn; use crate::worker::FormatJsErrorFn;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
use deno_core::op2; use deno_core::op2;
use deno_core::serde::Deserialize; use deno_core::serde::Deserialize;
use deno_core::CancelFuture; use deno_core::CancelFuture;
@ -22,6 +20,7 @@ use deno_permissions::ChildPermissionsArg;
use deno_permissions::PermissionsContainer; use deno_permissions::PermissionsContainer;
use deno_web::deserialize_js_transferables; use deno_web::deserialize_js_transferables;
use deno_web::JsMessageData; use deno_web::JsMessageData;
use deno_web::MessagePortError;
use log::debug; use log::debug;
use std::cell::RefCell; use std::cell::RefCell;
use std::collections::HashMap; use std::collections::HashMap;
@ -119,6 +118,20 @@ pub struct CreateWorkerArgs {
close_on_idle: bool, 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 /// Create worker as the host
#[op2] #[op2]
#[serde] #[serde]
@ -126,7 +139,7 @@ fn op_create_worker(
state: &mut OpState, state: &mut OpState,
#[serde] args: CreateWorkerArgs, #[serde] args: CreateWorkerArgs,
#[serde] maybe_worker_metadata: Option<JsMessageData>, #[serde] maybe_worker_metadata: Option<JsMessageData>,
) -> Result<WorkerId, AnyError> { ) -> Result<WorkerId, CreateWorkerError> {
let specifier = args.specifier.clone(); let specifier = args.specifier.clone();
let maybe_source_code = if args.has_source_code { let maybe_source_code = if args.has_source_code {
Some(args.source_code.clone()) Some(args.source_code.clone())
@ -137,10 +150,7 @@ fn op_create_worker(
let worker_type = args.worker_type; let worker_type = args.worker_type;
if let WebWorkerType::Classic = worker_type { if let WebWorkerType::Classic = worker_type {
if let TestingFeaturesEnabled(false) = state.borrow() { if let TestingFeaturesEnabled(false) = state.borrow() {
return Err(custom_error( return Err(CreateWorkerError::ClassicWorkers);
"DOMExceptionNotSupportedError",
"Classic workers are not supported.",
));
} }
} }
@ -154,7 +164,9 @@ fn op_create_worker(
let parent_permissions = state.borrow_mut::<PermissionsContainer>(); let parent_permissions = state.borrow_mut::<PermissionsContainer>();
let worker_permissions = if let Some(child_permissions_arg) = args.permissions 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 { } else {
parent_permissions.clone() parent_permissions.clone()
}; };
@ -166,9 +178,8 @@ fn op_create_worker(
let module_specifier = deno_core::resolve_url(&specifier)?; let module_specifier = deno_core::resolve_url(&specifier)?;
let worker_name = args_name.unwrap_or_default(); let worker_name = args_name.unwrap_or_default();
let (handle_sender, handle_receiver) = std::sync::mpsc::sync_channel::< let (handle_sender, handle_receiver) =
Result<SendableWebWorkerHandle, AnyError>, std::sync::mpsc::sync_channel::<SendableWebWorkerHandle>(1);
>(1);
// Setup new thread // Setup new thread
let thread_builder = std::thread::Builder::new().name(format!("{worker_id}")); 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 // 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); drop(handle_sender);
// At this point the only method of communication with host // At this point the only method of communication with host
@ -218,7 +229,7 @@ fn op_create_worker(
})?; })?;
// Receive WebWorkerHandle from newly created 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 { let worker_thread = WorkerThread {
worker_handle: worker_handle.into(), worker_handle: worker_handle.into(),
@ -291,7 +302,7 @@ fn close_channel(
async fn op_host_recv_ctrl( async fn op_host_recv_ctrl(
state: Rc<RefCell<OpState>>, state: Rc<RefCell<OpState>>,
#[serde] id: WorkerId, #[serde] id: WorkerId,
) -> Result<WorkerControlEvent, AnyError> { ) -> WorkerControlEvent {
let (worker_handle, cancel_handle) = { let (worker_handle, cancel_handle) = {
let state = state.borrow(); let state = state.borrow();
let workers_table = state.borrow::<WorkersTable>(); let workers_table = state.borrow::<WorkersTable>();
@ -300,7 +311,7 @@ async fn op_host_recv_ctrl(
(handle.worker_handle.clone(), handle.cancel_handle.clone()) (handle.worker_handle.clone(), handle.cancel_handle.clone())
} else { } else {
// If handle was not found it means worker has already shutdown // 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) .or_cancel(cancel_handle)
.await; .await;
match maybe_event { match maybe_event {
Ok(Ok(Some(event))) => { Ok(Some(event)) => {
// Terminal error means that worker should be removed from worker table. // Terminal error means that worker should be removed from worker table.
if let WorkerControlEvent::TerminalError(_) = &event { if let WorkerControlEvent::TerminalError(_) = &event {
close_channel(state, id, WorkerChannel::Ctrl); 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. // If there was no event from worker it means it has already been closed.
close_channel(state, id, WorkerChannel::Ctrl); close_channel(state, id, WorkerChannel::Ctrl);
Ok(WorkerControlEvent::Close) WorkerControlEvent::Close
} }
Ok(Err(err)) => Err(err),
Err(_) => { Err(_) => {
// The worker was terminated. // 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( async fn op_host_recv_message(
state: Rc<RefCell<OpState>>, state: Rc<RefCell<OpState>>,
#[serde] id: WorkerId, #[serde] id: WorkerId,
) -> Result<Option<JsMessageData>, AnyError> { ) -> Result<Option<JsMessageData>, MessagePortError> {
let (worker_handle, cancel_handle) = { let (worker_handle, cancel_handle) = {
let s = state.borrow(); let s = state.borrow();
let workers_table = s.borrow::<WorkersTable>(); let workers_table = s.borrow::<WorkersTable>();
@ -359,7 +369,7 @@ async fn op_host_recv_message(
} }
Ok(ret) Ok(ret)
} }
Ok(Err(err)) => Err(err.into()), Ok(Err(err)) => Err(err),
Err(_) => { Err(_) => {
// The worker was terminated. // The worker was terminated.
Ok(None) Ok(None)
@ -373,7 +383,7 @@ fn op_host_post_message(
state: &mut OpState, state: &mut OpState,
#[serde] id: WorkerId, #[serde] id: WorkerId,
#[serde] data: JsMessageData, #[serde] data: JsMessageData,
) -> Result<(), AnyError> { ) -> Result<(), MessagePortError> {
if let Some(worker_thread) = state.borrow::<WorkersTable>().get(&id) { if let Some(worker_thread) = state.borrow::<WorkersTable>().get(&id) {
debug!("post message to worker {}", id); debug!("post message to worker {}", id);
let worker_handle = worker_thread.worker_handle.clone(); let worker_handle = worker_thread.worker_handle.clone();

View file

@ -166,7 +166,10 @@ pub struct WebWorkerInternalHandle {
impl WebWorkerInternalHandle { impl WebWorkerInternalHandle {
/// Post WorkerEvent to parent as a worker /// Post WorkerEvent to parent as a worker
pub fn post_event(&self, event: WorkerControlEvent) -> Result<(), AnyError> { pub fn post_event(
&self,
event: WorkerControlEvent,
) -> Result<(), mpsc::TrySendError<WorkerControlEvent>> {
let mut sender = self.sender.clone(); let mut sender = self.sender.clone();
// If the channel is closed, // If the channel is closed,
// the worker must have terminated but the termination message has not yet been received. // the worker must have terminated but the termination message has not yet been received.
@ -176,8 +179,7 @@ impl WebWorkerInternalHandle {
self.has_terminated.store(true, Ordering::SeqCst); self.has_terminated.store(true, Ordering::SeqCst);
return Ok(()); return Ok(());
} }
sender.try_send(event)?; sender.try_send(event)
Ok(())
} }
/// Check if this worker is terminated or being terminated /// Check if this worker is terminated or being terminated
@ -263,11 +265,9 @@ impl WebWorkerHandle {
/// Get the WorkerEvent with lock /// Get the WorkerEvent with lock
/// Return error if more than one listener tries to get event /// Return error if more than one listener tries to get event
#[allow(clippy::await_holding_refcell_ref)] // TODO(ry) remove! #[allow(clippy::await_holding_refcell_ref)] // TODO(ry) remove!
pub async fn get_control_event( pub async fn get_control_event(&self) -> Option<WorkerControlEvent> {
&self,
) -> Result<Option<WorkerControlEvent>, AnyError> {
let mut receiver = self.receiver.borrow_mut(); let mut receiver = self.receiver.borrow_mut();
Ok(receiver.next().await) receiver.next().await
} }
/// Terminate the worker /// Terminate the worker

View file

@ -5,101 +5,101 @@ Deno.test(
{ ignore: Deno.build.os !== "windows" }, { ignore: Deno.build.os !== "windows" },
function signalsNotImplemented() { function signalsNotImplemented() {
const msg = const msg =
"Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK)."; "Windows only supports ctrl-c (SIGINT) and ctrl-break (SIGBREAK), but got ";
assertThrows( assertThrows(
() => { () => {
Deno.addSignalListener("SIGALRM", () => {}); Deno.addSignalListener("SIGALRM", () => {});
}, },
Error, Error,
msg, msg + "SIGALRM",
); );
assertThrows( assertThrows(
() => { () => {
Deno.addSignalListener("SIGCHLD", () => {}); Deno.addSignalListener("SIGCHLD", () => {});
}, },
Error, Error,
msg, msg + "SIGCHLD",
); );
assertThrows( assertThrows(
() => { () => {
Deno.addSignalListener("SIGHUP", () => {}); Deno.addSignalListener("SIGHUP", () => {});
}, },
Error, Error,
msg, msg + "SIGHUP",
); );
assertThrows( assertThrows(
() => { () => {
Deno.addSignalListener("SIGIO", () => {}); Deno.addSignalListener("SIGIO", () => {});
}, },
Error, Error,
msg, msg + "SIGIO",
); );
assertThrows( assertThrows(
() => { () => {
Deno.addSignalListener("SIGPIPE", () => {}); Deno.addSignalListener("SIGPIPE", () => {});
}, },
Error, Error,
msg, msg + "SIGPIPE",
); );
assertThrows( assertThrows(
() => { () => {
Deno.addSignalListener("SIGQUIT", () => {}); Deno.addSignalListener("SIGQUIT", () => {});
}, },
Error, Error,
msg, msg + "SIGQUIT",
); );
assertThrows( assertThrows(
() => { () => {
Deno.addSignalListener("SIGTERM", () => {}); Deno.addSignalListener("SIGTERM", () => {});
}, },
Error, Error,
msg, msg + "SIGTERM",
); );
assertThrows( assertThrows(
() => { () => {
Deno.addSignalListener("SIGUSR1", () => {}); Deno.addSignalListener("SIGUSR1", () => {});
}, },
Error, Error,
msg, msg + "SIGUSR1",
); );
assertThrows( assertThrows(
() => { () => {
Deno.addSignalListener("SIGUSR2", () => {}); Deno.addSignalListener("SIGUSR2", () => {});
}, },
Error, Error,
msg, msg + "SIGUSR2",
); );
assertThrows( assertThrows(
() => { () => {
Deno.addSignalListener("SIGWINCH", () => {}); Deno.addSignalListener("SIGWINCH", () => {});
}, },
Error, Error,
msg, msg + "SIGWINCH",
); );
assertThrows( assertThrows(
() => Deno.addSignalListener("SIGKILL", () => {}), () => Deno.addSignalListener("SIGKILL", () => {}),
Error, Error,
msg, msg + "SIGKILL",
); );
assertThrows( assertThrows(
() => Deno.addSignalListener("SIGSTOP", () => {}), () => Deno.addSignalListener("SIGSTOP", () => {}),
Error, Error,
msg, msg + "SIGSTOP",
); );
assertThrows( assertThrows(
() => Deno.addSignalListener("SIGILL", () => {}), () => Deno.addSignalListener("SIGILL", () => {}),
Error, Error,
msg, msg + "SIGILL",
); );
assertThrows( assertThrows(
() => Deno.addSignalListener("SIGFPE", () => {}), () => Deno.addSignalListener("SIGFPE", () => {}),
Error, Error,
msg, msg + "SIGFPE",
); );
assertThrows( assertThrows(
() => Deno.addSignalListener("SIGSEGV", () => {}), () => Deno.addSignalListener("SIGSEGV", () => {}),
Error, Error,
msg, msg + "SIGSEGV",
); );
}, },
); );