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 library _isolate_helper; | |
6 | |
7 import 'dart:isolate'; | |
8 import 'dart:uri'; | |
9 | |
10 /** | |
11 * Called by the compiler to support switching | |
12 * between isolates when we get a callback from the DOM. | |
13 */ | |
14 void _callInIsolate(_IsolateContext isolate, Function function) { | |
15 isolate.eval(function); | |
16 _globalState.topEventLoop.run(); | |
17 } | |
18 | |
19 /** | |
20 * Called by the compiler to fetch the current isolate context. | |
21 */ | |
22 _IsolateContext _currentIsolate() => _globalState.currentContext; | |
23 | |
24 /******************************************************** | |
25 Inserted from lib/isolate/dart2js/compiler_hooks.dart | |
26 ********************************************************/ | |
27 | |
28 /** | |
29 * Wrapper that takes the dart entry point and runs it within an isolate. The | |
30 * dart2js compiler will inject a call of the form | |
31 * [: startRootIsolate(main); :] when it determines that this wrapping | |
32 * is needed. For single-isolate applications (e.g. hello world), this | |
33 * call is not emitted. | |
34 */ | |
35 void startRootIsolate(entry) { | |
36 _globalState = new _Manager(); | |
37 | |
38 // Don't start the main loop again, if we are in a worker. | |
39 if (_globalState.isWorker) return; | |
40 final rootContext = new _IsolateContext(); | |
41 _globalState.rootContext = rootContext; | |
42 _fillStatics(rootContext); | |
43 | |
44 // BUG(5151491): Setting currentContext should not be necessary, but | |
45 // because closures passed to the DOM as event handlers do not bind their | |
46 // isolate automatically we try to give them a reasonable context to live in | |
47 // by having a "default" isolate (the first one created). | |
48 _globalState.currentContext = rootContext; | |
49 | |
50 rootContext.eval(entry); | |
51 _globalState.topEventLoop.run(); | |
52 } | |
53 | |
54 /******************************************************** | |
55 Inserted from lib/isolate/dart2js/isolateimpl.dart | |
56 ********************************************************/ | |
57 | |
58 /** | |
59 * Concepts used here: | |
60 * | |
61 * "manager" - A manager contains one or more isolates, schedules their | |
62 * execution, and performs other plumbing on their behalf. The isolate | |
63 * present at the creation of the manager is designated as its "root isolate". | |
64 * A manager may, for example, be implemented on a web Worker. | |
65 * | |
66 * [_Manager] - State present within a manager (exactly once, as a global). | |
67 * | |
68 * [_ManagerStub] - A handle held within one manager that allows interaction | |
69 * with another manager. A target manager may be addressed by zero or more | |
70 * [_ManagerStub]s. | |
71 * | |
72 */ | |
73 | |
74 /** | |
75 * A native object that is shared across isolates. This object is visible to all | |
76 * isolates running under the same manager (either UI or background web worker). | |
77 * | |
78 * This is code that is intended to 'escape' the isolate boundaries in order to | |
79 * implement the semantics of isolates in JavaScript. Without this we would have | |
80 * been forced to implement more code (including the top-level event loop) in | |
81 * JavaScript itself. | |
82 */ | |
83 // TODO(eub, sigmund): move the "manager" to be entirely in JS. | |
84 // Running any Dart code outside the context of an isolate gives it | |
85 // the change to break the isolate abstraction. | |
86 _Manager get _globalState => JS("_Manager", r"$globalState"); | |
87 set _globalState(_Manager val) { | |
88 JS("void", r"$globalState = #", val); | |
89 } | |
90 | |
91 void _fillStatics(context) { | |
92 JS("void", r"$globals = #.isolateStatics", context); | |
93 JS("void", r"$static_init()"); | |
94 } | |
95 | |
96 ReceivePort lazyPort; | |
97 | |
98 /** State associated with the current manager. See [globalState]. */ | |
99 // TODO(sigmund): split in multiple classes: global, thread, main-worker states? | |
100 class _Manager { | |
101 | |
102 /** Next available isolate id within this [_Manager]. */ | |
103 int nextIsolateId = 0; | |
104 | |
105 /** id assigned to this [_Manager]. */ | |
106 int currentManagerId = 0; | |
107 | |
108 /** | |
109 * Next available manager id. Only used by the main manager to assign a unique | |
110 * id to each manager created by it. | |
111 */ | |
112 int nextManagerId = 1; | |
113 | |
114 /** Context for the currently running [Isolate]. */ | |
115 _IsolateContext currentContext = null; | |
116 | |
117 /** Context for the root [Isolate] that first run in this [_Manager]. */ | |
118 _IsolateContext rootContext = null; | |
119 | |
120 /** The top-level event loop. */ | |
121 _EventLoop topEventLoop; | |
122 | |
123 /** Whether this program is running from the command line. */ | |
124 bool fromCommandLine; | |
125 | |
126 /** Whether this [_Manager] is running as a web worker. */ | |
127 bool isWorker; | |
128 | |
129 /** Whether we support spawning web workers. */ | |
130 bool supportsWorkers; | |
131 | |
132 /** | |
133 * Whether to use web workers when implementing isolates. Set to false for | |
134 * debugging/testing. | |
135 */ | |
136 bool get useWorkers => supportsWorkers; | |
137 | |
138 /** | |
139 * Whether to use the web-worker JSON-based message serialization protocol. By | |
140 * default this is only used with web workers. For debugging, you can force | |
141 * using this protocol by changing this field value to [true]. | |
142 */ | |
143 bool get needSerialization => useWorkers; | |
144 | |
145 /** | |
146 * Registry of isolates. Isolates must be registered if, and only if, receive | |
147 * ports are alive. Normally no open receive-ports means that the isolate is | |
148 * dead, but DOM callbacks could resurrect it. | |
149 */ | |
150 Map<int, _IsolateContext> isolates; | |
151 | |
152 /** Reference to the main [_Manager]. Null in the main [_Manager] itself. */ | |
153 _ManagerStub mainManager; | |
154 | |
155 /** Registry of active [_ManagerStub]s. Only used in the main [_Manager]. */ | |
156 Map<int, _ManagerStub> managers; | |
157 | |
158 _Manager() { | |
159 _nativeDetectEnvironment(); | |
160 topEventLoop = new _EventLoop(); | |
161 isolates = new Map<int, _IsolateContext>(); | |
162 managers = new Map<int, _ManagerStub>(); | |
163 if (isWorker) { // "if we are not the main manager ourself" is the intent. | |
164 mainManager = new _MainManagerStub(); | |
165 _nativeInitWorkerMessageHandler(); | |
166 } | |
167 } | |
168 | |
169 void _nativeDetectEnvironment() { | |
170 isWorker = JS("bool", r"$isWorker"); | |
171 supportsWorkers = JS("bool", r"$supportsWorkers"); | |
172 fromCommandLine = JS("bool", r"typeof(window) == 'undefined'"); | |
173 } | |
174 | |
175 void _nativeInitWorkerMessageHandler() { | |
176 JS("void", r""" | |
177 $globalThis.onmessage = function (e) { | |
178 IsolateNatives._processWorkerMessage(this.mainManager, e); | |
179 }"""); | |
180 } | |
181 /*: TODO: check that _processWorkerMessage is not discarded while treeshaking. | |
182 """ { | |
183 IsolateNatives._processWorkerMessage(null, null); | |
184 } | |
185 */ | |
186 | |
187 | |
188 /** Close the worker running this code if all isolates are done. */ | |
189 void maybeCloseWorker() { | |
190 if (isolates.isEmpty) { | |
191 mainManager.postMessage(_serializeMessage({'command': 'close'})); | |
192 } | |
193 } | |
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 = new Map<int, ReceivePort>(); | |
210 initGlobals(); | |
211 } | |
212 | |
213 // these are filled lazily the first time the isolate starts running. | |
214 void initGlobals() { JS("void", r'$initGlobals(#)', this); } | |
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 dynamic 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() { JS("void", r'$setGlobals(#)', this); } | |
235 | |
236 /** Lookup a port registered for this isolate. */ | |
237 ReceivePort lookup(int portId) => ports[portId]; | |
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 if (_globalState.isWorker) { | |
277 _globalState.maybeCloseWorker(); | |
278 } else if (_globalState.rootContext != null && | |
279 _globalState.isolates.containsKey( | |
280 _globalState.rootContext.id) && | |
281 _globalState.fromCommandLine && | |
282 _globalState.rootContext.ports.isEmpty) { | |
283 // We want to reach here only on the main [_Manager] and only | |
284 // on the command-line. In the browser the isolate might | |
285 // still be alive due to DOM callbacks, but the presumption is | |
286 // that on the command-line, no future events can be injected | |
287 // into the event queue once it's empty. Node has setTimeout | |
288 // so this presumption is incorrect there. We think(?) that | |
289 // in d8 this assumption is valid. | |
290 throw new Exception("Program exited with open ReceivePorts."); | |
291 } | |
292 return false; | |
293 } | |
294 event.process(); | |
295 return true; | |
296 } | |
297 | |
298 /** | |
299 * Runs multiple iterations of the run-loop. If possible, each iteration is | |
300 * run asynchronously. | |
301 */ | |
302 void _runHelper() { | |
303 if (hasWindow()) { | |
304 // Run each iteration from the browser's top event loop. | |
305 void next() { | |
306 if (!runIteration()) return; | |
307 _window.setTimeout(next, 0); | |
308 } | |
309 next(); | |
310 } else { | |
311 // Run synchronously until no more iterations are available. | |
312 while (runIteration()) {} | |
313 } | |
314 } | |
315 | |
316 /** | |
317 * Call [_runHelper] but ensure that worker exceptions are propragated. Note | |
318 * this is called from JavaScript (see $wrap_call in corejs.dart). | |
319 */ | |
320 void run() { | |
321 if (!_globalState.isWorker) { | |
322 _runHelper(); | |
323 } else { | |
324 try { | |
325 _runHelper(); | |
326 } catch (e, trace) { | |
327 _globalState.mainManager.postMessage(_serializeMessage( | |
328 {'command': 'error', 'msg': '$e\n$trace' })); | |
329 } | |
330 } | |
331 } | |
332 } | |
333 | |
334 /** An event in the top-level event queue. */ | |
335 class _IsolateEvent { | |
336 _IsolateContext isolate; | |
337 Function fn; | |
338 String message; | |
339 | |
340 _IsolateEvent(this.isolate, this.fn, this.message); | |
341 | |
342 void process() { | |
343 isolate.eval(fn); | |
344 } | |
345 } | |
346 | |
347 /** An interface for a stub used to interact with a manager. */ | |
348 abstract class _ManagerStub { | |
349 get id; | |
350 void set id(int i); | |
351 void set onmessage(Function f); | |
352 void postMessage(msg); | |
353 void terminate(); | |
354 } | |
355 | |
356 /** A stub for interacting with the main manager. */ | |
357 class _MainManagerStub implements _ManagerStub { | |
358 get id => 0; | |
359 void set id(int i) { throw new UnimplementedError(); } | |
360 void set onmessage(f) { | |
361 throw new Exception("onmessage should not be set on MainManagerStub"); | |
362 } | |
363 void postMessage(msg) { JS("void", r"$globalThis.postMessage(#)", msg); } | |
364 void terminate() {} // Nothing useful to do here. | |
365 } | |
366 | |
367 /** | |
368 * A stub for interacting with a manager built on a web worker. This | |
369 * definition uses a 'hidden' type (* prefix on the native name) to | |
370 * enforce that the type is defined dynamically only when web workers | |
371 * are actually available. | |
372 */ | |
373 // @Native("*Worker"); | |
374 class _WorkerStub implements _ManagerStub { | |
375 get id => JS("var", "#.id", this); | |
376 void set id(i) { JS("void", "#.id = #", this, i); } | |
377 void set onmessage(f) { JS("void", "#.onmessage = #", this, f); } | |
378 void postMessage(msg) => JS("void", "#.postMessage(#)", this, msg); | |
379 // terminate() is implemented by Worker. | |
380 void terminate(); | |
381 } | |
382 | |
383 const String _SPAWNED_SIGNAL = "spawned"; | |
384 | |
385 class IsolateNatives { | |
386 | |
387 /** | |
388 * The src url for the script tag that loaded this code. Used to create | |
389 * JavaScript workers. | |
390 */ | |
391 static String get _thisScript => JS("String", r"$thisScriptUrl"); | |
392 | |
393 /** Starts a new worker with the given URL. */ | |
394 static _WorkerStub _newWorker(url) => JS("_WorkerStub", r"new Worker(#)", url)
; | |
395 | |
396 /** | |
397 * Assume that [e] is a browser message event and extract its message data. | |
398 * We don't import the dom explicitly so, when workers are disabled, this | |
399 * library can also run on top of nodejs. | |
400 */ | |
401 //static _getEventData(e) => JS("Object", "#.data", e); | |
402 static _getEventData(e) => JS("", "#.data", e); | |
403 | |
404 /** | |
405 * Process messages on a worker, either to control the worker instance or to | |
406 * pass messages along to the isolate running in the worker. | |
407 */ | |
408 static void _processWorkerMessage(sender, e) { | |
409 var msg = _deserializeMessage(_getEventData(e)); | |
410 switch (msg['command']) { | |
411 case 'start': | |
412 _globalState.currentManagerId = msg['id']; | |
413 Function entryPoint = _getJSFunctionFromName(msg['functionName']); | |
414 var replyTo = _deserializeMessage(msg['replyTo']); | |
415 _globalState.topEventLoop.enqueue(new _IsolateContext(), function() { | |
416 _startIsolate(entryPoint, replyTo); | |
417 }, 'worker-start'); | |
418 _globalState.topEventLoop.run(); | |
419 break; | |
420 case 'spawn-worker': | |
421 _spawnWorker(msg['functionName'], msg['uri'], msg['replyPort']); | |
422 break; | |
423 case 'message': | |
424 msg['port'].send(msg['msg'], msg['replyTo']); | |
425 _globalState.topEventLoop.run(); | |
426 break; | |
427 case 'close': | |
428 _log("Closing Worker"); | |
429 _globalState.managers.remove(sender.id); | |
430 sender.terminate(); | |
431 _globalState.topEventLoop.run(); | |
432 break; | |
433 case 'log': | |
434 _log(msg['msg']); | |
435 break; | |
436 case 'print': | |
437 if (_globalState.isWorker) { | |
438 _globalState.mainManager.postMessage( | |
439 _serializeMessage({'command': 'print', 'msg': msg})); | |
440 } else { | |
441 print(msg['msg']); | |
442 } | |
443 break; | |
444 case 'error': | |
445 throw msg['msg']; | |
446 } | |
447 } | |
448 | |
449 /** Log a message, forwarding to the main [_Manager] if appropriate. */ | |
450 static _log(msg) { | |
451 if (_globalState.isWorker) { | |
452 _globalState.mainManager.postMessage( | |
453 _serializeMessage({'command': 'log', 'msg': msg })); | |
454 } else { | |
455 try { | |
456 _consoleLog(msg); | |
457 } catch (e, trace) { | |
458 throw new Exception(trace); | |
459 } | |
460 } | |
461 } | |
462 | |
463 static void _consoleLog(msg) { | |
464 JS("void", r"$globalThis.console.log(#)", msg); | |
465 } | |
466 | |
467 /** | |
468 * Extract the constructor of runnable, so it can be allocated in another | |
469 * isolate. | |
470 */ | |
471 static dynamic _getJSConstructor(Isolate runnable) { | |
472 return JS("Object", "#.constructor", runnable); | |
473 } | |
474 | |
475 /** Extract the constructor name of a runnable */ | |
476 // TODO(sigmund): find a browser-generic way to support this. | |
477 // TODO(floitsch): is this function still used? If yes, should we use | |
478 // Primitives.objectTypeName instead? | |
479 static dynamic _getJSConstructorName(Isolate runnable) { | |
480 return JS("Object", "#.constructor.name", runnable); | |
481 } | |
482 | |
483 /** Find a constructor given its name. */ | |
484 static dynamic _getJSConstructorFromName(String factoryName) { | |
485 return JS("Object", r"$globalThis[#]", factoryName); | |
486 } | |
487 | |
488 static dynamic _getJSFunctionFromName(String functionName) { | |
489 return JS("Object", r"$globalThis[#]", functionName); | |
490 } | |
491 | |
492 /** | |
493 * Get a string name for the function, if possible. The result for | |
494 * anonymous functions is browser-dependent -- it may be "" or "anonymous" | |
495 * but you should probably not count on this. | |
496 */ | |
497 static String _getJSFunctionName(Function f) { | |
498 return JS("Object", r"(#.$name || #)", f, null); | |
499 } | |
500 | |
501 /** Create a new JavaScript object instance given its constructor. */ | |
502 static dynamic _allocate(var ctor) { | |
503 return JS("Object", "new #()", ctor); | |
504 } | |
505 | |
506 static SendPort spawnFunction(void topLevelFunction()) { | |
507 final name = _getJSFunctionName(topLevelFunction); | |
508 if (name == null) { | |
509 throw new UnsupportedError( | |
510 "only top-level functions can be spawned."); | |
511 } | |
512 return spawn(name, null, false); | |
513 } | |
514 | |
515 // TODO(sigmund): clean up above, after we make the new API the default: | |
516 | |
517 static SendPort spawn(String functionName, String uri, bool isLight) { | |
518 Completer<SendPort> completer = new Completer<SendPort>(); | |
519 ReceivePort port = new ReceivePort(); | |
520 port.receive((msg, SendPort replyPort) { | |
521 port.close(); | |
522 assert(msg == _SPAWNED_SIGNAL); | |
523 completer.complete(replyPort); | |
524 }); | |
525 | |
526 SendPort signalReply = port.toSendPort(); | |
527 | |
528 if (_globalState.useWorkers && !isLight) { | |
529 _startWorker(functionName, uri, signalReply); | |
530 } else { | |
531 _startNonWorker(functionName, uri, signalReply); | |
532 } | |
533 return new _BufferingSendPort( | |
534 _globalState.currentContext.id, completer.future); | |
535 } | |
536 | |
537 static SendPort _startWorker( | |
538 String functionName, String uri, SendPort replyPort) { | |
539 if (_globalState.isWorker) { | |
540 _globalState.mainManager.postMessage(_serializeMessage({ | |
541 'command': 'spawn-worker', | |
542 'functionName': functionName, | |
543 'uri': uri, | |
544 'replyPort': replyPort})); | |
545 } else { | |
546 _spawnWorker(functionName, uri, replyPort); | |
547 } | |
548 } | |
549 | |
550 static SendPort _startNonWorker( | |
551 String functionName, String uri, SendPort replyPort) { | |
552 // TODO(eub): support IE9 using an iframe -- Dart issue 1702. | |
553 if (uri != null) throw new UnsupportedError( | |
554 "Currently spawnUri is not supported without web workers."); | |
555 _globalState.topEventLoop.enqueue(new _IsolateContext(), function() { | |
556 final func = _getJSFunctionFromName(functionName); | |
557 _startIsolate(func, replyPort); | |
558 }, 'nonworker start'); | |
559 } | |
560 | |
561 static void _startIsolate(Function topLevel, SendPort replyTo) { | |
562 _fillStatics(_globalState.currentContext); | |
563 lazyPort = new ReceivePort(); | |
564 replyTo.send(_SPAWNED_SIGNAL, port.toSendPort()); | |
565 | |
566 topLevel(); | |
567 } | |
568 | |
569 /** | |
570 * Spawns an isolate in a worker. [factoryName] is the Javascript constructor | |
571 * name for the isolate entry point class. | |
572 */ | |
573 static void _spawnWorker(functionName, uri, replyPort) { | |
574 if (functionName == null) functionName = 'main'; | |
575 if (uri == null) uri = _thisScript; | |
576 if (!(new Uri.fromString(uri).isAbsolute())) { | |
577 // The constructor of dom workers requires an absolute URL. If we use a | |
578 // relative path we will get a DOM exception. | |
579 String prefix = _thisScript.substring(0, _thisScript.lastIndexOf('/')); | |
580 uri = "$prefix/$uri"; | |
581 } | |
582 final worker = _newWorker(uri); | |
583 worker.onmessage = (e) { _processWorkerMessage(worker, e); }; | |
584 var workerId = _globalState.nextManagerId++; | |
585 // We also store the id on the worker itself so that we can unregister it. | |
586 worker.id = workerId; | |
587 _globalState.managers[workerId] = worker; | |
588 worker.postMessage(_serializeMessage({ | |
589 'command': 'start', | |
590 'id': workerId, | |
591 // Note: we serialize replyPort twice because the child worker needs to | |
592 // first deserialize the worker id, before it can correctly deserialize | |
593 // the port (port deserialization is sensitive to what is the current | |
594 // workerId). | |
595 'replyTo': _serializeMessage(replyPort), | |
596 'functionName': functionName })); | |
597 } | |
598 } | |
599 | |
600 /******************************************************** | |
601 Inserted from lib/isolate/dart2js/ports.dart | |
602 ********************************************************/ | |
603 | |
604 /** Common functionality to all send ports. */ | |
605 class _BaseSendPort implements SendPort { | |
606 /** Id for the destination isolate. */ | |
607 final int _isolateId; | |
608 | |
609 const _BaseSendPort(this._isolateId); | |
610 | |
611 void _checkReplyTo(SendPort replyTo) { | |
612 if (replyTo != null | |
613 && replyTo is! _NativeJsSendPort | |
614 && replyTo is! _WorkerSendPort | |
615 && replyTo is! _BufferingSendPort) { | |
616 throw new Exception("SendPort.send: Illegal replyTo port type"); | |
617 } | |
618 } | |
619 | |
620 Future call(var message) { | |
621 final completer = new Completer(); | |
622 final port = new ReceivePortImpl(); | |
623 send(message, port.toSendPort()); | |
624 port.receive((value, ignoreReplyTo) { | |
625 port.close(); | |
626 if (value is Exception) { | |
627 completer.completeException(value); | |
628 } else { | |
629 completer.complete(value); | |
630 } | |
631 }); | |
632 return completer.future; | |
633 } | |
634 | |
635 void send(var message, [SendPort replyTo]); | |
636 bool operator ==(var other); | |
637 int get hashCode; | |
638 } | |
639 | |
640 /** A send port that delivers messages in-memory via native JavaScript calls. */ | |
641 class _NativeJsSendPort extends _BaseSendPort implements SendPort { | |
642 final ReceivePortImpl _receivePort; | |
643 | |
644 const _NativeJsSendPort(this._receivePort, int isolateId) : super(isolateId); | |
645 | |
646 void send(var message, [SendPort replyTo = null]) { | |
647 _waitForPendingPorts([message, replyTo], () { | |
648 _checkReplyTo(replyTo); | |
649 // Check that the isolate still runs and the port is still open | |
650 final isolate = _globalState.isolates[_isolateId]; | |
651 if (isolate == null) return; | |
652 if (_receivePort._callback == null) return; | |
653 | |
654 // We force serialization/deserialization as a simple way to ensure | |
655 // isolate communication restrictions are respected between isolates that | |
656 // live in the same worker. [_NativeJsSendPort] delivers both messages | |
657 // from the same worker and messages from other workers. In particular, | |
658 // messages sent from a worker via a [_WorkerSendPort] are received at | |
659 // [_processWorkerMessage] and forwarded to a native port. In such cases, | |
660 // here we'll see [_globalState.currentContext == null]. | |
661 final shouldSerialize = _globalState.currentContext != null | |
662 && _globalState.currentContext.id != _isolateId; | |
663 var msg = message; | |
664 var reply = replyTo; | |
665 if (shouldSerialize) { | |
666 msg = _serializeMessage(msg); | |
667 reply = _serializeMessage(reply); | |
668 } | |
669 _globalState.topEventLoop.enqueue(isolate, () { | |
670 if (_receivePort._callback != null) { | |
671 if (shouldSerialize) { | |
672 msg = _deserializeMessage(msg); | |
673 reply = _deserializeMessage(reply); | |
674 } | |
675 _receivePort._callback(msg, reply); | |
676 } | |
677 }, 'receive $message'); | |
678 }); | |
679 } | |
680 | |
681 bool operator ==(var other) => (other is _NativeJsSendPort) && | |
682 (_receivePort == other._receivePort); | |
683 | |
684 int get hashCode => _receivePort._id; | |
685 } | |
686 | |
687 /** A send port that delivers messages via worker.postMessage. */ | |
688 // TODO(eub): abstract this for iframes. | |
689 class _WorkerSendPort extends _BaseSendPort implements SendPort { | |
690 final int _workerId; | |
691 final int _receivePortId; | |
692 | |
693 const _WorkerSendPort(this._workerId, int isolateId, this._receivePortId) | |
694 : super(isolateId); | |
695 | |
696 void send(var message, [SendPort replyTo = null]) { | |
697 _waitForPendingPorts([message, replyTo], () { | |
698 _checkReplyTo(replyTo); | |
699 final workerMessage = _serializeMessage({ | |
700 'command': 'message', | |
701 'port': this, | |
702 'msg': message, | |
703 'replyTo': replyTo}); | |
704 | |
705 if (_globalState.isWorker) { | |
706 // communication from one worker to another go through the main worker: | |
707 _globalState.mainManager.postMessage(workerMessage); | |
708 } else { | |
709 _globalState.managers[_workerId].postMessage(workerMessage); | |
710 } | |
711 }); | |
712 } | |
713 | |
714 bool operator ==(var other) { | |
715 return (other is _WorkerSendPort) && | |
716 (_workerId == other._workerId) && | |
717 (_isolateId == other._isolateId) && | |
718 (_receivePortId == other._receivePortId); | |
719 } | |
720 | |
721 int get hashCode { | |
722 // TODO(sigmund): use a standard hash when we get one available in corelib. | |
723 return (_workerId << 16) ^ (_isolateId << 8) ^ _receivePortId; | |
724 } | |
725 } | |
726 | |
727 /** A port that buffers messages until an underlying port gets resolved. */ | |
728 class _BufferingSendPort extends _BaseSendPort implements SendPort { | |
729 /** Internal counter to assign unique ids to each port. */ | |
730 static int _idCount = 0; | |
731 | |
732 /** For implementing equals and hashcode. */ | |
733 final int _id; | |
734 | |
735 /** Underlying port, when resolved. */ | |
736 SendPort _port; | |
737 | |
738 /** | |
739 * Future of the underlying port, so that we can detect when this port can be | |
740 * sent on messages. | |
741 */ | |
742 Future<SendPort> _futurePort; | |
743 | |
744 /** Pending messages (and reply ports). */ | |
745 List pending; | |
746 | |
747 _BufferingSendPort(isolateId, this._futurePort) | |
748 : super(isolateId), _id = _idCount, pending = [] { | |
749 _idCount++; | |
750 _futurePort.then((p) { | |
751 _port = p; | |
752 for (final item in pending) { | |
753 p.send(item['message'], item['replyTo']); | |
754 } | |
755 pending = null; | |
756 }); | |
757 } | |
758 | |
759 _BufferingSendPort.fromPort(isolateId, this._port) | |
760 : super(isolateId), _id = _idCount { | |
761 _idCount++; | |
762 } | |
763 | |
764 void send(var message, [SendPort replyTo]) { | |
765 if (_port != null) { | |
766 _port.send(message, replyTo); | |
767 } else { | |
768 pending.add({'message': message, 'replyTo': replyTo}); | |
769 } | |
770 } | |
771 | |
772 bool operator ==(var other) => | |
773 other is _BufferingSendPort && _id == other._id; | |
774 int get hashCode => _id; | |
775 } | |
776 | |
777 /** Implementation of a multi-use [ReceivePort] on top of JavaScript. */ | |
778 class ReceivePortImpl implements ReceivePort { | |
779 int _id; | |
780 Function _callback; | |
781 static int _nextFreeId = 1; | |
782 | |
783 ReceivePortImpl() | |
784 : _id = _nextFreeId++ { | |
785 _globalState.currentContext.register(_id, this); | |
786 } | |
787 | |
788 void receive(void onMessage(var message, SendPort replyTo)) { | |
789 _callback = onMessage; | |
790 } | |
791 | |
792 void close() { | |
793 _callback = null; | |
794 _globalState.currentContext.unregister(_id); | |
795 } | |
796 | |
797 SendPort toSendPort() { | |
798 return new _NativeJsSendPort(this, _globalState.currentContext.id); | |
799 } | |
800 } | |
801 | |
802 /** Wait until all ports in a message are resolved. */ | |
803 _waitForPendingPorts(var message, void callback()) { | |
804 final finder = new _PendingSendPortFinder(); | |
805 finder.traverse(message); | |
806 Futures.wait(finder.ports).then((_) => callback()); | |
807 } | |
808 | |
809 | |
810 /** Visitor that finds all unresolved [SendPort]s in a message. */ | |
811 class _PendingSendPortFinder extends _MessageTraverser { | |
812 List<Future<SendPort>> ports; | |
813 _PendingSendPortFinder() : super(), ports = [] { | |
814 _visited = new _JsVisitedMap(); | |
815 } | |
816 | |
817 visitPrimitive(x) {} | |
818 | |
819 visitList(List list) { | |
820 final seen = _visited[list]; | |
821 if (seen != null) return; | |
822 _visited[list] = true; | |
823 // TODO(sigmund): replace with the following: (bug #1660) | |
824 // list.forEach(_dispatch); | |
825 list.forEach((e) => _dispatch(e)); | |
826 } | |
827 | |
828 visitMap(Map map) { | |
829 final seen = _visited[map]; | |
830 if (seen != null) return; | |
831 | |
832 _visited[map] = true; | |
833 // TODO(sigmund): replace with the following: (bug #1660) | |
834 // map.values.forEach(_dispatch); | |
835 map.values.forEach((e) => _dispatch(e)); | |
836 } | |
837 | |
838 visitSendPort(SendPort port) { | |
839 if (port is _BufferingSendPort && port._port == null) { | |
840 ports.add(port._futurePort); | |
841 } | |
842 } | |
843 } | |
844 | |
845 /******************************************************** | |
846 Inserted from lib/isolate/dart2js/messages.dart | |
847 ********************************************************/ | |
848 | |
849 // Defines message visitors, serialization, and deserialization. | |
850 | |
851 /** Serialize [message] (or simulate serialization). */ | |
852 _serializeMessage(message) { | |
853 if (_globalState.needSerialization) { | |
854 return new _JsSerializer().traverse(message); | |
855 } else { | |
856 return new _JsCopier().traverse(message); | |
857 } | |
858 } | |
859 | |
860 /** Deserialize [message] (or simulate deserialization). */ | |
861 _deserializeMessage(message) { | |
862 if (_globalState.needSerialization) { | |
863 return new _JsDeserializer().deserialize(message); | |
864 } else { | |
865 // Nothing more to do. | |
866 return message; | |
867 } | |
868 } | |
869 | |
870 class _JsSerializer extends _Serializer { | |
871 | |
872 _JsSerializer() : super() { _visited = new _JsVisitedMap(); } | |
873 | |
874 visitSendPort(SendPort x) { | |
875 if (x is _NativeJsSendPort) return visitNativeJsSendPort(x); | |
876 if (x is _WorkerSendPort) return visitWorkerSendPort(x); | |
877 if (x is _BufferingSendPort) return visitBufferingSendPort(x); | |
878 throw "Illegal underlying port $x"; | |
879 } | |
880 | |
881 visitNativeJsSendPort(_NativeJsSendPort port) { | |
882 return ['sendport', _globalState.currentManagerId, | |
883 port._isolateId, port._receivePort._id]; | |
884 } | |
885 | |
886 visitWorkerSendPort(_WorkerSendPort port) { | |
887 return ['sendport', port._workerId, port._isolateId, port._receivePortId]; | |
888 } | |
889 | |
890 visitBufferingSendPort(_BufferingSendPort port) { | |
891 if (port._port != null) { | |
892 return visitSendPort(port._port); | |
893 } else { | |
894 // TODO(floitsch): Use real exception (which one?). | |
895 throw | |
896 "internal error: must call _waitForPendingPorts to ensure all" | |
897 " ports are resolved at this point."; | |
898 } | |
899 } | |
900 | |
901 } | |
902 | |
903 | |
904 class _JsCopier extends _Copier { | |
905 | |
906 _JsCopier() : super() { _visited = new _JsVisitedMap(); } | |
907 | |
908 visitSendPort(SendPort x) { | |
909 if (x is _NativeJsSendPort) return visitNativeJsSendPort(x); | |
910 if (x is _WorkerSendPort) return visitWorkerSendPort(x); | |
911 if (x is _BufferingSendPort) return visitBufferingSendPort(x); | |
912 throw "Illegal underlying port $p"; | |
913 } | |
914 | |
915 SendPort visitNativeJsSendPort(_NativeJsSendPort port) { | |
916 return new _NativeJsSendPort(port._receivePort, port._isolateId); | |
917 } | |
918 | |
919 SendPort visitWorkerSendPort(_WorkerSendPort port) { | |
920 return new _WorkerSendPort( | |
921 port._workerId, port._isolateId, port._receivePortId); | |
922 } | |
923 | |
924 SendPort visitBufferingSendPort(_BufferingSendPort port) { | |
925 if (port._port != null) { | |
926 return visitSendPort(port._port); | |
927 } else { | |
928 // TODO(floitsch): Use real exception (which one?). | |
929 throw | |
930 "internal error: must call _waitForPendingPorts to ensure all" | |
931 " ports are resolved at this point."; | |
932 } | |
933 } | |
934 | |
935 } | |
936 | |
937 class _JsDeserializer extends _Deserializer { | |
938 | |
939 SendPort deserializeSendPort(List x) { | |
940 int managerId = x[1]; | |
941 int isolateId = x[2]; | |
942 int receivePortId = x[3]; | |
943 // If two isolates are in the same manager, we use NativeJsSendPorts to | |
944 // deliver messages directly without using postMessage. | |
945 if (managerId == _globalState.currentManagerId) { | |
946 var isolate = _globalState.isolates[isolateId]; | |
947 if (isolate == null) return null; // Isolate has been closed. | |
948 var receivePort = isolate.lookup(receivePortId); | |
949 return new _NativeJsSendPort(receivePort, isolateId); | |
950 } else { | |
951 return new _WorkerSendPort(managerId, isolateId, receivePortId); | |
952 } | |
953 } | |
954 | |
955 } | |
956 | |
957 class _JsVisitedMap implements _MessageTraverserVisitedMap { | |
958 List tagged; | |
959 | |
960 /** Retrieves any information stored in the native object [object]. */ | |
961 operator[](var object) { | |
962 return _getAttachedInfo(object); | |
963 } | |
964 | |
965 /** Injects some information into the native [object]. */ | |
966 void operator[]=(var object, var info) { | |
967 tagged.add(object); | |
968 _setAttachedInfo(object, info); | |
969 } | |
970 | |
971 /** Get ready to rumble. */ | |
972 void reset() { | |
973 assert(tagged == null); | |
974 tagged = new List(); | |
975 } | |
976 | |
977 /** Remove all information injected in the native objects. */ | |
978 void cleanup() { | |
979 for (int i = 0, length = tagged.length; i < length; i++) { | |
980 _clearAttachedInfo(tagged[i]); | |
981 } | |
982 tagged = null; | |
983 } | |
984 | |
985 void _clearAttachedInfo(var o) { | |
986 JS("void", "#['__MessageTraverser__attached_info__'] = #", o, null); | |
987 } | |
988 | |
989 void _setAttachedInfo(var o, var info) { | |
990 JS("void", "#['__MessageTraverser__attached_info__'] = #", o, info); | |
991 } | |
992 | |
993 _getAttachedInfo(var o) { | |
994 return JS("", "#['__MessageTraverser__attached_info__']", o); | |
995 } | |
996 } | |
997 | |
998 // only visible for testing purposes | |
999 // TODO(sigmund): remove once we can disable privacy for testing (bug #1882) | |
1000 class TestingOnly { | |
1001 static copy(x) { | |
1002 return new _JsCopier().traverse(x); | |
1003 } | |
1004 | |
1005 // only visible for testing purposes | |
1006 static serialize(x) { | |
1007 _Serializer serializer = new _JsSerializer(); | |
1008 _Deserializer deserializer = new _JsDeserializer(); | |
1009 return deserializer.deserialize(serializer.traverse(x)); | |
1010 } | |
1011 } | |
1012 | |
1013 /******************************************************** | |
1014 Inserted from lib/isolate/serialization.dart | |
1015 ********************************************************/ | |
1016 | |
1017 class _MessageTraverserVisitedMap { | |
1018 | |
1019 operator[](var object) => null; | |
1020 void operator[]=(var object, var info) { } | |
1021 | |
1022 void reset() { } | |
1023 void cleanup() { } | |
1024 | |
1025 } | |
1026 | |
1027 /** Abstract visitor for dart objects that can be sent as isolate messages. */ | |
1028 class _MessageTraverser { | |
1029 | |
1030 _MessageTraverserVisitedMap _visited; | |
1031 _MessageTraverser() : _visited = new _MessageTraverserVisitedMap(); | |
1032 | |
1033 /** Visitor's entry point. */ | |
1034 traverse(var x) { | |
1035 if (isPrimitive(x)) return visitPrimitive(x); | |
1036 _visited.reset(); | |
1037 var result; | |
1038 try { | |
1039 result = _dispatch(x); | |
1040 } finally { | |
1041 _visited.cleanup(); | |
1042 } | |
1043 return result; | |
1044 } | |
1045 | |
1046 _dispatch(var x) { | |
1047 if (isPrimitive(x)) return visitPrimitive(x); | |
1048 if (x is List) return visitList(x); | |
1049 if (x is Map) return visitMap(x); | |
1050 if (x is SendPort) return visitSendPort(x); | |
1051 if (x is SendPortSync) return visitSendPortSync(x); | |
1052 | |
1053 // Overridable fallback. | |
1054 return visitObject(x); | |
1055 } | |
1056 | |
1057 visitPrimitive(x); | |
1058 visitList(List x); | |
1059 visitMap(Map x); | |
1060 visitSendPort(SendPort x); | |
1061 visitSendPortSync(SendPortSync x); | |
1062 | |
1063 visitObject(Object x) { | |
1064 // TODO(floitsch): make this a real exception. (which one)? | |
1065 throw "Message serialization: Illegal value $x passed"; | |
1066 } | |
1067 | |
1068 static bool isPrimitive(x) { | |
1069 return (x == null) || (x is String) || (x is num) || (x is bool); | |
1070 } | |
1071 } | |
1072 | |
1073 | |
1074 /** A visitor that recursively copies a message. */ | |
1075 class _Copier extends _MessageTraverser { | |
1076 | |
1077 visitPrimitive(x) => x; | |
1078 | |
1079 List visitList(List list) { | |
1080 List copy = _visited[list]; | |
1081 if (copy != null) return copy; | |
1082 | |
1083 int len = list.length; | |
1084 | |
1085 // TODO(floitsch): we loose the generic type of the List. | |
1086 copy = new List(len); | |
1087 _visited[list] = copy; | |
1088 for (int i = 0; i < len; i++) { | |
1089 copy[i] = _dispatch(list[i]); | |
1090 } | |
1091 return copy; | |
1092 } | |
1093 | |
1094 Map visitMap(Map map) { | |
1095 Map copy = _visited[map]; | |
1096 if (copy != null) return copy; | |
1097 | |
1098 // TODO(floitsch): we loose the generic type of the map. | |
1099 copy = new Map(); | |
1100 _visited[map] = copy; | |
1101 map.forEach((key, val) { | |
1102 copy[_dispatch(key)] = _dispatch(val); | |
1103 }); | |
1104 return copy; | |
1105 } | |
1106 | |
1107 } | |
1108 | |
1109 /** Visitor that serializes a message as a JSON array. */ | |
1110 class _Serializer extends _MessageTraverser { | |
1111 int _nextFreeRefId = 0; | |
1112 | |
1113 visitPrimitive(x) => x; | |
1114 | |
1115 visitList(List list) { | |
1116 int copyId = _visited[list]; | |
1117 if (copyId != null) return ['ref', copyId]; | |
1118 | |
1119 int id = _nextFreeRefId++; | |
1120 _visited[list] = id; | |
1121 var jsArray = _serializeList(list); | |
1122 // TODO(floitsch): we are losing the generic type. | |
1123 return ['list', id, jsArray]; | |
1124 } | |
1125 | |
1126 visitMap(Map map) { | |
1127 int copyId = _visited[map]; | |
1128 if (copyId != null) return ['ref', copyId]; | |
1129 | |
1130 int id = _nextFreeRefId++; | |
1131 _visited[map] = id; | |
1132 var keys = _serializeList(map.keys); | |
1133 var values = _serializeList(map.values); | |
1134 // TODO(floitsch): we are losing the generic type. | |
1135 return ['map', id, keys, values]; | |
1136 } | |
1137 | |
1138 _serializeList(List list) { | |
1139 int len = list.length; | |
1140 var result = new List(len); | |
1141 for (int i = 0; i < len; i++) { | |
1142 result[i] = _dispatch(list[i]); | |
1143 } | |
1144 return result; | |
1145 } | |
1146 } | |
1147 | |
1148 /** Deserializes arrays created with [_Serializer]. */ | |
1149 class _Deserializer { | |
1150 Map<int, dynamic> _deserialized; | |
1151 | |
1152 _Deserializer(); | |
1153 | |
1154 static bool isPrimitive(x) { | |
1155 return (x == null) || (x is String) || (x is num) || (x is bool); | |
1156 } | |
1157 | |
1158 deserialize(x) { | |
1159 if (isPrimitive(x)) return x; | |
1160 // TODO(floitsch): this should be new HashMap<int, var|Dynamic>() | |
1161 _deserialized = new HashMap(); | |
1162 return _deserializeHelper(x); | |
1163 } | |
1164 | |
1165 _deserializeHelper(x) { | |
1166 if (isPrimitive(x)) return x; | |
1167 assert(x is List); | |
1168 switch (x[0]) { | |
1169 case 'ref': return _deserializeRef(x); | |
1170 case 'list': return _deserializeList(x); | |
1171 case 'map': return _deserializeMap(x); | |
1172 case 'sendport': return deserializeSendPort(x); | |
1173 default: return deserializeObject(x); | |
1174 } | |
1175 } | |
1176 | |
1177 _deserializeRef(List x) { | |
1178 int id = x[1]; | |
1179 var result = _deserialized[id]; | |
1180 assert(result != null); | |
1181 return result; | |
1182 } | |
1183 | |
1184 List _deserializeList(List x) { | |
1185 int id = x[1]; | |
1186 // We rely on the fact that Dart-lists are directly mapped to Js-arrays. | |
1187 List dartList = x[2]; | |
1188 _deserialized[id] = dartList; | |
1189 int len = dartList.length; | |
1190 for (int i = 0; i < len; i++) { | |
1191 dartList[i] = _deserializeHelper(dartList[i]); | |
1192 } | |
1193 return dartList; | |
1194 } | |
1195 | |
1196 Map _deserializeMap(List x) { | |
1197 Map result = new Map(); | |
1198 int id = x[1]; | |
1199 _deserialized[id] = result; | |
1200 List keys = x[2]; | |
1201 List values = x[3]; | |
1202 int len = keys.length; | |
1203 assert(len == values.length); | |
1204 for (int i = 0; i < len; i++) { | |
1205 var key = _deserializeHelper(keys[i]); | |
1206 var value = _deserializeHelper(values[i]); | |
1207 result[key] = value; | |
1208 } | |
1209 return result; | |
1210 } | |
1211 | |
1212 deserializeSendPort(List x); | |
1213 | |
1214 deserializeObject(List x) { | |
1215 // TODO(floitsch): Use real exception (which one?). | |
1216 throw "Unexpected serialized object"; | |
1217 } | |
1218 } | |
1219 | |
1220 /******************************************************** | |
1221 Inserted from lib/isolate/dart2js/timer_provider.dart | |
1222 ********************************************************/ | |
1223 | |
1224 // We don't want to import the DOM library just because of window.setTimeout, | |
1225 // so we reconstruct the Window class here. The only conflict that could happen | |
1226 // with the other DOMWindow class would be because of subclasses. | |
1227 // Currently, none of the two Dart classes have subclasses. | |
1228 typedef void _TimeoutHandler(); | |
1229 | |
1230 // @Native("*DOMWindow"); | |
1231 class _Window { | |
1232 int setTimeout(_TimeoutHandler handler, int timeout) { | |
1233 return JS('int', | |
1234 '#.setTimeout(#, #)', | |
1235 this, | |
1236 convertDartClosureToJS(handler, 0), | |
1237 timeout); | |
1238 } | |
1239 | |
1240 int setInterval(_TimeoutHandler handler, int timeout) { | |
1241 return JS('int', | |
1242 '#.setInterval(#, #)', | |
1243 this, | |
1244 convertDartClosureToJS(handler, 0), | |
1245 timeout); | |
1246 } | |
1247 | |
1248 void clearTimeout(int handle) { | |
1249 JS('void', '#.clearTimeout(#)', this, handle); | |
1250 } | |
1251 | |
1252 void clearInterval(int handle) { | |
1253 JS('void', '#.clearInterval(#)', this, handle); | |
1254 } | |
1255 } | |
1256 | |
1257 _Window get _window => | |
1258 JS('bool', 'typeof window != "undefined"') ? JS('_Window', 'window') : null; | |
1259 | |
1260 bool hasWindow() => _window != null; | |
1261 | |
1262 class TimerImpl implements Timer { | |
1263 final bool _once; | |
1264 int _handle; | |
1265 | |
1266 TimerImpl(int milliseconds, void callback(Timer timer)) | |
1267 : _once = true { | |
1268 _handle = _window.setTimeout(() => callback(this), milliseconds); | |
1269 } | |
1270 | |
1271 TimerImpl.repeating(int milliseconds, void callback(Timer timer)) | |
1272 : _once = false { | |
1273 _handle = _window.setInterval(() => callback(this), milliseconds); | |
1274 } | |
1275 | |
1276 void cancel() { | |
1277 if (_once) { | |
1278 _window.clearTimeout(_handle); | |
1279 } else { | |
1280 _window.clearInterval(_handle); | |
1281 } | |
1282 } | |
1283 } | |
OLD | NEW |