Paint worklets: Implement timeout for worklet painter threads

When a paint worklet thread takes too long, we would like to move on,
since we have a ~16ms budget for rendering @60fps. At the moment, there
is no provision in the paintworklet spec to signal such timeouts to the
developer. ajeffrey opened an [issue][1] for this, but it got punted to
v2 of the spec. Hence we are silently timing out unresponsive paint
scripts.

The timeout value is chosen to be 10ms by default, and can be overridden
by setting the `dom.worklet.timeout_ms` pref.

In the absence of such a timeout, the reftest in this commit would fail
by timing out the testrunner itself, since the paint script never
returns. From my discussions with ajeffrey, this should do until we spec
out a way to signal timeouts to the script developer.

Since we did not have a better way to trigger a timeout than a busy
waiting loop (which would hog one core of the test machine until the
timeout was reached), we decided to implement a test only blocking
sleep, available to the PaintWorkletGlobalScope. Since
`dom.worklet.enabled` enables worklets in general, we also decided to
have another pref `dom.worklet.blockingsleep.enabled`, which, in
addition to `dom.worklet.enabled`, would be required for the blocking
sleep to be available.

This fixes #17370.

[1]: https://github.com/w3c/css-houdini-drafts/issues/507
This commit is contained in:
Yati Sagade 2017-11-25 20:28:13 +01:00
parent 2eb1512c22
commit aa48a2c2e3
9 changed files with 189 additions and 70 deletions

View file

@ -45,9 +45,10 @@ use js::rust::Runtime;
use msg::constellation_msg::PipelineId;
use net_traits::image::base::PixelFormat;
use net_traits::image_cache::ImageCache;
use script_traits::DrawAPaintImageResult;
use script_traits::{DrawAPaintImageResult, PaintWorkletError};
use script_traits::Painter;
use servo_atoms::Atom;
use servo_config::prefs::PREFS;
use servo_url::ServoUrl;
use std::cell::Cell;
use std::collections::HashMap;
@ -58,6 +59,8 @@ use std::sync::Arc;
use std::sync::Mutex;
use std::sync::mpsc;
use std::sync::mpsc::Sender;
use std::thread;
use std::time::Duration;
use style_traits::CSSPixel;
use style_traits::DevicePixel;
use style_traits::SpeculativePainter;
@ -343,7 +346,7 @@ impl PaintWorkletGlobalScope {
device_pixel_ratio: TypedScale<f32, CSSPixel, DevicePixel>,
properties: Vec<(Atom, String)>,
arguments: Vec<String>)
-> DrawAPaintImageResult {
-> Result<DrawAPaintImageResult, PaintWorkletError> {
let name = self.name.clone();
let (sender, receiver) = mpsc::channel();
let task = PaintWorkletTask::DrawAPaintImage(name,
@ -354,7 +357,14 @@ impl PaintWorkletGlobalScope {
sender);
self.executor.lock().expect("Locking a painter.")
.schedule_a_worklet_task(WorkletTask::Paint(task));
receiver.recv().expect("Worklet thread died?")
let timeout = PREFS.get("dom.worklet.timeout_ms")
.as_u64()
.unwrap_or(10u64);
let timeout_duration = Duration::from_millis(timeout);
receiver.recv_timeout(timeout_duration)
.map_err(|e| PaintWorkletError::from(e))
}
}
Box::new(WorkletPainter {
@ -364,6 +374,60 @@ impl PaintWorkletGlobalScope {
}
}
/// Tasks which can be peformed by a paint worklet
pub enum PaintWorkletTask {
DrawAPaintImage(Atom,
TypedSize2D<f32, CSSPixel>,
TypedScale<f32, CSSPixel, DevicePixel>,
Vec<(Atom, String)>,
Vec<String>,
Sender<DrawAPaintImageResult>),
SpeculativelyDrawAPaintImage(Atom,
Vec<(Atom, String)>,
Vec<String>),
}
/// A paint definition
/// <https://drafts.css-houdini.org/css-paint-api/#paint-definition>
/// This type is dangerous, because it contains uboxed `Heap<JSVal>` values,
/// which can't be moved.
#[derive(JSTraceable, MallocSizeOf)]
#[must_root]
struct PaintDefinition {
class_constructor: Heap<JSVal>,
paint_function: Heap<JSVal>,
constructor_valid_flag: Cell<bool>,
context_alpha_flag: bool,
// TODO: this should be a list of CSS syntaxes.
input_arguments_len: usize,
// TODO: the spec calls for fresh rendering contexts each time a paint image is drawn,
// but to avoid having the primary worklet thread create a new renering context,
// we recycle them.
context: Dom<PaintRenderingContext2D>,
}
impl PaintDefinition {
fn new(class_constructor: HandleValue,
paint_function: HandleValue,
alpha: bool,
input_arguments_len: usize,
context: &PaintRenderingContext2D)
-> Box<PaintDefinition>
{
let result = Box::new(PaintDefinition {
class_constructor: Heap::default(),
paint_function: Heap::default(),
constructor_valid_flag: Cell::new(true),
context_alpha_flag: alpha,
input_arguments_len: input_arguments_len,
context: Dom::from_ref(context),
});
result.class_constructor.set(class_constructor.get());
result.paint_function.set(paint_function.get());
result
}
}
impl PaintWorkletGlobalScopeMethods for PaintWorkletGlobalScope {
#[allow(unsafe_code)]
#[allow(unrooted_must_root)]
@ -445,58 +509,14 @@ impl PaintWorkletGlobalScopeMethods for PaintWorkletGlobalScope {
Ok(())
}
}
/// Tasks which can be peformed by a paint worklet
pub enum PaintWorkletTask {
DrawAPaintImage(Atom,
TypedSize2D<f32, CSSPixel>,
TypedScale<f32, CSSPixel, DevicePixel>,
Vec<(Atom, String)>,
Vec<String>,
Sender<DrawAPaintImageResult>),
SpeculativelyDrawAPaintImage(Atom,
Vec<(Atom, String)>,
Vec<String>),
}
/// A paint definition
/// <https://drafts.css-houdini.org/css-paint-api/#paint-definition>
/// This type is dangerous, because it contains uboxed `Heap<JSVal>` values,
/// which can't be moved.
#[derive(JSTraceable, MallocSizeOf)]
#[must_root]
struct PaintDefinition {
class_constructor: Heap<JSVal>,
paint_function: Heap<JSVal>,
constructor_valid_flag: Cell<bool>,
context_alpha_flag: bool,
// TODO: this should be a list of CSS syntaxes.
input_arguments_len: usize,
// TODO: the spec calls for fresh rendering contexts each time a paint image is drawn,
// but to avoid having the primary worklet thread create a new renering context,
// we recycle them.
context: Dom<PaintRenderingContext2D>,
}
impl PaintDefinition {
fn new(class_constructor: HandleValue,
paint_function: HandleValue,
alpha: bool,
input_arguments_len: usize,
context: &PaintRenderingContext2D)
-> Box<PaintDefinition>
{
let result = Box::new(PaintDefinition {
class_constructor: Heap::default(),
paint_function: Heap::default(),
constructor_valid_flag: Cell::new(true),
context_alpha_flag: alpha,
input_arguments_len: input_arguments_len,
context: Dom::from_ref(context),
});
result.class_constructor.set(class_constructor.get());
result.paint_function.set(paint_function.get());
result
/// This is a blocking sleep function available in the paint worklet
/// global scope behind the dom.worklet.enabled +
/// dom.worklet.blockingsleep.enabled prefs. It is to be used only for
/// testing, e.g., timeouts, where otherwise one would need busy waiting
/// to make sure a certain timeout is triggered.
/// check-tidy: no specs after this line
fn Sleep(&self, ms: u64) {
thread::sleep(Duration::from_millis(ms));
}
}