diff --git a/components/style/parallel.rs b/components/style/parallel.rs index 79126f03463..0526c07f94c 100644 --- a/components/style/parallel.rs +++ b/components/style/parallel.rs @@ -92,6 +92,7 @@ pub fn traverse_dom(traversal: &D, let nodes = nodes; traverse_nodes(&*nodes, DispatchMode::TailCall, + 0, root, traversal_data, scope, @@ -145,6 +146,7 @@ fn create_thread_local_context<'scope, E, D>( #[inline(always)] #[allow(unsafe_code)] fn top_down_dom<'a, 'scope, E, D>(nodes: &'a [SendNode], + recursion_depth: usize, root: OpaqueNode, mut traversal_data: PerLevelTraversalData, scope: &'a rayon::Scope<'scope>, @@ -186,6 +188,7 @@ fn top_down_dom<'a, 'scope, E, D>(nodes: &'a [SendNode], traversal_data_copy.current_dom_depth += 1; traverse_nodes(&*children, DispatchMode::NotTailCall, + recursion_depth, root, traversal_data_copy, scope, @@ -216,6 +219,7 @@ fn top_down_dom<'a, 'scope, E, D>(nodes: &'a [SendNode], traversal_data.current_dom_depth += 1; traverse_nodes(&discovered_child_nodes, DispatchMode::TailCall, + recursion_depth, root, traversal_data, scope, @@ -237,9 +241,16 @@ impl DispatchMode { fn is_tail_call(&self) -> bool { matches!(*self, DispatchMode::TailCall) } } +// On x86_64-linux, a recursive cycle requires 3472 bytes of stack. Limiting +// the depth to 150 therefore should keep the stack use by the recursion to +// 520800 bytes, which would give a generously conservative margin should we +// decide to reduce the thread stack size from its default of 2MB down to 1MB. +const RECURSION_DEPTH_LIMIT: usize = 150; + #[inline] fn traverse_nodes<'a, 'scope, E, D>(nodes: &[SendNode], mode: DispatchMode, + recursion_depth: usize, root: OpaqueNode, traversal_data: PerLevelTraversalData, scope: &'a rayon::Scope<'scope>, @@ -254,8 +265,13 @@ fn traverse_nodes<'a, 'scope, E, D>(nodes: &[SendNode], // This is a tail call from the perspective of the caller. However, we only // want to actually dispatch the job as a tail call if there's nothing left // in our local queue. Otherwise we need to return to it to maintain proper - // breadth-first ordering. + // breadth-first ordering. We also need to take care to avoid stack + // overflow due to excessive tail recursion. The stack overflow isn't + // observable to content -- we're still completely correct, just not + // using tail recursion any more. See bug 1368302. + debug_assert!(recursion_depth <= RECURSION_DEPTH_LIMIT); let may_dispatch_tail = mode.is_tail_call() && + recursion_depth != RECURSION_DEPTH_LIMIT && !pool.current_thread_has_pending_tasks().unwrap(); // In the common case, our children fit within a single work unit, in which @@ -263,11 +279,13 @@ fn traverse_nodes<'a, 'scope, E, D>(nodes: &[SendNode], if nodes.len() <= WORK_UNIT_MAX { let work = nodes.iter().cloned().collect::>(); if may_dispatch_tail { - top_down_dom(&work, root, traversal_data, scope, pool, traversal, tls); + top_down_dom(&work, recursion_depth + 1, root, + traversal_data, scope, pool, traversal, tls); } else { scope.spawn(move |scope| { let work = work; - top_down_dom(&work, root, traversal_data, scope, pool, traversal, tls); + top_down_dom(&work, 0, root, + traversal_data, scope, pool, traversal, tls); }); } } else { @@ -276,7 +294,8 @@ fn traverse_nodes<'a, 'scope, E, D>(nodes: &[SendNode], let traversal_data_copy = traversal_data.clone(); scope.spawn(move |scope| { let n = nodes; - top_down_dom(&*n, root, traversal_data_copy, scope, pool, traversal, tls) + top_down_dom(&*n, 0, root, + traversal_data_copy, scope, pool, traversal, tls) }); } }