Stall PaintTask exit until it can release all buffers

It is possible for a PaintTask to start exiting soon after sending new
buffers to the compositor. In that case, the compositor should return
the now unnecessary buffers to the PaintTask so that it can properly
free them.

To accomplish this, the compositor now keeps a hash map of paint task
channels per pipeline id. When a PaintTask exists, the constellation
informs the compositor that it can forget about it. Additionally, the
PaintTask should not wait for any buffers when the engine is doing a
complete shutdown. In that case, the compositor is already halted and
has simply let all buffers leak. We pipe through the shutdown type when
destroying the pipeline to make this decision.

Fixes #2641.
This commit is contained in:
Martin Robinson 2015-01-02 09:34:52 -08:00
parent 141b5d038f
commit c0b397322f
13 changed files with 180 additions and 103 deletions

View file

@ -23,8 +23,8 @@ use layers;
use native::task::NativeTaskBuilder;
use servo_msg::compositor_msg::{Epoch, IdlePaintState, LayerId};
use servo_msg::compositor_msg::{LayerMetadata, PaintListener, PaintingPaintState, ScrollPolicy};
use servo_msg::constellation_msg::{ConstellationChan, Failure, FailureMsg, PipelineId};
use servo_msg::constellation_msg::{PainterReadyMsg};
use servo_msg::constellation_msg::{ConstellationChan, Failure, FailureMsg, PipelineExitType};
use servo_msg::constellation_msg::{PipelineId, PainterReadyMsg};
use servo_msg::platform::surface::NativeSurfaceAzureMethods;
use servo_util::geometry::{Au, ZERO_POINT};
use servo_util::opts;
@ -73,7 +73,7 @@ pub enum Msg {
UnusedBufferMsg(Vec<Box<LayerBuffer>>),
PaintPermissionGranted,
PaintPermissionRevoked,
ExitMsg(Option<Sender<()>>),
ExitMsg(Option<Sender<()>>, PipelineExitType),
}
#[deriving(Clone)]
@ -122,6 +122,10 @@ pub struct PaintTask<C> {
/// Communication handles to each of the worker threads.
worker_threads: Vec<WorkerThreadProxy>,
/// Tracks the number of buffers that the compositor currently owns. The
/// PaintTask waits to exit until all buffers are returned.
used_buffer_count: uint,
}
// If we implement this as a function, we get borrowck errors from borrowing
@ -201,6 +205,7 @@ impl<C> PaintTask<C> where C: PaintListener + Send {
epoch: Epoch(0),
buffer_map: BufferMap::new(10000000),
worker_threads: worker_threads,
used_buffer_count: 0,
};
paint_task.start();
@ -223,8 +228,10 @@ impl<C> PaintTask<C> where C: PaintListener + Send {
}
fn start(&mut self) {
debug!("paint_task: beginning painting loop");
debug!("PaintTask: beginning painting loop");
let mut exit_response_channel : Option<Sender<()>> = None;
let mut waiting_for_compositor_buffers_to_exit = false;
loop {
match self.port.recv() {
Msg::PaintInitMsg(stacking_context) => {
@ -232,7 +239,7 @@ impl<C> PaintTask<C> where C: PaintListener + Send {
self.root_stacking_context = Some(stacking_context.clone());
if !self.paint_permission {
debug!("paint_task: paint ready msg");
debug!("PaintTask: paint ready msg");
let ConstellationChan(ref mut c) = self.constellation_chan;
c.send(PainterReadyMsg(self.id));
continue;
@ -245,7 +252,7 @@ impl<C> PaintTask<C> where C: PaintListener + Send {
}
Msg::PaintMsg(requests) => {
if !self.paint_permission {
debug!("paint_task: paint ready msg");
debug!("PaintTask: paint ready msg");
let ConstellationChan(ref mut c) = self.constellation_chan;
c.send(PainterReadyMsg(self.id));
self.compositor.paint_msg_discarded();
@ -265,13 +272,27 @@ impl<C> PaintTask<C> where C: PaintListener + Send {
self.compositor.set_paint_state(self.id, IdlePaintState);
debug!("paint_task: returning surfaces");
for reply in replies.iter() {
let &(_, ref buffer_set) = reply;
self.used_buffer_count += (*buffer_set).buffers.len();
}
debug!("PaintTask: returning surfaces");
self.compositor.paint(self.id, self.epoch, replies);
}
Msg::UnusedBufferMsg(unused_buffers) => {
debug!("PaintTask: Received {} unused buffers", unused_buffers.len());
self.used_buffer_count -= unused_buffers.len();
for buffer in unused_buffers.into_iter().rev() {
self.buffer_map.insert(native_graphics_context!(self), buffer);
}
if waiting_for_compositor_buffers_to_exit && self.used_buffer_count == 0 {
debug!("PaintTask: Received all loaned buffers, exiting.");
exit_response_channel.map(|channel| channel.send(()));
break;
}
}
Msg::PaintPermissionGranted => {
self.paint_permission = true;
@ -290,10 +311,24 @@ impl<C> PaintTask<C> where C: PaintListener + Send {
Msg::PaintPermissionRevoked => {
self.paint_permission = false;
}
Msg::ExitMsg(response_ch) => {
debug!("paint_task: exitmsg response send");
response_ch.map(|ch| ch.send(()));
break;
Msg::ExitMsg(response_channel, exit_type) => {
let should_wait_for_compositor_buffers = match exit_type {
PipelineExitType::Complete => false,
PipelineExitType::PipelineOnly => self.used_buffer_count != 0
};
if !should_wait_for_compositor_buffers {
debug!("PaintTask: Exiting without waiting for compositor buffers.");
response_channel.map(|channel| channel.send(()));
break;
}
// If we own buffers in the compositor and we are not exiting completely, wait
// for the compositor to return buffers, so that we can release them properly.
// When doing a complete exit, the compositor lets all buffers leak.
println!("PaintTask: Saw ExitMsg, {} buffers in use", self.used_buffer_count);
waiting_for_compositor_buffers_to_exit = true;
exit_response_channel = response_channel;
}
}
}