Auto merge of #17602 - bholley:rule_tree_custom_gc, r=emilio

Use GC machinery rather than recursion for post-rule-tree-teardown node dropping

https://bugzilla.mozilla.org/show_bug.cgi?id=1378005
This commit is contained in:
bors-servo 2017-07-04 13:44:52 -07:00 committed by GitHub
commit 006037f798

View file

@ -1408,16 +1408,44 @@ impl Drop for StrongRuleNode {
let free_list = &root.next_free; let free_list = &root.next_free;
let mut old_head = free_list.load(Ordering::Relaxed); let mut old_head = free_list.load(Ordering::Relaxed);
// If the free list is null, that means the last GC has already occurred. // If the free list is null, that means that the rule tree has been
// We require that any callers freeing at this point are on the main // formally torn down, and the last standard GC has already occurred.
// thread, and we drop the rule node synchronously. // We require that any callers using the rule tree at this point are
// on the main thread only, which lets us trigger a synchronous GC
// here to avoid leaking anything. We use the GC machinery, rather
// than just dropping directly, so that we benefit from the iterative
// destruction and don't trigger unbounded recursion during drop. See
// [1] and the associated crashtest.
//
// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=439184
if old_head.is_null() { if old_head.is_null() {
debug_assert!(!thread_state::get().is_worker() && debug_assert!(!thread_state::get().is_worker() &&
(thread_state::get().is_layout() || (thread_state::get().is_layout() ||
thread_state::get().is_script())); thread_state::get().is_script()));
unsafe { node.remove_from_child_list(); } // Add the node as the sole entry in the free list.
log_drop(self.ptr()); debug_assert!(node.next_free.load(Ordering::Relaxed).is_null());
let _ = unsafe { Box::from_raw(self.ptr()) }; node.next_free.store(FREE_LIST_SENTINEL, Ordering::Relaxed);
free_list.store(node as *const _ as *mut _, Ordering::Relaxed);
// Invoke the GC.
//
// Note that we need hold a strong reference to the root so that it
// doesn't go away during the GC (which would happen if we're freeing
// the last external reference into the rule tree). This is nicely
// enforced by having the gc() method live on StrongRuleNode rather than
// RuleNode.
let strong_root: StrongRuleNode = node.root.as_ref().unwrap().upgrade();
unsafe { strong_root.gc(); }
// Leave the free list null, like we found it, such that additional
// drops for straggling rule nodes will take this same codepath.
debug_assert_eq!(root.next_free.load(Ordering::Relaxed),
FREE_LIST_SENTINEL);
root.next_free.store(ptr::null_mut(), Ordering::Relaxed);
// Return. If strong_root is the last strong reference to the root,
// this re-enter StrongRuleNode::drop, and take the root-dropping
// path earlier in this function.
return; return;
} }