diff options
Diffstat (limited to 'runtime/ops/io.rs')
-rw-r--r-- | runtime/ops/io.rs | 304 |
1 files changed, 194 insertions, 110 deletions
diff --git a/runtime/ops/io.rs b/runtime/ops/io.rs index bef33e9fa..2dcb9964a 100644 --- a/runtime/ops/io.rs +++ b/runtime/ops/io.rs @@ -1,8 +1,8 @@ // Copyright 2018-2022 the Deno authors. All rights reserved. MIT license. +use deno_core::error::resource_unavailable; use deno_core::error::AnyError; use deno_core::op; -use deno_core::parking_lot::Mutex; use deno_core::AsyncMutFuture; use deno_core::AsyncRefCell; use deno_core::AsyncResult; @@ -22,7 +22,6 @@ use std::io::ErrorKind; use std::io::Read; use std::io::Write; use std::rc::Rc; -use std::sync::Arc; use tokio::io::AsyncRead; use tokio::io::AsyncReadExt; use tokio::io::AsyncWrite; @@ -125,23 +124,27 @@ pub fn init_stdio(stdio: Stdio) -> Extension { let t = &mut state.resource_table; t.add(StdFileResource::stdio( match stdio.stdin { - StdioPipe::Inherit => StdFileResourceInner::Stdin(Arc::new( - Mutex::new(STDIN_HANDLE.try_clone().unwrap()), - )), + StdioPipe::Inherit => { + StdFileResourceInner::Stdin(STDIN_HANDLE.try_clone().unwrap()) + } StdioPipe::File(pipe) => StdFileResourceInner::file(pipe), }, "stdin", )); t.add(StdFileResource::stdio( match stdio.stdout { - StdioPipe::Inherit => StdFileResourceInner::Stdout, + StdioPipe::Inherit => { + StdFileResourceInner::Stdout(STDOUT_HANDLE.try_clone().unwrap()) + } StdioPipe::File(pipe) => StdFileResourceInner::file(pipe), }, "stdout", )); t.add(StdFileResource::stdio( match stdio.stderr { - StdioPipe::Inherit => StdFileResourceInner::Stderr, + StdioPipe::Inherit => { + StdFileResourceInner::Stderr(STDERR_HANDLE.try_clone().unwrap()) + } StdioPipe::File(pipe) => StdFileResourceInner::file(pipe), }, "stderr", @@ -305,31 +308,28 @@ impl Resource for ChildStderrResource { } } -#[derive(Clone)] enum StdFileResourceInner { - File(Arc<Mutex<StdFile>>), - Stdin(Arc<Mutex<StdFile>>), - // Ideally we would store stdio as an StdFile, but we get some Windows - // specific functionality for free by using Rust std's wrappers. So we - // take a bit of a complexity hit here in order to not have to duplicate - // the functionality in Rust's std/src/sys/windows/stdio.rs - Stdout, - Stderr, + File(StdFile), + Stdin(StdFile), + // For stdout and stderr, we sometimes instead use std::io::stdout() directly, + // because we get some Windows specific functionality for free by using Rust + // std's wrappers. So we take a bit of a complexity hit in order to not + // have to duplicate the functionality in Rust's std/src/sys/windows/stdio.rs + Stdout(StdFile), + Stderr(StdFile), } impl StdFileResourceInner { pub fn file(fs_file: StdFile) -> Self { - StdFileResourceInner::File(Arc::new(Mutex::new(fs_file))) + StdFileResourceInner::File(fs_file) } - pub fn with_file<R>(&self, mut f: impl FnMut(&mut StdFile) -> R) -> R { + pub fn with_file<R>(&mut self, f: impl FnOnce(&mut StdFile) -> R) -> R { match self { - Self::File(file) | Self::Stdin(file) => { - let mut file = file.lock(); - f(&mut file) - } - Self::Stdout => f(&mut STDOUT_HANDLE.try_clone().unwrap()), - Self::Stderr => f(&mut STDERR_HANDLE.try_clone().unwrap()), + Self::File(file) + | Self::Stdin(file) + | Self::Stdout(file) + | Self::Stderr(file) => f(file), } } @@ -337,120 +337,163 @@ impl StdFileResourceInner { &mut self, buf: &[u8], ) -> Result<usize, AnyError> { - let nwritten = self.write(buf)?; - if !matches!(self, StdFileResourceInner::File(_)) { - // Rust will line buffer and we don't want that behavior - // (see https://github.com/denoland/deno/issues/948), so flush. - // Although an alternative solution could be to bypass Rust's std by - // using the raw fds/handles, it will cause encoding issues on Windows - // that we get solved for free by using Rust's stdio wrappers (see - // std/src/sys/windows/stdio.rs in Rust's source code). - self.flush()?; - } - Ok(nwritten) - } -} - -impl Read for StdFileResourceInner { - fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { + // Rust will line buffer and we don't want that behavior + // (see https://github.com/denoland/deno/issues/948), so flush stdout and stderr. + // Although an alternative solution could be to bypass Rust's std by + // using the raw fds/handles, it will cause encoding issues on Windows + // that we get solved for free by using Rust's stdio wrappers (see + // std/src/sys/windows/stdio.rs in Rust's source code). match self { - Self::File(file) | Self::Stdin(file) => file.lock().read(buf), - Self::Stdout => Err(ErrorKind::Unsupported.into()), - Self::Stderr => Err(ErrorKind::Unsupported.into()), + Self::File(file) => Ok(file.write(buf)?), + Self::Stdin(_) => { + Err(Into::<std::io::Error>::into(ErrorKind::Unsupported).into()) + } + Self::Stdout(_) => { + // bypass the file and use std::io::stdout() + let mut stdout = std::io::stdout().lock(); + let nwritten = stdout.write(buf)?; + stdout.flush()?; + Ok(nwritten) + } + Self::Stderr(_) => { + // bypass the file and use std::io::stderr() + let mut stderr = std::io::stderr().lock(); + let nwritten = stderr.write(buf)?; + stderr.flush()?; + Ok(nwritten) + } } } -} -impl Write for StdFileResourceInner { - fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> { + pub fn write_all_and_maybe_flush( + &mut self, + buf: &[u8], + ) -> Result<(), AnyError> { + // this method exists instead of using a `Write` implementation + // so that we can acquire the locks once and do both actions match self { - Self::File(file) => file.lock().write(buf), - Self::Stdin(_) => Err(ErrorKind::Unsupported.into()), - Self::Stdout => std::io::stdout().write(buf), - Self::Stderr => std::io::stderr().write(buf), + Self::File(file) => Ok(file.write_all(buf)?), + Self::Stdin(_) => { + Err(Into::<std::io::Error>::into(ErrorKind::Unsupported).into()) + } + Self::Stdout(_) => { + // bypass the file and use std::io::stdout() + let mut stdout = std::io::stdout().lock(); + stdout.write_all(buf)?; + stdout.flush()?; + Ok(()) + } + Self::Stderr(_) => { + // bypass the file and use std::io::stderr() + let mut stderr = std::io::stderr().lock(); + stderr.write_all(buf)?; + stderr.flush()?; + Ok(()) + } } } +} - fn flush(&mut self) -> std::io::Result<()> { +impl Read for StdFileResourceInner { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> { match self { - Self::File(file) => file.lock().flush(), - Self::Stdin(_) => Err(ErrorKind::Unsupported.into()), - Self::Stdout => std::io::stdout().flush(), - Self::Stderr => std::io::stderr().flush(), + Self::File(file) | Self::Stdin(file) => file.read(buf), + Self::Stdout(_) | Self::Stderr(_) => Err(ErrorKind::Unsupported.into()), } } } pub struct StdFileResource { - inner: StdFileResourceInner, - metadata: RefCell<FileMetadata>, name: String, + cell: AsyncRefCell<Option<(StdFileResourceInner, FileMetadata)>>, } impl StdFileResource { fn stdio(inner: StdFileResourceInner, name: &str) -> Self { Self { - inner, - metadata: Default::default(), + cell: AsyncRefCell::new(Some((inner, Default::default()))), name: name.to_string(), } } pub fn fs_file(fs_file: StdFile) -> Self { Self { - inner: StdFileResourceInner::file(fs_file), - metadata: Default::default(), + cell: AsyncRefCell::new(Some(( + StdFileResourceInner::file(fs_file), + Default::default(), + ))), name: "fsFile".to_string(), } } - pub fn std_file(&self) -> Arc<Mutex<StdFile>> { - match &self.inner { - StdFileResourceInner::File(fs_file) - | StdFileResourceInner::Stdin(fs_file) => fs_file.clone(), - StdFileResourceInner::Stdout => { - Arc::new(Mutex::new(STDOUT_HANDLE.try_clone().unwrap())) - } - StdFileResourceInner::Stderr => { - Arc::new(Mutex::new(STDERR_HANDLE.try_clone().unwrap())) + fn with_inner_and_metadata<TResult>( + self: Rc<Self>, + action: impl FnOnce( + &mut StdFileResourceInner, + &mut FileMetadata, + ) -> Result<TResult, AnyError>, + ) -> Result<TResult, AnyError> { + match RcRef::map(&self, |r| &r.cell).try_borrow_mut() { + Some(mut cell) => { + let mut file = cell.take().unwrap(); + let result = action(&mut file.0, &mut file.1); + cell.replace(file); + result } + None => Err(resource_unavailable()), } } - pub fn metadata_mut(&self) -> std::cell::RefMut<FileMetadata> { - self.metadata.borrow_mut() + async fn with_inner_blocking_task<F, R: Send + 'static>( + self: Rc<Self>, + action: F, + ) -> R + where + F: FnOnce(&mut StdFileResourceInner) -> R + Send + 'static, + { + // we take the value out of the cell, use it on a blocking task, + // then put it back into the cell when we're done + let mut cell = RcRef::map(&self, |r| &r.cell).borrow_mut().await; + let mut file = cell.take().unwrap(); + let (file, result) = tokio::task::spawn_blocking(move || { + let result = action(&mut file.0); + (file, result) + }) + .await + .unwrap(); + cell.replace(file); + result } async fn read( self: Rc<Self>, mut buf: ZeroCopyBuf, ) -> Result<(usize, ZeroCopyBuf), AnyError> { - let mut inner = self.inner.clone(); - tokio::task::spawn_blocking( - move || -> Result<(usize, ZeroCopyBuf), AnyError> { - Ok((inner.read(&mut buf)?, buf)) - }, - ) - .await? + self + .with_inner_blocking_task( + move |inner| -> Result<(usize, ZeroCopyBuf), AnyError> { + Ok((inner.read(&mut buf)?, buf)) + }, + ) + .await } async fn write(self: Rc<Self>, buf: ZeroCopyBuf) -> Result<usize, AnyError> { - let mut inner = self.inner.clone(); - tokio::task::spawn_blocking(move || inner.write_and_maybe_flush(&buf)) - .await? - .map_err(AnyError::from) + self + .with_inner_blocking_task(move |inner| inner.write_and_maybe_flush(&buf)) + .await } - fn with_inner<F, R>( + fn with_resource<F, R>( state: &mut OpState, rid: ResourceId, - mut f: F, + f: F, ) -> Result<R, AnyError> where - F: FnMut(StdFileResourceInner) -> Result<R, AnyError>, + F: FnOnce(Rc<StdFileResource>) -> Result<R, AnyError>, { let resource = state.resource_table.get::<StdFileResource>(rid)?; - f(resource.inner.clone()) + f(resource) } pub fn with_file<F, R>( @@ -459,10 +502,44 @@ impl StdFileResource { f: F, ) -> Result<R, AnyError> where - F: FnMut(&mut StdFile) -> Result<R, AnyError>, + F: FnOnce(&mut StdFile) -> Result<R, AnyError>, { - let resource = state.resource_table.get::<StdFileResource>(rid)?; - resource.inner.with_file(f) + Self::with_resource(state, rid, move |resource| { + resource.with_inner_and_metadata(move |inner, _| inner.with_file(f)) + }) + } + + pub fn with_file_and_metadata<F, R>( + state: &mut OpState, + rid: ResourceId, + f: F, + ) -> Result<R, AnyError> + where + F: FnOnce(&mut StdFile, &mut FileMetadata) -> Result<R, AnyError>, + { + Self::with_resource(state, rid, move |resource| { + resource.with_inner_and_metadata(move |inner, metadata| { + inner.with_file(move |file| f(file, metadata)) + }) + }) + } + + pub async fn with_file_blocking_task<F, R: Send + 'static>( + state: Rc<RefCell<OpState>>, + rid: ResourceId, + f: F, + ) -> Result<R, AnyError> + where + F: (FnOnce(&mut StdFile) -> Result<R, AnyError>) + Send + 'static, + { + let resource = state + .borrow_mut() + .resource_table + .get::<StdFileResource>(rid)?; + + resource + .with_inner_blocking_task(move |inner| inner.with_file(f)) + .await } pub fn clone_file( @@ -478,12 +555,14 @@ impl StdFileResource { state: &mut OpState, rid: u32, ) -> Result<std::process::Stdio, AnyError> { - Self::with_inner(state, rid, |inner| match inner { - StdFileResourceInner::File(file) => { - let file = file.lock().try_clone()?; - Ok(file.into()) - } - _ => Ok(std::process::Stdio::inherit()), + Self::with_resource(state, rid, |resource| { + resource.with_inner_and_metadata(|inner, _| match inner { + StdFileResourceInner::File(file) => { + let file = file.try_clone()?; + Ok(file.into()) + } + _ => Ok(std::process::Stdio::inherit()), + }) }) } } @@ -513,10 +592,11 @@ pub fn op_print( is_err: bool, ) -> Result<(), AnyError> { let rid = if is_err { 2 } else { 1 }; - StdFileResource::with_inner(state, rid, move |mut inner| { - inner.write_all(msg.as_bytes())?; - inner.flush().unwrap(); - Ok(()) + StdFileResource::with_resource(state, rid, move |resource| { + resource.with_inner_and_metadata(|inner, _| { + inner.write_all_and_maybe_flush(msg.as_bytes())?; + Ok(()) + }) }) } @@ -526,11 +606,13 @@ fn op_read_sync( rid: ResourceId, mut buf: ZeroCopyBuf, ) -> Result<u32, AnyError> { - StdFileResource::with_inner(state, rid, move |mut inner| { - inner - .read(&mut buf) - .map(|n: usize| n as u32) - .map_err(AnyError::from) + StdFileResource::with_resource(state, rid, move |resource| { + resource.with_inner_and_metadata(|inner, _| { + inner + .read(&mut buf) + .map(|n: usize| n as u32) + .map_err(AnyError::from) + }) }) } @@ -540,10 +622,12 @@ fn op_write_sync( rid: ResourceId, buf: ZeroCopyBuf, ) -> Result<u32, AnyError> { - StdFileResource::with_inner(state, rid, move |mut inner| { - inner - .write_and_maybe_flush(&buf) - .map(|nwritten: usize| nwritten as u32) - .map_err(AnyError::from) + StdFileResource::with_resource(state, rid, move |resource| { + resource.with_inner_and_metadata(|inner, _| { + inner + .write_and_maybe_flush(&buf) + .map(|nwritten: usize| nwritten as u32) + .map_err(AnyError::from) + }) }) } |