Add 'startPaused' option to Isolate.spawn*.

Initial implementation of startPaused in dart2js.

R=floitsch@google.com

Review URL: https://codereview.chromium.org//163523003

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@32722 260f80e4-7a28-3924-810f-c04153c831b5
This commit is contained in:
lrn@google.com 2014-02-17 13:06:06 +00:00
parent 38e5d47786
commit b23f5d0327
6 changed files with 149 additions and 32 deletions

View file

@ -227,7 +227,8 @@ void _startIsolate(Function entryPoint, bool isSpawnUri) {
patch class Isolate {
/* patch */ static Future<Isolate> spawn(
void entryPoint(message), var message) {
void entryPoint(message), var message, { bool paused: false }) {
// `paused` isn't handled yet.
try {
// The VM will invoke [_startIsolate] with entryPoint as argument.
SendPort controlPort = _spawnFunction(entryPoint);
@ -246,7 +247,8 @@ patch class Isolate {
}
/* patch */ static Future<Isolate> spawnUri(
Uri uri, List<String> args, var message) {
Uri uri, List<String> args, var message, { bool paused: false }) {
// `paused` isn't handled yet.
try {
// The VM will invoke [_startIsolate] and not `main`.
SendPort controlPort = _spawnUri(uri.toString());

View file

@ -264,7 +264,7 @@ class _IsolateContext implements IsolateContext {
final Capability pauseCapability = new Capability();
// TODO(lrn): Store these in single "pausestate" object, so they don't take
// TODO(lrn): Store these in single "PauseState" object, so they don't take
// up as much room when not pausing.
bool isPaused = false;
List<_IsolateEvent> delayedEvents = [];
@ -279,6 +279,7 @@ class _IsolateContext implements IsolateContext {
if (pauseTokens.add(resume) && !isPaused) {
isPaused = true;
}
_updateGlobalState();
}
void removePause(Capability resume) {
@ -291,6 +292,7 @@ class _IsolateContext implements IsolateContext {
}
isPaused = false;
}
_updateGlobalState();
}
/**
@ -354,7 +356,7 @@ class _IsolateContext implements IsolateContext {
}
_updateGlobalState() {
if (ports.length - weakPorts.length > 0) {
if (ports.length - weakPorts.length > 0 || isPaused) {
_globalState.isolates[id] = this; // indicate this isolate is active
} else {
_globalState.isolates.remove(id); // indicate this isolate is not active
@ -592,10 +594,12 @@ class IsolateNatives {
var args = msg['args'];
var message = _deserializeMessage(msg['msg']);
var isSpawnUri = msg['isSpawnUri'];
var startPaused = msg['startPaused'];
var replyTo = _deserializeMessage(msg['replyTo']);
var context = new _IsolateContext();
_globalState.topEventLoop.enqueue(context, () {
_startIsolate(entryPoint, args, message, isSpawnUri, replyTo);
_startIsolate(entryPoint, args, message,
isSpawnUri, startPaused, replyTo);
}, 'worker-start');
// Make sure we always have a current context in this worker.
// TODO(7907): This is currently needed because we're using
@ -609,7 +613,8 @@ class IsolateNatives {
case 'spawn-worker':
_spawnWorker(msg['functionName'], msg['uri'],
msg['args'], msg['msg'],
msg['isSpawnUri'], msg['replyPort']);
msg['isSpawnUri'], msg['startPaused'],
msg['replyPort']);
break;
case 'message':
SendPort port = msg['port'];
@ -677,17 +682,24 @@ class IsolateNatives {
}
static Future<List> spawnFunction(void topLevelFunction(message),
message) {
var message,
bool startPaused) {
final name = _getJSFunctionName(topLevelFunction);
if (name == null) {
throw new UnsupportedError(
"only top-level functions can be spawned.");
}
return spawn(name, null, null, message, false, false);
bool isLight = false;
bool isSpawnUri = false;
return spawn(name, null, null, message, isLight, isSpawnUri, startPaused);
}
static Future<List> spawnUri(Uri uri, List<String> args, message) {
return spawn(null, uri.toString(), args, message, false, true);
static Future<List> spawnUri(Uri uri, List<String> args, var message,
bool startPaused) {
bool isLight = false;
bool isSpawnUri = true;
return spawn(null, uri.toString(), args, message,
isLight, isSpawnUri, startPaused);
}
// TODO(sigmund): clean up above, after we make the new API the default:
@ -695,7 +707,7 @@ class IsolateNatives {
/// If [uri] is `null` it is replaced with the current script.
static Future<List> spawn(String functionName, String uri,
List<String> args, message,
bool isLight, bool isSpawnUri) {
bool isLight, bool isSpawnUri, bool startPaused) {
// Assume that the compiled version of the Dart file lives just next to the
// dart file.
// TODO(floitsch): support precompiled version of dart2js output.
@ -710,10 +722,12 @@ class IsolateNatives {
SendPort signalReply = port.sendPort;
if (_globalState.useWorkers && !isLight) {
_startWorker(functionName, uri, args, message, isSpawnUri, signalReply);
_startWorker(functionName, uri, args, message, isSpawnUri, startPaused,
signalReply);
} else {
_startNonWorker(
functionName, uri, args, message, isSpawnUri, signalReply);
functionName, uri, args, message, isSpawnUri, startPaused,
signalReply);
}
return result;
}
@ -722,6 +736,7 @@ class IsolateNatives {
String functionName, String uri,
List<String> args, message,
bool isSpawnUri,
bool startPaused,
SendPort replyPort) {
if (_globalState.isWorker) {
_globalState.mainManager.postMessage(_serializeMessage({
@ -731,16 +746,19 @@ class IsolateNatives {
'msg': message,
'uri': uri,
'isSpawnUri': isSpawnUri,
'startPaused': startPaused,
'replyPort': replyPort}));
} else {
_spawnWorker(functionName, uri, args, message, isSpawnUri, replyPort);
_spawnWorker(functionName, uri, args, message,
isSpawnUri, startPaused, replyPort);
}
}
static void _startNonWorker(
String functionName, String uri,
List<String> args, message,
List<String> args, var message,
bool isSpawnUri,
bool startPaused,
SendPort replyPort) {
// TODO(eub): support IE9 using an iframe -- Dart issue 1702.
if (uri != null) {
@ -749,13 +767,14 @@ class IsolateNatives {
}
_globalState.topEventLoop.enqueue(new _IsolateContext(), () {
final func = _getJSFunctionFromName(functionName);
_startIsolate(func, args, message, isSpawnUri, replyPort);
_startIsolate(func, args, message, isSpawnUri, startPaused, replyPort);
}, 'nonworker start');
}
static void _startIsolate(Function topLevel,
List<String> args, message,
bool isSpawnUri,
bool startPaused,
SendPort replyTo) {
_IsolateContext context = JS_CURRENT_ISOLATE_CONTEXT();
Primitives.initializeStatics(context.id);
@ -763,6 +782,8 @@ class IsolateNatives {
replyTo.send([_SPAWNED_SIGNAL,
context.controlPort.sendPort,
context.pauseCapability]);
void runStartFunction() {
if (!isSpawnUri) {
topLevel(message);
} else if (topLevel is _MainFunctionArgsMessage) {
@ -774,6 +795,15 @@ class IsolateNatives {
}
}
if (startPaused) {
context.addPause(context.pauseCapability, context.pauseCapability);
_globalState.topEventLoop.enqueue(context, runStartFunction,
'start isolate');
} else {
runStartFunction();
}
}
/**
* Spawns an isolate in a worker. [factoryName] is the Javascript constructor
* name for the isolate entry point class.
@ -781,6 +811,7 @@ class IsolateNatives {
static void _spawnWorker(functionName, String uri,
List<String> args, message,
bool isSpawnUri,
bool startPaused,
SendPort replyPort) {
if (uri == null) uri = thisScript;
final worker = JS('var', 'new Worker(#)', uri);
@ -805,6 +836,7 @@ class IsolateNatives {
'args': args,
'msg': _serializeMessage(message),
'isSpawnUri': isSpawnUri,
'startPaused': startPaused,
'functionName': functionName }));
}
}
@ -844,7 +876,6 @@ class _NativeJsSendPort extends _BaseSendPort implements SendPort {
final isolate = _globalState.isolates[_isolateId];
if (isolate == null) return;
if (_receivePort._isClosed) 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

View file

@ -12,9 +12,10 @@ import 'dart:_isolate_helper' show CapabilityImpl,
RawReceivePortImpl;
patch class Isolate {
patch static Future<Isolate> spawn(void entryPoint(message), var message) {
patch static Future<Isolate> spawn(void entryPoint(message), var message,
{ bool paused: false }) {
try {
return IsolateNatives.spawnFunction(entryPoint, message)
return IsolateNatives.spawnFunction(entryPoint, message, paused)
.then((msg) => new Isolate._fromControlPort(msg[1], msg[2]));
} catch (e, st) {
return new Future<Isolate>.error(e, st);
@ -22,7 +23,7 @@ patch class Isolate {
}
patch static Future<Isolate> spawnUri(
Uri uri, List<String> args, var message) {
Uri uri, List<String> args, var message, { bool paused: false }) {
try {
if (args is List<String>) {
for (int i = 0; i < args.length; i++) {
@ -33,7 +34,7 @@ patch class Isolate {
} else if (args != null) {
throw new ArgumentError("Args must be a list of Strings $args");
}
return IsolateNatives.spawnUri(uri, args, message)
return IsolateNatives.spawnUri(uri, args, message, paused)
.then((msg) => new Isolate._fromControlPort(msg[1], msg[2]));
} catch (e, st) {
return new Future<Isolate>.error(e, st);

View file

@ -58,7 +58,8 @@ class Isolate {
* Returns a future that will complete with an [Isolate] instance if the
* spawning succeeded. It will complete with an error otherwise.
*/
external static Future<Isolate> spawn(void entryPoint(message), var message);
external static Future<Isolate> spawn(void entryPoint(message), var message,
{ bool paused: false });
/**
* Creates and spawns an isolate that runs the code from the library with
@ -80,7 +81,7 @@ class Isolate {
* spawning succeeded. It will complete with an error otherwise.
*/
external static Future<Isolate> spawnUri(
Uri uri, List<String> args, var message);
Uri uri, List<String> args, var message, { bool paused: false });
/**

View file

@ -12,6 +12,7 @@ isolate_throws_test/01: Skip # Issue 12587
compile_time_error_test/01: Skip # Issue 12587
capability_test: Fail # Not implemented yet
pause_test: Fail # Not implemented yet
start_paused_test: Fail # Not implemented yet
[ $compiler == dart2js && $jscl ]
browser/*: SkipByDesign # Browser specific tests

View file

@ -0,0 +1,81 @@
// Copyright (c) 2014, 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.
library start_paused_test;
import "dart:isolate";
import "package:expect/expect.dart";
import "package:async_helper/async_helper.dart";
void isomain(SendPort p) {
p.send("DONE");
}
void notyet(_) {
throw "NOT YET";
}
void main() {
asyncStart();
test1();
test2();
asyncEnd();
}
void test1() {
// Test that a paused isolate doesn't send events.
// We start two isolates, one paused and one not.
// The unpaused one must send an event, after which
// we resume that paused isolate, and expect the second event.
// This is not a guaranteed test, since it can succeede even if the
// paused isolate isn't really paused.
// However, it must never fail, since that would mean that a paused
// isolate sends a message.
asyncStart();
RawReceivePort p1 = new RawReceivePort(notyet);
Isolate.spawn(isomain, p1.sendPort, paused: true)
.then((isolate) {
RawReceivePort p2;
p2 = new RawReceivePort((x) {
Expect.equals("DONE", x);
p2.close();
p1.handler = (x) {
Expect.equals("DONE", x);
p1.close();
asyncEnd();
};
isolate.resume(isolate.pauseCapability);
});
Isolate.spawn(isomain, p2.sendPort);
});
}
void test2() {
// Test that a paused isolate doesn't send events.
// Like the test above, except that we change the pause capability
// of the paused isolate by pausing it using another capability and
// then resuming the initial pause. This must not cause it to send
// a message before the second pause is resumed as well.
asyncStart();
RawReceivePort p1 = new RawReceivePort(notyet);
Isolate.spawn(isomain, p1.sendPort, paused: true)
.then((isolate) {
RawReceivePort p2;
Capability c2 = new Capability();
// Switch to another pause capability.
isolate.pause(c2);
isolate.resume(isolate.pauseCapability);
p2 = new RawReceivePort((x) {
Expect.equals("DONE", x);
p2.close();
p1.handler = (x) {
Expect.equals("DONE", x);
p1.close();
asyncEnd();
};
isolate.resume(c2);
});
Isolate.spawn(isomain, p2.sendPort);
});
}