servo: Merge #9688 - Implement Worker#terminate() (fixes #4427) (from KiChjang:worker-terminate); r=jdm

Adds support for terminating DOM workers. A closing flag was added to
WorkerGlobalScope per the spec.

Rebased #6652, with some comments addressed.
Fixes #4427.

Source-Repo: https://github.com/servo/servo
Source-Revision: c0aa049b0aaf39df979234dff1ed01ff5d9aa5c5
This commit is contained in:
Joe Wilm 2016-04-08 04:32:18 +05:01
Родитель 27d6b01682
Коммит b4b5f371df
9 изменённых файлов: 216 добавлений и 27 удалений

Просмотреть файл

@ -15,7 +15,7 @@ use dom::bindings::reflector::{Reflectable, Reflector};
use dom::window::{self, ScriptHelpers};
use dom::workerglobalscope::WorkerGlobalScope;
use ipc_channel::ipc::IpcSender;
use js::jsapi::GetGlobalForObjectCrossCompartment;
use js::jsapi::{CurrentGlobalOrNull, GetGlobalForObjectCrossCompartment};
use js::jsapi::{JSContext, JSObject, JS_GetClass, MutableHandleValue};
use js::{JSCLASS_IS_DOMJSCLASS, JSCLASS_IS_GLOBAL};
use msg::constellation_msg::{ConstellationChan, PipelineId};
@ -147,9 +147,8 @@ impl<'a> GlobalRef<'a> {
/// thread.
pub fn script_chan(&self) -> Box<ScriptChan + Send> {
match *self {
GlobalRef::Window(ref window) => {
MainThreadScriptChan(window.main_thread_script_chan().clone()).clone()
}
GlobalRef::Window(ref window) =>
MainThreadScriptChan(window.main_thread_script_chan().clone()).clone(),
GlobalRef::Worker(ref worker) => worker.script_chan(),
}
}
@ -280,11 +279,10 @@ pub fn global_root_from_reflector<T: Reflectable>(reflector: &T) -> GlobalRoot {
global_root_from_object(*reflector.reflector().get_jsobject())
}
/// Returns the global object of the realm that the given JS object was created in.
/// Returns the Rust global object from a JS global object.
#[allow(unrooted_must_root)]
pub fn global_root_from_object(obj: *mut JSObject) -> GlobalRoot {
pub fn global_root_from_global(global: *mut JSObject) -> GlobalRoot {
unsafe {
let global = GetGlobalForObjectCrossCompartment(obj);
let clasp = JS_GetClass(global);
assert!(((*clasp).flags & (JSCLASS_IS_DOMJSCLASS | JSCLASS_IS_GLOBAL)) != 0);
match root_from_object(global) {
@ -300,3 +298,22 @@ pub fn global_root_from_object(obj: *mut JSObject) -> GlobalRoot {
panic!("found DOM global that doesn't unwrap to Window or WorkerGlobalScope")
}
}
/// Returns the global object of the realm that the given JS object was created in.
#[allow(unrooted_must_root)]
pub fn global_root_from_object(obj: *mut JSObject) -> GlobalRoot {
unsafe {
let global = GetGlobalForObjectCrossCompartment(obj);
global_root_from_global(global)
}
}
/// Returns the global object for the given JSContext
#[allow(unrooted_must_root)]
pub fn global_root_from_context(cx: *mut JSContext) -> GlobalRoot {
unsafe {
let global = CurrentGlobalOrNull(cx);
assert!(!global.is_null());
global_root_from_global(global)
}
}

Просмотреть файл

@ -38,6 +38,7 @@ use dom::bindings::js::{JS, Root};
use dom::bindings::refcounted::Trusted;
use dom::bindings::reflector::{Reflectable, Reflector};
use dom::bindings::utils::WindowProxyHandler;
use dom::worker::SharedRt;
use encoding::types::EncodingRef;
use euclid::length::Length as EuclidLength;
use euclid::matrix2d::Matrix2D;
@ -319,6 +320,7 @@ no_jsmanaged_fields!(AttrIdentifier);
no_jsmanaged_fields!(AttrValue);
no_jsmanaged_fields!(ElementSnapshot);
no_jsmanaged_fields!(HttpsState);
no_jsmanaged_fields!(SharedRt);
no_jsmanaged_fields!(TouchpadPressurePhase);
impl JSTraceable for ConstellationChan<ScriptMsg> {

Просмотреть файл

@ -9,20 +9,21 @@ use dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding;
use dom::bindings::codegen::Bindings::DedicatedWorkerGlobalScopeBinding::DedicatedWorkerGlobalScopeMethods;
use dom::bindings::codegen::Bindings::EventHandlerBinding::EventHandlerNonNull;
use dom::bindings::error::ErrorResult;
use dom::bindings::global::GlobalRef;
use dom::bindings::global::{GlobalRef, global_root_from_context};
use dom::bindings::inheritance::Castable;
use dom::bindings::js::{Root, RootCollection};
use dom::bindings::refcounted::LiveDOMReferences;
use dom::bindings::reflector::Reflectable;
use dom::bindings::structuredclone::StructuredCloneData;
use dom::bindings::trace::JSTraceable;
use dom::messageevent::MessageEvent;
use dom::worker::{SimpleWorkerErrorHandler, TrustedWorkerAddress, WorkerMessageHandler};
use dom::worker::{SimpleWorkerErrorHandler, SharedRt, TrustedWorkerAddress, WorkerMessageHandler};
use dom::workerglobalscope::WorkerGlobalScope;
use dom::workerglobalscope::WorkerGlobalScopeInit;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use ipc_channel::router::ROUTER;
use js::jsapi::{HandleValue, JSContext, RootedValue};
use js::jsapi::{JSAutoCompartment, JSAutoRequest};
use js::jsapi::{HandleValue, JS_SetInterruptCallback};
use js::jsapi::{JSAutoCompartment, JSAutoRequest, JSContext, RootedValue};
use js::jsval::UndefinedValue;
use js::rust::Runtime;
use msg::constellation_msg::PipelineId;
@ -33,6 +34,7 @@ use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, StackRootTLS, get_
use script_traits::{TimerEvent, TimerSource};
use std::mem::replace;
use std::sync::mpsc::{Receiver, RecvError, Select, Sender, channel};
use std::sync::{Arc, Mutex};
use url::Url;
use util::str::DOMString;
use util::thread::spawn_named;
@ -206,10 +208,12 @@ impl DedicatedWorkerGlobalScope {
DedicatedWorkerGlobalScopeBinding::Wrap(cx, scope)
}
#[allow(unsafe_code)]
pub fn run_worker_scope(init: WorkerGlobalScopeInit,
worker_url: Url,
id: PipelineId,
from_devtools_receiver: IpcReceiver<DevtoolScriptControlMsg>,
main_thread_rt: Arc<Mutex<Option<SharedRt>>>,
worker: TrustedWorkerAddress,
parent_sender: Box<ScriptChan + Send>,
own_sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>,
@ -237,6 +241,7 @@ impl DedicatedWorkerGlobalScope {
};
let runtime = new_rt_and_cx();
*main_thread_rt.lock().unwrap() = Some(SharedRt::new(&runtime));
let (devtools_mpsc_chan, devtools_mpsc_port) = channel();
ROUTER.route_ipc_receiver_to_mpsc_sender(from_devtools_receiver, devtools_mpsc_chan);
@ -257,6 +262,15 @@ impl DedicatedWorkerGlobalScope {
// registration (#6631), so we instead use a random number and cross our fingers.
let scope = global.upcast::<WorkerGlobalScope>();
unsafe {
// Handle interrupt requests
JS_SetInterruptCallback(scope.runtime(), Some(interrupt_callback));
}
if scope.is_closing() {
return;
}
{
let _ar = AutoWorkerReset::new(global.r(), worker);
scope.execute_script(DOMString::from(source));
@ -265,6 +279,9 @@ impl DedicatedWorkerGlobalScope {
let reporter_name = format!("worker-reporter-{}", random::<u64>());
scope.mem_profiler_chan().run_with_memory_reporting(|| {
while let Ok(event) = global.receive_event() {
if scope.is_closing() {
break;
}
global.handle_event(event);
}
}, reporter_name, parent_sender, CommonScriptMsg::CollectReports);
@ -387,6 +404,19 @@ impl DedicatedWorkerGlobalScope {
}
}
#[allow(unsafe_code)]
unsafe extern "C" fn interrupt_callback(cx: *mut JSContext) -> bool {
let global = global_root_from_context(cx);
let worker = match global.r() {
GlobalRef::Worker(w) => w,
_ => panic!("global for worker is not a worker scope")
};
assert!(worker.is::<DedicatedWorkerGlobalScope>());
// A false response causes the script to terminate
!worker.is_closing()
}
impl DedicatedWorkerGlobalScopeMethods for DedicatedWorkerGlobalScope {
// https://html.spec.whatwg.org/multipage/#dom-dedicatedworkerglobalscope-postmessage
fn PostMessage(&self, cx: *mut JSContext, message: HandleValue) -> ErrorResult {

Просмотреть файл

@ -12,7 +12,7 @@ interface AbstractWorker {
// https://html.spec.whatwg.org/multipage/#worker
[Constructor(DOMString scriptURL)/*, Exposed=Window,Worker*/]
interface Worker : EventTarget {
//void terminate();
void terminate();
[Throws]
void postMessage(any message/*, optional sequence<Transferable> transfer*/);

Просмотреть файл

@ -21,12 +21,15 @@ use dom::eventtarget::EventTarget;
use dom::messageevent::MessageEvent;
use dom::workerglobalscope::WorkerGlobalScopeInit;
use ipc_channel::ipc;
use js::jsapi::{HandleValue, JSContext, RootedValue};
use js::jsapi::{JSAutoCompartment, JSAutoRequest};
use js::jsapi::{HandleValue, JSContext, JSRuntime, RootedValue};
use js::jsapi::{JSAutoCompartment, JSAutoRequest, JS_RequestInterruptCallback};
use js::jsval::UndefinedValue;
use js::rust::Runtime;
use script_runtime::ScriptChan;
use script_thread::Runnable;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::{Sender, channel};
use std::sync::{Arc, Mutex};
use util::str::DOMString;
pub type TrustedWorkerAddress = Trusted<Worker>;
@ -39,21 +42,26 @@ pub struct Worker {
/// Sender to the Receiver associated with the DedicatedWorkerGlobalScope
/// this Worker created.
sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>,
closing: Arc<AtomicBool>,
#[ignore_heap_size_of = "Defined in rust-mozjs"]
runtime: Arc<Mutex<Option<SharedRt>>>
}
impl Worker {
fn new_inherited(sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>)
-> Worker {
fn new_inherited(sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>,
closing: Arc<AtomicBool>) -> Worker {
Worker {
eventtarget: EventTarget::new_inherited(),
sender: sender,
closing: closing,
runtime: Arc::new(Mutex::new(None))
}
}
pub fn new(global: GlobalRef,
sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>)
-> Root<Worker> {
reflect_dom_object(box Worker::new_inherited(sender),
sender: Sender<(TrustedWorkerAddress, WorkerScriptMsg)>,
closing: Arc<AtomicBool>) -> Root<Worker> {
reflect_dom_object(box Worker::new_inherited(sender, closing),
global,
WorkerBinding::Wrap)
}
@ -71,7 +79,8 @@ impl Worker {
let scheduler_chan = global.scheduler_chan();
let (sender, receiver) = channel();
let worker = Worker::new(global, sender.clone());
let closing = Arc::new(AtomicBool::new(false));
let worker = Worker::new(global, sender.clone(), closing.clone());
let worker_ref = Trusted::new(worker.r(), global.script_chan());
let worker_id = global.get_next_worker_id();
@ -100,18 +109,28 @@ impl Worker {
constellation_chan: constellation_chan,
scheduler_chan: scheduler_chan,
worker_id: worker_id,
closing: closing,
};
DedicatedWorkerGlobalScope::run_worker_scope(
init, worker_url, global.pipeline(), devtools_receiver, worker_ref,
init, worker_url, global.pipeline(), devtools_receiver, worker.runtime.clone(), worker_ref,
global.script_chan(), sender, receiver);
Ok(worker)
}
pub fn is_closing(&self) -> bool {
self.closing.load(Ordering::SeqCst)
}
pub fn handle_message(address: TrustedWorkerAddress,
data: StructuredCloneData) {
let worker = address.root();
if worker.is_closing() {
return;
}
let global = worker.r().global();
let target = worker.upcast();
let _ar = JSAutoRequest::new(global.r().get_cx());
@ -129,6 +148,11 @@ impl Worker {
pub fn handle_error_message(address: TrustedWorkerAddress, message: DOMString,
filename: DOMString, lineno: u32, colno: u32) {
let worker = address.root();
if worker.is_closing() {
return;
}
let global = worker.r().global();
let error = RootedValue::new(global.r().get_cx(), UndefinedValue());
let errorevent = ErrorEvent::new(global.r(), atom!("error"),
@ -147,6 +171,19 @@ impl WorkerMethods for Worker {
Ok(())
}
// https://html.spec.whatwg.org/multipage/#terminate-a-worker
fn Terminate(&self) {
// Step 1
if self.closing.swap(true, Ordering::SeqCst) {
return;
}
// Step 4
if let Some(runtime) = *self.runtime.lock().unwrap() {
runtime.request_interrupt();
}
}
// https://html.spec.whatwg.org/multipage/#handler-worker-onmessage
event_handler!(message, GetOnmessage, SetOnmessage);
@ -221,3 +258,26 @@ impl Runnable for WorkerErrorHandler {
Worker::handle_error_message(this.addr, this.msg, this.file_name, this.line_num, this.col_num);
}
}
#[derive(Copy, Clone)]
pub struct SharedRt {
rt: *mut JSRuntime
}
impl SharedRt {
pub fn new(rt: &Runtime) -> SharedRt {
SharedRt {
rt: rt.rt()
}
}
#[allow(unsafe_code)]
pub fn request_interrupt(&self) {
unsafe {
JS_RequestInterruptCallback(self.rt);
}
}
}
#[allow(unsafe_code)]
unsafe impl Send for SharedRt {}

Просмотреть файл

@ -18,7 +18,7 @@ use dom::window::{base64_atob, base64_btoa};
use dom::workerlocation::WorkerLocation;
use dom::workernavigator::WorkerNavigator;
use ipc_channel::ipc::IpcSender;
use js::jsapi::{HandleValue, JSAutoRequest, JSContext};
use js::jsapi::{HandleValue, JSAutoRequest, JSContext, JSRuntime};
use js::rust::Runtime;
use msg::constellation_msg::{ConstellationChan, PipelineId};
use net_traits::{LoadContext, ResourceThread, load_whole_resource};
@ -29,6 +29,8 @@ use script_traits::{MsDuration, TimerEvent, TimerEventId, TimerEventRequest, Tim
use std::cell::Cell;
use std::default::Default;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::mpsc::Receiver;
use timers::{IsInterval, OneshotTimerCallback, OneshotTimerHandle, OneshotTimers, TimerCallback};
use url::Url;
@ -47,6 +49,7 @@ pub struct WorkerGlobalScopeInit {
pub constellation_chan: ConstellationChan<ConstellationMsg>,
pub scheduler_chan: IpcSender<TimerEventRequest>,
pub worker_id: WorkerId,
pub closing: Arc<AtomicBool>,
}
// https://html.spec.whatwg.org/multipage/#the-workerglobalscope-common-interface
@ -55,6 +58,7 @@ pub struct WorkerGlobalScope {
eventtarget: EventTarget,
worker_id: WorkerId,
worker_url: Url,
closing: Arc<AtomicBool>,
#[ignore_heap_size_of = "Defined in js"]
runtime: Runtime,
next_worker_id: Cell<WorkerId>,
@ -104,6 +108,7 @@ impl WorkerGlobalScope {
next_worker_id: Cell::new(WorkerId(0)),
worker_id: init.worker_id,
worker_url: worker_url,
closing: init.closing,
runtime: runtime,
resource_thread: init.resource_thread,
location: Default::default(),
@ -155,10 +160,18 @@ impl WorkerGlobalScope {
self.timers.unschedule_callback(handle);
}
pub fn runtime(&self) -> *mut JSRuntime {
self.runtime.rt()
}
pub fn get_cx(&self) -> *mut JSContext {
self.runtime.cx()
}
pub fn is_closing(&self) -> bool {
self.closing.load(Ordering::SeqCst)
}
pub fn resource_thread(&self) -> &ResourceThread {
&self.resource_thread
}
@ -307,6 +320,9 @@ impl WorkerGlobalScope {
self.reflector().get_jsobject(), String::from(source), self.worker_url.serialize(), 1) {
Ok(_) => (),
Err(_) => {
if self.is_closing() {
println!("evaluate_script failed (terminated)");
} else {
// TODO: An error needs to be dispatched to the parent.
// https://github.com/servo/servo/issues/6422
println!("evaluate_script failed");
@ -315,6 +331,7 @@ impl WorkerGlobalScope {
}
}
}
}
pub fn script_chan(&self) -> Box<ScriptChan + Send> {
let dedicated =

Просмотреть файл

@ -0,0 +1,9 @@
var prev = Date.now()
for (var i=0; true; i++) {
if (i % 100000000 == 0) {
var now = Date.now();
postMessage(now - prev);
prev = now;
}
}

Просмотреть файл

@ -0,0 +1,6 @@
var prev = Date.now()
setInterval(function () {
var now = Date.now();
postMessage(now - prev);
prev = now;
}, 500);

Просмотреть файл

@ -0,0 +1,48 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Worker Test</title>
</head>
<body>
<script>
var workerPath = (function () {
var workerType;
switch (window.location.search.match(/worker=(\w+)/)[1]) {
case 'block':
workerType = 'block';
break;
default:
workerType = 'interval';
break;
}
return './worker_post_' + workerType + '.js';
})();
function startWorker() {
window.w = new Worker(workerPath);
w.onmessage = function(m) {
var p = document.createElement('p');
p.innerHTML = JSON.stringify(m.data);
document.body.appendChild(p);
};
var ps = document.getElementsByTagName('p');
while (ps.length) {
document.body.removeChild(ps[0]);
}
}
function stopWorker() {
if (w) {
w.terminate();
}
window.w = null;
}
</script>
<button onclick="startWorker()">Start Worker</button>
<button onclick="stopWorker()">Stop Worker</button>
</body>
</html>