Chromium Code Reviews| Index: frog/lib/isolate.dart |
| diff --git a/frog/lib/isolate.dart b/frog/lib/isolate.dart |
| index 69af7d1ea50c6b1b8474f42efae9d4fb4d3ef84f..89206d9126f09dc0e005c022c104515d39a45e4e 100644 |
| --- a/frog/lib/isolate.dart |
| +++ b/frog/lib/isolate.dart |
| @@ -1,4 +1,4 @@ |
| -// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
| +// Copyright (c) 2012, 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. |
| @@ -162,6 +162,13 @@ _deserializeMessage(message) { |
| } |
| } |
| +/** 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()); |
| +} |
| + |
| /** Default worker. */ |
| class MainWorker { |
| int id = 0; |
| @@ -340,9 +347,11 @@ class BaseSendPort implements SendPort { |
| } |
| static void checkReplyTo(SendPort replyTo) { |
| - if (replyTo !== null && replyTo is! NativeJsSendPort |
| - && replyTo is! WorkerSendPort) { |
| - throw "SendPort.send: Illegal replyTo port type."; |
| + if (replyTo !== null |
| + && replyTo is! NativeJsSendPort |
| + && replyTo is! WorkerSendPort |
| + && replyTo is! BufferingSendPort) { |
| + throw new Exception("SendPort.send: Illegal replyTo port type"); |
| } |
| } |
| @@ -373,36 +382,38 @@ class NativeJsSendPort extends BaseSendPort implements SendPort { |
| const NativeJsSendPort(this._receivePort, int isolateId) : super(isolateId); |
| void send(var message, [SendPort replyTo = null]) { |
| - 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); |
| + _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 |
|
eub
2012/02/10 22:46:19
Line length?
Siggi Cherem (dart-lang)
2012/02/10 23:42:22
Done.
|
| + // 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); |
| } |
| - }, 'receive ' + message); |
| + _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) && |
| @@ -420,19 +431,21 @@ class WorkerSendPort extends BaseSendPort implements SendPort { |
| : super(isolateId); |
| void send(var message, [SendPort replyTo = null]) { |
| - 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.mainWorker.postMessage(workerMessage); |
| - } else { |
| - _globalState.workers[_workerId].postMessage(workerMessage); |
| - } |
| + _waitForPendingPorts([message, replyTo], () { |
| + checkReplyTo(replyTo); |
| + final workerMessage = _serializeMessage({ |
| + 'command': 'message', |
| + 'port': _serializeMessage(this), |
| + 'msg': message, |
| + 'replyTo': _serializeMessage(replyTo)}); |
| + |
| + if (_globalState.isWorker) { |
| + // communication from one worker to another go through the main worker: |
| + _globalState.mainWorker.postMessage(workerMessage); |
| + } else { |
| + _globalState.workers[_workerId].postMessage(workerMessage); |
| + } |
| + }); |
| } |
| bool operator ==(var other) { |
| @@ -448,6 +461,54 @@ class WorkerSendPort extends BaseSendPort implements SendPort { |
| } |
| } |
| +/** A port that buffers messages until an underlying port gets resolve. */ |
|
eub
2012/02/10 22:46:19
("resolved")
Siggi Cherem (dart-lang)
2012/02/10 23:42:22
Done.
|
| +class BufferingSendPort extends BaseSendPort implements SendPort { |
| + static int _bufferingCount = 0; |
|
eub
2012/02/10 22:46:19
A comment, please.
Siggi Cherem (dart-lang)
2012/02/10 23:42:22
Done.
|
| + |
| + /** For implementing equals and hashcode. */ |
| + final int id; |
|
eub
2012/02/10 22:46:19
Why public?
Siggi Cherem (dart-lang)
2012/02/10 23:42:22
no reason. done
|
| + |
| + /** 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 = _bufferingCount, pending = [] { |
| + _bufferingCount++; |
|
eub
2012/02/10 22:46:19
id = _bufferingCount++
?
or a static fn that expos
Siggi Cherem (dart-lang)
2012/02/10 23:42:22
Unfortunately, if I move the id initialization her
|
| + _futurePort.then((p) { |
| + _port = p; |
| + for (final message in pending) { |
| + p.send(message[0], message[1]); |
|
eub
2012/02/10 22:46:19
Raw access to numeric indices is oogy.
Siggi Cherem (dart-lang)
2012/02/10 23:42:22
Done - made into a map record.
|
| + } |
| + pending = null; |
| + }); |
| + } |
| + |
| + BufferingSendPort.fromPort(isolateId, this._port) |
| + : super(isolateId), id = _bufferingCount { |
| + _bufferingCount++; |
| + } |
| + |
| + void send(var message, [SendPort replyTo]) { |
| + if (_port != null) { |
| + _port.send(message, replyTo); |
| + } else { |
| + pending.add([message, replyTo]); |
| + } |
| + } |
| + |
| + bool operator ==(var other) => (other is BufferingSendPort && id == other.id); |
| + int hashCode() => id; |
| +} |
| + |
| /** Default factory for receive ports. */ |
| class ReceivePortFactory { |
| @@ -536,13 +597,12 @@ class IsolateNatives { |
| _globalState.mainWorker.postMessage(_serializeMessage({ |
| 'command': 'spawn-worker', |
| 'factoryName': factoryName, |
| - 'replyPort': replyPort})); |
| + 'replyPort': _serializeMessage(replyPort)})); |
| } else { |
| _spawnWorker(factoryName, _serializeMessage(replyPort)); |
| } |
| } |
| - |
| /** |
| * The src url for the script tag that loaded this code. Used to create |
| * JavaScript workers. |
| @@ -608,6 +668,7 @@ class IsolateNatives { |
| static void _processWorkerMessage(sender, e) { |
| var msg = _deserializeMessage(_getEventData(e)); |
| switch (msg['command']) { |
| + // TODO(sigmund): delete after we migrate to Isolate2 |
| case 'start': |
| _globalState.currentWorkerId = msg['id']; |
| var runnerObject = |
| @@ -615,15 +676,30 @@ class IsolateNatives { |
| var serializedReplyTo = msg['replyTo']; |
| _globalState.topEventLoop.enqueue(new IsolateContext(), function() { |
| var replyTo = _deserializeMessage(serializedReplyTo); |
| - IsolateNatives._startIsolate(runnerObject, replyTo); |
| + _startIsolate(runnerObject, replyTo); |
| }, 'worker-start'); |
| _globalState.topEventLoop.run(); |
| break; |
| + case 'start2': |
| + _globalState.currentWorkerId = msg['id']; |
| + Function entryPoint = _getJSFunctionFromName(msg['functionName']); |
| + var replyTo = _deserializeMessage(msg['replyTo']); |
| + _globalState.topEventLoop.enqueue(new IsolateContext(), function() { |
| + _startIsolate2(entryPoint, replyTo); |
| + }, 'worker-start'); |
| + _globalState.topEventLoop.run(); |
| + break; |
| + // TODO(sigmund): delete after we migrate to Isolate2 |
| case 'spawn-worker': |
| _spawnWorker(msg['factoryName'], msg['replyPort']); |
| break; |
| + case 'spawn-worker2': |
| + _spawnWorker2(msg['functionName'], msg['uri'], msg['replyPort']); |
| + break; |
| case 'message': |
| - msg['port'].send(msg['msg'], msg['replyTo']); |
| + final iid = _globalState.currentContext == null ? '?' : '${_globalState.currentContext.id }'; |
|
eub
2012/02/10 22:46:19
Dead code?
Siggi Cherem (dart-lang)
2012/02/10 23:42:22
yep, done.
|
| + final port = _deserializeMessage(msg['port']); |
| + port.send(msg['msg'], _deserializeMessage(msg['replyTo'])); |
| _globalState.topEventLoop.run(); |
| break; |
| case 'close': |
| @@ -684,6 +760,12 @@ class IsolateNatives { |
| return \$globalThis[factoryName]; |
| """; |
| + static var _getJSFunctionFromName(String functionName) native """ |
| + return \$globalThis[functionName]; |
| + """; |
| + |
| + static String _getJSFunctionName(Function f) native "return f.name || null;"; |
| + |
| /** Create a new JavasSript object instance given it's constructor. */ |
| static var _allocate(var ctor) native "return new ctor();"; |
| @@ -709,4 +791,100 @@ class IsolateNatives { |
| replyTo.send(_SPAWNED_SIGNAL, port.toSendPort()); |
| isolate._run(port); |
| } |
| + |
| + // TODO(sigmund): clean up above, after we make the new API the default: |
| + |
| + static _spawn2(String functionName, String uri, bool isLight) { |
| + Completer<SendPort> completer = new Completer<SendPort>(); |
| + ReceivePort port = new ReceivePort.singleShot(); |
| + port.receive((msg, SendPort replyPort) { |
| + assert(msg == _SPAWNED_SIGNAL); |
| + completer.complete(replyPort); |
| + }); |
| + |
| + SendPort signalReply = port.toSendPort(); |
| + |
| + if (_globalState.useWorkers && !isLight) { |
| + _startWorker2(functionName, uri, signalReply); |
| + } else { |
| + _startNonWorker2(functionName, uri, signalReply); |
| + } |
| + return new BufferingSendPort( |
| + _globalState.currentContext.id, completer.future); |
| + } |
| + |
| + static SendPort _startWorker2( |
| + String functionName, String uri, SendPort replyPort) { |
| + if (_globalState.isWorker) { |
| + _globalState.mainWorker.postMessage(_serializeMessage({ |
| + 'command': 'spawn-worker2', |
| + 'functionName': functionName, |
| + 'uri': uri, |
| + 'replyPort': replyPort})); |
| + } else { |
| + _spawnWorker2(functionName, uri, replyPort); |
| + } |
| + } |
| + |
| + static SendPort _startNonWorker2( |
| + String functionName, String uri, SendPort replyPort) { |
| + _globalState.topEventLoop.enqueue(new IsolateContext(), function() { |
| + final func = _getJSFunctionFromName(functionName); |
| + _startIsolate2(func, replyPort); |
| + }, 'nonworker start'); |
| + } |
| + |
| + static void _startIsolate2(Function topLevel, SendPort replyTo) { |
| + _fillStatics(_globalState.currentContext); |
| + final port = new ReceivePort(); |
| + replyTo.send(_SPAWNED_SIGNAL, port.toSendPort()); |
| + topLevel(port); |
| + } |
| + |
| + /** |
| + * Spawns an isolate in a worker. [factoryName] is the Javascript constructor |
| + * name for the isolate entry point class. |
| + */ |
| + static void _spawnWorker2(functionName, uri, replyPort) { |
| + if (uri == null) uri = _thisScript; |
| + final worker = _newWorker(uri); |
| + worker.onmessage = (e) { _processWorkerMessage(worker, e); }; |
| + var workerId = _globalState.nextWorkerId++; |
| + // We also store the id on the worker itself so that we can unregister it. |
| + worker.id = workerId; |
| + _globalState.workers[workerId] = worker; |
| + worker.postMessage(_serializeMessage({ |
| + 'command': 'start2', |
| + '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 })); |
| + } |
| +} |
| + |
| +class Isolate2Impl implements Isolate2 { |
| + SendPort sendPort; |
| + |
| + Isolate2Impl(this.sendPort); |
| + |
| + void stop() {} |
| +} |
| + |
| +class IsolateFactory implements Isolate2 { |
| + |
| + factory Isolate2.fromCode(Function topLevelFunction) { |
| + final name = IsolateNatives._getJSFunctionName(topLevelFunction); |
| + if (name == null) { |
| + throw new UnsupportedOperationException( |
| + "only top-level functions can be spawned."); |
| + } |
| + return new Isolate2Impl(IsolateNatives._spawn2(name, null, false)); |
| + } |
| + |
| + factory Isolate2.fromUri(String uri) { |
| + return new Isolate2Impl(IsolateNatives._spawn2(null, uri, false)); |
| + } |
| } |