Index: dart/sdk/lib/_internal/compiler/implementation/lib/isolate_patch.dart |
=================================================================== |
--- dart/sdk/lib/_internal/compiler/implementation/lib/isolate_patch.dart (revision 16250) |
+++ dart/sdk/lib/_internal/compiler/implementation/lib/isolate_patch.dart (working copy) |
@@ -4,45 +4,1295 @@ |
// Patch file for the dart:isolate library. |
+import 'dart:uri'; |
+ |
+/** |
+ * Called by the compiler to support switching |
+ * between isolates when we get a callback from the DOM. |
+ */ |
+void _callInIsolate(_IsolateContext isolate, Function function) { |
+ isolate.eval(function); |
+ _globalState.topEventLoop.run(); |
+} |
+ |
+/** |
+ * Called by the compiler to fetch the current isolate context. |
+ */ |
+_IsolateContext _currentIsolate() => _globalState.currentContext; |
+ |
+/******************************************************** |
+ Inserted from lib/isolate/dart2js/compiler_hooks.dart |
+ ********************************************************/ |
+ |
+/** |
+ * Wrapper that takes the dart entry point and runs it within an isolate. The |
+ * dart2js compiler will inject a call of the form |
+ * [: startRootIsolate(main); :] when it determines that this wrapping |
+ * is needed. For single-isolate applications (e.g. hello world), this |
+ * call is not emitted. |
+ */ |
+void startRootIsolate(entry) { |
+ _globalState = new _Manager(); |
+ |
+ // Don't start the main loop again, if we are in a worker. |
+ if (_globalState.isWorker) return; |
+ final rootContext = new _IsolateContext(); |
+ _globalState.rootContext = rootContext; |
+ _fillStatics(rootContext); |
+ |
+ // BUG(5151491): Setting currentContext 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). |
+ _globalState.currentContext = rootContext; |
+ |
+ rootContext.eval(entry); |
+ _globalState.topEventLoop.run(); |
+} |
+ |
+/******************************************************** |
+ Inserted from lib/isolate/dart2js/isolateimpl.dart |
+ ********************************************************/ |
+ |
+/** |
+ * Concepts used here: |
+ * |
+ * "manager" - A manager contains one or more isolates, schedules their |
+ * execution, and performs other plumbing on their behalf. The isolate |
+ * present at the creation of the manager is designated as its "root isolate". |
+ * A manager may, for example, be implemented on a web Worker. |
+ * |
+ * [_Manager] - State present within a manager (exactly once, as a global). |
+ * |
+ * [_ManagerStub] - A handle held within one manager that allows interaction |
+ * with another manager. A target manager may be addressed by zero or more |
+ * [_ManagerStub]s. |
+ * |
+ */ |
+ |
+/** |
+ * A native object that is shared across isolates. This object is visible to all |
+ * isolates running under the same manager (either UI or background web worker). |
+ * |
+ * This is code that is intended to 'escape' the isolate boundaries in order to |
+ * implement the semantics of isolates in JavaScript. Without this we would have |
+ * been forced to implement more code (including the top-level event loop) in |
+ * JavaScript itself. |
+ */ |
+// TODO(eub, sigmund): move the "manager" to be entirely in JS. |
+// Running any Dart code outside the context of an isolate gives it |
+// the change to break the isolate abstraction. |
+_Manager get _globalState => JS("_Manager", r"$globalState"); |
+set _globalState(_Manager val) { |
+ JS("void", r"$globalState = #", val); |
+} |
+ |
+void _fillStatics(context) { |
+ JS("void", r"$globals = #.isolateStatics", context); |
+ JS("void", r"$static_init()"); |
+} |
+ |
+ReceivePort _lazyPort; |
patch ReceivePort get port { |
- if (lazyPort == null) { |
- lazyPort = new ReceivePort(); |
+ if (_lazyPort == null) { |
+ _lazyPort = new ReceivePort(); |
} |
- return lazyPort; |
+ return _lazyPort; |
} |
patch SendPort spawnFunction(void topLevelFunction()) { |
- return IsolateNatives.spawnFunction(topLevelFunction); |
+ final name = _IsolateNatives._getJSFunctionName(topLevelFunction); |
+ if (name == null) { |
+ throw new UnsupportedError( |
+ "only top-level functions can be spawned."); |
+ } |
+ return _IsolateNatives._spawn(name, null, false); |
} |
patch SendPort spawnUri(String uri) { |
- return IsolateNatives.spawn(null, uri, false); |
+ return _IsolateNatives._spawn(null, uri, false); |
} |
+/** State associated with the current manager. See [globalState]. */ |
+// TODO(sigmund): split in multiple classes: global, thread, main-worker states? |
+class _Manager { |
+ /** Next available isolate id within this [_Manager]. */ |
+ int nextIsolateId = 0; |
+ |
+ /** id assigned to this [_Manager]. */ |
+ int currentManagerId = 0; |
+ |
+ /** |
+ * Next available manager id. Only used by the main manager to assign a unique |
+ * id to each manager created by it. |
+ */ |
+ int nextManagerId = 1; |
+ |
+ /** Context for the currently running [Isolate]. */ |
+ _IsolateContext currentContext = null; |
+ |
+ /** Context for the root [Isolate] that first run in this [_Manager]. */ |
+ _IsolateContext rootContext = null; |
+ |
+ /** The top-level event loop. */ |
+ _EventLoop topEventLoop; |
+ |
+ /** Whether this program is running from the command line. */ |
+ bool fromCommandLine; |
+ |
+ /** Whether this [_Manager] is running as a web worker. */ |
+ bool isWorker; |
+ |
+ /** Whether we support spawning web workers. */ |
+ bool supportsWorkers; |
+ |
+ /** |
+ * Whether to use web workers when implementing isolates. Set to false for |
+ * debugging/testing. |
+ */ |
+ bool get useWorkers => supportsWorkers; |
+ |
+ /** |
+ * Whether to use the web-worker JSON-based message serialization protocol. By |
+ * default this is only used with web workers. For debugging, you can force |
+ * using this protocol by changing this field value to [true]. |
+ */ |
+ bool get needSerialization => useWorkers; |
+ |
+ /** |
+ * Registry of isolates. 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. |
+ */ |
+ Map<int, _IsolateContext> isolates; |
+ |
+ /** Reference to the main [_Manager]. Null in the main [_Manager] itself. */ |
+ _ManagerStub mainManager; |
+ |
+ /** Registry of active [_ManagerStub]s. Only used in the main [_Manager]. */ |
+ Map<int, _ManagerStub> managers; |
+ |
+ _Manager() { |
+ _nativeDetectEnvironment(); |
+ topEventLoop = new _EventLoop(); |
+ isolates = new Map<int, _IsolateContext>(); |
+ managers = new Map<int, _ManagerStub>(); |
+ if (isWorker) { // "if we are not the main manager ourself" is the intent. |
+ mainManager = new _MainManagerStub(); |
+ _nativeInitWorkerMessageHandler(); |
+ } |
+ } |
+ |
+ void _nativeDetectEnvironment() { |
+ isWorker = JS("bool", r"$isWorker"); |
+ supportsWorkers = JS("bool", r"$supportsWorkers"); |
+ fromCommandLine = JS("bool", r"typeof(window) == 'undefined'"); |
+ } |
+ |
+ void _nativeInitWorkerMessageHandler() { |
+ JS("void", r""" |
+$globalThis.onmessage = function (e) { |
+ _IsolateNatives._processWorkerMessage(this.mainManager, e); |
+}"""); |
+ } |
+ /*: TODO: check that _processWorkerMessage is not discarded while treeshaking. |
+ """ { |
+ _IsolateNatives._processWorkerMessage(null, null); |
+ } |
+ */ |
+ |
+ |
+ /** Close the worker running this code if all isolates are done. */ |
+ void maybeCloseWorker() { |
+ if (isolates.isEmpty) { |
+ mainManager.postMessage(_serializeMessage({'command': 'close'})); |
+ } |
+ } |
+} |
+ |
+/** Context information tracked for each isolate. */ |
+class _IsolateContext { |
+ /** Current isolate id. */ |
+ int id; |
+ |
+ /** Registry of receive ports currently active on this isolate. */ |
+ Map<int, ReceivePort> ports; |
+ |
+ /** Holds isolate globals (statics and top-level properties). */ |
+ var isolateStatics; // native object containing all globals of an isolate. |
+ |
+ _IsolateContext() { |
+ id = _globalState.nextIsolateId++; |
+ ports = new Map<int, ReceivePort>(); |
+ initGlobals(); |
+ } |
+ |
+ // these are filled lazily the first time the isolate starts running. |
+ void initGlobals() { JS("void", r'$initGlobals(#)', this); } |
+ |
+ /** |
+ * Run [code] in the context of the isolate represented by [this]. Note this |
+ * is called from JavaScript (see $wrap_call in corejs.dart). |
+ */ |
+ dynamic eval(Function code) { |
+ var old = _globalState.currentContext; |
+ _globalState.currentContext = this; |
+ this._setGlobals(); |
+ var result = null; |
+ try { |
+ result = code(); |
+ } finally { |
+ _globalState.currentContext = old; |
+ if (old != null) old._setGlobals(); |
+ } |
+ return result; |
+ } |
+ |
+ void _setGlobals() { JS("void", r'$setGlobals(#)', this); } |
+ |
+ /** Lookup a port registered for this isolate. */ |
+ ReceivePort lookup(int portId) => ports[portId]; |
+ |
+ /** Register a port on this isolate. */ |
+ void register(int portId, ReceivePort port) { |
+ if (ports.containsKey(portId)) { |
+ throw new Exception("Registry: ports must be registered only once."); |
+ } |
+ ports[portId] = port; |
+ _globalState.isolates[id] = this; // indicate this isolate is active |
+ } |
+ |
+ /** Unregister a port on this isolate. */ |
+ void unregister(int portId) { |
+ ports.remove(portId); |
+ if (ports.isEmpty) { |
+ _globalState.isolates.remove(id); // indicate this isolate is not active |
+ } |
+ } |
+} |
+ |
+/** Represent the event loop on a javascript thread (DOM or worker). */ |
+class _EventLoop { |
+ Queue<_IsolateEvent> events; |
+ |
+ _EventLoop() : events = new Queue<_IsolateEvent>(); |
+ |
+ void enqueue(isolate, fn, msg) { |
+ events.addLast(new _IsolateEvent(isolate, fn, msg)); |
+ } |
+ |
+ _IsolateEvent dequeue() { |
+ if (events.isEmpty) return null; |
+ return events.removeFirst(); |
+ } |
+ |
+ /** Process a single event, if any. */ |
+ bool runIteration() { |
+ final event = dequeue(); |
+ if (event == null) { |
+ if (_globalState.isWorker) { |
+ _globalState.maybeCloseWorker(); |
+ } else if (_globalState.rootContext != null && |
+ _globalState.isolates.containsKey( |
+ _globalState.rootContext.id) && |
+ _globalState.fromCommandLine && |
+ _globalState.rootContext.ports.isEmpty) { |
+ // We want to reach here only on the main [_Manager] and only |
+ // on the command-line. In the browser the isolate might |
+ // still be alive due to DOM callbacks, but the presumption is |
+ // that on the command-line, no future events can be injected |
+ // into the event queue once it's empty. Node has setTimeout |
+ // so this presumption is incorrect there. We think(?) that |
+ // in d8 this assumption is valid. |
+ throw new Exception("Program exited with open ReceivePorts."); |
+ } |
+ return false; |
+ } |
+ event.process(); |
+ return true; |
+ } |
+ |
+ /** |
+ * Runs multiple iterations of the run-loop. If possible, each iteration is |
+ * run asynchronously. |
+ */ |
+ void _runHelper() { |
+ // [_window] is defined in timer_provider.dart. |
+ if (_window != null) { |
+ // Run each iteration from the browser's top event loop. |
+ void next() { |
+ if (!runIteration()) return; |
+ _window.setTimeout(next, 0); |
+ } |
+ next(); |
+ } else { |
+ // Run synchronously until no more iterations are available. |
+ while (runIteration()) {} |
+ } |
+ } |
+ |
+ /** |
+ * Call [_runHelper] but ensure that worker exceptions are propragated. Note |
+ * this is called from JavaScript (see $wrap_call in corejs.dart). |
+ */ |
+ void run() { |
+ if (!_globalState.isWorker) { |
+ _runHelper(); |
+ } else { |
+ try { |
+ _runHelper(); |
+ } catch (e, trace) { |
+ _globalState.mainManager.postMessage(_serializeMessage( |
+ {'command': 'error', 'msg': '$e\n$trace' })); |
+ } |
+ } |
+ } |
+} |
+ |
+/** An event in the top-level event queue. */ |
+class _IsolateEvent { |
+ _IsolateContext isolate; |
+ Function fn; |
+ String message; |
+ |
+ _IsolateEvent(this.isolate, this.fn, this.message); |
+ |
+ void process() { |
+ isolate.eval(fn); |
+ } |
+} |
+ |
+/** An interface for a stub used to interact with a manager. */ |
+abstract class _ManagerStub { |
+ get id; |
+ void set id(int i); |
+ void set onmessage(Function f); |
+ void postMessage(msg); |
+ void terminate(); |
+} |
+ |
+/** A stub for interacting with the main manager. */ |
+class _MainManagerStub implements _ManagerStub { |
+ get id => 0; |
+ void set id(int i) { throw new UnimplementedError(); } |
+ void set onmessage(f) { |
+ throw new Exception("onmessage should not be set on MainManagerStub"); |
+ } |
+ void postMessage(msg) { JS("void", r"$globalThis.postMessage(#)", msg); } |
+ void terminate() {} // Nothing useful to do here. |
+} |
+ |
+/** |
+ * A stub for interacting with a manager built on a web worker. This |
+ * definition uses a 'hidden' type (* prefix on the native name) to |
+ * enforce that the type is defined dynamically only when web workers |
+ * are actually available. |
+ */ |
+class _WorkerStub implements _ManagerStub native "*Worker" { |
+ get id => JS("var", "#.id", this); |
+ void set id(i) { JS("void", "#.id = #", this, i); } |
+ void set onmessage(f) { JS("void", "#.onmessage = #", this, f); } |
+ void postMessage(msg) => JS("void", "#.postMessage(#)", this, msg); |
+ // terminate() is implemented by Worker. |
+ void terminate(); |
+} |
+ |
+const String _SPAWNED_SIGNAL = "spawned"; |
+ |
+class _IsolateNatives { |
+ |
+ /** |
+ * The src url for the script tag that loaded this code. Used to create |
+ * JavaScript workers. |
+ */ |
+ static String get _thisScript => JS("String", r"$thisScriptUrl"); |
+ |
+ /** Starts a new worker with the given URL. */ |
+ static _WorkerStub _newWorker(url) => JS("_WorkerStub", r"new Worker(#)", url); |
+ |
+ /** |
+ * Assume that [e] is a browser message event and extract its message data. |
+ * We don't import the dom explicitly so, when workers are disabled, this |
+ * library can also run on top of nodejs. |
+ */ |
+ //static _getEventData(e) => JS("Object", "#.data", e); |
+ static _getEventData(e) => JS("", "#.data", e); |
+ |
+ /** |
+ * Process messages on a worker, either to control the worker instance or to |
+ * pass messages along to the isolate running in the worker. |
+ */ |
+ static void _processWorkerMessage(sender, e) { |
+ var msg = _deserializeMessage(_getEventData(e)); |
+ switch (msg['command']) { |
+ case 'start': |
+ _globalState.currentManagerId = msg['id']; |
+ Function entryPoint = _getJSFunctionFromName(msg['functionName']); |
+ var replyTo = _deserializeMessage(msg['replyTo']); |
+ _globalState.topEventLoop.enqueue(new _IsolateContext(), function() { |
+ _startIsolate(entryPoint, replyTo); |
+ }, 'worker-start'); |
+ _globalState.topEventLoop.run(); |
+ break; |
+ case 'spawn-worker': |
+ _spawnWorker(msg['functionName'], msg['uri'], msg['replyPort']); |
+ break; |
+ case 'message': |
+ msg['port'].send(msg['msg'], msg['replyTo']); |
+ _globalState.topEventLoop.run(); |
+ break; |
+ case 'close': |
+ _log("Closing Worker"); |
+ _globalState.managers.remove(sender.id); |
+ sender.terminate(); |
+ _globalState.topEventLoop.run(); |
+ break; |
+ case 'log': |
+ _log(msg['msg']); |
+ break; |
+ case 'print': |
+ if (_globalState.isWorker) { |
+ _globalState.mainManager.postMessage( |
+ _serializeMessage({'command': 'print', 'msg': msg})); |
+ } else { |
+ print(msg['msg']); |
+ } |
+ break; |
+ case 'error': |
+ throw msg['msg']; |
+ } |
+ } |
+ |
+ /** Log a message, forwarding to the main [_Manager] if appropriate. */ |
+ static _log(msg) { |
+ if (_globalState.isWorker) { |
+ _globalState.mainManager.postMessage( |
+ _serializeMessage({'command': 'log', 'msg': msg })); |
+ } else { |
+ try { |
+ _consoleLog(msg); |
+ } catch (e, trace) { |
+ throw new Exception(trace); |
+ } |
+ } |
+ } |
+ |
+ static void _consoleLog(msg) { |
+ JS("void", r"$globalThis.console.log(#)", msg); |
+ } |
+ |
+ /** |
+ * Extract the constructor of runnable, so it can be allocated in another |
+ * isolate. |
+ */ |
+ static dynamic _getJSConstructor(Isolate runnable) { |
+ return JS("Object", "#.constructor", runnable); |
+ } |
+ |
+ /** Extract the constructor name of a runnable */ |
+ // TODO(sigmund): find a browser-generic way to support this. |
+ // TODO(floitsch): is this function still used? If yes, should we use |
+ // Primitives.objectTypeName instead? |
+ static dynamic _getJSConstructorName(Isolate runnable) { |
+ return JS("Object", "#.constructor.name", runnable); |
+ } |
+ |
+ /** Find a constructor given its name. */ |
+ static dynamic _getJSConstructorFromName(String factoryName) { |
+ return JS("Object", r"$globalThis[#]", factoryName); |
+ } |
+ |
+ static dynamic _getJSFunctionFromName(String functionName) { |
+ return JS("Object", r"$globalThis[#]", functionName); |
+ } |
+ |
+ /** |
+ * Get a string name for the function, if possible. The result for |
+ * anonymous functions is browser-dependent -- it may be "" or "anonymous" |
+ * but you should probably not count on this. |
+ */ |
+ static String _getJSFunctionName(Function f) { |
+ return JS("Object", r"(#.$name || #)", f, null); |
+ } |
+ |
+ /** Create a new JavaScript object instance given its constructor. */ |
+ static dynamic _allocate(var ctor) { |
+ return JS("Object", "new #()", ctor); |
+ } |
+ |
+ // TODO(sigmund): clean up above, after we make the new API the default: |
+ |
+ static _spawn(String functionName, String uri, bool isLight) { |
+ Completer<SendPort> completer = new Completer<SendPort>(); |
+ ReceivePort port = new ReceivePort(); |
+ port.receive((msg, SendPort replyPort) { |
+ port.close(); |
+ assert(msg == _SPAWNED_SIGNAL); |
+ completer.complete(replyPort); |
+ }); |
+ |
+ SendPort signalReply = port.toSendPort(); |
+ |
+ if (_globalState.useWorkers && !isLight) { |
+ _startWorker(functionName, uri, signalReply); |
+ } else { |
+ _startNonWorker(functionName, uri, signalReply); |
+ } |
+ return new _BufferingSendPort( |
+ _globalState.currentContext.id, completer.future); |
+ } |
+ |
+ static SendPort _startWorker( |
+ String functionName, String uri, SendPort replyPort) { |
+ if (_globalState.isWorker) { |
+ _globalState.mainManager.postMessage(_serializeMessage({ |
+ 'command': 'spawn-worker', |
+ 'functionName': functionName, |
+ 'uri': uri, |
+ 'replyPort': replyPort})); |
+ } else { |
+ _spawnWorker(functionName, uri, replyPort); |
+ } |
+ } |
+ |
+ static SendPort _startNonWorker( |
+ String functionName, String uri, SendPort replyPort) { |
+ // TODO(eub): support IE9 using an iframe -- Dart issue 1702. |
+ if (uri != null) throw new UnsupportedError( |
+ "Currently spawnUri is not supported without web workers."); |
+ _globalState.topEventLoop.enqueue(new _IsolateContext(), function() { |
+ final func = _getJSFunctionFromName(functionName); |
+ _startIsolate(func, replyPort); |
+ }, 'nonworker start'); |
+ } |
+ |
+ static void _startIsolate(Function topLevel, SendPort replyTo) { |
+ _fillStatics(_globalState.currentContext); |
+ _lazyPort = new ReceivePort(); |
+ replyTo.send(_SPAWNED_SIGNAL, port.toSendPort()); |
+ |
+ topLevel(); |
+ } |
+ |
+ /** |
+ * Spawns an isolate in a worker. [factoryName] is the Javascript constructor |
+ * name for the isolate entry point class. |
+ */ |
+ static void _spawnWorker(functionName, uri, replyPort) { |
+ if (functionName == null) functionName = 'main'; |
+ if (uri == null) uri = _thisScript; |
+ if (!(new Uri.fromString(uri).isAbsolute())) { |
+ // The constructor of dom workers requires an absolute URL. If we use a |
+ // relative path we will get a DOM exception. |
+ String prefix = _thisScript.substring(0, _thisScript.lastIndexOf('/')); |
+ uri = "$prefix/$uri"; |
+ } |
+ final worker = _newWorker(uri); |
+ worker.onmessage = (e) { _processWorkerMessage(worker, e); }; |
+ var workerId = _globalState.nextManagerId++; |
+ // We also store the id on the worker itself so that we can unregister it. |
+ worker.id = workerId; |
+ _globalState.managers[workerId] = worker; |
+ worker.postMessage(_serializeMessage({ |
+ 'command': 'start', |
+ 'id': workerId, |
+ // Note: we serialize replyPort twice because the child worker needs to |
+ // first deserialize the worker id, before it can correctly deserialize |
+ // the port (port deserialization is sensitive to what is the current |
+ // workerId). |
+ 'replyTo': _serializeMessage(replyPort), |
+ 'functionName': functionName })); |
+ } |
+} |
+ |
+/******************************************************** |
+ Inserted from lib/isolate/dart2js/ports.dart |
+ ********************************************************/ |
+ |
+/** Common functionality to all send ports. */ |
+class _BaseSendPort implements SendPort { |
+ /** Id for the destination isolate. */ |
+ final int _isolateId; |
+ |
+ const _BaseSendPort(this._isolateId); |
+ |
+ void _checkReplyTo(SendPort replyTo) { |
+ if (replyTo != null |
+ && replyTo is! _NativeJsSendPort |
+ && replyTo is! _WorkerSendPort |
+ && replyTo is! _BufferingSendPort) { |
+ throw new Exception("SendPort.send: Illegal replyTo port type"); |
+ } |
+ } |
+ |
+ Future call(var message) { |
+ final completer = new Completer(); |
+ final port = new _ReceivePortImpl(); |
+ send(message, port.toSendPort()); |
+ port.receive((value, ignoreReplyTo) { |
+ port.close(); |
+ if (value is Exception) { |
+ completer.completeException(value); |
+ } else { |
+ completer.complete(value); |
+ } |
+ }); |
+ return completer.future; |
+ } |
+ |
+ void send(var message, [SendPort replyTo]); |
+ bool operator ==(var other); |
+ int get hashCode; |
+} |
+ |
+/** A send port that delivers messages in-memory via native JavaScript calls. */ |
+class _NativeJsSendPort extends _BaseSendPort implements SendPort { |
+ final _ReceivePortImpl _receivePort; |
+ |
+ const _NativeJsSendPort(this._receivePort, int isolateId) : super(isolateId); |
+ |
+ void send(var message, [SendPort replyTo = null]) { |
+ _waitForPendingPorts([message, replyTo], () { |
+ _checkReplyTo(replyTo); |
+ // Check that the isolate still runs and the port is still open |
+ final isolate = _globalState.isolates[_isolateId]; |
+ if (isolate == null) return; |
+ if (_receivePort._callback == null) return; |
+ |
+ // We force serialization/deserialization as a simple way to ensure |
+ // isolate communication restrictions are respected between isolates that |
+ // live in the same worker. [_NativeJsSendPort] delivers both messages |
+ // from the same worker and messages from other workers. In particular, |
+ // messages sent from a worker via a [_WorkerSendPort] are received at |
+ // [_processWorkerMessage] and forwarded to a native port. In such cases, |
+ // here we'll see [_globalState.currentContext == null]. |
+ final shouldSerialize = _globalState.currentContext != null |
+ && _globalState.currentContext.id != _isolateId; |
+ var msg = message; |
+ var reply = replyTo; |
+ if (shouldSerialize) { |
+ msg = _serializeMessage(msg); |
+ reply = _serializeMessage(reply); |
+ } |
+ _globalState.topEventLoop.enqueue(isolate, () { |
+ if (_receivePort._callback != null) { |
+ if (shouldSerialize) { |
+ msg = _deserializeMessage(msg); |
+ reply = _deserializeMessage(reply); |
+ } |
+ _receivePort._callback(msg, reply); |
+ } |
+ }, 'receive $message'); |
+ }); |
+ } |
+ |
+ bool operator ==(var other) => (other is _NativeJsSendPort) && |
+ (_receivePort == other._receivePort); |
+ |
+ int get hashCode => _receivePort._id; |
+} |
+ |
+/** A send port that delivers messages via worker.postMessage. */ |
+// TODO(eub): abstract this for iframes. |
+class _WorkerSendPort extends _BaseSendPort implements SendPort { |
+ final int _workerId; |
+ final int _receivePortId; |
+ |
+ const _WorkerSendPort(this._workerId, int isolateId, this._receivePortId) |
+ : super(isolateId); |
+ |
+ void send(var message, [SendPort replyTo = null]) { |
+ _waitForPendingPorts([message, replyTo], () { |
+ _checkReplyTo(replyTo); |
+ final workerMessage = _serializeMessage({ |
+ 'command': 'message', |
+ 'port': this, |
+ 'msg': message, |
+ 'replyTo': replyTo}); |
+ |
+ if (_globalState.isWorker) { |
+ // communication from one worker to another go through the main worker: |
+ _globalState.mainManager.postMessage(workerMessage); |
+ } else { |
+ _globalState.managers[_workerId].postMessage(workerMessage); |
+ } |
+ }); |
+ } |
+ |
+ bool operator ==(var other) { |
+ return (other is _WorkerSendPort) && |
+ (_workerId == other._workerId) && |
+ (_isolateId == other._isolateId) && |
+ (_receivePortId == other._receivePortId); |
+ } |
+ |
+ int get hashCode { |
+ // TODO(sigmund): use a standard hash when we get one available in corelib. |
+ return (_workerId << 16) ^ (_isolateId << 8) ^ _receivePortId; |
+ } |
+} |
+ |
+/** A port that buffers messages until an underlying port gets resolved. */ |
+class _BufferingSendPort extends _BaseSendPort implements SendPort { |
+ /** Internal counter to assign unique ids to each port. */ |
+ static int _idCount = 0; |
+ |
+ /** For implementing equals and hashcode. */ |
+ final int _id; |
+ |
+ /** Underlying port, when resolved. */ |
+ SendPort _port; |
+ |
+ /** |
+ * Future of the underlying port, so that we can detect when this port can be |
+ * sent on messages. |
+ */ |
+ Future<SendPort> _futurePort; |
+ |
+ /** Pending messages (and reply ports). */ |
+ List pending; |
+ |
+ _BufferingSendPort(isolateId, this._futurePort) |
+ : super(isolateId), _id = _idCount, pending = [] { |
+ _idCount++; |
+ _futurePort.then((p) { |
+ _port = p; |
+ for (final item in pending) { |
+ p.send(item['message'], item['replyTo']); |
+ } |
+ pending = null; |
+ }); |
+ } |
+ |
+ _BufferingSendPort.fromPort(isolateId, this._port) |
+ : super(isolateId), _id = _idCount { |
+ _idCount++; |
+ } |
+ |
+ void send(var message, [SendPort replyTo]) { |
+ if (_port != null) { |
+ _port.send(message, replyTo); |
+ } else { |
+ pending.add({'message': message, 'replyTo': replyTo}); |
+ } |
+ } |
+ |
+ bool operator ==(var other) => |
+ other is _BufferingSendPort && _id == other._id; |
+ int get hashCode => _id; |
+} |
+ |
/** Default factory for receive ports. */ |
patch class ReceivePort { |
patch factory ReceivePort() { |
- return new ReceivePortImpl(); |
+ return new _ReceivePortImpl(); |
} |
+ |
} |
+/** Implementation of a multi-use [ReceivePort] on top of JavaScript. */ |
+class _ReceivePortImpl implements ReceivePort { |
+ int _id; |
+ Function _callback; |
+ static int _nextFreeId = 1; |
+ |
+ _ReceivePortImpl() |
+ : _id = _nextFreeId++ { |
+ _globalState.currentContext.register(_id, this); |
+ } |
+ |
+ void receive(void onMessage(var message, SendPort replyTo)) { |
+ _callback = onMessage; |
+ } |
+ |
+ void close() { |
+ _callback = null; |
+ _globalState.currentContext.unregister(_id); |
+ } |
+ |
+ SendPort toSendPort() { |
+ return new _NativeJsSendPort(this, _globalState.currentContext.id); |
+ } |
+} |
+ |
+/** Wait until all ports in a message are resolved. */ |
+_waitForPendingPorts(var message, void callback()) { |
+ final finder = new _PendingSendPortFinder(); |
+ finder.traverse(message); |
+ Futures.wait(finder.ports).then((_) => callback()); |
+} |
+ |
+ |
+/** Visitor that finds all unresolved [SendPort]s in a message. */ |
+class _PendingSendPortFinder extends _MessageTraverser { |
+ List<Future<SendPort>> ports; |
+ _PendingSendPortFinder() : super(), ports = [] { |
+ _visited = new _JsVisitedMap(); |
+ } |
+ |
+ visitPrimitive(x) {} |
+ |
+ visitList(List list) { |
+ final seen = _visited[list]; |
+ if (seen != null) return; |
+ _visited[list] = true; |
+ // TODO(sigmund): replace with the following: (bug #1660) |
+ // list.forEach(_dispatch); |
+ list.forEach((e) => _dispatch(e)); |
+ } |
+ |
+ visitMap(Map map) { |
+ final seen = _visited[map]; |
+ if (seen != null) return; |
+ |
+ _visited[map] = true; |
+ // TODO(sigmund): replace with the following: (bug #1660) |
+ // map.values.forEach(_dispatch); |
+ map.values.forEach((e) => _dispatch(e)); |
+ } |
+ |
+ visitSendPort(SendPort port) { |
+ if (port is _BufferingSendPort && port._port == null) { |
+ ports.add(port._futurePort); |
+ } |
+ } |
+} |
+ |
+/******************************************************** |
+ Inserted from lib/isolate/dart2js/messages.dart |
+ ********************************************************/ |
+ |
+// Defines message visitors, serialization, and deserialization. |
+ |
+/** Serialize [message] (or simulate serialization). */ |
+_serializeMessage(message) { |
+ if (_globalState.needSerialization) { |
+ return new _JsSerializer().traverse(message); |
+ } else { |
+ return new _JsCopier().traverse(message); |
+ } |
+} |
+ |
+/** Deserialize [message] (or simulate deserialization). */ |
+_deserializeMessage(message) { |
+ if (_globalState.needSerialization) { |
+ return new _JsDeserializer().deserialize(message); |
+ } else { |
+ // Nothing more to do. |
+ return message; |
+ } |
+} |
+ |
+class _JsSerializer extends _Serializer { |
+ |
+ _JsSerializer() : super() { _visited = new _JsVisitedMap(); } |
+ |
+ visitSendPort(SendPort x) { |
+ if (x is _NativeJsSendPort) return visitNativeJsSendPort(x); |
+ if (x is _WorkerSendPort) return visitWorkerSendPort(x); |
+ if (x is _BufferingSendPort) return visitBufferingSendPort(x); |
+ throw "Illegal underlying port $x"; |
+ } |
+ |
+ visitNativeJsSendPort(_NativeJsSendPort port) { |
+ return ['sendport', _globalState.currentManagerId, |
+ port._isolateId, port._receivePort._id]; |
+ } |
+ |
+ visitWorkerSendPort(_WorkerSendPort port) { |
+ return ['sendport', port._workerId, port._isolateId, port._receivePortId]; |
+ } |
+ |
+ visitBufferingSendPort(_BufferingSendPort port) { |
+ if (port._port != null) { |
+ return visitSendPort(port._port); |
+ } else { |
+ // TODO(floitsch): Use real exception (which one?). |
+ throw |
+ "internal error: must call _waitForPendingPorts to ensure all" |
+ " ports are resolved at this point."; |
+ } |
+ } |
+ |
+} |
+ |
+ |
+class _JsCopier extends _Copier { |
+ |
+ _JsCopier() : super() { _visited = new _JsVisitedMap(); } |
+ |
+ visitSendPort(SendPort x) { |
+ if (x is _NativeJsSendPort) return visitNativeJsSendPort(x); |
+ if (x is _WorkerSendPort) return visitWorkerSendPort(x); |
+ if (x is _BufferingSendPort) return visitBufferingSendPort(x); |
+ throw "Illegal underlying port $p"; |
+ } |
+ |
+ SendPort visitNativeJsSendPort(_NativeJsSendPort port) { |
+ return new _NativeJsSendPort(port._receivePort, port._isolateId); |
+ } |
+ |
+ SendPort visitWorkerSendPort(_WorkerSendPort port) { |
+ return new _WorkerSendPort( |
+ port._workerId, port._isolateId, port._receivePortId); |
+ } |
+ |
+ SendPort visitBufferingSendPort(_BufferingSendPort port) { |
+ if (port._port != null) { |
+ return visitSendPort(port._port); |
+ } else { |
+ // TODO(floitsch): Use real exception (which one?). |
+ throw |
+ "internal error: must call _waitForPendingPorts to ensure all" |
+ " ports are resolved at this point."; |
+ } |
+ } |
+ |
+} |
+ |
+class _JsDeserializer extends _Deserializer { |
+ |
+ SendPort deserializeSendPort(List x) { |
+ int managerId = x[1]; |
+ int isolateId = x[2]; |
+ int receivePortId = x[3]; |
+ // If two isolates are in the same manager, we use NativeJsSendPorts to |
+ // deliver messages directly without using postMessage. |
+ if (managerId == _globalState.currentManagerId) { |
+ var isolate = _globalState.isolates[isolateId]; |
+ if (isolate == null) return null; // Isolate has been closed. |
+ var receivePort = isolate.lookup(receivePortId); |
+ return new _NativeJsSendPort(receivePort, isolateId); |
+ } else { |
+ return new _WorkerSendPort(managerId, isolateId, receivePortId); |
+ } |
+ } |
+ |
+} |
+ |
+class _JsVisitedMap implements _MessageTraverserVisitedMap { |
+ List tagged; |
+ |
+ /** Retrieves any information stored in the native object [object]. */ |
+ operator[](var object) { |
+ return _getAttachedInfo(object); |
+ } |
+ |
+ /** Injects some information into the native [object]. */ |
+ void operator[]=(var object, var info) { |
+ tagged.add(object); |
+ _setAttachedInfo(object, info); |
+ } |
+ |
+ /** Get ready to rumble. */ |
+ void reset() { |
+ assert(tagged == null); |
+ tagged = new List(); |
+ } |
+ |
+ /** Remove all information injected in the native objects. */ |
+ void cleanup() { |
+ for (int i = 0, length = tagged.length; i < length; i++) { |
+ _clearAttachedInfo(tagged[i]); |
+ } |
+ tagged = null; |
+ } |
+ |
+ void _clearAttachedInfo(var o) { |
+ JS("void", "#['__MessageTraverser__attached_info__'] = #", o, null); |
+ } |
+ |
+ void _setAttachedInfo(var o, var info) { |
+ JS("void", "#['__MessageTraverser__attached_info__'] = #", o, info); |
+ } |
+ |
+ _getAttachedInfo(var o) { |
+ return JS("", "#['__MessageTraverser__attached_info__']", o); |
+ } |
+} |
+ |
+// only visible for testing purposes |
+// TODO(sigmund): remove once we can disable privacy for testing (bug #1882) |
+class TestingOnly { |
+ static copy(x) { |
+ return new _JsCopier().traverse(x); |
+ } |
+ |
+ // only visible for testing purposes |
+ static serialize(x) { |
+ _Serializer serializer = new _JsSerializer(); |
+ _Deserializer deserializer = new _JsDeserializer(); |
+ return deserializer.deserialize(serializer.traverse(x)); |
+ } |
+} |
+ |
+/******************************************************** |
+ Inserted from lib/isolate/serialization.dart |
+ ********************************************************/ |
+ |
+class _MessageTraverserVisitedMap { |
+ |
+ operator[](var object) => null; |
+ void operator[]=(var object, var info) { } |
+ |
+ void reset() { } |
+ void cleanup() { } |
+ |
+} |
+ |
+/** Abstract visitor for dart objects that can be sent as isolate messages. */ |
+class _MessageTraverser { |
+ |
+ _MessageTraverserVisitedMap _visited; |
+ _MessageTraverser() : _visited = new _MessageTraverserVisitedMap(); |
+ |
+ /** Visitor's entry point. */ |
+ traverse(var x) { |
+ if (isPrimitive(x)) return visitPrimitive(x); |
+ _visited.reset(); |
+ var result; |
+ try { |
+ result = _dispatch(x); |
+ } finally { |
+ _visited.cleanup(); |
+ } |
+ return result; |
+ } |
+ |
+ _dispatch(var x) { |
+ if (isPrimitive(x)) return visitPrimitive(x); |
+ if (x is List) return visitList(x); |
+ if (x is Map) return visitMap(x); |
+ if (x is SendPort) return visitSendPort(x); |
+ if (x is SendPortSync) return visitSendPortSync(x); |
+ |
+ // Overridable fallback. |
+ return visitObject(x); |
+ } |
+ |
+ visitPrimitive(x); |
+ visitList(List x); |
+ visitMap(Map x); |
+ visitSendPort(SendPort x); |
+ visitSendPortSync(SendPortSync x); |
+ |
+ visitObject(Object x) { |
+ // TODO(floitsch): make this a real exception. (which one)? |
+ throw "Message serialization: Illegal value $x passed"; |
+ } |
+ |
+ static bool isPrimitive(x) { |
+ return (x == null) || (x is String) || (x is num) || (x is bool); |
+ } |
+} |
+ |
+ |
+/** A visitor that recursively copies a message. */ |
+class _Copier extends _MessageTraverser { |
+ |
+ visitPrimitive(x) => x; |
+ |
+ List visitList(List list) { |
+ List copy = _visited[list]; |
+ if (copy != null) return copy; |
+ |
+ int len = list.length; |
+ |
+ // TODO(floitsch): we loose the generic type of the List. |
+ copy = new List(len); |
+ _visited[list] = copy; |
+ for (int i = 0; i < len; i++) { |
+ copy[i] = _dispatch(list[i]); |
+ } |
+ return copy; |
+ } |
+ |
+ Map visitMap(Map map) { |
+ Map copy = _visited[map]; |
+ if (copy != null) return copy; |
+ |
+ // TODO(floitsch): we loose the generic type of the map. |
+ copy = new Map(); |
+ _visited[map] = copy; |
+ map.forEach((key, val) { |
+ copy[_dispatch(key)] = _dispatch(val); |
+ }); |
+ return copy; |
+ } |
+ |
+} |
+ |
+/** Visitor that serializes a message as a JSON array. */ |
+class _Serializer extends _MessageTraverser { |
+ int _nextFreeRefId = 0; |
+ |
+ visitPrimitive(x) => x; |
+ |
+ visitList(List list) { |
+ int copyId = _visited[list]; |
+ if (copyId != null) return ['ref', copyId]; |
+ |
+ int id = _nextFreeRefId++; |
+ _visited[list] = id; |
+ var jsArray = _serializeList(list); |
+ // TODO(floitsch): we are losing the generic type. |
+ return ['list', id, jsArray]; |
+ } |
+ |
+ visitMap(Map map) { |
+ int copyId = _visited[map]; |
+ if (copyId != null) return ['ref', copyId]; |
+ |
+ int id = _nextFreeRefId++; |
+ _visited[map] = id; |
+ var keys = _serializeList(map.keys); |
+ var values = _serializeList(map.values); |
+ // TODO(floitsch): we are losing the generic type. |
+ return ['map', id, keys, values]; |
+ } |
+ |
+ _serializeList(List list) { |
+ int len = list.length; |
+ var result = new List(len); |
+ for (int i = 0; i < len; i++) { |
+ result[i] = _dispatch(list[i]); |
+ } |
+ return result; |
+ } |
+} |
+ |
+/** Deserializes arrays created with [_Serializer]. */ |
+class _Deserializer { |
+ Map<int, dynamic> _deserialized; |
+ |
+ _Deserializer(); |
+ |
+ static bool isPrimitive(x) { |
+ return (x == null) || (x is String) || (x is num) || (x is bool); |
+ } |
+ |
+ deserialize(x) { |
+ if (isPrimitive(x)) return x; |
+ // TODO(floitsch): this should be new HashMap<int, var|Dynamic>() |
+ _deserialized = new HashMap(); |
+ return _deserializeHelper(x); |
+ } |
+ |
+ _deserializeHelper(x) { |
+ if (isPrimitive(x)) return x; |
+ assert(x is List); |
+ switch (x[0]) { |
+ case 'ref': return _deserializeRef(x); |
+ case 'list': return _deserializeList(x); |
+ case 'map': return _deserializeMap(x); |
+ case 'sendport': return deserializeSendPort(x); |
+ default: return deserializeObject(x); |
+ } |
+ } |
+ |
+ _deserializeRef(List x) { |
+ int id = x[1]; |
+ var result = _deserialized[id]; |
+ assert(result != null); |
+ return result; |
+ } |
+ |
+ List _deserializeList(List x) { |
+ int id = x[1]; |
+ // We rely on the fact that Dart-lists are directly mapped to Js-arrays. |
+ List dartList = x[2]; |
+ _deserialized[id] = dartList; |
+ int len = dartList.length; |
+ for (int i = 0; i < len; i++) { |
+ dartList[i] = _deserializeHelper(dartList[i]); |
+ } |
+ return dartList; |
+ } |
+ |
+ Map _deserializeMap(List x) { |
+ Map result = new Map(); |
+ int id = x[1]; |
+ _deserialized[id] = result; |
+ List keys = x[2]; |
+ List values = x[3]; |
+ int len = keys.length; |
+ assert(len == values.length); |
+ for (int i = 0; i < len; i++) { |
+ var key = _deserializeHelper(keys[i]); |
+ var value = _deserializeHelper(values[i]); |
+ result[key] = value; |
+ } |
+ return result; |
+ } |
+ |
+ deserializeSendPort(List x); |
+ |
+ deserializeObject(List x) { |
+ // TODO(floitsch): Use real exception (which one?). |
+ throw "Unexpected serialized object"; |
+ } |
+} |
+ |
+/******************************************************** |
+ Inserted from lib/isolate/dart2js/timer_provider.dart |
+ ********************************************************/ |
+ |
+// We don't want to import the DOM library just because of window.setTimeout, |
+// so we reconstruct the Window class here. The only conflict that could happen |
+// with the other DOMWindow class would be because of subclasses. |
+// Currently, none of the two Dart classes have subclasses. |
+typedef void _TimeoutHandler(); |
+ |
+class _Window native "@*DOMWindow" { |
+ int setTimeout(_TimeoutHandler handler, int timeout) native; |
+ int setInterval(_TimeoutHandler handler, int timeout) native; |
+ void clearTimeout(int handle) native; |
+ void clearInterval(int handle) native; |
+} |
+ |
+_Window get _window => |
+ JS('bool', 'typeof window != "undefined"') ? JS('_Window', 'window') : null; |
+ |
+class _Timer implements Timer { |
+ final bool _once; |
+ int _handle; |
+ |
+ _Timer(int milliSeconds, void callback(Timer timer)) |
+ : _once = true { |
+ _handle = _window.setTimeout(() => callback(this), milliSeconds); |
+ } |
+ |
+ _Timer.repeating(int milliSeconds, void callback(Timer timer)) |
+ : _once = false { |
+ _handle = _window.setInterval(() => callback(this), milliSeconds); |
+ } |
+ |
+ void cancel() { |
+ if (_once) { |
+ _window.clearTimeout(_handle); |
+ } else { |
+ _window.clearInterval(_handle); |
+ } |
+ } |
+} |
+ |
patch class Timer { |
- patch factory Timer(int milliseconds, void callback(Timer timer)) { |
- if (!hasWindow()) { |
+ patch factory Timer(int milliSeconds, void callback(Timer timer)) { |
+ if (_window == null) { |
throw new UnsupportedError("Timer interface not supported."); |
} |
- return new TimerImpl(milliseconds, callback); |
+ return new _Timer(milliSeconds, callback); |
} |
/** |
* Creates a new repeating timer. The [callback] is invoked every |
- * [milliseconds] millisecond until cancelled. |
+ * [milliSeconds] millisecond until cancelled. |
*/ |
- patch factory Timer.repeating(int milliseconds, void callback(Timer timer)) { |
- if (!hasWindow()) { |
+ patch factory Timer.repeating(int milliSeconds, void callback(Timer timer)) { |
+ if (_window == null) { |
throw new UnsupportedError("Timer interface not supported."); |
} |
- return new TimerImpl.repeating(milliseconds, callback); |
+ return new _Timer.repeating(milliSeconds, callback); |
} |
} |