Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /** | |
| 6 * A native object that is shared across isolates. This object is visible to all | |
| 7 * isolates running on the same worker (either UI or background web worker). | |
| 8 * | |
| 9 * This is code that is intended to 'escape' the isolate boundaries in order to | |
| 10 * implement the semantics of friendly isolates in JavaScript. Without this we | |
| 11 * would have been forced to implement more code (including the top-level event | |
| 12 * loop) in JavaScript itself. | |
| 13 */ | |
| 14 GlobalState get _globalState() native "return \$globalState;"; | |
| 15 set _globalState(GlobalState val) native "\$globalState = val;"; | |
| 16 | |
| 17 /** | |
| 18 * Wrapper that takes the dart entry point and runs it within an isolate. The | |
| 19 * frog compiler will inject a call of the form [: startAsIsolate(main); :] when | |
| 20 * it determines that this wrapping is needed. For single-isolate applications | |
| 21 * (e.g. hello world), this call is not emited. | |
|
mattsh
2011/11/16 19:09:17
emitted
Siggi Cherem (dart-lang)
2011/11/17 02:03:04
Done.
| |
| 22 */ | |
| 23 void startAsIsolate(entry) { | |
|
mattsh
2011/11/16 19:09:17
startRootIsolate
Siggi Cherem (dart-lang)
2011/11/17 02:03:04
Done.
| |
| 24 _globalState = new GlobalState(); | |
| 25 | |
| 26 // Don't start the main loop again, if we are in a worker. | |
| 27 if (_globalState.inWorker) return; | |
| 28 final entryIsolate = new IsolateContext(); | |
|
mattsh
2011/11/16 19:09:17
maybe rootContext
Siggi Cherem (dart-lang)
2011/11/17 02:03:04
Done.
| |
| 29 _globalState.rootIsolate = entryIsolate; | |
| 30 | |
| 31 // BUG(5151491): Setting _thisISolate should not be necessary, but because | |
| 32 // closures passed to the DOM as event handlers do not bind their isolate | |
| 33 // automatically we try to give them a reasonable context to live in by having | |
| 34 // a "default" isolate (the first one created). | |
| 35 _globalState.currentIsolate = entryIsolate; | |
|
jimhug
2011/11/16 18:00:45
I'm glad there's a bug here - this should be fixed
| |
| 36 | |
| 37 entryIsolate.eval(entry); | |
| 38 _globalState.topEventLoop.run(); | |
| 39 } | |
| 40 | |
| 41 /** Global state associated with the current worker. See [_globalState]. */ | |
|
mattsh
2011/11/16 19:09:17
mabye call this WorkerState or ThreadState? It lo
Siggi Cherem (dart-lang)
2011/11/17 02:03:04
yeah - this is a nice suggestion. I'm hope that we
| |
| 42 class GlobalState { | |
| 43 | |
| 44 /** Next available isolate id. */ | |
| 45 int nextIsolateId = 0; | |
| 46 | |
| 47 /** Worker id associated with this worker. */ | |
| 48 int currentWorkerId = 0; | |
| 49 | |
| 50 /** | |
| 51 * Next available worker id. Only used by the main worker to assign a unique | |
| 52 * id to each worker created by it. | |
| 53 */ | |
| 54 int nextWorkerId = 1; | |
| 55 | |
| 56 /** Context for the currently running [Isolate]. */ | |
| 57 IsolateContext currentIsolate = null; | |
|
mattsh
2011/11/16 19:09:17
isolateContext
Siggi Cherem (dart-lang)
2011/11/17 02:03:04
Done
| |
| 58 | |
| 59 /** Context for the root [Isolate] that first run in this worker. */ | |
| 60 IsolateContext rootIsolate = null; | |
|
mattsh
2011/11/16 19:09:17
rootContext
Siggi Cherem (dart-lang)
2011/11/17 02:03:04
Done.
| |
| 61 | |
| 62 /** The top-level event loop. */ | |
| 63 EventLoop topEventLoop; | |
| 64 | |
| 65 /** Whether this program is running in a background worker. */ | |
| 66 bool inWorker; | |
|
mattsh
2011/11/16 19:09:17
inWorker suggests this value might change. Perhap
Siggi Cherem (dart-lang)
2011/11/17 02:03:04
Done.
| |
| 67 | |
| 68 /** Whether this program is running in a UI worker. */ | |
| 69 bool inWindow; | |
| 70 | |
| 71 /** Whether we support spawning workers. */ | |
| 72 bool supportsWorkers; | |
| 73 | |
| 74 /** | |
| 75 * Whether to use web workers when implementing isolates. Set to false for | |
| 76 * debugging/testing. | |
| 77 */ | |
| 78 bool get useWorkers() => supportsWorkers; | |
|
Jennifer Messerly
2011/11/17 02:28:11
a lot of these properties are redundant?
Should th
Siggi Cherem (dart-lang)
2011/11/17 16:50:11
For now the 'set to false' is only done by modifyi
| |
| 79 | |
| 80 /** | |
| 81 * Whether to use the web-worker JSON-based message serialization protocol, | |
| 82 * even if not using web workers. Set to true to always use the web-worker | |
| 83 * JSON-based message serialization protocol, e.g. for testing purposes. | |
| 84 */ | |
| 85 bool get useWorkerSerializationProtocol() => useWorkers; | |
|
Jennifer Messerly
2011/11/17 02:28:11
Was this an old name? It's reeeeeeallllllyyyy long
Siggi Cherem (dart-lang)
2011/11/17 16:50:11
I know :) - copied from JS. I'll think of somethin
| |
| 86 | |
| 87 /** | |
| 88 * Registry of isolates. Isolates must be registered if, and only if, receive | |
| 89 * ports are alive. Normally no open receive-ports means that the isolate is | |
| 90 * dead, but DOM callbacks could resurrect it. | |
| 91 */ | |
| 92 Map<int, IsolateContext> isolates; | |
| 93 | |
| 94 /** Reference to the main worker. */ | |
| 95 MainWorker mainWorker; | |
|
mattsh
2011/11/16 19:09:17
I think we're using somewhat confusing terminology
| |
| 96 | |
| 97 /** Registry of active workers. Only used in the main worker. */ | |
| 98 Map<int, var> workers; | |
|
mattsh
2011/11/16 19:09:17
I think let's have a separate structure for fields
| |
| 99 | |
| 100 GlobalState() { | |
| 101 topEventLoop = new EventLoop(); | |
| 102 isolates = {}; | |
| 103 workers = {}; | |
| 104 mainWorker = new MainWorker(); | |
| 105 _nativeInit(); | |
| 106 } | |
| 107 | |
| 108 void _nativeInit() native @""" | |
| 109 this.inWorker = typeof ($globalThis['importScripts']) != 'undefined'; | |
| 110 this.inWindow = typeof(window) !== 'undefined'; | |
| 111 this.supportsWorkers = this.inWorker || | |
| 112 ((typeof $globalThis['Worker']) != 'undefined'); | |
| 113 | |
| 114 // if workers are supported, treat this as a main worker: | |
| 115 if (this.supportsWorkers) { | |
| 116 $globalThis.onmessage = function(e) { | |
| 117 IsolateNatives._processWorkerMessage(this.mainWorker, e); | |
|
Jennifer Messerly
2011/11/17 02:28:11
does the compiler know to emit this if _nativeInit
Siggi Cherem (dart-lang)
2011/11/17 16:50:11
It doesn't - fortunately that method is also reach
| |
| 118 }; | |
| 119 } | |
| 120 """; | |
| 121 | |
| 122 /** | |
| 123 * Close the worker running this code, called when there is nothing else to | |
| 124 * run. | |
| 125 */ | |
| 126 void closeWorker() { | |
| 127 if (inWorker) { | |
| 128 if (!isolates.isEmpty()) return; | |
| 129 mainWorker.postMessage( | |
| 130 _serializeMessage({'command': 'close'})); | |
| 131 } else if (isolates.containsKey(rootIsolate.id) && workers.isEmpty() && | |
| 132 !supportsWorkers && !inWindow) { | |
| 133 // This should only trigger when running on the command-line. | |
| 134 // We don't want this check to execute in the browser where the isolate | |
| 135 // might still be alive due to DOM callbacks. | |
| 136 throw new Exception("Program exited with open ReceivePorts."); | |
| 137 } | |
| 138 } | |
| 139 } | |
| 140 | |
| 141 _serializeMessage(message) { | |
| 142 if (_globalState.useWorkerSerializationProtocol) { | |
| 143 return new Serializer().traverse(message); | |
| 144 } else { | |
| 145 return new Copier().traverse(message); | |
| 146 } | |
| 147 } | |
| 148 | |
| 149 _deserializeMessage(message) { | |
| 150 if (_globalState.useWorkerSerializationProtocol) { | |
| 151 return new Deserializer().deserialize(message); | |
| 152 } else { | |
| 153 // Nothing more to do. | |
| 154 return message; | |
| 155 } | |
| 156 } | |
| 157 | |
| 158 /** Default worker. */ | |
| 159 class MainWorker { | |
| 160 int id = 0; | |
| 161 void postMessage(msg) native "return \$globalThis.postMessage(msg);"; | |
| 162 } | |
| 163 | |
| 164 /** Context information tracked for each isolate. */ | |
| 165 class IsolateContext { | |
| 166 /** Current isolate id. */ | |
| 167 int id; | |
| 168 | |
| 169 /** Registry of receive ports currently active on this isolate. */ | |
| 170 Map<int, ReceivePort> ports; | |
| 171 | |
| 172 /** Holds isolate globals (statics and top-level properties). */ | |
| 173 var isolateStatics; // native object containing all globals of an isolate. | |
| 174 | |
| 175 IsolateContext() { | |
| 176 id = _globalState.nextIsolateId++; | |
| 177 ports = {}; | |
| 178 initGlobals(); | |
| 179 } | |
| 180 | |
| 181 // TODO(sigmund): actually do the initialization too. | |
| 182 void initGlobals() native "this.isolateStatics = {};"; | |
| 183 | |
| 184 /** | |
| 185 * Run [code] in the context of the isolate represented by [this]. Note this | |
| 186 * is marked as native because it is called from JavaScript (see $wrap_call in | |
| 187 * corejs.dart). | |
| 188 */ | |
| 189 void eval(Function code) native { | |
| 190 var old = _globalState.currentIsolate; | |
| 191 _globalState.currentIsolate = this; | |
| 192 var result = null; | |
| 193 try { | |
| 194 result = code(); | |
| 195 } finally { | |
| 196 _globalState.currentIsolate = old; | |
| 197 } | |
| 198 return result; | |
| 199 } | |
| 200 | |
| 201 /** Lookup a port registered for this isolate. */ | |
| 202 ReceivePort lookup(int id) => ports[id]; | |
| 203 | |
| 204 /** Register a port on this isolate. */ | |
| 205 void register(int portId, ReceivePort port) { | |
| 206 if (ports.containsKey(portId)) { | |
| 207 throw new Exception("Registry: ports must be registered only once."); | |
| 208 } | |
| 209 ports[portId] = port; | |
| 210 _globalState.isolates[id] = this; // indicate this isolate is active | |
| 211 } | |
| 212 | |
| 213 /** Unregister a port on this isolate. */ | |
| 214 void unregister(int portId) { | |
| 215 ports.remove(portId); | |
| 216 if (ports.isEmpty()) { | |
| 217 _globalState.isolates.remove(id); // indicate this isolate is not active | |
| 218 } | |
| 219 } | |
| 220 } | |
| 221 | |
| 222 /** Represent the event loop on a javascript thread (DOM or worker). */ | |
| 223 class EventLoop { | |
| 224 Queue<IsolateEvent> events; | |
| 225 | |
| 226 EventLoop() : events = new Queue<IsolateEvent>(); | |
| 227 | |
| 228 void enqueue(isolate, fn, msg) { | |
| 229 events.addLast(new IsolateEvent(isolate, fn, msg)); | |
| 230 } | |
| 231 | |
| 232 IsolateEvent dequeue() { | |
| 233 if (events.isEmpty()) return null; | |
| 234 return events.removeFirst(); | |
| 235 } | |
| 236 | |
| 237 /** Process a single event, if any. */ | |
| 238 bool runIteration() { | |
| 239 final event = dequeue(); | |
| 240 if (event == null) { | |
| 241 _globalState.closeWorker(); | |
| 242 return false; | |
| 243 } | |
| 244 event.process(); | |
| 245 return true; | |
| 246 } | |
| 247 | |
| 248 /** Function equivalent to [:window.setTimeout:] when available, or null. */ | |
| 249 static Function _platformDefer() native """ | |
|
mattsh
2011/11/16 19:09:17
platformDefer is kind of vague, suggest rename to
Siggi Cherem (dart-lang)
2011/11/17 02:03:04
Done.
| |
| 250 return typeof window != 'undefined' ? | |
| 251 function(a, b) { window.setTimeout(a, b); } : undefined; | |
| 252 """; | |
| 253 | |
| 254 /** | |
| 255 * Runs multiple iterations of the run-loop. If possible, each iteration is | |
| 256 * run asynchronously. | |
| 257 */ | |
| 258 void _runHelper() { | |
| 259 final setTimeout = _platformDefer(); | |
| 260 if (setTimeout != null) { | |
| 261 // Run each iteration from the browser's top event loop. | |
| 262 void next() { | |
| 263 if (!runIteration()) return; | |
| 264 setTimeout(next, 0); | |
| 265 } | |
| 266 next(); | |
| 267 } else { | |
| 268 // Run synchronously until no more iterations are available. | |
| 269 while (runIteration()) {} | |
| 270 } | |
| 271 } | |
| 272 | |
| 273 /** | |
| 274 * Call [_runHelper] but ensure that worker exceptions are propragated. Note | |
| 275 * this is marked as native because it is called from JavaScript (see | |
| 276 * $wrap_call in corejs.dart). | |
| 277 */ | |
| 278 void run() native { | |
| 279 if (!_globalState.inWorker) { | |
| 280 _runHelper(); | |
| 281 } else { | |
| 282 try { | |
| 283 _runHelper(); | |
| 284 } catch(e) { | |
| 285 // TODO(floitsch): try to send stack-trace to the other side. | |
| 286 _globalState.mainWorker.postMessage(_serializeMessage( | |
| 287 {'command': 'error', 'msg': "" + e })); | |
| 288 } | |
| 289 } | |
| 290 } | |
| 291 } | |
| 292 | |
| 293 /** An event in the top-level event queue. */ | |
| 294 class IsolateEvent { | |
| 295 IsolateContext isolate; | |
| 296 Function fn; | |
| 297 String message; | |
| 298 | |
| 299 IsolateEvent(this.isolate, this.fn, this.message); | |
| 300 | |
| 301 void process() { | |
| 302 isolate.eval(fn); | |
| 303 } | |
| 304 } | |
| 305 | |
| 306 /** Implementation of a send port on top of JavaScript. */ | |
| 5 class SendPortImpl implements SendPort { | 307 class SendPortImpl implements SendPort { |
| 6 | 308 |
| 7 const SendPortImpl(this._workerId, this._isolateId, this._receivePortId); | 309 const SendPortImpl(this._workerId, this._isolateId, this._receivePortId); |
| 8 | 310 |
| 9 void send(var message, [SendPort replyTo = null]) { | 311 void send(var message, [SendPort replyTo = null]) { |
| 10 if (replyTo !== null && !(replyTo is SendPortImpl)) { | 312 if (replyTo !== null && !(replyTo is SendPortImpl)) { |
| 11 throw "SendPort::send: Illegal replyTo type."; | 313 throw "SendPort::send: Illegal replyTo type."; |
| 12 } | 314 } |
| 13 IsolateNatives.sendMessage(_workerId, _isolateId, _receivePortId, | 315 IsolateNatives._sendMessage(_workerId, _isolateId, _receivePortId, |
| 14 _serializeMessage(message), _serializeMessage(replyTo)); | 316 _serializeMessage(message), _serializeMessage(replyTo)); |
| 15 } | 317 } |
| 16 | 318 |
| 17 // TODO(sigmund): get rid of _sendNow | 319 // TODO(sigmund): get rid of _sendNow (still used in corelib code) |
| 18 void _sendNow(var message, replyTo) { send(message, replyTo); } | 320 void _sendNow(var message, replyTo) { send(message, replyTo); } |
| 19 | 321 |
| 20 _serializeMessage(message) { | |
| 21 if (IsolateNatives.shouldSerialize) { | |
| 22 return _IsolateJsUtil._serializeObject(message); | |
| 23 } else { | |
| 24 return _IsolateJsUtil._copyObject(message); | |
| 25 } | |
| 26 } | |
| 27 | |
| 28 ReceivePortSingleShotImpl call(var message) { | 322 ReceivePortSingleShotImpl call(var message) { |
| 29 final result = new ReceivePortSingleShotImpl(); | 323 final result = new ReceivePortSingleShotImpl(); |
| 30 this.send(message, result.toSendPort()); | 324 this.send(message, result.toSendPort()); |
| 31 return result; | 325 return result; |
| 32 } | 326 } |
| 33 | 327 |
| 34 ReceivePortSingleShotImpl _callNow(var message) { | 328 ReceivePortSingleShotImpl _callNow(var message) { |
| 35 final result = new ReceivePortSingleShotImpl(); | 329 final result = new ReceivePortSingleShotImpl(); |
| 36 send(message, result.toSendPort()); | 330 send(message, result.toSendPort()); |
| 37 return result; | 331 return result; |
| 38 } | 332 } |
| 39 | 333 |
| 40 bool operator==(var other) { | 334 bool operator==(var other) { |
| 41 return (other is SendPortImpl) && | 335 return (other is SendPortImpl) && |
| 42 (_workerId == other._workerId) && | 336 (_workerId == other._workerId) && |
| 43 (_isolateId == other._isolateId) && | 337 (_isolateId == other._isolateId) && |
| 44 (_receivePortId == other._receivePortId); | 338 (_receivePortId == other._receivePortId); |
| 45 } | 339 } |
| 46 | 340 |
| 47 int hashCode() { | 341 int hashCode() { |
| 48 return (_workerId << 16) ^ (_isolateId << 8) ^ _receivePortId; | 342 return (_workerId << 16) ^ (_isolateId << 8) ^ _receivePortId; |
| 49 } | 343 } |
| 50 | 344 |
| 51 final int _receivePortId; | 345 final int _receivePortId; |
| 52 final int _isolateId; | 346 final int _isolateId; |
| 53 final int _workerId; | 347 final int _workerId; |
| 54 | |
| 55 static _create(int workerId, int isolateId, int receivePortId) native { | |
| 56 return new SendPortImpl(workerId, isolateId, receivePortId); | |
| 57 } | |
| 58 static _getReceivePortId(SendPortImpl port) native { | |
| 59 return port._receivePortId; | |
| 60 } | |
| 61 static _getIsolateId(SendPortImpl port) native { | |
| 62 return port._isolateId; | |
| 63 } | |
| 64 static _getWorkerId(SendPortImpl port) native { | |
| 65 return port._workerId; | |
| 66 } | |
| 67 } | 348 } |
| 68 | 349 |
| 69 | 350 /** Default factory for receive ports. */ |
| 70 class ReceivePortFactory { | 351 class ReceivePortFactory { |
| 71 | 352 |
| 72 factory ReceivePort() { | 353 factory ReceivePort() { |
| 73 return new ReceivePortImpl(); | 354 return new ReceivePortImpl(); |
| 74 } | 355 } |
| 75 | 356 |
| 76 factory ReceivePort.singleShot() { | 357 factory ReceivePort.singleShot() { |
| 77 return new ReceivePortSingleShotImpl(); | 358 return new ReceivePortSingleShotImpl(); |
| 78 } | 359 } |
| 79 | |
| 80 } | 360 } |
| 81 | 361 |
| 82 | 362 /** Implementation of a multi-use [ReceivePort] on top of JavaScript. */ |
| 83 class ReceivePortImpl implements ReceivePort { | 363 class ReceivePortImpl implements ReceivePort { |
| 84 ReceivePortImpl() | 364 ReceivePortImpl() |
| 85 : _id = _nextFreeId++ { | 365 : _id = _nextFreeId++ { |
| 86 IsolateNatives.registerPort(_id, this); | 366 _globalState.currentIsolate.register(_id, this); |
| 87 } | 367 } |
| 88 | 368 |
| 89 void receive(void onMessage(var message, SendPort replyTo)) { | 369 void receive(void onMessage(var message, SendPort replyTo)) { |
| 90 _callback = onMessage; | 370 _callback = onMessage; |
| 91 } | 371 } |
| 92 | 372 |
| 93 void close() { | 373 void close() { |
| 94 _callback = null; | 374 _callback = null; |
| 95 IsolateNatives.unregisterPort(_id); | 375 _globalState.currentIsolate.unregister(_id); |
| 96 } | |
| 97 | |
| 98 SendPort toSendPort() { | |
| 99 return _toNewSendPort(); | |
| 100 } | 376 } |
| 101 | 377 |
| 102 /** | 378 /** |
| 103 * Returns a fresh [SendPort]. The implementation is not allowed to cache | 379 * Returns a fresh [SendPort]. The implementation is not allowed to cache |
| 104 * existing ports. | 380 * existing ports. |
| 105 */ | 381 */ |
| 106 SendPort _toNewSendPort() { | 382 SendPort toSendPort() { |
| 107 return new SendPortImpl( | 383 return new SendPortImpl( |
| 108 IsolateNatives._currentWorkerId(), | 384 _globalState.currentWorkerId, _globalState.currentIsolate.id, _id); |
| 109 IsolateNatives._currentIsolateId(), _id); | |
| 110 } | 385 } |
| 111 | 386 |
| 112 int _id; | 387 int _id; |
| 113 Function _callback; | 388 Function _callback; |
| 114 | 389 |
| 115 static int _nextFreeId = 1; | 390 static int _nextFreeId = 1; |
| 116 | |
| 117 static int _getId(ReceivePortImpl port) native { | |
| 118 return port._id; | |
| 119 } | |
| 120 | |
| 121 static Function _getCallback(ReceivePortImpl port) native { | |
| 122 return port._callback; | |
| 123 } | |
| 124 } | 391 } |
| 125 | 392 |
| 126 | 393 /** Implementation of a single-shot [ReceivePort]. */ |
| 127 class ReceivePortSingleShotImpl implements ReceivePort { | 394 class ReceivePortSingleShotImpl implements ReceivePort { |
| 128 | 395 |
| 129 ReceivePortSingleShotImpl() : _port = new ReceivePortImpl() { } | 396 ReceivePortSingleShotImpl() : _port = new ReceivePortImpl() { } |
| 130 | 397 |
| 131 void receive(void callback(var message, SendPort replyTo)) { | 398 void receive(void callback(var message, SendPort replyTo)) { |
| 132 _port.receive((var message, SendPort replyTo) { | 399 _port.receive((var message, SendPort replyTo) { |
| 133 _port.close(); | 400 _port.close(); |
| 134 callback(message, replyTo); | 401 callback(message, replyTo); |
| 135 }); | 402 }); |
| 136 } | 403 } |
| 137 | 404 |
| 138 void close() { | 405 void close() { |
| 139 _port.close(); | 406 _port.close(); |
| 140 } | 407 } |
| 141 | 408 |
| 142 SendPort toSendPort() { | 409 SendPort toSendPort() => _port.toSendPort(); |
| 143 return _toNewSendPort(); | |
| 144 } | |
| 145 | |
| 146 /** | |
| 147 * Returns a fresh [SendPort]. The implementation is not allowed to cache | |
| 148 * existing ports. | |
| 149 */ | |
| 150 SendPort _toNewSendPort() { | |
| 151 return _port._toNewSendPort(); | |
| 152 } | |
| 153 | 410 |
| 154 final ReceivePortImpl _port; | 411 final ReceivePortImpl _port; |
| 155 | |
| 156 } | 412 } |
| 157 | 413 |
| 158 final String _SPAWNED_SIGNAL = "spawned"; | 414 final String _SPAWNED_SIGNAL = "spawned"; |
| 159 | 415 |
| 160 class IsolateNatives native "IsolateNatives" { | 416 class IsolateNatives { |
| 417 | |
| 418 /** JavaScript-specific implementation to spawn an isolate. */ | |
| 161 static Future<SendPort> spawn(Isolate isolate, bool isLight) { | 419 static Future<SendPort> spawn(Isolate isolate, bool isLight) { |
| 162 Completer<SendPort> completer = new Completer<SendPort>(); | 420 Completer<SendPort> completer = new Completer<SendPort>(); |
| 163 ReceivePort port = new ReceivePort.singleShot(); | 421 ReceivePort port = new ReceivePort.singleShot(); |
| 164 port.receive((msg, SendPort replyPort) { | 422 port.receive((msg, SendPort replyPort) { |
| 165 assert(msg == _SPAWNED_SIGNAL); | 423 assert(msg == _SPAWNED_SIGNAL); |
| 166 completer.complete(replyPort); | 424 completer.complete(replyPort); |
| 167 }); | 425 }); |
| 168 _spawn(isolate, isLight, port.toSendPort()); | 426 |
| 169 if (false) { | 427 // TODO(floitsch): throw exception if isolate's class doesn't have a |
| 170 // TODO(sigmund): delete this code. This is temporarily added because we | 428 // default constructor. |
| 171 // are tree-shaking methods that are only reachable from js | 429 if (_globalState.useWorkers && !isLight) { |
| 172 _IsolateJsUtil._startIsolate(null, null); | 430 _startWorker(isolate, port.toSendPort()); |
| 173 _IsolateJsUtil._deserializeMessage(null); | 431 } else { |
| 174 _IsolateJsUtil._print(null); | 432 _startNonWorker(isolate, port.toSendPort()); |
| 175 } | 433 } |
| 434 | |
| 176 return completer.future; | 435 return completer.future; |
| 177 } | 436 } |
| 178 | 437 |
| 179 static SendPort _spawn(Isolate isolate, bool light, SendPort port) native; | 438 static SendPort _startWorker(Isolate runnable, SendPort replyPort) { |
| 180 | 439 var factoryName = _getJSConstructorName(runnable); |
| 181 static bool get shouldSerialize() native; | 440 if (_globalState.inWorker) { |
| 182 | 441 _globalState.mainWorker.postMessage(_serializeMessage({ |
| 183 static void sendMessage(int workerId, int isolateId, int receivePortId, | 442 'command': 'spawn-worker', |
| 184 message, replyTo) native; | 443 'factoryName': factoryName, |
| 185 | 444 'replyPort': replyPort})); |
| 186 /** Registers an active receive port. */ | 445 } else { |
| 187 static void registerPort(int id, ReceivePort port) native; | 446 _spawnWorker(factoryName, _serializeMessage(replyPort)); |
| 188 | 447 } |
| 189 /** Unregister an inactive receive port. */ | 448 } |
| 190 static void unregisterPort(int id) native; | 449 |
| 191 | 450 |
| 192 static int _currentWorkerId() native; | 451 /** |
| 193 | 452 * The src url for the script tag that loaded this code. Used to create |
| 194 static int _currentIsolateId() native; | 453 * JavaScript workers. |
| 195 } | 454 */ |
| 196 | 455 static String get _thisScript() => |
| 197 | 456 _thisScriptCache != null ? _thisScriptCache : _computeThisScript(); |
| 198 class _IsolateJsUtil native "_IsolateJsUtil" { | 457 |
| 199 static void _startIsolate(Isolate isolate, SendPort replyTo) native { | 458 static String _thisScriptCache; |
| 459 | |
| 460 // TODO(sigmund): fix - this code should be run synchronously when loading the | |
| 461 // script. Running lazily on DOMContentLoaded will yield incorrect results. | |
| 462 static String _computeThisScript() native @""" | |
| 463 if (!$globalState.supportsWorkers || $globalState.inWorker) return null; | |
| 464 | |
| 465 // TODO(5334778): Find a cross-platform non-brittle way of getting the | |
| 466 // currently running script. | |
| 467 var scripts = document.getElementsByTagName('script'); | |
| 468 // The scripts variable only contains the scripts that have already been | |
| 469 // executed. The last one is the currently running script. | |
| 470 var script = scripts[scripts.length - 1]; | |
| 471 var src = script.src; | |
| 472 if (!src) { | |
| 473 // TODO() | |
| 474 src = "FIXME:5407062" + "_" + Math.random().toString(); | |
| 475 script.src = src; | |
| 476 } | |
| 477 IsolateNatives._thisScriptCache = src; | |
| 478 return src; | |
| 479 """; | |
| 480 | |
| 481 /** Starts a new worker with the given URL. */ | |
| 482 static void _newWorker(url) native "return new Worker(url)"; | |
| 483 | |
| 484 /** | |
| 485 * Spawns an isolate in a worker. [factoryName] is the Javascript constructor | |
| 486 * name for the isolate entry point class. | |
| 487 */ | |
| 488 static void _spawnWorker(factoryName, serializedReplyPort) { | |
| 489 var worker = _newWorker(_thisScript); | |
| 490 // TODO(sigmund): make this work. | |
| 491 worker.onmessage = function(e) { | |
| 492 _processWorkerMessage(worker, e); | |
| 493 }; | |
| 494 var workerId = _globalState.nextWorkerId++; | |
| 495 // We also store the id on the worker itself so that we can unregister it. | |
| 496 worker.id = workerId; | |
| 497 _globalState.workers[workerId] = worker; | |
| 498 worker.postMessage(_serializeMessage({ | |
| 499 'command': 'start', | |
| 500 'id': workerId, | |
| 501 'replyTo': serializedReplyPort, | |
| 502 'factoryName': factoryName })); | |
| 503 } | |
| 504 | |
| 505 /** | |
| 506 * Process messages on a worker, either to control the worker instance or to | |
| 507 * pass messages along to the isolate running in the worker. | |
| 508 */ | |
| 509 static void _processWorkerMessage(sender, e) { | |
| 510 var msg = _deserializeMessage(e.data); | |
| 511 switch (msg['command']) { | |
| 512 case 'start': | |
| 513 _log("starting worker: " + msg['id'] + " " + msg['factoryName']); | |
| 514 _globalState.currentWorkerId = msg['id']; | |
| 515 var runnerObject = | |
| 516 _allocate(_getJSConstructorFromName(msg['factoryName'])); | |
| 517 var serializedReplyTo = msg['replyTo']; | |
| 518 _globalState.topEventLoop.enqueue(new IsolateContext(), function() { | |
| 519 var replyTo = _deserializeMessage(serializedReplyTo); | |
| 520 IsolateNatives._startIsolate(runnerObject, replyTo); | |
| 521 }, 'worker-start'); | |
| 522 _globalState.topEventLoop.run(); | |
| 523 break; | |
| 524 case 'spawn-worker': | |
| 525 _spawnWorker(msg['factoryName'], msg['replyPort']); | |
| 526 break; | |
| 527 case 'message': | |
| 528 _sendMessage(msg['workerId'], msg['isolateId'], msg['portId'], | |
| 529 msg['msg'], msg['replyTo']); | |
| 530 _globalState.topEventLoop.run(); | |
| 531 break; | |
| 532 case 'close': | |
| 533 _log("Closing Worker"); | |
| 534 _globalState.workers.remove(sender.id); | |
| 535 sender.terminate(); | |
| 536 _globalState.topEventLoop.run(); | |
| 537 break; | |
| 538 case 'log': | |
| 539 _log(msg['msg']); | |
| 540 break; | |
| 541 case 'print': | |
| 542 if (_globalState.inWorker) { | |
| 543 _globalState.mainWorker.postMessage( | |
| 544 _serializeMessage({'command': 'print', 'msg': msg})); | |
| 545 } else { | |
| 546 print(msg['msg']); | |
| 547 } | |
| 548 break; | |
| 549 case 'error': | |
| 550 throw msg['msg']; | |
| 551 break; | |
| 552 } | |
| 553 } | |
| 554 | |
| 555 /** Log a message, forwarding to the main worker if appropriate. */ | |
| 556 static _log(msg) { | |
| 557 if (_globalState.inWorker) { | |
| 558 _globalState.mainWorker.postMessage({'command': 'log', 'msg': msg }); | |
| 559 } else { | |
| 560 try { | |
| 561 _consoleLog(msg); | |
| 562 } catch(e, trace) { | |
| 563 throw new Exception(trace); | |
| 564 } | |
| 565 } | |
| 566 } | |
| 567 | |
| 568 static void _consoleLog(msg) native "\$globalThis.console.log(msg);"; | |
| 569 | |
| 570 | |
| 571 /** | |
| 572 * Extract the constructor of runnable, so it can be allocated in another | |
| 573 * isolate. | |
| 574 */ | |
| 575 static var _getJSConstructor(Isolate runnable) native """ | |
| 576 return runnable.constructor; | |
| 577 """; | |
| 578 | |
| 579 /** Extract the constructor name of a runnable */ | |
| 580 // TODO(sigmund): find a browser-generic way to support this. | |
| 581 static var _getJSConstructorName(Isolate runnable) native """ | |
| 582 return runnable.constructor.name; | |
| 583 """; | |
| 584 | |
| 585 /** Find a constructor given it's name. */ | |
| 586 static var _getJSConstructorFromName(String factoryName) native """ | |
| 587 return \$globalThis[factoryName]; | |
| 588 """; | |
| 589 | |
| 590 /** Create a new JavasSript object instance given it's constructor. */ | |
| 591 static var _allocate(var ctor) native "return new ctor();"; | |
| 592 | |
| 593 /** Starts a non-worker isolate. */ | |
| 594 static SendPort _startNonWorker(Isolate runnable, SendPort replyTo) { | |
| 595 // Spawn a new isolate and create the receive port in it. | |
| 596 final spawned = new IsolateContext(); | |
| 597 | |
| 598 // Instead of just running the provided runnable, we create a | |
| 599 // new cloned instance of it with a fresh state in the spawned | |
| 600 // isolate. This way, we do not get cross-isolate references | |
| 601 // through the runnable. | |
| 602 final ctor = _getJSConstructor(runnable); | |
| 603 _globalState.topEventLoop.enqueue(spawned, function() { | |
| 604 _startIsolate(_allocate(ctor), replyTo); | |
| 605 }, 'nonworker start'); | |
| 606 } | |
| 607 | |
| 608 /** Given a ready-to-start runnable, start running it. */ | |
| 609 static void _startIsolate(Isolate isolate, SendPort replyTo) { | |
| 200 ReceivePort port = new ReceivePort(); | 610 ReceivePort port = new ReceivePort(); |
| 201 replyTo.send(_SPAWNED_SIGNAL, port.toSendPort()); | 611 replyTo.send(_SPAWNED_SIGNAL, port.toSendPort()); |
| 202 isolate._run(port); | 612 isolate._run(port); |
| 203 } | 613 } |
| 204 | 614 |
| 205 static void _print(String msg) native { | 615 static void _sendMessage(int workerId, int isolateId, int receivePortId, |
| 206 print(msg); | 616 message, replyTo) { |
| 207 } | 617 // Both the message and the replyTo are already serialized. |
| 208 | 618 if (workerId == _globalState.currentWorkerId) { |
| 209 static _copyObject(obj) native { | 619 var isolate = _globalState.isolates[isolateId]; |
| 210 return new Copier().traverse(obj); | 620 if (isolate == null) return; // Isolate has been closed. |
| 211 } | 621 var receivePort = isolate.lookup(receivePortId); |
| 212 | 622 if (receivePort == null) return; // ReceivePort has been closed. |
| 213 static _serializeObject(obj) native { | 623 _globalState.topEventLoop.enqueue(isolate, () { |
| 214 return new Serializer().traverse(obj); | 624 if (receivePort._callback != null) { |
| 215 } | 625 receivePort._callback( |
| 216 | 626 _deserializeMessage(message), _deserializeMessage(replyTo)); |
| 217 static _deserializeMessage(message) native { | 627 } |
| 218 return new Deserializer().deserialize(message); | 628 }, 'receive ' + message); |
| 629 } else { | |
| 630 var worker; | |
| 631 // communication between workers go through the main worker | |
| 632 if (_globalState.inWorker) { | |
| 633 worker = _globalState.mainWorker; | |
| 634 } else { | |
| 635 // TODO(sigmund): make sure this works | |
| 636 worker = _globalState.workers[workerId]; | |
| 637 } | |
| 638 worker.postMessage(_serializeMessage({ | |
| 639 'command': 'message', | |
| 640 'workerId': workerId, | |
| 641 'isolateId': isolateId, | |
| 642 'portId': receivePortId, | |
| 643 'msg': message, | |
| 644 'replyTo': replyTo })); | |
| 645 } | |
| 219 } | 646 } |
| 220 } | 647 } |
| OLD | NEW |