diff options
author | Alan Jeffrey <ajeffrey@mozilla.com> | 2016-08-16 19:02:57 -0500 |
---|---|---|
committer | Anthony Ramine <n.oxyde@gmail.com> | 2016-08-29 11:56:28 +0200 |
commit | 6a271b05277b1c60c3aefa69474e75a662b402a3 (patch) | |
tree | 13212cf566b6e3c3d80a101ad99ca083fb39b288 /components/script/dom/bindings/refcounted.rs | |
parent | d37d4d697ae8c8897384213be82f25a51a4aa835 (diff) | |
download | servo-6a271b05277b1c60c3aefa69474e75a662b402a3.tar.gz servo-6a271b05277b1c60c3aefa69474e75a662b402a3.zip |
Remove mutex from Trusted
Use weak references rather than message passing to
garbage-collect dead references.
Diffstat (limited to 'components/script/dom/bindings/refcounted.rs')
-rw-r--r-- | components/script/dom/bindings/refcounted.rs | 129 |
1 files changed, 52 insertions, 77 deletions
diff --git a/components/script/dom/bindings/refcounted.rs b/components/script/dom/bindings/refcounted.rs index 25239a3dc3b..ea4c47b1148 100644 --- a/components/script/dom/bindings/refcounted.rs +++ b/components/script/dom/bindings/refcounted.rs @@ -6,21 +6,21 @@ //! 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 -//! message passing. Ownership of a `Trusted<T>` object means the DOM object of +//! weak refcounts. Ownership of a `Trusted<T>` 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<T>` 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<T>` via the `to_temporary` method. //! -//! The implementation of Trusted<T> is as follows: -//! A hashtable resides in the script thread, keyed on the pointer to the Rust DOM object. -//! The values in this hashtable are atomic reference counts. When a Trusted<T> object is -//! created or cloned, this count is increased. When a Trusted<T> is dropped, the count -//! decreases. If the count hits zero, a message is dispatched to the script thread to remove -//! the entry from the hashmap if the count is still zero. The JS reflector for the DOM object -//! is rooted when a hashmap entry is first created, and unrooted when the hashmap entry -//! is removed. +//! The implementation of `Trusted<T>` is as follows: +//! The `Trusted<T>` 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<T>` object is +//! created or cloned, the reference count is increased. When a `Trusted<T>` 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 core::nonzero::NonZero; use dom::bindings::js::Root; @@ -28,13 +28,13 @@ use dom::bindings::reflector::{Reflectable, Reflector}; use dom::bindings::trace::trace_reflector; use js::jsapi::JSTracer; use libc; -use script_runtime::{CommonScriptMsg, ScriptChan}; 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::os; -use std::sync::{Arc, Mutex}; +use std::sync::{Arc, Weak}; #[allow(missing_docs)] // FIXME @@ -52,6 +52,12 @@ pub use self::dummy::LIVE_REFERENCES; pub struct TrustedReference(*const libc::c_void); unsafe impl Send for TrustedReference {} +impl TrustedReference { + fn new<T: Reflectable>(ptr: *const T) -> TrustedReference { + TrustedReference(ptr as *const libc::c_void) + } +} + /// 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 @@ -60,9 +66,7 @@ unsafe impl Send for TrustedReference {} pub struct Trusted<T: Reflectable> { /// A pointer to the Rust DOM object of type T, but void to allow /// sending `Trusted<T>` between threads, regardless of T's sendability. - ptr: *const libc::c_void, - refcount: Arc<Mutex<usize>>, - script_chan: Box<ScriptChan + Send>, + refcount: Arc<TrustedReference>, owner_thread: *const libc::c_void, phantom: PhantomData<T>, } @@ -74,15 +78,12 @@ impl<T: Reflectable> Trusted<T> { /// be prevented from being GCed for the duration of the resulting `Trusted<T>` object's /// lifetime. pub fn new(ptr: &T) -> Trusted<T> { - let script_chan = ptr.global().r().script_chan(); 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 { - ptr: &*ptr as *const T as *const libc::c_void, refcount: refcount, - script_chan: script_chan.clone(), owner_thread: (&*live_references) as *const _ as *const libc::c_void, phantom: PhantomData, } @@ -99,48 +100,26 @@ impl<T: Reflectable> Trusted<T> { self.owner_thread == (&*live_references) as *const _ as *const libc::c_void })); unsafe { - Root::new(NonZero::new(self.ptr as *const T)) + Root::new(NonZero::new(self.refcount.0 as *const T)) } } } impl<T: Reflectable> Clone for Trusted<T> { fn clone(&self) -> Trusted<T> { - { - let mut refcount = self.refcount.lock().unwrap(); - *refcount += 1; - } - Trusted { - ptr: self.ptr, refcount: self.refcount.clone(), - script_chan: self.script_chan.clone(), owner_thread: self.owner_thread, phantom: PhantomData, } } } -impl<T: Reflectable> Drop for Trusted<T> { - fn drop(&mut self) { - let mut refcount = self.refcount.lock().unwrap(); - assert!(*refcount > 0); - *refcount -= 1; - if *refcount == 0 { - // It's possible this send will fail if the script thread - // has already exited. There's not much we can do at this - // point though. - let msg = CommonScriptMsg::RefcountCleanup(TrustedReference(self.ptr)); - let _ = self.script_chan.send(msg); - } - } -} - /// The set of live, pinned DOM objects that are currently prevented /// from being garbage collected due to outstanding references. pub struct LiveDOMReferences { // keyed on pointer to Rust DOM object - table: RefCell<HashMap<*const libc::c_void, Arc<Mutex<usize>>>>, + table: RefCell<HashMap<*const libc::c_void, Weak<TrustedReference>>>, } impl LiveDOMReferences { @@ -153,59 +132,55 @@ impl LiveDOMReferences { }); } - fn addref<T: Reflectable>(&self, ptr: *const T) -> Arc<Mutex<usize>> { + fn addref<T: Reflectable>(&self, ptr: *const T) -> Arc<TrustedReference> { let mut table = self.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) => { - let refcount = entry.get_mut(); - *refcount.lock().unwrap() += 1; - refcount.clone() - } + 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(Mutex::new(1)); - entry.insert(refcount.clone()); + let refcount = Arc::new(TrustedReference::new(ptr)); + entry.insert(Arc::downgrade(&refcount)); refcount } } } +} - /// Unpin the given DOM object if its refcount is 0. - pub fn cleanup(raw_reflectable: TrustedReference) { - let TrustedReference(raw_reflectable) = raw_reflectable; - LIVE_REFERENCES.with(|ref r| { - let r = r.borrow(); - let live_references = r.as_ref().unwrap(); - let mut table = live_references.table.borrow_mut(); - match table.entry(raw_reflectable) { - Occupied(entry) => { - if *entry.get().lock().unwrap() != 0 { - // there could have been a new reference taken since - // this message was dispatched. - return; - } - - let _ = entry.remove(); - } - Vacant(_) => { - // there could be a cleanup message dispatched, then a new - // pinned reference obtained and released before the message - // is processed, at which point there would be no matching - // hashtable entry. - info!("attempt to cleanup an unrecognized reflector"); - } - } - }) +/// Remove null entries from the live references table +fn remove_nulls<K: Eq + Hash + Clone, V> (table: &mut HashMap<K, Weak<V>>) { + let to_remove: Vec<K> = + 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 pub unsafe extern "C" fn trace_refcounted_objects(tracer: *mut JSTracer, _data: *mut os::raw::c_void) { - debug!("tracing live refcounted references"); + info!("tracing live refcounted references"); LIVE_REFERENCES.with(|ref r| { let r = r.borrow(); let live_references = r.as_ref().unwrap(); - let table = live_references.table.borrow(); + let mut table = live_references.table.borrow_mut(); + remove_nulls(&mut table); for obj in table.keys() { let reflectable = &*(*obj as *const Reflector); trace_reflector(tracer, "refcounted", reflectable); |