diff options
Diffstat (limited to 'core/resources.rs')
-rw-r--r-- | core/resources.rs | 253 |
1 files changed, 119 insertions, 134 deletions
diff --git a/core/resources.rs b/core/resources.rs index 753fa9713..da3b634fc 100644 --- a/core/resources.rs +++ b/core/resources.rs @@ -1,20 +1,63 @@ // Copyright 2018-2020 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 to refer to various rust objects that need to be -// referenced between multiple ops. For example, network sockets are resources. -// Resources may or may not correspond to a real operating system file -// descriptor (hence the different name). +// 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 crate::resources2::ResourceId; +use std::any::type_name; use std::any::Any; +use std::any::TypeId; +use std::borrow::Cow; use std::collections::HashMap; +use std::iter::Iterator; +use std::rc::Rc; + +/// All objects that can be store in the resource table should implement the +/// `Resource` trait. +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 + /// returns the Rust type name, but specific resource types may override this + /// trait method. + fn name(&self) -> Cow<str> { + type_name::<Self>().into() + } + + /// Resources may implement the `close()` trait method if they need to do + /// resource specific clean-ups, such as cancelling pending futures, after a + /// resource has been removed from the resource table. + fn close(self: Rc<Self>) {} +} + +impl dyn Resource { + #[inline(always)] + fn is<T: Resource>(&self) -> bool { + self.type_id() == TypeId::of::<T>() + } -/// These store Deno's file descriptors. These are not necessarily the operating -/// system ones. -type ResourceMap = HashMap<ResourceId, (String, Box<dyn Any>)>; + #[inline(always)] + #[allow(clippy::needless_lifetimes)] + pub fn downcast_rc<'a, T: Resource>(self: &'a Rc<Self>) -> Option<&'a Rc<T>> { + if self.is::<T>() { + let ptr = self as *const Rc<_> as *const Rc<T>; + Some(unsafe { &*ptr }) + } else { + None + } + } +} -/// Map-like data structure storing Deno's resources (equivalent to file descriptors). +/// A `ResourceId` is an integer value referencing a resource. It could be +/// considered to be the Deno equivalent of a `file descriptor` in POSIX like +/// operating systems. Elsewhere in the code base it is commonly abbreviated +/// to `rid`. +// TODO: use `u64` instead? +pub type ResourceId = u32; + +/// Map-like data structure storing Deno's resources (equivalent to file +/// descriptors). /// /// Provides basic methods for element access. A resource can be of any type. /// Different types of resources can be stored in the same map, and provided @@ -24,156 +67,98 @@ type ResourceMap = HashMap<ResourceId, (String, Box<dyn Any>)>; /// the key in the map. #[derive(Default)] pub struct ResourceTable { - map: ResourceMap, - next_id: u32, + index: HashMap<ResourceId, Rc<dyn Resource>>, + next_rid: ResourceId, } impl ResourceTable { - /// Checks if the given resource ID is contained. - pub fn has(&self, rid: ResourceId) -> bool { - self.map.contains_key(&rid) - } - - /// Returns a shared reference to a resource. + /// Inserts resource into the resource table, which takes ownership of it. /// - /// Returns `None`, if `rid` is not stored or has a type different from `T`. - pub fn get<T: Any>(&self, rid: ResourceId) -> Option<&T> { - let (_, resource) = self.map.get(&rid)?; - resource.downcast_ref::<T>() - } - - /// Returns a mutable reference to a resource. + /// The resource type is erased at runtime and must be statically known + /// when retrieving it through `get()`. /// - /// Returns `None`, if `rid` is not stored or has a type different from `T`. - pub fn get_mut<T: Any>(&mut self, rid: ResourceId) -> Option<&mut T> { - let (_, resource) = self.map.get_mut(&rid)?; - resource.downcast_mut::<T>() - } - - // TODO: resource id allocation should probably be randomized for security. - fn next_rid(&mut self) -> ResourceId { - let next_rid = self.next_id; - self.next_id += 1; - next_rid as ResourceId + /// Returns a unique resource ID, which acts as a key for this resource. + pub fn add<T: Resource>(&mut self, resource: T) -> ResourceId { + self.add_rc(Rc::new(resource)) } - /// Inserts a resource, taking ownership of it. + /// Inserts a `Rc`-wrapped resource into the resource table. /// /// The resource type is erased at runtime and must be statically known /// when retrieving it through `get()`. /// /// Returns a unique resource ID, which acts as a key for this resource. - pub fn add(&mut self, name: &str, resource: Box<dyn Any>) -> ResourceId { - let rid = self.next_rid(); - let r = self.map.insert(rid, (name.to_string(), resource)); - assert!(r.is_none()); + pub fn add_rc<T: Resource>(&mut self, resource: Rc<T>) -> ResourceId { + let resource = resource as Rc<dyn Resource>; + let rid = self.next_rid; + let removed_resource = self.index.insert(rid, resource); + assert!(removed_resource.is_none()); + self.next_rid += 1; rid } - /// Returns a map of resource IDs to names. - /// - /// The name is the one specified during `add()`. To access resources themselves, - /// use the `get()` or `get_mut()` functions. - pub fn entries(&self) -> HashMap<ResourceId, String> { - self - .map - .iter() - .map(|(key, (name, _resource))| (*key, name.clone())) - .collect() - } - - // close(2) is done by dropping the value. Therefore we just need to remove - // the resource from the resource table. - pub fn close(&mut self, rid: ResourceId) -> Option<()> { - self.map.remove(&rid).map(|(_name, _resource)| ()) - } - - /// Removes the resource identified by `rid` and returns it. - /// - /// When the provided `rid` is stored, the associated resource will be removed. - /// Otherwise, nothing happens and `None` is returned. - /// - /// If the type `T` matches the resource's type, the resource will be returned. - /// If the type mismatches, `None` is returned, but the resource is still removed. - pub fn remove<T: Any>(&mut self, rid: ResourceId) -> Option<Box<T>> { - if let Some((_name, resource)) = self.map.remove(&rid) { - let res = match resource.downcast::<T>() { - Ok(res) => Some(res), - Err(_e) => None, - }; - return res; - } - None - } -} - -#[cfg(test)] -mod tests { - use super::*; - - struct FakeResource { - not_empty: u128, - } - - impl FakeResource { - fn new(value: u128) -> FakeResource { - FakeResource { not_empty: value } - } + /// Returns true if any resource with the given `rid` exists. + pub fn has(&self, rid: ResourceId) -> bool { + self.index.contains_key(&rid) } - #[test] - fn test_create_resource_table_default() { - let table = ResourceTable::default(); - assert_eq!(table.map.len(), 0); + /// Returns a reference counted pointer to the resource of type `T` with the + /// given `rid`. If `rid` is not present or has a type different than `T`, + /// this function returns `None`. + pub fn get<T: Resource>(&self, rid: ResourceId) -> Option<Rc<T>> { + self + .index + .get(&rid) + .and_then(|rc| rc.downcast_rc::<T>()) + .map(Clone::clone) } - #[test] - fn test_add_to_resource_table_not_empty() { - let mut table = ResourceTable::default(); - table.add("fake1", Box::new(FakeResource::new(1))); - table.add("fake2", Box::new(FakeResource::new(2))); - assert_eq!(table.map.len(), 2); + pub fn get_any(&self, rid: ResourceId) -> Option<Rc<dyn Resource>> { + self.index.get(&rid).map(Clone::clone) } - #[test] - fn test_add_to_resource_table_are_contiguous() { - let mut table = ResourceTable::default(); - let rid1 = table.add("fake1", Box::new(FakeResource::new(1))); - let rid2 = table.add("fake2", Box::new(FakeResource::new(2))); - assert_eq!(rid1 + 1, rid2); + /// Removes a resource of type `T` from the resource table and returns it. + /// If a resource with the given `rid` exists but its type does not match `T`, + /// it is not removed from the resource table. Note that the resource's + /// `close()` method is *not* called. + pub fn take<T: Resource>(&mut self, rid: ResourceId) -> Option<Rc<T>> { + let resource = self.get::<T>(rid)?; + self.index.remove(&rid); + Some(resource) } - #[test] - fn test_get_from_resource_table_is_what_was_given() { - let mut table = ResourceTable::default(); - let rid = table.add("fake", Box::new(FakeResource::new(7))); - let resource = table.get::<FakeResource>(rid); - assert_eq!(resource.unwrap().not_empty, 7); + /// Removes a resource from the resource table and returns it. Note that the + /// resource's `close()` method is *not* called. + pub fn take_any(&mut self, rid: ResourceId) -> Option<Rc<dyn Resource>> { + self.index.remove(&rid) } - #[test] - fn test_remove_from_resource_table() { - let mut table = ResourceTable::default(); - let rid1 = table.add("fake1", Box::new(FakeResource::new(1))); - let rid2 = table.add("fake2", Box::new(FakeResource::new(2))); - assert_eq!(table.map.len(), 2); - table.close(rid1); - assert_eq!(table.map.len(), 1); - table.close(rid2); - assert_eq!(table.map.len(), 0); + /// Removes the resource with the given `rid` from the resource table. If the + /// only reference to this resource existed in the resource table, this will + /// cause the resource to be dropped. However, since resources are reference + /// counted, therefore pending ops are not automatically cancelled. A resource + /// may implement the `close()` method to perform clean-ups such as canceling + /// ops. + pub fn close(&mut self, rid: ResourceId) -> Option<()> { + self.index.remove(&rid).map(|resource| resource.close()) } - #[test] - fn test_take_from_resource_table() { - let mut table = ResourceTable::default(); - let rid1 = table.add("fake1", Box::new(FakeResource::new(1))); - let rid2 = table.add("fake2", Box::new(FakeResource::new(2))); - assert_eq!(table.map.len(), 2); - let res1 = table.remove::<FakeResource>(rid1); - assert_eq!(table.map.len(), 1); - assert!(res1.is_some()); - let res2 = table.remove::<FakeResource>(rid2); - assert_eq!(table.map.len(), 0); - assert!(res2.is_some()); + /// Returns an iterator that yields a `(id, name)` pair for every resource + /// that's currently in the resource table. This can be used for debugging + /// purposes or to implement the `op_resources` op. Note that the order in + /// which items appear is not specified. + /// + /// # Example + /// + /// ``` + /// # use deno_core::ResourceTable; + /// # let resource_table = ResourceTable::default(); + /// let resource_names = resource_table.names().collect::<Vec<_>>(); + /// ``` + pub fn names(&self) -> impl Iterator<Item = (ResourceId, Cow<str>)> { + self + .index + .iter() + .map(|(&id, resource)| (id, resource.name())) } } |