Files
sdk/compiler/lib/implementation/isolate.js
T

547 lines
16 KiB
JavaScript

// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
var isolate$current = null;
var isolate$rootIsolate = null; // Will only be set in the main worker.
var isolate$inits = [];
var isolate$globalThis = this;
// These declarations are needed to avoid errors from the Closure Compiler
// optimizer. They are defined in client/dom/generated/dart_dom_wrapping.js.
var __dom_wrap;
var __dom_unwrap;
var isolate$inWorker =
(typeof isolate$globalThis['importScripts']) != "undefined";
var isolate$supportsWorkers =
isolate$inWorker || ((typeof isolate$globalThis['Worker']) != 'undefined');
var isolate$MAIN_WORKER_ID = 0;
// Non-main workers will update the id variable.
var isolate$thisWorkerId = isolate$MAIN_WORKER_ID;
// Whether to use web workers when implementing isolates.
var isolate$useWorkers = isolate$supportsWorkers;
// Uncomment this to not use web workers even if they're available.
// isolate$useWorkers = false;
// Whether to use the web-worker JSON-based message serialization protocol,
// even if not using web workers.
var isolate$useWorkerSerializationProtocol = false;
// Uncomment this to always use the web-worker JSON-based message
// serialization protocol, e.g. for testing purposes.
// isolate$useWorkerSerializationProtocol = true;
// ------- SendPort -------
function isolate$sendMessage(workerId, isolateId, receivePortId,
message, replyTo) {
// Both, the message and the replyTo are already serialized.
if (workerId == isolate$thisWorkerId) {
var isolate = isolate$isolateRegistry.get(isolateId);
if (!isolate) return; // Isolate has been closed.
var receivePort = isolate.getReceivePortForId(receivePortId);
if (!receivePort) return; // ReceivePort has been closed.
isolate$receiveMessage(receivePort, isolate, message, replyTo);
} else {
var worker;
if (isolate$inWorker) {
worker = isolate$mainWorker;
} else {
worker = isolate$workerRegistry.get(workerId);
}
worker.postMessage({ command: 'message',
workerId: workerId,
isolateId: isolateId,
portId: receivePortId,
msg: message,
replyTo: replyTo });
}
}
function isolate$receiveMessage(port, isolate,
serializedMessage, serializedReplyTo) {
isolate$IsolateEvent.enqueue(isolate, function() {
var message = isolate$deserializeMessage(serializedMessage);
var replyTo = isolate$deserializeMessage(serializedReplyTo);
native_ReceivePortImpl__invokeCallback(port, message, replyTo);
native__IsolateJsUtil__promiseQueueProcess();
});
}
// ------- ReceivePort -------
function native_ReceivePortImpl__register(id) {
isolate$current.registerReceivePort(id, this);
}
function native_ReceivePortImpl__unregister(id) {
isolate$current.unregisterReceivePort(id);
}
function native_ReceivePortImpl__currentWorkerId() {
return isolate$thisWorkerId;
}
function native_ReceivePortImpl__currentIsolateId() {
return isolate$current.id;
}
// -------- Registry ---------
function isolate$Registry() {
this.map = {};
this.count = 0;
}
isolate$Registry.prototype.register = function(id, val) {
if (this.map[id]) {
throw Error("Registry: Elements must be registered only once.");
}
this.map[id] = val;
this.count++;
};
isolate$Registry.prototype.unregister = function(id) {
if (id in this.map) {
delete this.map[id];
this.count--;
}
};
isolate$Registry.prototype.get = function(id) {
return this.map[id];
};
isolate$Registry.prototype.isEmpty = function() {
return this.count === 0;
};
// ------- Worker registry -------
// Only used in the main worker.
var isolate$workerRegistry = new isolate$Registry();
// ------- Isolate registry -------
// Isolates must be registered if, and only if, receive ports are alive.
// Normally no open receive-ports means that the isolate is dead, but
// DOM callbacks could resurrect it.
var isolate$isolateRegistry = new isolate$Registry();
// ------- Debugging log function -------
function isolate$log(msg) {
return;
if (isolate$inWorker) {
isolate$mainWorker.postMessage({ command: 'log', msg: msg });
} else {
try {
isolate$globalThis.console.log(msg);
} catch(e) {
throw String(e.stack);
}
}
}
function isolate$initializeWorker(workerId) {
isolate$thisWorkerId = workerId;
}
var isolate$workerPrint = false;
if (isolate$inWorker) {
isolate$workerPrint = function(msg){
isolate$mainWorker.postMessage({ command: 'print', msg: msg });
}
}
// ------- Message handler -------
function isolate$processWorkerMessage(sender, e) {
var msg = e.data;
switch (msg.command) {
case 'start':
isolate$log("starting worker: " + msg.id + " " + msg.factoryName);
isolate$initializeWorker(msg.id);
var runnerObject = (isolate$globalThis[msg.factoryName])();
var serializedReplyTo = msg.replyTo;
isolate$IsolateEvent.enqueue(new isolate$Isolate(), function() {
var replyTo = isolate$deserializeMessage(serializedReplyTo);
native__IsolateJsUtil__startIsolate(runnerObject, replyTo);
});
isolate$runEventLoop();
break;
case 'spawn-worker':
isolate$spawnWorker(msg.factoryName, msg.replyPort);
break;
case 'message':
isolate$sendMessage(msg.workerId, msg.isolateId, msg.portId,
msg.msg, msg.replyTo);
isolate$runEventLoop();
break;
case 'close':
isolate$log("Closing Worker");
isolate$workerRegistry.unregister(sender.id);
sender.terminate();
isolate$runEventLoop();
break;
case 'log':
isolate$log(msg.msg);
break;
case 'print':
native__IsolateJsUtil__print(msg.msg);
break;
case 'error':
throw msg.msg;
break;
}
}
if (isolate$supportsWorkers) {
isolate$globalThis.onmessage = function(e) {
isolate$processWorkerMessage(isolate$mainWorker, e);
};
}
// ------- Default Worker -------
function isolate$MainWorker() {
this.id = isolate$MAIN_WORKER_ID;
}
var isolate$mainWorker = new isolate$MainWorker();
isolate$mainWorker.postMessage = function(msg) {
isolate$globalThis.postMessage(msg);
};
var isolate$nextFreeIsolateId = 1;
// Native methods for isolate functionality.
/**
* @constructor
*/
function isolate$Isolate() {
// The isolate ids is only unique within the current worker and frame.
this.id = isolate$nextFreeIsolateId++;
// When storing information on DOM nodes the isolate's id is not enough.
// We instead use a token with a hashcode. The token can be stored in the
// DOM node (since it is small and will not keep much data alive).
this.token = new Object();
this.token.hashCode = (Math.random() * 0xFFFFFFF) >>> 0;
this.receivePorts = new isolate$Registry();
this.run(function() {
// The Dart-to-JavaScript compiler builds a list of functions that
// need to run for each isolate to setup the state of static
// variables. Run through the list and execute each function.
for (var i = 0, len = isolate$inits.length; i < len; i++) {
isolate$inits[i]();
}
});
}
// It is allowed to stack 'run' calls. The stacked isolates can be different.
// That is Isolate1.run could call the DOM which then calls Isolate2.run.
isolate$Isolate.prototype.run = function(code) {
var old = isolate$current;
isolate$current = this;
var result = null;
try {
result = code();
} finally {
isolate$current = old;
}
return result;
};
isolate$Isolate.prototype.registerReceivePort = function(id, port) {
if (this.receivePorts.isEmpty()) {
isolate$isolateRegistry.register(this.id, this);
}
this.receivePorts.register(id, port);
};
isolate$Isolate.prototype.unregisterReceivePort = function(id) {
this.receivePorts.unregister(id);
if (this.receivePorts.isEmpty()) {
isolate$isolateRegistry.unregister(this.id);
}
};
isolate$Isolate.prototype.getReceivePortForId = function(id) {
return this.receivePorts.get(id);
};
var isolate$events = [];
/**
* @constructor
*/
function isolate$IsolateEvent(isolate, fn) {
this.isolate = isolate;
this.fn = fn;
}
isolate$IsolateEvent.prototype.process = function() {
this.isolate.run(this.fn);
};
isolate$IsolateEvent.enqueue = function(isolate, fn) {
isolate$events.push(new isolate$IsolateEvent(isolate, fn));
};
isolate$IsolateEvent.dequeue = function() {
if (isolate$events.length == 0) return $Dart$Null;
var result = isolate$events[0];
isolate$events.splice(0, 1);
return result;
};
var isolate$lightCount = 0;
var isolate$heavyCount = 0;
function native_IsolateNatives__spawn(runnable, light, replyPort) {
// TODO(kasperl): Allow mixing heavy and light isolates.
if (light) {
isolate$lightCount++;
if (isolate$heavyCount != 0) {
throw Error("Cannot mix light and heavy isolates.");
}
isolate$useWorkers = false;
} else {
isolate$heavyCount++;
if (isolate$lightCount != 0) {
throw Error("Cannot mix light and heavy isolates.");
}
}
// TODO(floitsch): throw exception if runnable's class doesn't have a
// default constructor.
if (isolate$useWorkers) {
isolate$startWorker(runnable, replyPort);
} else {
isolate$startNonWorker(runnable, replyPort);
}
}
function native_IsolateNatives_bind(fn) {
var isolate = isolate$current;
return function() {
var self = this;
var args = arguments;
isolate.run(function() {
fn.apply(self, args);
});
isolate$runEventLoop();
};
}
function isolate$startNonWorker(runnable, replyTo) {
// Spawn a new isolate and create the receive port in it.
var spawned = new isolate$Isolate();
// Instead of just running the provided runnable, we create a
// new cloned instance of it with a fresh state in the spawned
// isolate. This way, we do not get cross-isolate references
// through the runnable.
var factory = runnable.getIsolateFactory();
isolate$IsolateEvent.enqueue(spawned, function() {
native__IsolateJsUtil__startIsolate(factory(), replyTo);
});
}
// This field is only used by the main worker.
var isolate$nextFreeWorkerId = isolate$thisWorkerId + 1;
var isolate$thisScript = function() {
if (!isolate$supportsWorkers || isolate$inWorker) return null;
// TODO(5334778): Find a cross-platform non-brittle way of getting the
// currently running script.
var scripts = document.getElementsByTagName('script');
// The scripts variable only contains the scripts that have already been
// executed. The last one is the currently running script.
var script = scripts[scripts.length - 1];
var src = script.src;
if (!src) {
// TODO()
src = "FIXME:5407062" + "_" + Math.random().toString();
script.src = src;
}
return src;
}();
function isolate$startWorker(runnable, replyPort) {
var factory = runnable.getIsolateFactory();
var factoryName = factory.name;
var serializedReplyPort = isolate$serializeMessage(replyPort);
if (isolate$inWorker) {
isolate$mainWorker.postMessage({ command: 'spawn-worker',
factoryName: factoryName,
replyPort: serializedReplyPort } );
} else {
isolate$spawnWorker(factoryName, serializedReplyPort);
}
}
function isolate$spawnWorker(factoryName, serializedReplyPort) {
var worker = new Worker(isolate$thisScript);
worker.onmessage = function(e) {
isolate$processWorkerMessage(worker, e);
};
var workerId = isolate$nextFreeWorkerId++;
// We also store the id on the worker itself so that we can unregister it.
worker.id = workerId;
isolate$workerRegistry.register(workerId, worker);
worker.postMessage({ command: 'start',
id: workerId,
replyTo: serializedReplyPort,
factoryName: factoryName });
}
function native_SendPortImpl__sendNow(message, replyTo) {
if (replyTo !== $Dart$Null && !(replyTo instanceof SendPortImpl$Dart)) {
throw "SendPort::send: Illegal replyTo type.";
}
message = isolate$serializeMessage(message);
replyTo = isolate$serializeMessage(replyTo);
var workerId = native_SendPortImpl__getWorkerId(this);
var isolateId = native_SendPortImpl__getIsolateId(this);
var receivePortId = native_SendPortImpl__getReceivePortId(this);
isolate$sendMessage(workerId, isolateId, receivePortId, message, replyTo);
}
function isolate$closeWorkerIfNecessary() {
if (!isolate$isolateRegistry.isEmpty()) return;
isolate$mainWorker.postMessage( { command: 'close' } );
}
function isolate$doOneEventLoopIteration() {
var CONTINUE_LOOP = true;
var STOP_LOOP = false;
var event = isolate$IsolateEvent.dequeue();
if (!event) {
if (isolate$inWorker) {
isolate$closeWorkerIfNecessary();
} else if (!isolate$isolateRegistry.isEmpty() &&
isolate$workerRegistry.isEmpty() &&
!isolate$supportsWorkers && (typeof(window) == 'undefined')) {
// This should only trigger when running on the command-line.
// We don't want this check to execute in the browser where the isolate
// might still be alive due to DOM callbacks.
// throw Error("Program exited with open ReceivePorts.");
}
return STOP_LOOP;
} else {
event.process();
return CONTINUE_LOOP;
}
}
function isolate$doRunEventLoop() {
if (typeof window != 'undefined' && window.setTimeout) {
(function next() {
var continueLoop = isolate$doOneEventLoopIteration();
if (!continueLoop) return;
// TODO(kasperl): It might turn out to be too expensive to call
// setTimeout for every single event. This needs more investigation.
window.setTimeout(next, 0);
})();
} else {
while (true) {
var continueLoop = isolate$doOneEventLoopIteration();
if (!continueLoop) break;
}
}
}
function isolate$runEventLoop() {
if (!isolate$inWorker) {
isolate$doRunEventLoop();
} else {
try {
isolate$doRunEventLoop();
} catch(e) {
// TODO(floitsch): try to send stack-trace to the other side.
isolate$mainWorker.postMessage({ command: 'error', msg: "" + e });
}
}
}
function RunEntry(entry, args) {
// Don't start the main loop again, if we are in a worker.
if (isolate$inWorker) return;
var isolate = new isolate$Isolate();
isolate$rootIsolate = isolate;
isolate$IsolateEvent.enqueue(isolate, function() {
entry(args);
});
isolate$runEventLoop();
// BUG(5151491): This should not be necessary, but because closures
// passed to the DOM as event handlers do not bind their isolate
// automatically we try to give them a reasonable context to live in
// by having a "default" isolate (the first one created).
isolate$current = isolate;
}
// ------- Message Serializing and Deserializing -------
function native_MessageTraverser__clearAttachedInfo(o) {
o['__MessageTraverser__attached_info__'] = (void 0);
}
function native_MessageTraverser__setAttachedInfo(o, info) {
o['__MessageTraverser__attached_info__'] = info;
}
function native_MessageTraverser__getAttachedInfo(o) {
return o['__MessageTraverser__attached_info__'];
}
function native_Serializer__newJsArray(len) {
return new Array(len);
}
function native_Serializer__jsArrayIndexSet(jsArray, index, val) {
jsArray[index] = val;
}
function native_Serializer__dartListToJsArrayNoCopy(list) {
if (list instanceof Array) {
RTT.removeTypeInfo(list);
return list;
} else {
var len = native__ArrayJsUtil__arrayLength(list);
var array = new Array(len);
for (var i = 0; i < len; i++) {
array[i] = INDEX$operator(list, i);
}
return array;
}
}
function native_Deserializer__isJsArray(x) {
return x instanceof Array;
}
function native_Deserializer__jsArrayIndex(x, index) {
return x[index];
}
function native_Deserializer__jsArrayLength(x) {
return x.length;
}
function isolate$serializeMessage(message) {
if (isolate$useWorkers || isolate$useWorkerSerializationProtocol) {
return native__IsolateJsUtil__serializeObject(message);
} else {
return native__IsolateJsUtil__copyObject(message);
}
}
function isolate$deserializeMessage(message) {
if (isolate$useWorkers || isolate$useWorkerSerializationProtocol) {
return native__IsolateJsUtil__deserializeMessage(message);
} else {
// Nothing more to do.
return message;
}
}