deno_core/io/
resource.rs

1// Copyright 2018-2025 the Deno authors. MIT license.
2
3// Think of Resources as File Descriptors. They are integers that are allocated
4// by the privileged side of Deno which refer to various rust objects that need
5// to be persisted between various ops. For example, network sockets are
6// resources. Resources may or may not correspond to a real operating system
7// file descriptor (hence the different name).
8
9use crate::ResourceHandle;
10use crate::ResourceHandleFd;
11use crate::io::AsyncResult;
12use crate::io::BufMutView;
13use crate::io::BufView;
14use crate::io::WriteOutcome;
15use deno_error::JsErrorBox;
16use deno_error::JsErrorClass;
17use std::any::Any;
18use std::any::TypeId;
19use std::any::type_name;
20use std::borrow::Cow;
21use std::rc::Rc;
22
23/// Resources are Rust objects that are attached to a [deno_core::JsRuntime].
24/// They are identified in JS by a numeric ID (the resource ID, or rid).
25/// Resources can be created in ops. Resources can also be retrieved in ops by
26/// their rid. Resources are not thread-safe - they can only be accessed from
27/// the thread that the JsRuntime lives on.
28///
29/// Resources are reference counted in Rust. This means that they can be
30/// cloned and passed around. When the last reference is dropped, the resource
31/// is automatically closed. As long as the resource exists in the resource
32/// table, the reference count is at least 1.
33///
34/// ### Readable
35///
36/// Readable resources are resources that can have data read from. Examples of
37/// this are files, sockets, or HTTP streams.
38///
39/// Readables can be read from from either JS or Rust. In JS one can use
40/// `Deno.core.read()` to read from a single chunk of data from a readable. In
41/// Rust one can directly call `read()` or `read_byob()`. The Rust side code is
42/// used to implement ops like `op_slice`.
43///
44/// A distinction can be made between readables that produce chunks of data
45/// themselves (they allocate the chunks), and readables that fill up
46/// bring-your-own-buffers (BYOBs). The former is often the case for framed
47/// protocols like HTTP, while the latter is often the case for kernel backed
48/// resources like files and sockets.
49///
50/// All readables must implement `read()`. If resources can support an optimized
51/// path for BYOBs, they should also implement `read_byob()`. For kernel backed
52/// resources it often makes sense to implement `read_byob()` first, and then
53/// implement `read()` as an operation that allocates a new chunk with
54/// `len == limit`, then calls `read_byob()`, and then returns a chunk sliced to
55/// the number of bytes read. Kernel backed resources can use the
56/// [deno_core::impl_readable_byob] macro to implement optimized `read_byob()`
57/// and `read()` implementations from a single `Self::read()` method.
58///
59/// ### Writable
60///
61/// Writable resources are resources that can have data written to. Examples of
62/// this are files, sockets, or HTTP streams.
63///
64/// Writables can be written to from either JS or Rust. In JS one can use
65/// `Deno.core.write()` to write to a single chunk of data to a writable. In
66/// Rust one can directly call `write()`. The latter is used to implement ops
67/// like `op_slice`.
68pub trait Resource: Any + 'static {
69  /// Returns a string representation of the resource which is made available
70  /// to JavaScript code through `op_resources`. The default implementation
71  /// returns the Rust type name, but specific resource types may override this
72  /// trait method.
73  fn name(&self) -> Cow<'_, str> {
74    type_name::<Self>().into()
75  }
76
77  /// Read a single chunk of data from the resource. This operation returns a
78  /// `BufView` that represents the data that was read. If a zero length buffer
79  /// is returned, it indicates that the resource has reached EOF.
80  ///
81  /// If this method is not implemented, the default implementation will error
82  /// with a "not supported" error.
83  ///
84  /// If a readable can provide an optimized path for BYOBs, it should also
85  /// implement `read_byob()`.
86  fn read(self: Rc<Self>, limit: usize) -> AsyncResult<BufView> {
87    _ = limit;
88    Box::pin(std::future::ready(Err(JsErrorBox::not_supported())))
89  }
90
91  /// Read a single chunk of data from the resource into the provided `BufMutView`.
92  ///
93  /// This operation returns the number of bytes read. If zero bytes are read,
94  /// it indicates that the resource has reached EOF.
95  ///
96  /// If this method is not implemented explicitly, the default implementation
97  /// will call `read()` and then copy the data into the provided buffer. For
98  /// readable resources that can provide an optimized path for BYOBs, it is
99  /// strongly recommended to override this method.
100  fn read_byob(
101    self: Rc<Self>,
102    mut buf: BufMutView,
103  ) -> AsyncResult<(usize, BufMutView)> {
104    Box::pin(async move {
105      let read = self.read(buf.len()).await?;
106      let nread = read.len();
107      buf[..nread].copy_from_slice(&read);
108      Ok((nread, buf))
109    })
110  }
111
112  /// Write an error state to this resource, if the resource supports it.
113  fn write_error(self: Rc<Self>, _error: &dyn JsErrorClass) -> AsyncResult<()> {
114    Box::pin(std::future::ready(Err(JsErrorBox::not_supported())))
115  }
116
117  /// Write a single chunk of data to the resource. The operation may not be
118  /// able to write the entire chunk, in which case it should return the number
119  /// of bytes written. Additionally it should return the `BufView` that was
120  /// passed in.
121  ///
122  /// If this method is not implemented, the default implementation will error
123  /// with a "not supported" error.
124  fn write(self: Rc<Self>, buf: BufView) -> AsyncResult<WriteOutcome> {
125    _ = buf;
126    Box::pin(std::future::ready(Err(JsErrorBox::not_supported())))
127  }
128
129  /// Write an entire chunk of data to the resource. Unlike `write()`, this will
130  /// ensure the entire chunk is written. If the operation is not able to write
131  /// the entire chunk, an error is to be returned.
132  ///
133  /// By default this method will call `write()` repeatedly until the entire
134  /// chunk is written. Resources that can write the entire chunk in a single
135  /// operation using an optimized path should override this method.
136  fn write_all(self: Rc<Self>, view: BufView) -> AsyncResult<()> {
137    Box::pin(async move {
138      let mut view = view;
139      let this = self;
140      while !view.is_empty() {
141        let resp = this.clone().write(view).await?;
142        match resp {
143          WriteOutcome::Partial {
144            nwritten,
145            view: new_view,
146          } => {
147            view = new_view;
148            view.advance_cursor(nwritten);
149          }
150          WriteOutcome::Full { .. } => break,
151        }
152      }
153      Ok(())
154    })
155  }
156
157  /// The same as [`read_byob()`][Resource::read_byob], but synchronous.
158  fn read_byob_sync(
159    self: Rc<Self>,
160    data: &mut [u8],
161  ) -> Result<usize, JsErrorBox> {
162    _ = data;
163    Err(JsErrorBox::not_supported())
164  }
165
166  /// The same as [`write()`][Resource::write], but synchronous.
167  fn write_sync(self: Rc<Self>, data: &[u8]) -> Result<usize, JsErrorBox> {
168    _ = data;
169    Err(JsErrorBox::not_supported())
170  }
171
172  /// The shutdown method can be used to asynchronously close the resource. It
173  /// is not automatically called when the resource is dropped or closed.
174  ///
175  /// If this method is not implemented, the default implementation will error
176  /// with a "not supported" error.
177  fn shutdown(self: Rc<Self>) -> AsyncResult<()> {
178    Box::pin(std::future::ready(Err(JsErrorBox::not_supported())))
179  }
180
181  /// Resources may implement the `close()` trait method if they need to do
182  /// resource specific clean-ups, such as cancelling pending futures, after a
183  /// resource has been removed from the resource table.
184  fn close(self: Rc<Self>) {}
185
186  /// Resources backed by a file descriptor or socket handle can let ops know
187  /// to allow for low-level optimizations.
188  fn backing_handle(self: Rc<Self>) -> Option<ResourceHandle> {
189    #[allow(deprecated)]
190    self.backing_fd().map(ResourceHandle::Fd)
191  }
192
193  /// Resources backed by a file descriptor can let ops know to allow for
194  /// low-level optimizations.
195  #[deprecated = "Use backing_handle"]
196  fn backing_fd(self: Rc<Self>) -> Option<ResourceHandleFd> {
197    None
198  }
199
200  fn size_hint(&self) -> (u64, Option<u64>) {
201    (0, None)
202  }
203
204  fn transfer(
205    self: Rc<Self>,
206  ) -> Result<Box<dyn TransferredResource>, JsErrorBox> {
207    Err(JsErrorBox::not_supported())
208  }
209}
210
211impl dyn Resource {
212  #[inline(always)]
213  fn is<T: Resource>(&self) -> bool {
214    self.type_id() == TypeId::of::<T>()
215  }
216
217  #[inline(always)]
218  #[allow(clippy::needless_lifetimes)]
219  pub fn downcast_rc<'a, T: Resource>(self: &'a Rc<Self>) -> Option<&'a Rc<T>> {
220    if self.is::<T>() {
221      let ptr = self as *const Rc<_> as *const Rc<T>;
222      // TODO(piscisaureus): safety comment
223      #[allow(clippy::undocumented_unsafe_blocks)]
224      Some(unsafe { &*ptr })
225    } else {
226      None
227    }
228  }
229}
230
231#[macro_export]
232macro_rules! impl_readable_byob {
233  () => {
234    fn read(
235      self: ::std::rc::Rc<Self>,
236      limit: ::core::primitive::usize,
237    ) -> AsyncResult<$crate::BufView> {
238      ::std::boxed::Box::pin(async move {
239        let mut vec = ::std::vec![0; limit];
240        let nread = self.read(&mut vec).await.map_err(::deno_error::JsErrorBox::from_err)?;
241        if nread != vec.len() {
242          vec.truncate(nread);
243        }
244        let view = $crate::BufView::from(vec);
245        ::std::result::Result::Ok(view)
246      })
247    }
248
249    fn read_byob(
250      self: ::std::rc::Rc<Self>,
251      mut buf: $crate::BufMutView,
252    ) -> AsyncResult<(::core::primitive::usize, $crate::BufMutView)> {
253      ::std::boxed::Box::pin(async move {
254        let nread = self.read(buf.as_mut()).await.map_err(::deno_error::JsErrorBox::from_err)?;
255        ::std::result::Result::Ok((nread, buf))
256      })
257    }
258  };
259}
260
261#[macro_export]
262macro_rules! impl_writable {
263  (__write) => {
264    fn write(
265      self: ::std::rc::Rc<Self>,
266      view: $crate::BufView,
267    ) -> $crate::AsyncResult<$crate::WriteOutcome> {
268      ::std::boxed::Box::pin(async move {
269        let nwritten = self
270          .write(&view)
271          .await
272          .map_err(::deno_error::JsErrorBox::from_err)?;
273        ::std::result::Result::Ok($crate::WriteOutcome::Partial {
274          nwritten,
275          view,
276        })
277      })
278    }
279  };
280  (__write_all) => {
281    fn write_all(
282      self: ::std::rc::Rc<Self>,
283      view: $crate::BufView,
284    ) -> $crate::AsyncResult<()> {
285      ::std::boxed::Box::pin(async move {
286        self
287          .write_all(&view)
288          .await
289          .map_err(::deno_error::JsErrorBox::from_err)?;
290        ::std::result::Result::Ok(())
291      })
292    }
293  };
294  () => {
295    $crate::impl_writable!(__write);
296  };
297  (with_all) => {
298    $crate::impl_writable!(__write);
299    $crate::impl_writable!(__write_all);
300  };
301}
302
303pub trait TransferredResource: Send {
304  fn receive(self: Box<Self>) -> Rc<dyn Resource>;
305}
306
307impl dyn TransferredResource {}