/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! A generic, safe mechanism by which DOM objects can be pinned and transferred //! between threads (or intra-thread for asynchronous events). Akin to Gecko's //! nsMainThreadPtrHandle, this uses thread-safe reference counting and ensures //! that the actual SpiderMonkey GC integration occurs on the script thread via //! weak refcounts. Ownership of a `Trusted` object means the DOM object of //! type T to which it points remains alive. Any other behaviour is undefined. //! To guarantee the lifetime of a DOM object when performing asynchronous operations, //! obtain a `Trusted` from that object and pass it along with each operation. //! A usable pointer to the original DOM object can be obtained on the script thread //! from a `Trusted` via the `root` method. //! //! The implementation of `Trusted` is as follows: //! The `Trusted` object contains an atomic reference counted pointer to the Rust DOM object. //! A hashtable resides in the script thread, keyed on the pointer. //! The values in this hashtable are weak reference counts. When a `Trusted` object is //! created or cloned, the reference count is increased. When a `Trusted` is dropped, the count //! decreases. If the count hits zero, the weak reference is emptied, and is removed from //! its hash table during the next GC. During GC, the entries of the hash table are counted //! as JS roots. use crate::dom::bindings::conversions::ToJSValConvertible; use crate::dom::bindings::error::Error; use crate::dom::bindings::reflector::{DomObject, Reflector}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::trace::trace_reflector; use crate::dom::promise::Promise; use crate::task::TaskOnce; use js::jsapi::JSTracer; use std::cell::RefCell; use std::collections::hash_map::Entry::{Occupied, Vacant}; use std::collections::hash_map::HashMap; use std::hash::Hash; use std::marker::PhantomData; use std::rc::Rc; use std::sync::{Arc, Weak}; #[allow(missing_docs)] // FIXME mod dummy { // Attributes don’t apply through the macro. use super::LiveDOMReferences; use std::cell::RefCell; use std::rc::Rc; thread_local!(pub static LIVE_REFERENCES: Rc>> = Rc::new(RefCell::new(None))); } pub use self::dummy::LIVE_REFERENCES; /// A pointer to a Rust DOM object that needs to be destroyed. pub struct TrustedReference(*const libc::c_void); unsafe impl Send for TrustedReference {} impl TrustedReference { fn new(ptr: *const T) -> TrustedReference { TrustedReference(ptr as *const libc::c_void) } } /// A safe wrapper around a DOM Promise object that can be shared among threads for use /// in asynchronous operations. The underlying DOM object is guaranteed to live at least /// as long as the last outstanding `TrustedPromise` instance. These values cannot be cloned, /// only created from existing Rc values. pub struct TrustedPromise { dom_object: *const Promise, owner_thread: *const libc::c_void, } unsafe impl Send for TrustedPromise {} impl TrustedPromise { /// Create a new `TrustedPromise` instance from an existing DOM object. The object will /// be prevented from being GCed for the duration of the resulting `TrustedPromise` object's /// lifetime. #[allow(unrooted_must_root)] pub fn new(promise: Rc) -> TrustedPromise { LIVE_REFERENCES.with(|ref r| { let r = r.borrow(); let live_references = r.as_ref().unwrap(); let ptr = &*promise as *const Promise; live_references.addref_promise(promise); TrustedPromise { dom_object: ptr, owner_thread: (&*live_references) as *const _ as *const libc::c_void, } }) } /// Obtain a usable DOM Promise from a pinned `TrustedPromise` value. Fails if used on /// a different thread than the original value from which this `TrustedPromise` was /// obtained. pub fn root(self) -> Rc { LIVE_REFERENCES.with(|ref r| { let r = r.borrow(); let live_references = r.as_ref().unwrap(); assert_eq!( self.owner_thread, (&*live_references) as *const _ as *const libc::c_void ); // Borrow-check error requires the redundant `let promise = ...; promise` here. let promise = match live_references .promise_table .borrow_mut() .entry(self.dom_object) { Occupied(mut entry) => { let promise = { let promises = entry.get_mut(); promises .pop() .expect("rooted promise list unexpectedly empty") }; if entry.get().is_empty() { entry.remove(); } promise }, Vacant(_) => unreachable!(), }; promise }) } /// A task which will reject the promise. #[allow(unrooted_must_root)] pub fn reject_task(self, error: Error) -> impl TaskOnce { let this = self; task!(reject_promise: move || { debug!("Rejecting promise."); this.root().reject_error(error); }) } /// A task which will resolve the promise. #[allow(unrooted_must_root)] pub fn resolve_task(self, value: T) -> impl TaskOnce where T: ToJSValConvertible + Send, { let this = self; task!(resolve_promise: move || { debug!("Resolving promise."); this.root().resolve_native(&value); }) } } /// A safe wrapper around a raw pointer to a DOM object that can be /// shared among threads for use in asynchronous operations. The underlying /// DOM object is guaranteed to live at least as long as the last outstanding /// `Trusted` instance. #[allow_unrooted_interior] pub struct Trusted { /// A pointer to the Rust DOM object of type T, but void to allow /// sending `Trusted` between threads, regardless of T's sendability. refcount: Arc, owner_thread: *const libc::c_void, phantom: PhantomData, } unsafe impl Send for Trusted {} impl Trusted { /// Create a new `Trusted` instance from an existing DOM pointer. The DOM object will /// be prevented from being GCed for the duration of the resulting `Trusted` object's /// lifetime. pub fn new(ptr: &T) -> Trusted { LIVE_REFERENCES.with(|ref r| { let r = r.borrow(); let live_references = r.as_ref().unwrap(); let refcount = live_references.addref(&*ptr as *const T); Trusted { refcount: refcount, owner_thread: (&*live_references) as *const _ as *const libc::c_void, phantom: PhantomData, } }) } /// Obtain a usable DOM pointer from a pinned `Trusted` value. Fails if used on /// a different thread than the original value from which this `Trusted` was /// obtained. pub fn root(&self) -> DomRoot { assert!(LIVE_REFERENCES.with(|ref r| { let r = r.borrow(); let live_references = r.as_ref().unwrap(); self.owner_thread == (&*live_references) as *const _ as *const libc::c_void })); unsafe { DomRoot::from_ref(&*(self.refcount.0 as *const T)) } } } impl Clone for Trusted { fn clone(&self) -> Trusted { Trusted { refcount: self.refcount.clone(), owner_thread: self.owner_thread, phantom: PhantomData, } } } /// The set of live, pinned DOM objects that are currently prevented /// from being garbage collected due to outstanding references. #[allow(unrooted_must_root)] pub struct LiveDOMReferences { // keyed on pointer to Rust DOM object reflectable_table: RefCell>>, promise_table: RefCell>>>, } impl LiveDOMReferences { /// Set up the thread-local data required for storing the outstanding DOM references. pub fn initialize() { LIVE_REFERENCES.with(|ref r| { *r.borrow_mut() = Some(LiveDOMReferences { reflectable_table: RefCell::new(HashMap::new()), promise_table: RefCell::new(HashMap::new()), }) }); } pub fn destruct() { LIVE_REFERENCES.with(|ref r| { *r.borrow_mut() = None; }); } #[allow(unrooted_must_root)] fn addref_promise(&self, promise: Rc) { let mut table = self.promise_table.borrow_mut(); table.entry(&*promise).or_insert(vec![]).push(promise) } fn addref(&self, ptr: *const T) -> Arc { let mut table = self.reflectable_table.borrow_mut(); let capacity = table.capacity(); let len = table.len(); if (0 < capacity) && (capacity <= len) { info!("growing refcounted references by {}", len); remove_nulls(&mut table); table.reserve(len); } match table.entry(ptr as *const libc::c_void) { Occupied(mut entry) => match entry.get().upgrade() { Some(refcount) => refcount, None => { let refcount = Arc::new(TrustedReference::new(ptr)); entry.insert(Arc::downgrade(&refcount)); refcount }, }, Vacant(entry) => { let refcount = Arc::new(TrustedReference::new(ptr)); entry.insert(Arc::downgrade(&refcount)); refcount }, } } } /// Remove null entries from the live references table fn remove_nulls(table: &mut HashMap>) { let to_remove: Vec = table .iter() .filter(|&(_, value)| Weak::upgrade(value).is_none()) .map(|(key, _)| key.clone()) .collect(); info!("removing {} refcounted references", to_remove.len()); for key in to_remove { table.remove(&key); } } /// A JSTraceDataOp for tracing reflectors held in LIVE_REFERENCES #[allow(unrooted_must_root)] pub unsafe fn trace_refcounted_objects(tracer: *mut JSTracer) { info!("tracing live refcounted references"); LIVE_REFERENCES.with(|ref r| { let r = r.borrow(); let live_references = r.as_ref().unwrap(); { let mut table = live_references.reflectable_table.borrow_mut(); remove_nulls(&mut table); for obj in table.keys() { let reflectable = &*(*obj as *const Reflector); trace_reflector(tracer, "refcounted", reflectable); } } { let table = live_references.promise_table.borrow_mut(); for promise in table.keys() { trace_reflector(tracer, "refcounted", (**promise).reflector()); } } }); }