diff options
Diffstat (limited to 'core/resources.rs')
-rw-r--r-- | core/resources.rs | 191 |
1 files changed, 179 insertions, 12 deletions
diff --git a/core/resources.rs b/core/resources.rs index 1a1ba3193..ee9ee689f 100644 --- a/core/resources.rs +++ b/core/resources.rs @@ -8,7 +8,9 @@ use crate::error::bad_resource_id; use crate::error::not_supported; -use crate::ZeroCopyBuf; +use crate::io::BufMutView; +use crate::io::BufView; +use crate::io::WriteOutcome; use anyhow::Error; use futures::Future; use std::any::type_name; @@ -23,9 +25,51 @@ use std::rc::Rc; /// Returned by resource read/write/shutdown methods pub type AsyncResult<T> = Pin<Box<dyn Future<Output = Result<T, Error>>>>; -/// All objects that can be store in the resource table should implement the -/// `Resource` trait. -/// TODO(@AaronO): investigate avoiding alloc on read/write/shutdown +/// Resources are Rust objects that are attached to a [deno_core::JsRuntime]. +/// They are identified in JS by a numeric ID (the resource ID, or rid). +/// Resources can be created in ops. Resources can also be retrieved in ops by +/// their rid. Resources are not thread-safe - they can only be accessed from +/// the thread that the JsRuntime lives on. +/// +/// Resources are reference counted in Rust. This means that they can be +/// cloned and passed around. When the last reference is dropped, the resource +/// is automatically closed. As long as the resource exists in the resource +/// table, the reference count is at least 1. +/// +/// ### Readable +/// +/// Readable resources are resources that can have data read from. Examples of +/// this are files, sockets, or HTTP streams. +/// +/// Readables can be read from from either JS or Rust. In JS one can use +/// `Deno.core.read()` to read from a single chunk of data from a readable. In +/// Rust one can directly call `read()` or `read_byob()`. The Rust side code is +/// used to implement ops like `op_slice`. +/// +/// A distinction can be made between readables that produce chunks of data +/// themselves (they allocate the chunks), and readables that fill up +/// bring-your-own-buffers (BYOBs). The former is often the case for framed +/// protocols like HTTP, while the latter is often the case for kernel backed +/// resources like files and sockets. +/// +/// All readables must implement `read()`. If resources can support an optimized +/// path for BYOBs, they should also implement `read_byob()`. For kernel backed +/// resources it often makes sense to implement `read_byob()` first, and then +/// implement `read()` as an operation that allocates a new chunk with +/// `len == limit`, then calls `read_byob()`, and then returns a chunk sliced to +/// the number of bytes read. Kernel backed resources can use the +/// [deno_core::impl_readable_byob] macro to implement optimized `read_byob()` +/// and `read()` implementations from a single `Self::read()` method. +/// +/// ### Writable +/// +/// Writable resources are resources that can have data written to. Examples of +/// this are files, sockets, or HTTP streams. +/// +/// Writables can be written to from either JS or Rust. In JS one can use +/// `Deno.core.write()` to write to a single chunk of data to a writable. In +/// Rust one can directly call `write()`. The latter is used to implement ops +/// like `op_slice`. pub trait Resource: Any + 'static { /// Returns a string representation of the resource which is made available /// to JavaScript code through `op_resources`. The default implementation @@ -35,20 +79,86 @@ pub trait Resource: Any + 'static { type_name::<Self>().into() } - /// Resources may implement `read_return()` to be a readable stream - fn read_return( - self: Rc<Self>, - _buf: ZeroCopyBuf, - ) -> AsyncResult<(usize, ZeroCopyBuf)> { + /// Read a single chunk of data from the resource. This operation returns a + /// `BufView` that represents the data that was read. If a zero length buffer + /// is returned, it indicates that the resource has reached EOF. + /// + /// If this method is not implemented, the default implementation will error + /// with a "not supported" error. + /// + /// If a readable can provide an optimized path for BYOBs, it should also + /// implement `read_byob()`. + fn read(self: Rc<Self>, limit: usize) -> AsyncResult<BufView> { + _ = limit; Box::pin(futures::future::err(not_supported())) } - /// Resources may implement `write()` to be a writable stream - fn write(self: Rc<Self>, _buf: ZeroCopyBuf) -> AsyncResult<usize> { + /// Read a single chunk of data from the resource into the provided `BufMutView`. + /// + /// This operation returns the number of bytes read. If zero bytes are read, + /// it indicates that the resource has reached EOF. + /// + /// If this method is not implemented explicitly, the default implementation + /// will call `read()` and then copy the data into the provided buffer. For + /// readable resources that can provide an optimized path for BYOBs, it is + /// strongly recommended to override this method. + fn read_byob( + self: Rc<Self>, + mut buf: BufMutView, + ) -> AsyncResult<(usize, BufMutView)> { + Box::pin(async move { + let read = self.read(buf.len()).await?; + let nread = read.len(); + buf[..nread].copy_from_slice(&read); + Ok((nread, buf)) + }) + } + + /// Write a single chunk of data to the resource. The operation may not be + /// able to write the entire chunk, in which case it should return the number + /// of bytes written. Additionally it should return the `BufView` that was + /// passed in. + /// + /// If this method is not implemented, the default implementation will error + /// with a "not supported" error. + fn write(self: Rc<Self>, buf: BufView) -> AsyncResult<WriteOutcome> { + _ = buf; Box::pin(futures::future::err(not_supported())) } - /// Resources may implement `shutdown()` for graceful async shutdowns + /// Write an entire chunk of data to the resource. Unlike `write()`, this will + /// ensure the entire chunk is written. If the operation is not able to write + /// the entire chunk, an error is to be returned. + /// + /// By default this method will call `write()` repeatedly until the entire + /// chunk is written. Resources that can write the entire chunk in a single + /// operation using an optimized path should override this method. + fn write_all(self: Rc<Self>, view: BufView) -> AsyncResult<()> { + Box::pin(async move { + let mut view = view; + let this = self; + while !view.is_empty() { + let resp = this.clone().write(view).await?; + match resp { + WriteOutcome::Partial { + nwritten, + view: new_view, + } => { + view = new_view; + view.advance_cursor(nwritten); + } + WriteOutcome::Full { .. } => break, + } + } + Ok(()) + }) + } + + /// The shutdown method can be used to asynchronously close the resource. It + /// is not automatically called when the resource is dropped or closed. + /// + /// If this method is not implemented, the default implementation will error + /// with a "not supported" error. fn shutdown(self: Rc<Self>) -> AsyncResult<()> { Box::pin(futures::future::err(not_supported())) } @@ -229,3 +339,60 @@ impl ResourceTable { .map(|(&id, resource)| (id, resource.name())) } } + +#[macro_export] +macro_rules! impl_readable_byob { + () => { + fn read(self: Rc<Self>, limit: usize) -> AsyncResult<$crate::BufView> { + Box::pin(async move { + let mut vec = vec![0; limit]; + let nread = self.read(&mut vec).await?; + if nread != vec.len() { + vec.truncate(nread); + } + let view = $crate::BufView::from(vec); + Ok(view) + }) + } + + fn read_byob( + self: Rc<Self>, + mut buf: $crate::BufMutView, + ) -> AsyncResult<(usize, $crate::BufMutView)> { + Box::pin(async move { + let nread = self.read(buf.as_mut()).await?; + Ok((nread, buf)) + }) + } + }; +} + +#[macro_export] +macro_rules! impl_writable { + (__write) => { + fn write( + self: Rc<Self>, + view: $crate::BufView, + ) -> AsyncResult<$crate::WriteOutcome> { + Box::pin(async move { + let nwritten = self.write(&view).await?; + Ok($crate::WriteOutcome::Partial { nwritten, view }) + }) + } + }; + (__write_all) => { + fn write_all(self: Rc<Self>, view: $crate::BufView) -> AsyncResult<()> { + Box::pin(async move { + self.write_all(&view).await?; + Ok(()) + }) + } + }; + () => { + $crate::impl_writable!(__write); + }; + (with_all) => { + $crate::impl_writable!(__write); + $crate::impl_writable!(__write_all); + }; +} |