mirror of
https://github.com/servo/servo.git
synced 2025-07-30 18:50:36 +01:00
Auto merge of #6131 - glennw:jquery-exit-fix, r=jdm
This fixes a hang found while testing the jQuery test suite. <!-- Reviewable:start --> [<img src="https://reviewable.io/review_button.png" height=40 alt="Review on Reviewable"/>](https://reviewable.io/reviews/servo/servo/6131) <!-- Reviewable:end -->
This commit is contained in:
commit
c51e9f0455
7 changed files with 74 additions and 21 deletions
|
@ -94,7 +94,13 @@ impl PaintListener for Box<CompositorProxy+'static+Send> {
|
||||||
fn get_graphics_metadata(&mut self) -> Option<NativeGraphicsMetadata> {
|
fn get_graphics_metadata(&mut self) -> Option<NativeGraphicsMetadata> {
|
||||||
let (chan, port) = channel();
|
let (chan, port) = channel();
|
||||||
self.send(Msg::GetGraphicsMetadata(chan));
|
self.send(Msg::GetGraphicsMetadata(chan));
|
||||||
port.recv().unwrap()
|
// If the compositor is shutting down when a paint task
|
||||||
|
// is being created, the compositor won't respond to
|
||||||
|
// this message, resulting in an eventual panic. Instead,
|
||||||
|
// just return None in this case, since the paint task
|
||||||
|
// will exit shortly and never actually be requested
|
||||||
|
// to paint buffers by the compositor.
|
||||||
|
port.recv().unwrap_or(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assign_painted_buffers(&mut self,
|
fn assign_painted_buffers(&mut self,
|
||||||
|
|
|
@ -145,7 +145,7 @@ impl<C> PaintTask<C> where C: PaintListener + Send + 'static {
|
||||||
time_profiler_chan: time::ProfilerChan,
|
time_profiler_chan: time::ProfilerChan,
|
||||||
shutdown_chan: Sender<()>) {
|
shutdown_chan: Sender<()>) {
|
||||||
let ConstellationChan(c) = constellation_chan.clone();
|
let ConstellationChan(c) = constellation_chan.clone();
|
||||||
spawn_named_with_send_on_failure("PaintTask", task_state::PAINT, move || {
|
spawn_named_with_send_on_failure(format!("PaintTask {:?}", id), task_state::PAINT, move || {
|
||||||
{
|
{
|
||||||
// Ensures that the paint task and graphics context are destroyed before the
|
// Ensures that the paint task and graphics context are destroyed before the
|
||||||
// shutdown message.
|
// shutdown message.
|
||||||
|
|
|
@ -220,7 +220,7 @@ impl LayoutTaskFactory for LayoutTask {
|
||||||
memory_profiler_chan: mem::ProfilerChan,
|
memory_profiler_chan: mem::ProfilerChan,
|
||||||
shutdown_chan: Sender<()>) {
|
shutdown_chan: Sender<()>) {
|
||||||
let ConstellationChan(con_chan) = constellation_chan.clone();
|
let ConstellationChan(con_chan) = constellation_chan.clone();
|
||||||
spawn_named_with_send_on_failure("LayoutTask", task_state::LAYOUT, move || {
|
spawn_named_with_send_on_failure(format!("LayoutTask {:?}", id), task_state::LAYOUT, move || {
|
||||||
{ // Ensures layout task is destroyed before we send shutdown message
|
{ // Ensures layout task is destroyed before we send shutdown message
|
||||||
let sender = chan.sender();
|
let sender = chan.sender();
|
||||||
let layout = LayoutTask::new(id,
|
let layout = LayoutTask::new(id,
|
||||||
|
|
|
@ -366,7 +366,7 @@ pub struct SubpageId(pub u32);
|
||||||
|
|
||||||
// The type of pipeline exit. During complete shutdowns, pipelines do not have to
|
// The type of pipeline exit. During complete shutdowns, pipelines do not have to
|
||||||
// release resources automatically released on process termination.
|
// release resources automatically released on process termination.
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone, Debug)]
|
||||||
pub enum PipelineExitType {
|
pub enum PipelineExitType {
|
||||||
PipelineOnly,
|
PipelineOnly,
|
||||||
Complete,
|
Complete,
|
||||||
|
|
|
@ -584,6 +584,17 @@ impl<'a> WindowHelpers for JSRef<'a, Window> {
|
||||||
let document = self.Document().root();
|
let document = self.Document().root();
|
||||||
NodeCast::from_ref(document.r()).teardown();
|
NodeCast::from_ref(document.r()).teardown();
|
||||||
|
|
||||||
|
// The above code may not catch all DOM objects
|
||||||
|
// (e.g. DOM objects removed from the tree that haven't
|
||||||
|
// been collected yet). Forcing a GC here means that
|
||||||
|
// those DOM objects will be able to call dispose()
|
||||||
|
// to free their layout data before the layout task
|
||||||
|
// exits. Without this, those remaining objects try to
|
||||||
|
// send a message to free their layout data to the
|
||||||
|
// layout task when the script task is dropped,
|
||||||
|
// which causes a panic!
|
||||||
|
self.Gc();
|
||||||
|
|
||||||
*self.js_runtime.borrow_mut() = None;
|
*self.js_runtime.borrow_mut() = None;
|
||||||
*self.browser_context.borrow_mut() = None;
|
*self.browser_context.borrow_mut() = None;
|
||||||
}
|
}
|
||||||
|
|
|
@ -317,6 +317,9 @@ pub struct ScriptTask {
|
||||||
js_runtime: Rc<Runtime>,
|
js_runtime: Rc<Runtime>,
|
||||||
|
|
||||||
mouse_over_targets: DOMRefCell<Vec<JS<Node>>>,
|
mouse_over_targets: DOMRefCell<Vec<JS<Node>>>,
|
||||||
|
|
||||||
|
/// List of pipelines that have been owned and closed by this script task.
|
||||||
|
closed_pipelines: RefCell<HashSet<PipelineId>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// In the event of task failure, all data on the stack runs its destructor. However, there
|
/// In the event of task failure, all data on the stack runs its destructor. However, there
|
||||||
|
@ -386,7 +389,7 @@ impl ScriptTaskFactory for ScriptTask {
|
||||||
let ConstellationChan(const_chan) = constellation_chan.clone();
|
let ConstellationChan(const_chan) = constellation_chan.clone();
|
||||||
let (script_chan, script_port) = channel();
|
let (script_chan, script_port) = channel();
|
||||||
let layout_chan = LayoutChan(layout_chan.sender());
|
let layout_chan = LayoutChan(layout_chan.sender());
|
||||||
spawn_named_with_send_on_failure("ScriptTask", task_state::SCRIPT, move || {
|
spawn_named_with_send_on_failure(format!("ScriptTask {:?}", id), task_state::SCRIPT, move || {
|
||||||
let script_task = ScriptTask::new(box compositor as Box<ScriptListener>,
|
let script_task = ScriptTask::new(box compositor as Box<ScriptListener>,
|
||||||
script_port,
|
script_port,
|
||||||
NonWorkerScriptChan(script_chan),
|
NonWorkerScriptChan(script_chan),
|
||||||
|
@ -494,7 +497,8 @@ impl ScriptTask {
|
||||||
devtools_marker_sender: RefCell::new(None),
|
devtools_marker_sender: RefCell::new(None),
|
||||||
|
|
||||||
js_runtime: Rc::new(runtime),
|
js_runtime: Rc::new(runtime),
|
||||||
mouse_over_targets: DOMRefCell::new(vec!())
|
mouse_over_targets: DOMRefCell::new(vec!()),
|
||||||
|
closed_pipelines: RefCell::new(HashSet::new()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1027,12 +1031,16 @@ impl ScriptTask {
|
||||||
fn handle_reflow_complete_msg(&self, pipeline_id: PipelineId, reflow_id: u32) {
|
fn handle_reflow_complete_msg(&self, pipeline_id: PipelineId, reflow_id: u32) {
|
||||||
debug!("Script: Reflow {:?} complete for {:?}", reflow_id, pipeline_id);
|
debug!("Script: Reflow {:?} complete for {:?}", reflow_id, pipeline_id);
|
||||||
let page = self.root_page();
|
let page = self.root_page();
|
||||||
let page = page.find(pipeline_id).expect(
|
match page.find(pipeline_id) {
|
||||||
"ScriptTask: received a load message for a layout channel that is not associated \
|
Some(page) => {
|
||||||
with this script task. This is a bug.");
|
|
||||||
let window = page.window().root();
|
let window = page.window().root();
|
||||||
window.r().handle_reflow_complete_msg(reflow_id);
|
window.r().handle_reflow_complete_msg(reflow_id);
|
||||||
}
|
}
|
||||||
|
None => {
|
||||||
|
assert!(self.closed_pipelines.borrow().contains(&pipeline_id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Window was resized, but this script was not active, so don't reflow yet
|
/// Window was resized, but this script was not active, so don't reflow yet
|
||||||
fn handle_resize_inactive_msg(&self, id: PipelineId, new_size: WindowSizeData) {
|
fn handle_resize_inactive_msg(&self, id: PipelineId, new_size: WindowSizeData) {
|
||||||
|
@ -1062,13 +1070,21 @@ impl ScriptTask {
|
||||||
/// Kick off the document and frame tree creation process using the result.
|
/// Kick off the document and frame tree creation process using the result.
|
||||||
fn handle_page_fetch_complete(&self, id: PipelineId, subpage: Option<SubpageId>,
|
fn handle_page_fetch_complete(&self, id: PipelineId, subpage: Option<SubpageId>,
|
||||||
response: LoadResponse) {
|
response: LoadResponse) {
|
||||||
// Any notification received should refer to an existing, in-progress load that is tracked.
|
|
||||||
let idx = self.incomplete_loads.borrow().iter().position(|load| {
|
let idx = self.incomplete_loads.borrow().iter().position(|load| {
|
||||||
load.pipeline_id == id && load.parent_info.map(|info| info.1) == subpage
|
load.pipeline_id == id && load.parent_info.map(|info| info.1) == subpage
|
||||||
}).unwrap();
|
});
|
||||||
|
// The matching in progress load structure may not exist if
|
||||||
|
// the pipeline exited before the page load completed.
|
||||||
|
match idx {
|
||||||
|
Some(idx) => {
|
||||||
let load = self.incomplete_loads.borrow_mut().remove(idx);
|
let load = self.incomplete_loads.borrow_mut().remove(idx);
|
||||||
self.load(response, load);
|
self.load(response, load);
|
||||||
}
|
}
|
||||||
|
None => {
|
||||||
|
assert!(self.closed_pipelines.borrow().contains(&id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Handles a request for the window title.
|
/// Handles a request for the window title.
|
||||||
fn handle_get_title_msg(&self, pipeline_id: PipelineId) {
|
fn handle_get_title_msg(&self, pipeline_id: PipelineId) {
|
||||||
|
@ -1080,11 +1096,31 @@ impl ScriptTask {
|
||||||
/// Handles a request to exit the script task and shut down layout.
|
/// Handles a request to exit the script task and shut down layout.
|
||||||
/// Returns true if the script task should shut down and false otherwise.
|
/// Returns true if the script task should shut down and false otherwise.
|
||||||
fn handle_exit_pipeline_msg(&self, id: PipelineId, exit_type: PipelineExitType) -> bool {
|
fn handle_exit_pipeline_msg(&self, id: PipelineId, exit_type: PipelineExitType) -> bool {
|
||||||
if self.page.borrow().is_none() {
|
self.closed_pipelines.borrow_mut().insert(id);
|
||||||
// The root page doesn't even exist yet, so it
|
|
||||||
// is safe to exit this script task.
|
// Check if the exit message is for an in progress load.
|
||||||
// TODO(gw): This probably leaks resources!
|
let idx = self.incomplete_loads.borrow().iter().position(|load| {
|
||||||
return true;
|
load.pipeline_id == id
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(idx) = idx {
|
||||||
|
let load = self.incomplete_loads.borrow_mut().remove(idx);
|
||||||
|
|
||||||
|
// Tell the layout task to begin shutting down, and wait until it
|
||||||
|
// processed this message.
|
||||||
|
let (response_chan, response_port) = channel();
|
||||||
|
let LayoutChan(chan) = load.layout_chan;
|
||||||
|
if chan.send(layout_interface::Msg::PrepareToExit(response_chan)).is_ok() {
|
||||||
|
debug!("shutting down layout for page {:?}", id);
|
||||||
|
response_port.recv().unwrap();
|
||||||
|
chan.send(layout_interface::Msg::ExitNow(exit_type)).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
let has_pending_loads = self.incomplete_loads.borrow().len() > 0;
|
||||||
|
let has_root_page = self.page.borrow().is_some();
|
||||||
|
|
||||||
|
// Exit if no pending loads and no root page
|
||||||
|
return !has_pending_loads && !has_root_page;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If root is being exited, shut down all pages
|
// If root is being exited, shut down all pages
|
||||||
|
|
|
@ -18,7 +18,7 @@ pub fn spawn_named<F>(name: String, f: F)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Arrange to send a particular message to a channel if the task fails.
|
/// Arrange to send a particular message to a channel if the task fails.
|
||||||
pub fn spawn_named_with_send_on_failure<F, T>(name: &'static str,
|
pub fn spawn_named_with_send_on_failure<F, T>(name: String,
|
||||||
state: task_state::TaskState,
|
state: task_state::TaskState,
|
||||||
f: F,
|
f: F,
|
||||||
msg: T,
|
msg: T,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue