Auto merge of #15189 - jdm:microtasks, r=nox

Implement microtask checkpoints

This generalizes the work previously done for Promise job callbacks. There is now a microtask queue that correctly processes all queued microtasks after each turn of the event loop, as well as after a scripted callback finishes executing, and after a classic script executes.

---
- [X] `./mach build -d` does not report any errors
- [X] `./mach test-tidy` does not report any errors
- [X] These changes fix #4283
- [X] There are tests for these changes

<!-- Reviewable:start -->
---
This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/15189)
<!-- Reviewable:end -->
This commit is contained in:
bors-servo 2017-02-03 07:53:17 -08:00 committed by GitHub
commit cbcafd18f4
14 changed files with 340 additions and 307 deletions

View file

@ -11,6 +11,7 @@ use js::jsapi::JSTracer;
use js::jsapi::UnhideScriptedCaller;
use js::rust::Runtime;
use std::cell::RefCell;
use std::thread;
thread_local!(static STACK: RefCell<Vec<StackEntry>> = RefCell::new(Vec::new()));
@ -36,7 +37,7 @@ pub unsafe fn trace(tracer: *mut JSTracer) {
/// RAII struct that pushes and pops entries from the script settings stack.
pub struct AutoEntryScript {
global: usize,
global: Root<GlobalScope>,
}
impl AutoEntryScript {
@ -50,7 +51,7 @@ impl AutoEntryScript {
kind: StackEntryKind::Entry,
});
AutoEntryScript {
global: global as *const _ as usize,
global: Root::from_ref(global),
}
})
}
@ -62,12 +63,17 @@ impl Drop for AutoEntryScript {
STACK.with(|stack| {
let mut stack = stack.borrow_mut();
let entry = stack.pop().unwrap();
assert_eq!(&*entry.global as *const GlobalScope as usize,
self.global,
assert_eq!(&*entry.global as *const GlobalScope,
&*self.global as *const GlobalScope,
"Dropped AutoEntryScript out of order.");
assert_eq!(entry.kind, StackEntryKind::Entry);
trace!("Clean up after running script with {:p}", &*entry.global);
})
});
// Step 5
if !thread::panicking() && incumbent_global().is_none() {
self.global.perform_a_microtask_checkpoint();
}
}
}

View file

@ -230,17 +230,23 @@ impl DedicatedWorkerGlobalScope {
}
{
let _ar = AutoWorkerReset::new(&global, worker);
let _ar = AutoWorkerReset::new(&global, worker.clone());
scope.execute_script(DOMString::from(source));
}
let reporter_name = format!("dedicated-worker-reporter-{}", random::<u64>());
scope.upcast::<GlobalScope>().mem_profiler_chan().run_with_memory_reporting(|| {
// https://html.spec.whatwg.org/multipage/#event-loop-processing-model
// Step 1
while let Ok(event) = global.receive_event() {
if scope.is_closing() {
break;
}
// Step 3
global.handle_event(event);
// Step 6
let _ar = AutoWorkerReset::new(&global, worker.clone());
global.upcast::<WorkerGlobalScope>().perform_a_microtask_checkpoint();
}
}, reporter_name, parent_sender, CommonScriptMsg::CollectReports);
}).expect("Thread spawning failed");

View file

@ -29,10 +29,11 @@ use js::jsapi::{JS_GetObjectRuntime, MutableHandleValue};
use js::panic::maybe_resume_unwind;
use js::rust::{CompileOptionsWrapper, Runtime, get_object_class};
use libc;
use microtask::Microtask;
use msg::constellation_msg::PipelineId;
use net_traits::{CoreResourceThread, ResourceThreads, IpcSend};
use profile_traits::{mem, time};
use script_runtime::{CommonScriptMsg, EnqueuedPromiseCallback, ScriptChan, ScriptPort};
use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort};
use script_thread::{MainThreadScriptChan, RunnableWrapper, ScriptThread};
use script_traits::{MsDuration, ScriptMsg as ConstellationMsg, TimerEvent};
use script_traits::{TimerEventId, TimerEventRequest, TimerSource};
@ -448,25 +449,24 @@ impl GlobalScope {
unreachable!();
}
/// Start the process of executing the pending promise callbacks. They will be invoked
/// in FIFO order, synchronously, at some point in the future.
pub fn flush_promise_jobs(&self) {
/// Perform a microtask checkpoint.
pub fn perform_a_microtask_checkpoint(&self) {
if self.is::<Window>() {
return ScriptThread::flush_promise_jobs(self);
return ScriptThread::invoke_perform_a_microtask_checkpoint();
}
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
return worker.flush_promise_jobs();
return worker.perform_a_microtask_checkpoint();
}
unreachable!();
}
/// Enqueue a promise callback for subsequent execution.
pub fn enqueue_promise_job(&self, job: EnqueuedPromiseCallback) {
/// Enqueue a microtask for subsequent execution.
pub fn enqueue_microtask(&self, job: Microtask) {
if self.is::<Window>() {
return ScriptThread::enqueue_promise_job(job, self);
return ScriptThread::enqueue_microtask(job);
}
if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
return worker.enqueue_promise_job(job);
return worker.enqueue_microtask(job);
}
unreachable!();
}

View file

@ -486,11 +486,7 @@ impl HTMLScriptElement {
document.set_current_script(Some(self));
// Step 5.a.2.
let window = window_from_node(self);
let line_number = if script.external { 1 } else { self.line_number as u32 };
rooted!(in(window.get_cx()) let mut rval = UndefinedValue());
window.upcast::<GlobalScope>().evaluate_script_on_global_with_result(
&script.text, script.url.as_str(), rval.handle_mut(), line_number);
self.run_a_classic_script(&script);
// Step 6.
document.set_current_script(old_script.r());
@ -506,6 +502,24 @@ impl HTMLScriptElement {
}
}
// https://html.spec.whatwg.org/multipage/#run-a-classic-script
pub fn run_a_classic_script(&self, script: &ClassicScript) {
// TODO use a settings object rather than this element's document/window
// Step 2
let document = document_from_node(self);
if !document.is_fully_active() || !document.is_scripting_enabled() {
return;
}
// Steps 4-10
let window = window_from_node(self);
let line_number = if script.external { 1 } else { self.line_number as u32 };
rooted!(in(window.get_cx()) let mut rval = UndefinedValue());
let global = window.upcast::<GlobalScope>();
global.evaluate_script_on_global_with_result(
&script.text, script.url.as_str(), rval.handle_mut(), line_number);
}
pub fn queue_error_event(&self) {
let window = window_from_node(self);
window.dom_manipulation_task_source().queue_simple_event(self.upcast(), atom!("error"), &window);

View file

@ -210,10 +210,15 @@ impl ServiceWorkerGlobalScope {
global.dispatch_activate();
let reporter_name = format!("service-worker-reporter-{}", random::<u64>());
scope.upcast::<GlobalScope>().mem_profiler_chan().run_with_memory_reporting(|| {
// https://html.spec.whatwg.org/multipage/#event-loop-processing-model
// Step 1
while let Ok(event) = global.receive_event() {
// Step 3
if !global.handle_event(event) {
break;
}
// Step 6
global.upcast::<WorkerGlobalScope>().perform_a_microtask_checkpoint();
}
}, reporter_name, scope.script_chan(), CommonScriptMsg::CollectReports);
}).expect("Thread spawning failed");

View file

@ -11,7 +11,6 @@ use dom::bindings::codegen::UnionTypes::RequestOrUSVString;
use dom::bindings::error::{Error, ErrorResult, Fallible, report_pending_exception};
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{MutNullableJS, Root};
use dom::bindings::refcounted::Trusted;
use dom::bindings::reflector::DomObject;
use dom::bindings::settings_stack::AutoEntryScript;
use dom::bindings::str::DOMString;
@ -29,11 +28,11 @@ use js::jsapi::{HandleValue, JSAutoCompartment, JSContext, JSRuntime};
use js::jsval::UndefinedValue;
use js::panic::maybe_resume_unwind;
use js::rust::Runtime;
use microtask::{MicrotaskQueue, Microtask};
use net_traits::{IpcSend, load_whole_resource};
use net_traits::request::{CredentialsMode, Destination, RequestInit as NetRequestInit, Type as RequestType};
use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort};
use script_runtime::{ScriptThreadEventCategory, PromiseJobQueue, EnqueuedPromiseCallback};
use script_thread::{Runnable, RunnableWrapper};
use script_thread::RunnableWrapper;
use script_traits::{TimerEvent, TimerEventId};
use script_traits::WorkerGlobalScopeInit;
use servo_url::ServoUrl;
@ -87,7 +86,7 @@ pub struct WorkerGlobalScope {
/// `IpcSender` doesn't exist
from_devtools_receiver: Receiver<DevtoolScriptControlMsg>,
promise_job_queue: PromiseJobQueue,
microtask_queue: MicrotaskQueue,
}
impl WorkerGlobalScope {
@ -117,7 +116,7 @@ impl WorkerGlobalScope {
navigator: Default::default(),
from_devtools_sender: init.from_devtools_sender,
from_devtools_receiver: from_devtools_receiver,
promise_job_queue: PromiseJobQueue::new(),
microtask_queue: MicrotaskQueue::default(),
}
}
@ -159,20 +158,12 @@ impl WorkerGlobalScope {
}
}
pub fn enqueue_promise_job(&self, job: EnqueuedPromiseCallback) {
self.promise_job_queue.enqueue(job, self.upcast());
pub fn enqueue_microtask(&self, job: Microtask) {
self.microtask_queue.enqueue(job);
}
pub fn flush_promise_jobs(&self) {
self.script_chan().send(CommonScriptMsg::RunnableMsg(
ScriptThreadEventCategory::WorkerEvent,
box FlushPromiseJobs {
global: Trusted::new(self),
})).unwrap();
}
fn do_flush_promise_jobs(&self) {
self.promise_job_queue.flush_promise_jobs(|id| {
pub fn perform_a_microtask_checkpoint(&self) {
self.microtask_queue.checkpoint(|id| {
let global = self.upcast::<GlobalScope>();
assert_eq!(global.pipeline_id(), id);
Some(Root::from_ref(global))
@ -393,6 +384,8 @@ impl WorkerGlobalScope {
} else {
panic!("need to implement a sender for SharedWorker")
}
//XXXjdm should we do a microtask checkpoint here?
}
pub fn handle_fire_timer(&self, timer_id: TimerEventId) {
@ -405,14 +398,3 @@ impl WorkerGlobalScope {
}
}
}
struct FlushPromiseJobs {
global: Trusted<WorkerGlobalScope>,
}
impl Runnable for FlushPromiseJobs {
fn handler(self: Box<FlushPromiseJobs>) {
let global = self.global.root();
global.do_flush_promise_jobs();
}
}