OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
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. | |
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 [: startRootIsolate(main); :] | |
20 * when it determines that this wrapping is needed. For single-isolate | |
21 * applications (e.g. hello world), this call is not emitted. | |
22 */ | |
23 void startRootIsolate(entry) { | |
24 _globalState = new GlobalState(); | |
25 | |
26 // Don't start the main loop again, if we are in a worker. | |
27 if (_globalState.isWorker) return; | |
28 final rootContext = new IsolateContext(); | |
29 _globalState.rootContext = rootContext; | |
30 _fillStatics(rootContext); | |
31 | |
32 // BUG(5151491): Setting currentContext should not be necessary, but | |
33 // because closures passed to the DOM as event handlers do not bind their | |
34 // isolate automatically we try to give them a reasonable context to live in | |
35 // by having a "default" isolate (the first one created). | |
36 _globalState.currentContext = rootContext; | |
37 | |
38 rootContext.eval(entry); | |
39 _globalState.topEventLoop.run(); | |
40 } | |
41 | |
42 void _fillStatics(context) native @""" | |
43 $globals = context.isolateStatics; | |
44 $static_init(); | |
45 """; | |
46 | |
47 /** Global state associated with the current worker. See [_globalState]. */ | |
48 // TODO(sigmund): split in multiple classes: global, thread, main-worker states? | |
49 class GlobalState { | |
50 | |
51 /** Next available isolate id. */ | |
52 int nextIsolateId = 0; | |
53 | |
54 /** Worker id associated with this worker. */ | |
55 int currentWorkerId = 0; | |
56 | |
57 /** | |
58 * Next available worker id. Only used by the main worker to assign a unique | |
59 * id to each worker created by it. | |
60 */ | |
61 int nextWorkerId = 1; | |
62 | |
63 /** Context for the currently running [Isolate]. */ | |
64 IsolateContext currentContext = null; | |
65 | |
66 /** Context for the root [Isolate] that first run in this worker. */ | |
67 IsolateContext rootContext = null; | |
68 | |
69 /** The top-level event loop. */ | |
70 EventLoop topEventLoop; | |
71 | |
72 /** Whether this program is running in a background worker. */ | |
73 bool isWorker; | |
74 | |
75 /** Whether this program is running in a UI worker. */ | |
76 bool inWindow; | |
77 | |
78 /** Whether we support spawning workers. */ | |
79 bool supportsWorkers; | |
80 | |
81 /** | |
82 * Whether to use web workers when implementing isolates. Set to false for | |
83 * debugging/testing. | |
84 */ | |
85 bool get useWorkers() => supportsWorkers; | |
86 | |
87 /** | |
88 * Whether to use the web-worker JSON-based message serialization protocol. By | |
89 * default this is only used with web workers. For debugging, you can force | |
90 * using this protocol by changing this field value to [true]. | |
91 */ | |
92 bool get needSerialization() => useWorkers; | |
93 | |
94 /** | |
95 * Registry of isolates. Isolates must be registered if, and only if, receive | |
96 * ports are alive. Normally no open receive-ports means that the isolate is | |
97 * dead, but DOM callbacks could resurrect it. | |
98 */ | |
99 Map<int, IsolateContext> isolates; | |
100 | |
101 /** Reference to the main worker. */ | |
102 MainWorker mainWorker; | |
103 | |
104 /** Registry of active workers. Only used in the main worker. */ | |
105 Map<int, var> workers; | |
106 | |
107 GlobalState() { | |
108 topEventLoop = new EventLoop(); | |
109 isolates = {}; | |
110 workers = {}; | |
111 mainWorker = new MainWorker(); | |
112 _nativeInit(); | |
113 } | |
114 | |
115 void _nativeInit() native @""" | |
116 this.isWorker = typeof ($globalThis['importScripts']) != 'undefined'; | |
117 this.inWindow = typeof(window) !== 'undefined'; | |
118 this.supportsWorkers = this.isWorker || | |
119 ((typeof $globalThis['Worker']) != 'undefined'); | |
120 | |
121 // if workers are supported, treat this as a main worker: | |
122 if (this.supportsWorkers) { | |
123 $globalThis.onmessage = function(e) { | |
124 IsolateNatives._processWorkerMessage(this.mainWorker, e); | |
125 }; | |
126 } | |
127 """ { | |
128 // Declare that the native code has a dependency on this fn. | |
129 IsolateNatives._processWorkerMessage(null, null); | |
130 } | |
131 | |
132 /** | |
133 * Close the worker running this code, called when there is nothing else to | |
134 * run. | |
135 */ | |
136 void closeWorker() { | |
137 if (isWorker) { | |
138 if (!isolates.isEmpty()) return; | |
139 mainWorker.postMessage( | |
140 _serializeMessage({'command': 'close'})); | |
141 } else if (isolates.containsKey(rootContext.id) && workers.isEmpty() && | |
142 !supportsWorkers && !inWindow) { | |
143 // This should only trigger when running on the command-line. | |
144 // We don't want this check to execute in the browser where the isolate | |
145 // might still be alive due to DOM callbacks. | |
146 throw new Exception("Program exited with open ReceivePorts."); | |
147 } | |
148 } | |
149 } | |
150 | |
151 _serializeMessage(message) { | |
152 if (_globalState.needSerialization) { | |
153 return new Serializer().traverse(message); | |
154 } else { | |
155 return new Copier().traverse(message); | |
156 } | |
157 } | |
158 | |
159 _deserializeMessage(message) { | |
160 if (_globalState.needSerialization) { | |
161 return new Deserializer().deserialize(message); | |
162 } else { | |
163 // Nothing more to do. | |
164 return message; | |
165 } | |
166 } | |
167 | |
168 /** Wait until all ports in a message are resolved. */ | |
169 _waitForPendingPorts(var message, void callback()) { | |
170 final finder = new PendingSendPortFinder(); | |
171 finder.traverse(message); | |
172 Futures.wait(finder.ports).then((_) => callback()); | |
173 } | |
174 | |
175 /** Default worker. */ | |
176 class MainWorker { | |
177 int id = 0; | |
178 void postMessage(msg) native "return \$globalThis.postMessage(msg);"; | |
179 void set onmessage(f) native "\$globalThis.onmessage = f;"; | |
180 void terminate() {} | |
181 } | |
182 | |
183 /** | |
184 * A web worker. This type is also defined in 'dart:dom', but we define it here | |
185 * to avoid introducing a dependency from corelib to dom. This definition uses a | |
186 * 'hidden' type (* prefix on the native name) to enforce that the type is | |
187 * defined dynamically only when web workers are actually available. | |
188 */ | |
189 class _Worker native "*Worker" { | |
190 get id() native "return this.id;"; | |
191 void set id(i) native "this.id = i;"; | |
192 void set onmessage(f) native "this.onmessage = f;"; | |
193 void postMessage(msg) native "return this.postMessage(msg);"; | |
194 } | |
195 | |
196 /** Context information tracked for each isolate. */ | |
197 class IsolateContext { | |
198 /** Current isolate id. */ | |
199 int id; | |
200 | |
201 /** Registry of receive ports currently active on this isolate. */ | |
202 Map<int, ReceivePort> ports; | |
203 | |
204 /** Holds isolate globals (statics and top-level properties). */ | |
205 var isolateStatics; // native object containing all globals of an isolate. | |
206 | |
207 IsolateContext() { | |
208 id = _globalState.nextIsolateId++; | |
209 ports = {}; | |
210 initGlobals(); | |
211 } | |
212 | |
213 // these are filled lazily the first time the isolate starts running. | |
214 void initGlobals() native 'this.isolateStatics = {};'; | |
215 | |
216 /** | |
217 * Run [code] in the context of the isolate represented by [this]. Note this | |
218 * is called from JavaScript (see $wrap_call in corejs.dart). | |
219 */ | |
220 void eval(Function code) { | |
221 var old = _globalState.currentContext; | |
222 _globalState.currentContext = this; | |
223 this._setGlobals(); | |
224 var result = null; | |
225 try { | |
226 result = code(); | |
227 } finally { | |
228 _globalState.currentContext = old; | |
229 if (old != null) old._setGlobals(); | |
230 } | |
231 return result; | |
232 } | |
233 | |
234 void _setGlobals() native @'$globals = this.isolateStatics;'; | |
235 | |
236 /** Lookup a port registered for this isolate. */ | |
237 ReceivePort lookup(int id) => ports[id]; | |
238 | |
239 /** Register a port on this isolate. */ | |
240 void register(int portId, ReceivePort port) { | |
241 if (ports.containsKey(portId)) { | |
242 throw new Exception("Registry: ports must be registered only once."); | |
243 } | |
244 ports[portId] = port; | |
245 _globalState.isolates[id] = this; // indicate this isolate is active | |
246 } | |
247 | |
248 /** Unregister a port on this isolate. */ | |
249 void unregister(int portId) { | |
250 ports.remove(portId); | |
251 if (ports.isEmpty()) { | |
252 _globalState.isolates.remove(id); // indicate this isolate is not active | |
253 } | |
254 } | |
255 } | |
256 | |
257 /** Represent the event loop on a javascript thread (DOM or worker). */ | |
258 class EventLoop { | |
259 Queue<IsolateEvent> events; | |
260 | |
261 EventLoop() : events = new Queue<IsolateEvent>(); | |
262 | |
263 void enqueue(isolate, fn, msg) { | |
264 events.addLast(new IsolateEvent(isolate, fn, msg)); | |
265 } | |
266 | |
267 IsolateEvent dequeue() { | |
268 if (events.isEmpty()) return null; | |
269 return events.removeFirst(); | |
270 } | |
271 | |
272 /** Process a single event, if any. */ | |
273 bool runIteration() { | |
274 final event = dequeue(); | |
275 if (event == null) { | |
276 _globalState.closeWorker(); | |
277 return false; | |
278 } | |
279 event.process(); | |
280 return true; | |
281 } | |
282 | |
283 /** Function equivalent to [:window.setTimeout:] when available, or null. */ | |
284 static Function _wrapSetTimeout() native """ | |
285 return typeof window != 'undefined' ? | |
286 function(a, b) { window.setTimeout(a, b); } : undefined; | |
287 """; | |
288 | |
289 /** | |
290 * Runs multiple iterations of the run-loop. If possible, each iteration is | |
291 * run asynchronously. | |
292 */ | |
293 void _runHelper() { | |
294 final setTimeout = _wrapSetTimeout(); | |
295 if (setTimeout != null) { | |
296 // Run each iteration from the browser's top event loop. | |
297 void next() { | |
298 if (!runIteration()) return; | |
299 setTimeout(next, 0); | |
300 } | |
301 next(); | |
302 } else { | |
303 // Run synchronously until no more iterations are available. | |
304 while (runIteration()) {} | |
305 } | |
306 } | |
307 | |
308 /** | |
309 * Call [_runHelper] but ensure that worker exceptions are propragated. Note | |
310 * this is called from JavaScript (see $wrap_call in corejs.dart). | |
311 */ | |
312 void run() { | |
313 if (!_globalState.isWorker) { | |
314 _runHelper(); | |
315 } else { | |
316 try { | |
317 _runHelper(); | |
318 } catch(var e, var trace) { | |
319 _globalState.mainWorker.postMessage(_serializeMessage( | |
320 {'command': 'error', 'msg': '$e\n$trace' })); | |
321 } | |
322 } | |
323 } | |
324 } | |
325 | |
326 /** An event in the top-level event queue. */ | |
327 class IsolateEvent { | |
328 IsolateContext isolate; | |
329 Function fn; | |
330 String message; | |
331 | |
332 IsolateEvent(this.isolate, this.fn, this.message); | |
333 | |
334 void process() { | |
335 isolate.eval(fn); | |
336 } | |
337 } | |
338 | |
339 /** Common functionality to all send ports. */ | |
340 class BaseSendPort implements SendPort { | |
341 /** Id for the destination isolate. */ | |
342 final int _isolateId; | |
343 | |
344 BaseSendPort(this._isolateId); | |
345 | |
346 ReceivePortSingleShotImpl call(var message) { | |
347 final result = new ReceivePortSingleShotImpl(); | |
348 this.send(message, result.toSendPort()); | |
349 return result; | |
350 } | |
351 | |
352 static void checkReplyTo(SendPort replyTo) { | |
353 if (replyTo !== null | |
354 && replyTo is! NativeJsSendPort | |
355 && replyTo is! WorkerSendPort | |
356 && replyTo is! BufferingSendPort) { | |
357 throw new Exception("SendPort.send: Illegal replyTo port type"); | |
358 } | |
359 } | |
360 | |
361 // TODO(sigmund): replace the current SendPort.call with the following: | |
362 //Future call(var message) { | |
363 // Â final completer = new Completer(); | |
364 // Â final port = new ReceivePort.singleShot(); | |
365 // Â send(message, port.toSendPort()); | |
366 // Â port.receive((value, ignoreReplyTo) { | |
367 // Â Â Â if (value is Exception) { | |
368 // Â completer.completeException(value); | |
369 // } else { | |
370 // completer.complete(value); | |
371 // } | |
372 // }); | |
373 // Â return completer.future; | |
374 //} | |
375 | |
376 abstract void send(var message, [SendPort replyTo]); | |
377 abstract bool operator ==(var other); | |
378 abstract int hashCode(); | |
379 } | |
380 | |
381 /** A send port that delivers messages in-memory via native JavaScript calls. */ | |
382 class NativeJsSendPort extends BaseSendPort implements SendPort { | |
383 final ReceivePortImpl _receivePort; | |
384 | |
385 const NativeJsSendPort(this._receivePort, int isolateId) : super(isolateId); | |
386 | |
387 void send(var message, [SendPort replyTo = null]) { | |
388 _waitForPendingPorts([message, replyTo], () { | |
389 checkReplyTo(replyTo); | |
390 // Check that the isolate still runs and the port is still open | |
391 final isolate = _globalState.isolates[_isolateId]; | |
392 if (isolate == null) return; | |
393 if (_receivePort._callback == null) return; | |
394 | |
395 // We force serialization/deserialization as a simple way to ensure | |
396 // isolate communication restrictions are respected between isolates that | |
397 // live in the same worker. NativeJsSendPort delivers both messages from | |
398 // the same worker and messages from other workers. In particular, | |
399 // messages sent from a worker via a WorkerSendPort are received at | |
400 // [_processWorkerMessage] and forwarded to a native port. In such cases, | |
401 // here we'll see [_globalState.currentContext == null]. | |
402 final shouldSerialize = _globalState.currentContext != null | |
403 && _globalState.currentContext.id != _isolateId; | |
404 var msg = message; | |
405 var reply = replyTo; | |
406 if (shouldSerialize) { | |
407 msg = _serializeMessage(msg); | |
408 reply = _serializeMessage(reply); | |
409 } | |
410 _globalState.topEventLoop.enqueue(isolate, () { | |
411 if (_receivePort._callback != null) { | |
412 if (shouldSerialize) { | |
413 msg = _deserializeMessage(msg); | |
414 reply = _deserializeMessage(reply); | |
415 } | |
416 _receivePort._callback(msg, reply); | |
417 } | |
418 }, 'receive ' + message); | |
419 }); | |
420 } | |
421 | |
422 bool operator ==(var other) => (other is NativeJsSendPort) && | |
423 (_receivePort == other._receivePort); | |
424 | |
425 int hashCode() => _receivePort._id; | |
426 } | |
427 | |
428 /** A send port that delivers messages via worker.postMessage. */ | |
429 class WorkerSendPort extends BaseSendPort implements SendPort { | |
430 final int _workerId; | |
431 final int _receivePortId; | |
432 | |
433 const WorkerSendPort(this._workerId, int isolateId, this._receivePortId) | |
434 : super(isolateId); | |
435 | |
436 void send(var message, [SendPort replyTo = null]) { | |
437 _waitForPendingPorts([message, replyTo], () { | |
438 checkReplyTo(replyTo); | |
439 final workerMessage = _serializeMessage({ | |
440 'command': 'message', | |
441 'port': this, | |
442 'msg': message, | |
443 'replyTo': replyTo}); | |
444 | |
445 if (_globalState.isWorker) { | |
446 // communication from one worker to another go through the main worker: | |
447 _globalState.mainWorker.postMessage(workerMessage); | |
448 } else { | |
449 _globalState.workers[_workerId].postMessage(workerMessage); | |
450 } | |
451 }); | |
452 } | |
453 | |
454 bool operator ==(var other) { | |
455 return (other is WorkerSendPort) && | |
456 (_workerId == other._workerId) && | |
457 (_isolateId == other._isolateId) && | |
458 (_receivePortId == other._receivePortId); | |
459 } | |
460 | |
461 int hashCode() { | |
462 // TODO(sigmund): use a standard hash when we get one available in corelib. | |
463 return (_workerId << 16) ^ (_isolateId << 8) ^ _receivePortId; | |
464 } | |
465 } | |
466 | |
467 /** A port that buffers messages until an underlying port gets resolved. */ | |
468 class BufferingSendPort extends BaseSendPort implements SendPort { | |
469 /** Internal counter to assign unique ids to each port. */ | |
470 static int _idCount = 0; | |
471 | |
472 /** For implementing equals and hashcode. */ | |
473 final int _id; | |
474 | |
475 /** Underlying port, when resolved. */ | |
476 SendPort _port; | |
477 | |
478 /** | |
479 * Future of the underlying port, so that we can detect when this port can be | |
480 * sent on messages. | |
481 */ | |
482 Future<SendPort> _futurePort; | |
483 | |
484 /** Pending messages (and reply ports). */ | |
485 List pending; | |
486 | |
487 BufferingSendPort(isolateId, this._futurePort) | |
488 : super(isolateId), _id = _idCount, pending = [] { | |
489 _idCount++; | |
490 _futurePort.then((p) { | |
491 _port = p; | |
492 for (final item in pending) { | |
493 p.send(item['message'], item['replyTo']); | |
494 } | |
495 pending = null; | |
496 }); | |
497 } | |
498 | |
499 BufferingSendPort.fromPort(isolateId, this._port) | |
500 : super(isolateId), _id = _idCount { | |
501 _idCount++; | |
502 } | |
503 | |
504 void send(var message, [SendPort replyTo]) { | |
505 if (_port != null) { | |
506 _port.send(message, replyTo); | |
507 } else { | |
508 pending.add({'message': message, 'replyTo': replyTo}); | |
509 } | |
510 } | |
511 | |
512 bool operator ==(var other) => other is BufferingSendPort && _id == other._id; | |
513 int hashCode() => _id; | |
514 } | |
515 | |
516 /** Default factory for receive ports. */ | |
517 class ReceivePortFactory { | |
518 | |
519 factory ReceivePort() { | |
520 return new ReceivePortImpl(); | |
521 } | |
522 | |
523 factory ReceivePort.singleShot() { | |
524 return new ReceivePortSingleShotImpl(); | |
525 } | |
526 } | |
527 | |
528 /** Implementation of a multi-use [ReceivePort] on top of JavaScript. */ | |
529 class ReceivePortImpl implements ReceivePort { | |
530 int _id; | |
531 Function _callback; | |
532 static int _nextFreeId = 1; | |
533 | |
534 ReceivePortImpl() | |
535 : _id = _nextFreeId++ { | |
536 _globalState.currentContext.register(_id, this); | |
537 } | |
538 | |
539 void receive(void onMessage(var message, SendPort replyTo)) { | |
540 _callback = onMessage; | |
541 } | |
542 | |
543 void close() { | |
544 _callback = null; | |
545 _globalState.currentContext.unregister(_id); | |
546 } | |
547 | |
548 SendPort toSendPort() { | |
549 return new NativeJsSendPort(this, _globalState.currentContext.id); | |
550 } | |
551 } | |
552 | |
553 /** Implementation of a single-shot [ReceivePort]. */ | |
554 class ReceivePortSingleShotImpl implements ReceivePort { | |
555 | |
556 ReceivePortSingleShotImpl() : _port = new ReceivePortImpl() { } | |
557 | |
558 void receive(void callback(var message, SendPort replyTo)) { | |
559 _port.receive((var message, SendPort replyTo) { | |
560 _port.close(); | |
561 callback(message, replyTo); | |
562 }); | |
563 } | |
564 | |
565 void close() { | |
566 _port.close(); | |
567 } | |
568 | |
569 SendPort toSendPort() => _port.toSendPort(); | |
570 | |
571 final ReceivePortImpl _port; | |
572 } | |
573 | |
574 final String _SPAWNED_SIGNAL = "spawned"; | |
575 | |
576 class IsolateNatives { | |
577 | |
578 /** JavaScript-specific implementation to spawn an isolate. */ | |
579 static Future<SendPort> spawn(Isolate isolate, bool isLight) { | |
580 Completer<SendPort> completer = new Completer<SendPort>(); | |
581 ReceivePort port = new ReceivePort.singleShot(); | |
582 port.receive((msg, SendPort replyPort) { | |
583 assert(msg == _SPAWNED_SIGNAL); | |
584 completer.complete(replyPort); | |
585 }); | |
586 | |
587 // TODO(floitsch): throw exception if isolate's class doesn't have a | |
588 // default constructor. | |
589 if (_globalState.useWorkers && !isLight) { | |
590 _startWorker(isolate, port.toSendPort()); | |
591 } else { | |
592 _startNonWorker(isolate, port.toSendPort()); | |
593 } | |
594 | |
595 return completer.future; | |
596 } | |
597 | |
598 static SendPort _startWorker(Isolate runnable, SendPort replyPort) { | |
599 var factoryName = _getJSConstructorName(runnable); | |
600 if (_globalState.isWorker) { | |
601 _globalState.mainWorker.postMessage(_serializeMessage({ | |
602 'command': 'spawn-worker', | |
603 'factoryName': factoryName, | |
604 'replyPort': _serializeMessage(replyPort)})); | |
605 } else { | |
606 _spawnWorker(factoryName, _serializeMessage(replyPort)); | |
607 } | |
608 } | |
609 | |
610 /** | |
611 * The src url for the script tag that loaded this code. Used to create | |
612 * JavaScript workers. | |
613 */ | |
614 static String get _thisScript() => | |
615 _thisScriptCache != null ? _thisScriptCache : _computeThisScript(); | |
616 | |
617 static String _thisScriptCache; | |
618 | |
619 // TODO(sigmund): fix - this code should be run synchronously when loading the | |
620 // script. Running lazily on DOMContentLoaded will yield incorrect results. | |
621 static String _computeThisScript() native @""" | |
622 if (!$globalState.supportsWorkers || $globalState.isWorker) return null; | |
623 | |
624 // TODO(5334778): Find a cross-platform non-brittle way of getting the | |
625 // currently running script. | |
626 var scripts = document.getElementsByTagName('script'); | |
627 // The scripts variable only contains the scripts that have already been | |
628 // executed. The last one is the currently running script. | |
629 var script = scripts[scripts.length - 1]; | |
630 var src = script && script.src; | |
631 if (!src) { | |
632 // TODO() | |
633 src = "FIXME:5407062" + "_" + Math.random().toString(); | |
634 if (script) script.src = src; | |
635 } | |
636 IsolateNatives._thisScriptCache = src; | |
637 return src; | |
638 """; | |
639 | |
640 /** Starts a new worker with the given URL. */ | |
641 static _Worker _newWorker(url) native "return new Worker(url);"; | |
642 | |
643 /** | |
644 * Spawns an isolate in a worker. [factoryName] is the Javascript constructor | |
645 * name for the isolate entry point class. | |
646 */ | |
647 static void _spawnWorker(factoryName, serializedReplyPort) { | |
648 final worker = _newWorker(_thisScript); | |
649 worker.onmessage = (e) { _processWorkerMessage(worker, e); }; | |
650 var workerId = _globalState.nextWorkerId++; | |
651 // We also store the id on the worker itself so that we can unregister it. | |
652 worker.id = workerId; | |
653 _globalState.workers[workerId] = worker; | |
654 worker.postMessage(_serializeMessage({ | |
655 'command': 'start', | |
656 'id': workerId, | |
657 'replyTo': serializedReplyPort, | |
658 'factoryName': factoryName })); | |
659 } | |
660 | |
661 /** | |
662 * Assume that [e] is a browser message event and extract its message data. | |
663 * We don't import the dom explicitly so, when workers are disabled, this | |
664 * library can also run on top of nodejs. | |
665 */ | |
666 static _getEventData(e) native "return e.data"; | |
667 | |
668 /** | |
669 * Process messages on a worker, either to control the worker instance or to | |
670 * pass messages along to the isolate running in the worker. | |
671 */ | |
672 static void _processWorkerMessage(sender, e) { | |
673 var msg = _deserializeMessage(_getEventData(e)); | |
674 switch (msg['command']) { | |
675 // TODO(sigmund): delete after we migrate to Isolate2 | |
676 case 'start': | |
677 _globalState.currentWorkerId = msg['id']; | |
678 var runnerObject = | |
679 _allocate(_getJSConstructorFromName(msg['factoryName'])); | |
680 var serializedReplyTo = msg['replyTo']; | |
681 _globalState.topEventLoop.enqueue(new IsolateContext(), function() { | |
682 var replyTo = _deserializeMessage(serializedReplyTo); | |
683 _startIsolate(runnerObject, replyTo); | |
684 }, 'worker-start'); | |
685 _globalState.topEventLoop.run(); | |
686 break; | |
687 case 'start2': | |
688 _globalState.currentWorkerId = msg['id']; | |
689 Function entryPoint = _getJSFunctionFromName(msg['functionName']); | |
690 var replyTo = _deserializeMessage(msg['replyTo']); | |
691 _globalState.topEventLoop.enqueue(new IsolateContext(), function() { | |
692 _startIsolate2(entryPoint, replyTo); | |
693 }, 'worker-start'); | |
694 _globalState.topEventLoop.run(); | |
695 break; | |
696 // TODO(sigmund): delete after we migrate to Isolate2 | |
697 case 'spawn-worker': | |
698 _spawnWorker(msg['factoryName'], msg['replyPort']); | |
699 break; | |
700 case 'spawn-worker2': | |
701 _spawnWorker2(msg['functionName'], msg['uri'], msg['replyPort']); | |
702 break; | |
703 case 'message': | |
704 msg['port'].send(msg['msg'], msg['replyTo']); | |
705 _globalState.topEventLoop.run(); | |
706 break; | |
707 case 'close': | |
708 _log("Closing Worker"); | |
709 _globalState.workers.remove(sender.id); | |
710 sender.terminate(); | |
711 _globalState.topEventLoop.run(); | |
712 break; | |
713 case 'log': | |
714 _log(msg['msg']); | |
715 break; | |
716 case 'print': | |
717 if (_globalState.isWorker) { | |
718 _globalState.mainWorker.postMessage( | |
719 _serializeMessage({'command': 'print', 'msg': msg})); | |
720 } else { | |
721 print(msg['msg']); | |
722 } | |
723 break; | |
724 case 'error': | |
725 throw msg['msg']; | |
726 } | |
727 } | |
728 | |
729 /** Log a message, forwarding to the main worker if appropriate. */ | |
730 static _log(msg) { | |
731 if (_globalState.isWorker) { | |
732 _globalState.mainWorker.postMessage( | |
733 _serializeMessage({'command': 'log', 'msg': msg })); | |
734 } else { | |
735 try { | |
736 _consoleLog(msg); | |
737 } catch(e, trace) { | |
738 throw new Exception(trace); | |
739 } | |
740 } | |
741 } | |
742 | |
743 static void _consoleLog(msg) native "\$globalThis.console.log(msg);"; | |
744 | |
745 | |
746 /** | |
747 * Extract the constructor of runnable, so it can be allocated in another | |
748 * isolate. | |
749 */ | |
750 static var _getJSConstructor(Isolate runnable) native """ | |
751 return runnable.constructor; | |
752 """; | |
753 | |
754 /** Extract the constructor name of a runnable */ | |
755 // TODO(sigmund): find a browser-generic way to support this. | |
756 static var _getJSConstructorName(Isolate runnable) native """ | |
757 return runnable.constructor.name; | |
758 """; | |
759 | |
760 /** Find a constructor given it's name. */ | |
761 static var _getJSConstructorFromName(String factoryName) native """ | |
762 return \$globalThis[factoryName]; | |
763 """; | |
764 | |
765 static var _getJSFunctionFromName(String functionName) native """ | |
766 return \$globalThis[functionName]; | |
767 """; | |
768 | |
769 static String _getJSFunctionName(Function f) native "return f.name || null;"; | |
770 | |
771 /** Create a new JavasSript object instance given it's constructor. */ | |
772 static var _allocate(var ctor) native "return new ctor();"; | |
773 | |
774 /** Starts a non-worker isolate. */ | |
775 static SendPort _startNonWorker(Isolate runnable, SendPort replyTo) { | |
776 // Spawn a new isolate and create the receive port in it. | |
777 final spawned = new IsolateContext(); | |
778 | |
779 // Instead of just running the provided runnable, we create a | |
780 // new cloned instance of it with a fresh state in the spawned | |
781 // isolate. This way, we do not get cross-isolate references | |
782 // through the runnable. | |
783 final ctor = _getJSConstructor(runnable); | |
784 _globalState.topEventLoop.enqueue(spawned, function() { | |
785 _startIsolate(_allocate(ctor), replyTo); | |
786 }, 'nonworker start'); | |
787 } | |
788 | |
789 /** Given a ready-to-start runnable, start running it. */ | |
790 static void _startIsolate(Isolate isolate, SendPort replyTo) { | |
791 _fillStatics(_globalState.currentContext); | |
792 ReceivePort port = new ReceivePort(); | |
793 replyTo.send(_SPAWNED_SIGNAL, port.toSendPort()); | |
794 isolate._run(port); | |
795 } | |
796 | |
797 // TODO(sigmund): clean up above, after we make the new API the default: | |
798 | |
799 static _spawn2(String functionName, String uri, bool isLight) { | |
800 Completer<SendPort> completer = new Completer<SendPort>(); | |
801 ReceivePort port = new ReceivePort.singleShot(); | |
802 port.receive((msg, SendPort replyPort) { | |
803 assert(msg == _SPAWNED_SIGNAL); | |
804 completer.complete(replyPort); | |
805 }); | |
806 | |
807 SendPort signalReply = port.toSendPort(); | |
808 | |
809 if (_globalState.useWorkers && !isLight) { | |
810 _startWorker2(functionName, uri, signalReply); | |
811 } else { | |
812 _startNonWorker2(functionName, uri, signalReply); | |
813 } | |
814 return new BufferingSendPort( | |
815 _globalState.currentContext.id, completer.future); | |
816 } | |
817 | |
818 static SendPort _startWorker2( | |
819 String functionName, String uri, SendPort replyPort) { | |
820 if (_globalState.isWorker) { | |
821 _globalState.mainWorker.postMessage(_serializeMessage({ | |
822 'command': 'spawn-worker2', | |
823 'functionName': functionName, | |
824 'uri': uri, | |
825 'replyPort': replyPort})); | |
826 } else { | |
827 _spawnWorker2(functionName, uri, replyPort); | |
828 } | |
829 } | |
830 | |
831 static SendPort _startNonWorker2( | |
832 String functionName, String uri, SendPort replyPort) { | |
833 // TODO(eub): support IE9 using an iframe -- Dart issue 1702. | |
834 if (uri != null) throw new UnsupportedOperationException( | |
835 "Currently Isolate2.fromUri is not supported without web workers."); | |
836 _globalState.topEventLoop.enqueue(new IsolateContext(), function() { | |
837 final func = _getJSFunctionFromName(functionName); | |
838 _startIsolate2(func, replyPort); | |
839 }, 'nonworker start'); | |
840 } | |
841 | |
842 static void _startIsolate2(Function topLevel, SendPort replyTo) { | |
843 _fillStatics(_globalState.currentContext); | |
844 final port = new ReceivePort(); | |
845 replyTo.send(_SPAWNED_SIGNAL, port.toSendPort()); | |
846 topLevel(port); | |
847 } | |
848 | |
849 /** | |
850 * Spawns an isolate in a worker. [factoryName] is the Javascript constructor | |
851 * name for the isolate entry point class. | |
852 */ | |
853 static void _spawnWorker2(functionName, uri, replyPort) { | |
854 // TODO(eub): convert to 'main' once we switch back to port at top-level. | |
855 if (functionName == null) functionName = 'isolateMain'; | |
856 if (uri == null) uri = _thisScript; | |
857 final worker = _newWorker(uri); | |
858 worker.onmessage = (e) { _processWorkerMessage(worker, e); }; | |
859 var workerId = _globalState.nextWorkerId++; | |
860 // We also store the id on the worker itself so that we can unregister it. | |
861 worker.id = workerId; | |
862 _globalState.workers[workerId] = worker; | |
863 worker.postMessage(_serializeMessage({ | |
864 'command': 'start2', | |
865 'id': workerId, | |
866 // Note: we serialize replyPort twice because the child worker needs to | |
867 // first deserialize the worker id, before it can correctly deserialize | |
868 // the port (port deserialization is sensitive to what is the current | |
869 // workerId). | |
870 'replyTo': _serializeMessage(replyPort), | |
871 'functionName': functionName })); | |
872 } | |
873 } | |
874 | |
875 class Isolate2Impl implements Isolate2 { | |
876 SendPort sendPort; | |
877 | |
878 Isolate2Impl(this.sendPort); | |
879 | |
880 void stop() {} | |
881 } | |
882 | |
883 class IsolateFactory implements Isolate2 { | |
884 | |
885 factory Isolate2.fromCode(Function topLevelFunction) { | |
886 final name = IsolateNatives._getJSFunctionName(topLevelFunction); | |
887 if (name == null) { | |
888 throw new UnsupportedOperationException( | |
889 "only top-level functions can be spawned."); | |
890 } | |
891 return new Isolate2Impl(IsolateNatives._spawn2(name, null, false)); | |
892 } | |
893 | |
894 factory Isolate2.fromUri(String uri) { | |
895 return new Isolate2Impl(IsolateNatives._spawn2(null, uri, false)); | |
896 } | |
897 } | |
OLD | NEW |