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",
"tempfile",
"test_server",
"thiserror",
"tokio",
"tokio-metrics",
"twox-hash",

View file

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

View file

@ -9,6 +9,14 @@
//! Diagnostics are compile-time type errors, whereas JsErrors are runtime
//! 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_cache::CacheError;
use deno_canvas::CanvasError;
@ -49,6 +57,7 @@ use deno_web::WebError;
use deno_websocket::HandshakeError;
use deno_websocket::WebsocketError;
use deno_webstorage::WebStorageError;
use rustyline::error::ReadlineError;
use std::env;
use std::error::Error;
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 {
match error {
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> {
deno_core::error::get_custom_error_class(e)
.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::<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(|| {
e.downcast_ref::<CompressionError>()
.map(get_web_compression_error_class)

View file

@ -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<mpsc::Receiver<Result<FsEvent, AnyError>>>,
receiver: AsyncRefCell<mpsc::Receiver<Result<FsEvent, NotifyError>>>,
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)]
pub struct OpenArgs {
recursive: bool,
@ -104,12 +115,12 @@ pub struct OpenArgs {
fn op_fs_events_open(
state: &mut OpState,
#[serde] args: OpenArgs,
) -> Result<ResourceId, AnyError> {
let (sender, receiver) = mpsc::channel::<Result<FsEvent, AnyError>>(16);
) -> Result<ResourceId, FsEventsError> {
let (sender, receiver) = mpsc::channel::<Result<FsEvent, NotifyError>>(16);
let sender = Mutex::new(sender);
let mut watcher: RecommendedWatcher = Watcher::new(
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();
// 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::<PermissionsContainer>()
.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<RefCell<OpState>>,
#[smi] rid: ResourceId,
) -> Result<Option<FsEvent>, AnyError> {
let resource = state.borrow().resource_table.get::<FsEventsResource>(rid)?;
) -> Result<Option<FsEvent>, FsEventsError> {
let resource = state
.borrow()
.resource_table
.get::<FsEventsResource>(rid)
.map_err(FsEventsError::Resource)?;
let mut receiver = RcRef::map(&resource, |r| &r.receiver).borrow_mut().await;
let cancel = RcRef::map(resource, |r| &r.cancel);
let maybe_result = receiver.recv().or_cancel(cancel).await?;
match maybe_result {
Some(Ok(value)) => Ok(Some(value)),
Some(Err(err)) => Err(err),
Some(Err(err)) => Err(FsEventsError::Notify(err)),
None => Ok(None),
}
}

View file

@ -2,9 +2,6 @@
use std::rc::Rc;
use deno_core::error::bad_resource_id;
use deno_core::error::custom_error;
use deno_core::error::AnyError;
use deno_core::op2;
use deno_core::OpState;
use deno_core::ResourceId;
@ -16,12 +13,31 @@ pub const UNSTABLE_FEATURE_NAME: &str = "http";
deno_core::extension!(deno_http_runtime, ops = [op_http_start],);
#[derive(Debug, thiserror::Error)]
pub enum HttpStartError {
#[error("TCP stream is currently in use")]
TcpStreamInUse,
#[error("TLS stream is currently in use")]
TlsStreamInUse,
#[error("Unix socket is currently in use")]
UnixSocketInUse,
#[error(transparent)]
ReuniteTcp(#[from] tokio::net::tcp::ReuniteError),
#[cfg(unix)]
#[error(transparent)]
ReuniteUnix(#[from] tokio::net::unix::ReuniteError),
#[error("{0}")]
Io(#[from] std::io::Error),
#[error(transparent)]
Other(deno_core::error::AnyError),
}
#[op2(fast)]
#[smi]
fn op_http_start(
state: &mut OpState,
#[smi] tcp_stream_rid: ResourceId,
) -> Result<ResourceId, AnyError> {
) -> Result<ResourceId, HttpStartError> {
if let Ok(resource_rc) = state
.resource_table
.take::<TcpStreamResource>(tcp_stream_rid)
@ -30,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()))
}

View file

@ -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;

View file

@ -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<String, AnyError> {
fn op_exec_path(state: &mut OpState) -> Result<String, OsError> {
let current_exe = env::current_exe().unwrap();
state
.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
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::<PermissionsContainer>().check_env(key)?;
) -> Result<(), OsError> {
state
.borrow_mut::<PermissionsContainer>()
.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<HashMap<String, String>, AnyError> {
fn op_env(
state: &mut OpState,
) -> Result<HashMap<String, String>, deno_core::error::AnyError> {
state.borrow_mut::<PermissionsContainer>().check_env_all()?;
Ok(env::vars().collect())
}
@ -122,21 +142,22 @@ fn op_env(state: &mut OpState) -> Result<HashMap<String, String>, AnyError> {
fn op_get_env(
state: &mut OpState,
#[string] key: String,
) -> Result<Option<String>, AnyError> {
) -> Result<Option<String>, OsError> {
let skip_permission_check = NODE_ENV_VAR_ALLOWLIST.contains(&key);
if !skip_permission_check {
state.borrow_mut::<PermissionsContainer>().check_env(&key)?;
state
.borrow_mut::<PermissionsContainer>()
.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::<PermissionsContainer>().check_env(&key)?;
) -> Result<(), OsError> {
state
.borrow_mut::<PermissionsContainer>()
.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::<PermissionsContainer>()
.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<String, AnyError> {
fn op_hostname(
state: &mut OpState,
) -> Result<String, deno_core::error::AnyError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("hostname", "Deno.hostname()")?;
@ -196,7 +224,9 @@ fn op_hostname(state: &mut OpState) -> Result<String, AnyError> {
#[op2]
#[string]
fn op_os_release(state: &mut OpState) -> Result<String, AnyError> {
fn op_os_release(
state: &mut OpState,
) -> Result<String, deno_core::error::AnyError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("osRelease", "Deno.osRelease()")?;
@ -207,10 +237,11 @@ fn op_os_release(state: &mut OpState) -> Result<String, AnyError> {
#[serde]
fn op_network_interfaces(
state: &mut OpState,
) -> Result<Vec<NetworkInterface>, AnyError> {
) -> Result<Vec<NetworkInterface>, OsError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("networkInterfaces", "Deno.networkInterfaces()")?;
.check_sys("networkInterfaces", "Deno.networkInterfaces()")
.map_err(OsError::Permission)?;
Ok(netif::up()?.map(NetworkInterface::from).collect())
}
@ -259,7 +290,7 @@ impl From<netif::Interface> for NetworkInterface {
#[serde]
fn op_system_memory_info(
state: &mut OpState,
) -> Result<Option<sys_info::MemInfo>, AnyError> {
) -> Result<Option<sys_info::MemInfo>, deno_core::error::AnyError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("systemMemoryInfo", "Deno.systemMemoryInfo()")?;
@ -269,7 +300,9 @@ fn op_system_memory_info(
#[cfg(not(windows))]
#[op2]
#[smi]
fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
fn op_gid(
state: &mut OpState,
) -> Result<Option<u32>, deno_core::error::AnyError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("gid", "Deno.gid()")?;
@ -283,7 +316,9 @@ fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
#[cfg(windows)]
#[op2]
#[smi]
fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
fn op_gid(
state: &mut OpState,
) -> Result<Option<u32>, deno_core::error::AnyError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("gid", "Deno.gid()")?;
@ -293,7 +328,9 @@ fn op_gid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
#[cfg(not(windows))]
#[op2]
#[smi]
fn op_uid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
fn op_uid(
state: &mut OpState,
) -> Result<Option<u32>, deno_core::error::AnyError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("uid", "Deno.uid()")?;
@ -307,7 +344,9 @@ fn op_uid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
#[cfg(windows)]
#[op2]
#[smi]
fn op_uid(state: &mut OpState) -> Result<Option<u32>, AnyError> {
fn op_uid(
state: &mut OpState,
) -> Result<Option<u32>, deno_core::error::AnyError> {
state
.borrow_mut::<PermissionsContainer>()
.check_sys("uid", "Deno.uid()")?;
@ -485,7 +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
.borrow_mut::<PermissionsContainer>()
.check_sys("osUptime", "Deno.osUptime()")?;
@ -494,6 +533,8 @@ fn os_uptime(state: &mut OpState) -> Result<u64, AnyError> {
#[op2(fast)]
#[number]
fn op_os_uptime(state: &mut OpState) -> Result<u64, AnyError> {
fn op_os_uptime(
state: &mut OpState,
) -> Result<u64, deno_core::error::AnyError> {
os_uptime(state)
}

View file

@ -1,8 +1,5 @@
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
use deno_core::anyhow::Context;
use deno_core::error::type_error;
use deno_core::error::AnyError;
use deno_core::op2;
use deno_core::serde_json;
use deno_core::AsyncMutFuture;
@ -35,6 +32,7 @@ use tokio::process::Command;
#[cfg(windows)]
use std::os::windows::process::CommandExt;
use crate::ops::signal::SignalError;
#[cfg(unix)]
use std::os::unix::prelude::ExitStatusExt;
#[cfg(unix)]
@ -105,11 +103,12 @@ impl StdioOrRid {
pub fn as_stdio(
&self,
state: &mut OpState,
) -> Result<std::process::Stdio, AnyError> {
) -> Result<std::process::Stdio, ProcessError> {
match &self {
StdioOrRid::Stdio(val) => Ok(val.as_stdio()),
StdioOrRid::Rid(rid) => {
FileResource::with_file(state, *rid, |file| Ok(file.as_stdio()?))
.map_err(ProcessError::Resource)
}
}
}
@ -191,6 +190,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<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)]
#[serde(rename_all = "camelCase")]
pub struct ChildStdio {
@ -208,7 +240,7 @@ pub struct ChildStatus {
}
impl TryFrom<ExitStatus> for ChildStatus {
type Error = AnyError;
type Error = SignalError;
fn try_from(status: ExitStatus) -> Result<Self, Self::Error> {
let code = status.code();
@ -259,7 +291,7 @@ type CreateCommand = (
pub fn npm_process_state_tempfile(
contents: &[u8],
) -> Result<deno_io::RawIoHandle, AnyError> {
) -> Result<deno_io::RawIoHandle, std::io::Error> {
let mut temp_file = tempfile::tempfile()?;
temp_file.write_all(contents)?;
let handle = temp_file.into_raw_io_handle();
@ -301,7 +333,7 @@ fn create_command(
state: &mut OpState,
mut args: SpawnArgs,
api_name: &str,
) -> Result<CreateCommand, AnyError> {
) -> Result<CreateCommand, ProcessError> {
let maybe_npm_process_state = if args.needs_npm_process_state {
let provider = state.borrow::<NpmProcessStateProviderRc>();
let process_state = provider.get_npm_process_state();
@ -505,7 +537,7 @@ fn spawn_child(
ipc_pipe_rid: Option<ResourceId>,
extra_pipe_rids: Vec<Option<ResourceId>>,
detached: bool,
) -> Result<Child, AnyError> {
) -> Result<Child, ProcessError> {
let mut command = tokio::process::Command::from(command);
// TODO(@crowlkats): allow detaching processes.
// currently deno will orphan a process when exiting with an error or Deno.exit()
@ -554,10 +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<RunEnv, AnyError> {
) -> Result<RunEnv, ProcessError> {
#[allow(clippy::disallowed_methods)]
let cwd = std::env::current_dir().context("failed resolving cwd")?;
let cwd =
std::env::current_dir().map_err(ProcessError::FailedResolvingCwd)?;
let cwd = arg_cwd
.map(|cwd_arg| resolve_path(cwd_arg, &cwd))
.unwrap_or(cwd);
@ -670,7 +712,7 @@ fn compute_run_env(
Ok(RunEnv { envs, cwd })
}
fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result<PathBuf, AnyError> {
fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result<PathBuf, ProcessError> {
let is_path = cmd.contains('/');
#[cfg(windows)]
let is_path = is_path || cmd.contains('\\') || Path::new(&cmd).is_absolute();
@ -683,7 +725,7 @@ fn resolve_cmd(cmd: &str, env: &RunEnv) -> Result<PathBuf, AnyError> {
Err(which::Error::CannotFindBinaryPath) => {
Err(std::io::Error::from(std::io::ErrorKind::NotFound).into())
}
Err(err) => Err(err.into()),
Err(err) => Err(ProcessError::Which(err)),
}
}
}
@ -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::<PermissionsContainer>();
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<Child, AnyError> {
) -> Result<Child, ProcessError> {
let detached = args.detached;
let (command, pipe_rid, extra_pipe_rids, handles_to_close) =
create_command(state, args, &api_name)?;
@ -771,16 +813,23 @@ fn op_spawn_child(
async fn op_spawn_wait(
state: Rc<RefCell<OpState>>,
#[smi] rid: ResourceId,
) -> Result<ChildStatus, AnyError> {
) -> Result<ChildStatus, ProcessError> {
let resource = state
.borrow_mut()
.resource_table
.get::<ChildResource>(rid)?;
let result = resource.0.try_borrow_mut()?.wait().await?.try_into();
.get::<ChildResource>(rid)
.map_err(ProcessError::Resource)?;
let result = resource
.0
.try_borrow_mut()
.map_err(ProcessError::BorrowMut)?
.wait()
.await?
.try_into()?;
if let Ok(resource) = state.borrow_mut().resource_table.take_any(rid) {
resource.close();
}
result
Ok(result)
}
#[op2]
@ -788,16 +837,14 @@ async fn op_spawn_wait(
fn op_spawn_sync(
state: &mut OpState,
#[serde] args: SpawnArgs,
) -> Result<SpawnOutput, AnyError> {
) -> Result<SpawnOutput, ProcessError> {
let stdout = matches!(args.stdio.stdout, StdioOrRid::Stdio(Stdio::Piped));
let stderr = matches!(args.stdio.stderr, StdioOrRid::Stdio(Stdio::Piped));
let (mut command, _, _, _) =
create_command(state, args, "Deno.Command().outputSync()")?;
let output = command.output().with_context(|| {
format!(
"Failed to spawn '{}'",
command.get_program().to_string_lossy()
)
let output = command.output().map_err(|e| ProcessError::SpawnFailed {
command: command.get_program().to_string_lossy().to_string(),
error: Box::new(e.into()),
})?;
Ok(SpawnOutput {
@ -820,17 +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::<ChildResource>(rid) {
deprecated::kill(child_resource.1 as i32, &signal)?;
return Ok(());
}
Err(type_error("Child process has already terminated."))
Err(ProcessError::ChildProcessAlreadyTerminated)
}
mod deprecated {
use deno_core::anyhow;
use super::*;
#[derive(Deserialize)]
@ -876,9 +921,9 @@ mod deprecated {
pub fn op_run(
state: &mut OpState,
#[serde] run_args: RunArgs,
) -> Result<RunInfo, AnyError> {
) -> Result<RunInfo, ProcessError> {
let args = run_args.cmd;
let cmd = args.first().ok_or_else(|| anyhow::anyhow!("Missing cmd"))?;
let cmd = args.first().ok_or(ProcessError::MissingCmd)?;
let (cmd, run_env) = compute_run_cmd_and_check_permissions(
cmd,
run_args.cwd.as_deref(),
@ -990,11 +1035,12 @@ mod deprecated {
pub async fn op_run_status(
state: Rc<RefCell<OpState>>,
#[smi] rid: ResourceId,
) -> Result<ProcessStatus, AnyError> {
) -> Result<ProcessStatus, ProcessError> {
let resource = state
.borrow_mut()
.resource_table
.get::<ChildResource>(rid)?;
.get::<ChildResource>(rid)
.map_err(ProcessError::Resource)?;
let mut child = resource.borrow_mut().await;
let run_status = child.wait().await?;
let code = run_status.code();
@ -1017,17 +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::<PermissionsContainer>()
.check_run_all(&api_name)?;
kill(pid, &signal)?;
Ok(())
.check_run_all(&api_name)
.map_err(ProcessError::Permission)?;
kill(pid, &signal)
}
}

View file

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

View file

@ -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<libc::c_int, AnyError> {
($(($number:literal, $($name:literal)|+)),*) => {
pub fn signal_str_to_int(s: &str) -> Result<libc::c_int, SignalError> {
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<ResourceId, AnyError> {
) -> Result<ResourceId, SignalError> {
let signo = signal_str_to_int(sig)?;
if signal_hook_registry::FORBIDDEN.contains(&signo) {
return Err(type_error(format!(
"Binding to signal '{sig}' is not allowed",
)));
return Err(SignalError::SignalNotAllowed(sig.to_string()));
}
let signal = AsyncRefCell::new(signal(SignalKind::from_raw(signo))?);
@ -413,7 +436,7 @@ fn op_signal_bind(
fn op_signal_bind(
state: &mut OpState,
#[string] sig: &str,
) -> Result<ResourceId, AnyError> {
) -> Result<ResourceId, SignalError> {
let signo = signal_str_to_int(sig)?;
let resource = SignalStreamResource {
signal: AsyncRefCell::new(match signo {
@ -437,7 +460,7 @@ fn op_signal_bind(
async fn op_signal_poll(
state: Rc<RefCell<OpState>>,
#[smi] rid: ResourceId,
) -> Result<bool, AnyError> {
) -> Result<bool, deno_core::error::AnyError> {
let resource = state
.borrow_mut()
.resource_table
@ -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::<SignalStreamResource>(rid)?;
#[cfg(unix)]

View file

@ -2,7 +2,6 @@
use std::io::Error;
use deno_core::error::AnyError;
use deno_core::op2;
use deno_core::OpState;
use rustyline::config::Configurer;
@ -64,6 +63,19 @@ deno_core::extension!(
},
);
#[derive(Debug, thiserror::Error)]
pub enum TtyError {
#[error(transparent)]
Resource(deno_core::error::AnyError),
#[error("{0}")]
Io(#[from] std::io::Error),
#[cfg(unix)]
#[error(transparent)]
Nix(nix::Error),
#[error(transparent)]
Other(deno_core::error::AnyError),
}
// ref: <https://learn.microsoft.com/en-us/windows/console/setconsolemode>
#[cfg(windows)]
const COOKED_MODE: DWORD =
@ -90,8 +102,11 @@ fn op_set_raw(
rid: u32,
is_raw: bool,
cbreak: bool,
) -> Result<(), AnyError> {
let handle_or_fd = state.resource_table.get_fd(rid)?;
) -> Result<(), TtyError> {
let handle_or_fd = state
.resource_table
.get_fd(rid)
.map_err(TtyError::Resource)?;
// From https://github.com/kkawakam/rustyline/blob/master/src/tty/windows.rs
// and https://github.com/kkawakam/rustyline/blob/master/src/tty/unix.rs
@ -107,7 +122,7 @@ fn op_set_raw(
let handle = handle_or_fd;
if cbreak {
return Err(deno_core::error::not_supported());
return Err(TtyError::Other(deno_core::error::not_supported()));
}
let mut original_mode: DWORD = 0;
@ -115,7 +130,7 @@ fn op_set_raw(
if unsafe { consoleapi::GetConsoleMode(handle, &mut original_mode) }
== FALSE
{
return Err(Error::last_os_error().into());
return Err(TtyError::Io(Error::last_os_error()));
}
let new_mode = if is_raw {
@ -185,7 +200,7 @@ fn op_set_raw(
winapi::um::wincon::WriteConsoleInputW(handle, &record, 1, &mut 0)
} == FALSE
{
return Err(Error::last_os_error().into());
return Err(TtyError::Io(Error::last_os_error()));
}
/* Wait for read thread to acknowledge the cancellation to ensure that nothing
@ -199,7 +214,7 @@ fn op_set_raw(
// SAFETY: winapi call
if unsafe { consoleapi::SetConsoleMode(handle, new_mode) } == FALSE {
return Err(Error::last_os_error().into());
return Err(TtyError::Io(Error::last_os_error()));
}
Ok(())
@ -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<Option<String>, AnyError> {
) -> Result<Option<String>, 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),
}
}

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

View file

@ -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<String>,
loose_mime_checks: bool,
) -> Result<Vec<SyncFetchScript>, AnyError> {
) -> Result<Vec<SyncFetchScript>, SyncFetchError> {
let handle = state.borrow::<WebWorkerInternalHandle>().clone();
assert_eq!(handle.worker_type, WebWorkerType::Classic);
// it's not safe to share a client across tokio runtimes, so create a fresh one
// https://github.com/seanmonstar/reqwest/issues/1148#issuecomment-910868788
let options = state.borrow::<deno_fetch::Options>().clone();
let client = deno_fetch::create_client_from_options(&options)?;
let client = deno_fetch::create_client_from_options(&options)
.map_err(FetchError::ClientCreate)?;
// TODO(andreubotella) It's not good to throw an exception related to blob
// URLs when none of the script URLs use the blob scheme.
// Also, in which contexts are blob URLs not supported?
let blob_store = state
.try_borrow::<Arc<BlobStore>>()
.ok_or_else(|| type_error("Blob URLs are not supported in this context."))?
.ok_or(SyncFetchError::BlobUrlsNotSupportedInContext)?
.clone();
// TODO(andreubotella): make the below thread into a resource that can be
@ -74,7 +99,7 @@ pub fn op_worker_sync_fetch(
let blob_store = blob_store.clone();
deno_core::unsync::spawn(async move {
let script_url = Url::parse(&script)
.map_err(|_| type_error("Invalid script URL"))?;
.map_err(|_| SyncFetchError::InvalidScriptUrl)?;
let mut loose_mime_checks = loose_mime_checks;
let (body, mime_type, res_url) = match script_url.scheme() {
@ -86,15 +111,13 @@ pub fn op_worker_sync_fetch(
);
*req.uri_mut() = script_url.as_str().parse()?;
let resp = client.send(req).await?;
let resp =
client.send(req).await.map_err(FetchError::ClientSend)?;
if resp.status().is_client_error()
|| resp.status().is_server_error()
{
return Err(type_error(format!(
"http status error: {}",
resp.status()
)));
return Err(SyncFetchError::InvalidStatusCode(resp.status()));
}
// TODO(andreubotella) Properly run fetch's "extract a MIME type".
@ -107,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),
}
}

View file

@ -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<JsMessageData>,
) -> Result<WorkerId, AnyError> {
) -> Result<WorkerId, CreateWorkerError> {
let specifier = args.specifier.clone();
let maybe_source_code = if args.has_source_code {
Some(args.source_code.clone())
@ -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::<PermissionsContainer>();
let worker_permissions = if let Some(child_permissions_arg) = args.permissions
{
parent_permissions.create_child_permissions(child_permissions_arg)?
parent_permissions
.create_child_permissions(child_permissions_arg)
.map_err(CreateWorkerError::Permission)?
} else {
parent_permissions.clone()
};
@ -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<SendableWebWorkerHandle, AnyError>,
>(1);
let (handle_sender, handle_receiver) =
std::sync::mpsc::sync_channel::<SendableWebWorkerHandle>(1);
// Setup new thread
let thread_builder = std::thread::Builder::new().name(format!("{worker_id}"));
@ -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<RefCell<OpState>>,
#[serde] id: WorkerId,
) -> Result<WorkerControlEvent, AnyError> {
) -> WorkerControlEvent {
let (worker_handle, cancel_handle) = {
let state = state.borrow();
let workers_table = state.borrow::<WorkersTable>();
@ -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<RefCell<OpState>>,
#[serde] id: WorkerId,
) -> Result<Option<JsMessageData>, AnyError> {
) -> Result<Option<JsMessageData>, MessagePortError> {
let (worker_handle, cancel_handle) = {
let s = state.borrow();
let workers_table = s.borrow::<WorkersTable>();
@ -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::<WorkersTable>().get(&id) {
debug!("post message to worker {}", id);
let worker_handle = worker_thread.worker_handle.clone();

View file

@ -166,7 +166,10 @@ pub struct WebWorkerInternalHandle {
impl WebWorkerInternalHandle {
/// 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();
// If the channel is closed,
// 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);
return Ok(());
}
sender.try_send(event)?;
Ok(())
sender.try_send(event)
}
/// Check if this worker is terminated or being terminated
@ -263,11 +265,9 @@ impl WebWorkerHandle {
/// Get the WorkerEvent with lock
/// Return error if more than one listener tries to get event
#[allow(clippy::await_holding_refcell_ref)] // TODO(ry) remove!
pub async fn get_control_event(
&self,
) -> Result<Option<WorkerControlEvent>, AnyError> {
pub async fn get_control_event(&self) -> Option<WorkerControlEvent> {
let mut receiver = self.receiver.borrow_mut();
Ok(receiver.next().await)
receiver.next().await
}
/// Terminate the worker

View file

@ -5,101 +5,101 @@ Deno.test(
{ ignore: Deno.build.os !== "windows" },
function signalsNotImplemented() {
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(
() => {
Deno.addSignalListener("SIGALRM", () => {});
},
Error,
msg,
msg + "SIGALRM",
);
assertThrows(
() => {
Deno.addSignalListener("SIGCHLD", () => {});
},
Error,
msg,
msg + "SIGCHLD",
);
assertThrows(
() => {
Deno.addSignalListener("SIGHUP", () => {});
},
Error,
msg,
msg + "SIGHUP",
);
assertThrows(
() => {
Deno.addSignalListener("SIGIO", () => {});
},
Error,
msg,
msg + "SIGIO",
);
assertThrows(
() => {
Deno.addSignalListener("SIGPIPE", () => {});
},
Error,
msg,
msg + "SIGPIPE",
);
assertThrows(
() => {
Deno.addSignalListener("SIGQUIT", () => {});
},
Error,
msg,
msg + "SIGQUIT",
);
assertThrows(
() => {
Deno.addSignalListener("SIGTERM", () => {});
},
Error,
msg,
msg + "SIGTERM",
);
assertThrows(
() => {
Deno.addSignalListener("SIGUSR1", () => {});
},
Error,
msg,
msg + "SIGUSR1",
);
assertThrows(
() => {
Deno.addSignalListener("SIGUSR2", () => {});
},
Error,
msg,
msg + "SIGUSR2",
);
assertThrows(
() => {
Deno.addSignalListener("SIGWINCH", () => {});
},
Error,
msg,
msg + "SIGWINCH",
);
assertThrows(
() => Deno.addSignalListener("SIGKILL", () => {}),
Error,
msg,
msg + "SIGKILL",
);
assertThrows(
() => Deno.addSignalListener("SIGSTOP", () => {}),
Error,
msg,
msg + "SIGSTOP",
);
assertThrows(
() => Deno.addSignalListener("SIGILL", () => {}),
Error,
msg,
msg + "SIGILL",
);
assertThrows(
() => Deno.addSignalListener("SIGFPE", () => {}),
Error,
msg,
msg + "SIGFPE",
);
assertThrows(
() => Deno.addSignalListener("SIGSEGV", () => {}),
Error,
msg,
msg + "SIGSEGV",
);
},
);