Skip to content

Commit

Permalink
refactor(core): split up resource code and add a resource test (#472)
Browse files Browse the repository at this point in the history
Partially extracted from #440

Also pulls in `BufMutViewWhole` from that PR that will be required for
buffer soundness in a later PR -- you should not split a buffer that is
owned by JavaScript.
  • Loading branch information
mmastrac authored Jan 24, 2024
1 parent 91643d5 commit 1051b6c
Show file tree
Hide file tree
Showing 13 changed files with 897 additions and 21 deletions.
File renamed without changes.
139 changes: 130 additions & 9 deletions core/io.rs → core/io/buffers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,14 @@ impl From<BufView> for bytes::Bytes {
}
}

/// BufMutViewWhole is equivalent to `BufMutView`, but cannot be split, preventing
/// someone from accidentally holding a `BufView` down the road that is being actively
/// mutated from JavaScript.
pub struct BufMutViewWhole {
inner: BufMutViewInner,
cursor: usize,
}

/// BufMutView is a wrapper around an underlying contiguous chunk of writable
/// bytes. It can be created from a `JsBuffer` or a `Vec<u8>` and implements
/// `DerefMut<[u8]>` and `AsMut<[u8]>`.
Expand All @@ -196,6 +204,15 @@ enum BufMutViewInner {
Bytes(BytesMut),
}

impl Default for BufMutView {
fn default() -> Self {
BufMutView {
inner: BufMutViewInner::Bytes(BytesMut::default()),
cursor: 0,
}
}
}

impl BufMutView {
fn from_inner(inner: BufMutViewInner) -> Self {
Self { inner, cursor: 0 }
Expand Down Expand Up @@ -412,18 +429,122 @@ impl From<BytesMut> for BufMutView {
}
}

pub enum WriteOutcome {
Partial { nwritten: usize, view: BufView },
Full { nwritten: usize },
}
impl BufMutViewWhole {
fn from_inner(inner: BufMutViewInner) -> Self {
Self { inner, cursor: 0 }
}

pub fn new(len: usize) -> Self {
let bytes = BytesMut::zeroed(len);
Self::from_inner(BufMutViewInner::Bytes(bytes))
}

impl WriteOutcome {
pub fn nwritten(&self) -> usize {
match self {
WriteOutcome::Partial { nwritten, .. } => *nwritten,
WriteOutcome::Full { nwritten } => *nwritten,
/// Get the length of the buffer view. This is the length of the underlying
/// buffer minus the cursor position.
pub fn len(&self) -> usize {
match &self.inner {
BufMutViewInner::JsBuffer(js_buf) => js_buf.len() - self.cursor,
BufMutViewInner::Bytes(bytes) => bytes.len() - self.cursor,
}
}

/// Is the buffer view empty?
pub fn is_empty(&self) -> bool {
self.len() == 0
}

/// Advance the internal cursor of the buffer view by `n` bytes.
pub fn advance_cursor(&mut self, n: usize) {
assert!(self.len() >= n);
self.cursor += n;
}

/// Reset the internal cursor of the buffer view to the beginning of the
/// buffer. Returns the old cursor position.
pub fn reset_cursor(&mut self) -> usize {
let old = self.cursor;
self.cursor = 0;
old
}

/// Turn this `BufMutView` into a `BufView`.
pub fn into_view(self) -> BufView {
let inner = match self.inner {
BufMutViewInner::JsBuffer(js_buf) => BufViewInner::JsBuffer(js_buf),
BufMutViewInner::Bytes(bytes) => BufViewInner::Bytes(bytes.into()),
};
BufView {
inner,
cursor: self.cursor,
}
}

/// Attempts to unwrap the underlying buffer into a [`BytesMut`], consuming the `BufMutView`. If
/// this buffer does not have a [`BytesMut`], returns `Self`.
pub fn maybe_unwrap_bytes(self) -> Result<BytesMut, Self> {
match self.inner {
BufMutViewInner::JsBuffer(_) => Err(self),
BufMutViewInner::Bytes(bytes) => Ok(bytes),
}
}

/// Adjust the length of the remaining buffer and ensure that the cursor continues to
/// stay in-bounds.
pub fn truncate(&mut self, size: usize) {
match &mut self.inner {
BufMutViewInner::Bytes(bytes) => bytes.truncate(size + self.cursor),
BufMutViewInner::JsBuffer(buffer) => buffer.truncate(size + self.cursor),
}
self.cursor = std::cmp::min(self.cursor, self.len());
}
}

impl Buf for BufMutViewWhole {
fn remaining(&self) -> usize {
self.len()
}

fn chunk(&self) -> &[u8] {
self.deref()
}

fn advance(&mut self, cnt: usize) {
self.advance_cursor(cnt)
}
}

impl Deref for BufMutViewWhole {
type Target = [u8];

fn deref(&self) -> &[u8] {
let buf = match &self.inner {
BufMutViewInner::JsBuffer(js_buf) => js_buf.deref(),
BufMutViewInner::Bytes(vec) => vec.deref(),
};
&buf[self.cursor..]
}
}

impl DerefMut for BufMutViewWhole {
fn deref_mut(&mut self) -> &mut [u8] {
let buf = match &mut self.inner {
BufMutViewInner::JsBuffer(js_buf) => js_buf.deref_mut(),
BufMutViewInner::Bytes(vec) => vec.deref_mut(),
};
&mut buf[self.cursor..]
}
}

impl From<JsBuffer> for BufMutViewWhole {
fn from(buf: JsBuffer) -> Self {
Self::from_inner(BufMutViewInner::JsBuffer(buf.into_parts()))
}
}

impl From<BytesMut> for BufMutViewWhole {
fn from(buf: BytesMut) -> Self {
Self::from_inner(BufMutViewInner::Bytes(buf))
}
}

#[cfg(test)]
Expand Down
45 changes: 45 additions & 0 deletions core/io/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.

// Think of Resources as File Descriptors. They are integers that are allocated
// by the privileged side of Deno which refer to various rust objects that need
// to be persisted between various ops. For example, network sockets are
// resources. Resources may or may not correspond to a real operating system
// file descriptor (hence the different name).

use anyhow::Error;
use futures::Future;
use std::pin::Pin;

mod buffer_strategy;
mod buffers;
mod resource;
mod resource_handle;
mod resource_table;

pub use buffer_strategy::AdaptiveBufferStrategy;
pub use buffers::BufMutView;
pub use buffers::BufMutViewWhole;
pub use buffers::BufView;
pub use resource::Resource;
pub use resource_handle::ResourceHandle;
pub use resource_handle::ResourceHandleFd;
pub use resource_handle::ResourceHandleSocket;
pub use resource_table::ResourceId;
pub use resource_table::ResourceTable;

/// Returned by resource shutdown methods
pub type AsyncResult<T> = Pin<Box<dyn Future<Output = Result<T, Error>>>>;

pub enum WriteOutcome {
Partial { nwritten: usize, view: BufView },
Full { nwritten: usize },
}

impl WriteOutcome {
pub fn nwritten(&self) -> usize {
match self {
WriteOutcome::Partial { nwritten, .. } => *nwritten,
WriteOutcome::Full { nwritten } => *nwritten,
}
}
}
Loading

0 comments on commit 1051b6c

Please sign in to comment.