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:async'; | |
8 import 'dart:collection' show Queue, HashMap; | |
9 import 'dart:isolate'; | |
10 import 'dart:_js_helper' show | |
11 Closure, | |
12 Null, | |
13 Primitives, | |
14 convertDartClosureToJS, | |
15 random64, | |
16 requiresPreamble; | |
17 import 'dart:_foreign_helper' show DART_CLOSURE_TO_JS, | |
18 JS, | |
19 JS_CREATE_ISOLATE, | |
20 JS_CURRENT_ISOLATE_CONTEXT, | |
21 JS_CURRENT_ISOLATE, | |
22 JS_SET_CURRENT_ISOLATE, | |
23 IsolateContext; | |
24 import 'dart:_interceptors' show JSExtendableArray; | |
25 | |
26 /** | |
27 * Called by the compiler to support switching | |
28 * between isolates when we get a callback from the DOM. | |
29 */ | |
30 _callInIsolate(_IsolateContext isolate, Function function) { | |
31 var result = isolate.eval(function); | |
32 _globalState.topEventLoop.run(); | |
33 return result; | |
34 } | |
35 | |
36 /// Marks entering a JavaScript async operation to keep the worker alive. | |
37 /// | |
38 /// To be called by library code before starting an async operation controlled | |
39 /// by the JavaScript event handler. | |
40 /// | |
41 /// Also call [leaveJsAsync] in all callback handlers marking the end of that | |
42 /// async operation (also error handlers) so the worker can be released. | |
43 /// | |
44 /// These functions only has to be called for code that can be run from a | |
45 /// worker-isolate (so not for general dom operations). | |
46 enterJsAsync() { | |
47 _globalState.topEventLoop._activeJsAsyncCount++; | |
48 } | |
49 | |
50 /// Marks leaving a javascript async operation. | |
51 /// | |
52 /// See [enterJsAsync]. | |
53 leaveJsAsync() { | |
54 _globalState.topEventLoop._activeJsAsyncCount--; | |
55 assert(_globalState.topEventLoop._activeJsAsyncCount >= 0); | |
56 } | |
57 | |
58 /// Returns true if we are currently in a worker context. | |
59 bool isWorker() => _globalState.isWorker; | |
60 | |
61 /** | |
62 * Called by the compiler to fetch the current isolate context. | |
63 */ | |
64 _IsolateContext _currentIsolate() => _globalState.currentContext; | |
65 | |
66 /** | |
67 * Wrapper that takes the dart entry point and runs it within an isolate. The | |
68 * dart2js compiler will inject a call of the form | |
69 * [: startRootIsolate(main); :] when it determines that this wrapping | |
70 * is needed. For single-isolate applications (e.g. hello world), this | |
71 * call is not emitted. | |
72 */ | |
73 void startRootIsolate(entry, args) { | |
74 // The dartMainRunner can inject a new arguments array. We pass the arguments | |
75 // through a "JS", so that the type-inferrer loses track of it. | |
76 args = JS("", "#", args); | |
77 if (args == null) args = []; | |
78 if (args is! List) { | |
79 throw new ArgumentError("Arguments to main must be a List: $args"); | |
80 } | |
81 _globalState = new _Manager(entry); | |
82 | |
83 // Don't start the main loop again, if we are in a worker. | |
84 if (_globalState.isWorker) return; | |
85 final rootContext = new _IsolateContext(); | |
86 _globalState.rootContext = rootContext; | |
87 | |
88 // BUG(5151491): Setting currentContext should not be necessary, but | |
89 // because closures passed to the DOM as event handlers do not bind their | |
90 // isolate automatically we try to give them a reasonable context to live in | |
91 // by having a "default" isolate (the first one created). | |
92 _globalState.currentContext = rootContext; | |
93 if (entry is _MainFunctionArgs) { | |
94 rootContext.eval(() { entry(args); }); | |
95 } else if (entry is _MainFunctionArgsMessage) { | |
96 rootContext.eval(() { entry(args, null); }); | |
97 } else { | |
98 rootContext.eval(entry); | |
99 } | |
100 _globalState.topEventLoop.run(); | |
101 } | |
102 | |
103 /******************************************************** | |
104 Inserted from lib/isolate/dart2js/isolateimpl.dart | |
105 ********************************************************/ | |
106 | |
107 /** | |
108 * Concepts used here: | |
109 * | |
110 * "manager" - A manager contains one or more isolates, schedules their | |
111 * execution, and performs other plumbing on their behalf. The isolate | |
112 * present at the creation of the manager is designated as its "root isolate". | |
113 * A manager may, for example, be implemented on a web Worker. | |
114 * | |
115 * [_Manager] - State present within a manager (exactly once, as a global). | |
116 * | |
117 * [_ManagerStub] - A handle held within one manager that allows interaction | |
118 * with another manager. A target manager may be addressed by zero or more | |
119 * [_ManagerStub]s. | |
120 * TODO(ahe): The _ManagerStub concept is broken. It was an attempt | |
121 * to create a common interface between the native Worker class and | |
122 * _MainManagerStub. | |
123 */ | |
124 | |
125 /** | |
126 * A native object that is shared across isolates. This object is visible to all | |
127 * isolates running under the same manager (either UI or background web worker). | |
128 * | |
129 * This is code that is intended to 'escape' the isolate boundaries in order to | |
130 * implement the semantics of isolates in JavaScript. Without this we would have | |
131 * been forced to implement more code (including the top-level event loop) in | |
132 * JavaScript itself. | |
133 */ | |
134 // TODO(eub, sigmund): move the "manager" to be entirely in JS. | |
135 // Running any Dart code outside the context of an isolate gives it | |
136 // the chance to break the isolate abstraction. | |
137 _Manager get _globalState => JS("_Manager", "init.globalState"); | |
138 | |
139 set _globalState(_Manager val) { | |
140 JS("void", "init.globalState = #", val); | |
141 } | |
142 | |
143 /** State associated with the current manager. See [globalState]. */ | |
144 // TODO(sigmund): split in multiple classes: global, thread, main-worker states? | |
145 class _Manager { | |
146 | |
147 /** Next available isolate id within this [_Manager]. */ | |
148 int nextIsolateId = 0; | |
149 | |
150 /** id assigned to this [_Manager]. */ | |
151 int currentManagerId = 0; | |
152 | |
153 /** | |
154 * Next available manager id. Only used by the main manager to assign a unique | |
155 * id to each manager created by it. | |
156 */ | |
157 int nextManagerId = 1; | |
158 | |
159 /** Context for the currently running [Isolate]. */ | |
160 _IsolateContext currentContext = null; | |
161 | |
162 /** Context for the root [Isolate] that first run in this [_Manager]. */ | |
163 _IsolateContext rootContext = null; | |
164 | |
165 /** The top-level event loop. */ | |
166 _EventLoop topEventLoop; | |
167 | |
168 /** Whether this program is running from the command line. */ | |
169 bool fromCommandLine; | |
170 | |
171 /** Whether this [_Manager] is running as a web worker. */ | |
172 bool isWorker; | |
173 | |
174 /** Whether we support spawning web workers. */ | |
175 bool supportsWorkers; | |
176 | |
177 /** | |
178 * Whether to use web workers when implementing isolates. Set to false for | |
179 * debugging/testing. | |
180 */ | |
181 bool get useWorkers => supportsWorkers; | |
182 | |
183 /** | |
184 * Whether to use the web-worker JSON-based message serialization protocol. By | |
185 * default this is only used with web workers. For debugging, you can force | |
186 * using this protocol by changing this field value to [:true:]. | |
187 */ | |
188 bool get needSerialization => useWorkers; | |
189 | |
190 /** | |
191 * Registry of isolates. Isolates must be registered if, and only if, receive | |
192 * ports are alive. Normally no open receive-ports means that the isolate is | |
193 * dead, but DOM callbacks could resurrect it. | |
194 */ | |
195 Map<int, _IsolateContext> isolates; | |
196 | |
197 /** Reference to the main [_Manager]. Null in the main [_Manager] itself. */ | |
198 _MainManagerStub mainManager; | |
199 | |
200 /// Registry of active Web Workers. Only used in the main [_Manager]. | |
201 Map<int, dynamic /* Worker */> managers; | |
202 | |
203 /** The entry point given by [startRootIsolate]. */ | |
204 final Function entry; | |
205 | |
206 _Manager(this.entry) { | |
207 _nativeDetectEnvironment(); | |
208 topEventLoop = new _EventLoop(); | |
209 isolates = new Map<int, _IsolateContext>(); | |
210 managers = new Map<int, dynamic>(); | |
211 if (isWorker) { // "if we are not the main manager ourself" is the intent. | |
212 mainManager = new _MainManagerStub(); | |
213 _nativeInitWorkerMessageHandler(); | |
214 } | |
215 } | |
216 | |
217 void _nativeDetectEnvironment() { | |
218 bool isWindowDefined = globalWindow != null; | |
219 bool isWorkerDefined = globalWorker != null; | |
220 | |
221 isWorker = !isWindowDefined && globalPostMessageDefined; | |
222 supportsWorkers = isWorker | |
223 || (isWorkerDefined && IsolateNatives.thisScript != null); | |
224 fromCommandLine = !isWindowDefined && !isWorker; | |
225 } | |
226 | |
227 void _nativeInitWorkerMessageHandler() { | |
228 var function = JS('', | |
229 "(function (f, a) { return function (e) { f(a, e); }})(#, #)", | |
230 DART_CLOSURE_TO_JS(IsolateNatives._processWorkerMessage), | |
231 mainManager); | |
232 JS("void", r"self.onmessage = #", function); | |
233 // We ensure dartPrint is defined so that the implementation of the Dart | |
234 // print method knows what to call. | |
235 JS('', '''self.dartPrint = self.dartPrint || (function(serialize) { | |
236 return function (object) { | |
237 if (self.console && self.console.log) { | |
238 self.console.log(object) | |
239 } else { | |
240 self.postMessage(serialize(object)); | |
241 } | |
242 } | |
243 })(#)''', DART_CLOSURE_TO_JS(_serializePrintMessage)); | |
244 } | |
245 | |
246 static _serializePrintMessage(object) { | |
247 return _serializeMessage({"command": "print", "msg": object}); | |
248 } | |
249 | |
250 /** | |
251 * Close the worker running this code if all isolates are done and | |
252 * there are no active async JavaScript tasks still running. | |
253 */ | |
254 void maybeCloseWorker() { | |
255 if (isWorker | |
256 && isolates.isEmpty | |
257 && topEventLoop._activeJsAsyncCount == 0) { | |
258 mainManager.postMessage(_serializeMessage({'command': 'close'})); | |
259 } | |
260 } | |
261 } | |
262 | |
263 /** Context information tracked for each isolate. */ | |
264 class _IsolateContext implements IsolateContext { | |
265 /** Current isolate id. */ | |
266 final int id = _globalState.nextIsolateId++; | |
267 | |
268 /** Registry of receive ports currently active on this isolate. */ | |
269 final Map<int, RawReceivePortImpl> ports = new Map<int, RawReceivePortImpl>(); | |
270 | |
271 /** Registry of weak receive ports currently active on this isolate. */ | |
272 final Set<int> weakPorts = new Set<int>(); | |
273 | |
274 /** Holds isolate globals (statics and top-level properties). */ | |
275 // native object containing all globals of an isolate. | |
276 final isolateStatics = JS_CREATE_ISOLATE(); | |
277 | |
278 final RawReceivePortImpl controlPort = new RawReceivePortImpl._controlPort(); | |
279 | |
280 final Capability pauseCapability = new Capability(); | |
281 final Capability terminateCapability = new Capability(); // License to kill. | |
282 | |
283 /// Boolean flag set when the initial method of the isolate has been executed. | |
284 /// | |
285 /// Used to avoid considering the isolate dead when it has no open | |
286 /// receive ports and no scheduled timers, because it hasn't had time to | |
287 /// create them yet. | |
288 bool initialized = false; | |
289 | |
290 // TODO(lrn): Store these in single "PauseState" object, so they don't take | |
291 // up as much room when not pausing. | |
292 bool isPaused = false; | |
293 List<_IsolateEvent> delayedEvents = []; | |
294 Set<Capability> pauseTokens = new Set(); | |
295 | |
296 // Container with the "on exit" handler send-ports. | |
297 var doneHandlers; | |
298 | |
299 /** | |
300 * Queue of functions to call when the current event is complete. | |
301 * | |
302 * These events are not just put at the front of the event queue, because | |
303 * they represent control messages, and should be handled even if the | |
304 * event queue is paused. | |
305 */ | |
306 var _scheduledControlEvents; | |
307 bool _isExecutingEvent = false; | |
308 | |
309 /** Whether uncaught errors are considered fatal. */ | |
310 bool errorsAreFatal = true; | |
311 | |
312 // Set of ports that listen to uncaught errors. | |
313 Set<SendPort> errorPorts = new Set(); | |
314 | |
315 _IsolateContext() { | |
316 this.registerWeak(controlPort._id, controlPort); | |
317 } | |
318 | |
319 void addPause(Capability authentification, Capability resume) { | |
320 if (pauseCapability != authentification) return; | |
321 if (pauseTokens.add(resume) && !isPaused) { | |
322 isPaused = true; | |
323 } | |
324 _updateGlobalState(); | |
325 } | |
326 | |
327 void removePause(Capability resume) { | |
328 if (!isPaused) return; | |
329 pauseTokens.remove(resume); | |
330 if (pauseTokens.isEmpty) { | |
331 while(delayedEvents.isNotEmpty) { | |
332 _IsolateEvent event = delayedEvents.removeLast(); | |
333 _globalState.topEventLoop.prequeue(event); | |
334 } | |
335 isPaused = false; | |
336 } | |
337 _updateGlobalState(); | |
338 } | |
339 | |
340 void addDoneListener(SendPort responsePort) { | |
341 if (doneHandlers == null) { | |
342 doneHandlers = []; | |
343 } | |
344 // If necessary, we can switch doneHandlers to a Set if it gets larger. | |
345 // That is not expected to happen in practice. | |
346 if (doneHandlers.contains(responsePort)) return; | |
347 doneHandlers.add(responsePort); | |
348 } | |
349 | |
350 void removeDoneListener(SendPort responsePort) { | |
351 if (doneHandlers == null) return; | |
352 doneHandlers.remove(responsePort); | |
353 } | |
354 | |
355 void setErrorsFatal(Capability authentification, bool errorsAreFatal) { | |
356 if (terminateCapability != authentification) return; | |
357 this.errorsAreFatal = errorsAreFatal; | |
358 } | |
359 | |
360 void handlePing(SendPort responsePort, int pingType) { | |
361 if (pingType == Isolate.IMMEDIATE || | |
362 (pingType == Isolate.BEFORE_NEXT_EVENT && | |
363 !_isExecutingEvent)) { | |
364 responsePort.send(null); | |
365 return; | |
366 } | |
367 void respond() { responsePort.send(null); } | |
368 if (pingType == Isolate.AS_EVENT) { | |
369 _globalState.topEventLoop.enqueue(this, respond, "ping"); | |
370 return; | |
371 } | |
372 assert(pingType == Isolate.BEFORE_NEXT_EVENT); | |
373 if (_scheduledControlEvents == null) { | |
374 _scheduledControlEvents = new Queue(); | |
375 } | |
376 _scheduledControlEvents.addLast(respond); | |
377 } | |
378 | |
379 void handleKill(Capability authentification, int priority) { | |
380 if (this.terminateCapability != authentification) return; | |
381 if (priority == Isolate.IMMEDIATE || | |
382 (priority == Isolate.BEFORE_NEXT_EVENT && | |
383 !_isExecutingEvent)) { | |
384 kill(); | |
385 return; | |
386 } | |
387 if (priority == Isolate.AS_EVENT) { | |
388 _globalState.topEventLoop.enqueue(this, kill, "kill"); | |
389 return; | |
390 } | |
391 assert(priority == Isolate.BEFORE_NEXT_EVENT); | |
392 if (_scheduledControlEvents == null) { | |
393 _scheduledControlEvents = new Queue(); | |
394 } | |
395 _scheduledControlEvents.addLast(kill); | |
396 } | |
397 | |
398 void addErrorListener(SendPort port) { | |
399 errorPorts.add(port); | |
400 } | |
401 | |
402 void removeErrorListener(SendPort port) { | |
403 errorPorts.remove(port); | |
404 } | |
405 | |
406 /** Function called with an uncaught error. */ | |
407 void handleUncaughtError(error, StackTrace stackTrace) { | |
408 // Just print the error if there is no error listener registered. | |
409 if (errorPorts.isEmpty) { | |
410 // An uncaught error in the root isolate will terminate the program? | |
411 if (errorsAreFatal && identical(this, _globalState.rootContext)) { | |
412 // The error will be rethrown to reach the global scope, so | |
413 // don't print it. | |
414 return; | |
415 } | |
416 if (JS('bool', 'self.console && self.console.error')) { | |
417 JS('void', 'self.console.error(#, #)', error, stackTrace); | |
418 } else { | |
419 print(error); | |
420 if (stackTrace != null) print(stackTrace); | |
421 } | |
422 return; | |
423 } | |
424 List message = new List(2) | |
425 ..[0] = error.toString() | |
426 ..[1] = (stackTrace == null) ? null : stackTrace.toString(); | |
427 for (SendPort port in errorPorts) port.send(message); | |
428 } | |
429 | |
430 /** | |
431 * Run [code] in the context of the isolate represented by [this]. | |
432 */ | |
433 dynamic eval(Function code) { | |
434 var old = _globalState.currentContext; | |
435 _globalState.currentContext = this; | |
436 this._setGlobals(); | |
437 var result = null; | |
438 _isExecutingEvent = true; | |
439 try { | |
440 result = code(); | |
441 } catch (e, s) { | |
442 handleUncaughtError(e, s); | |
443 if (errorsAreFatal) { | |
444 kill(); | |
445 // An uncaught error in the root context terminates all isolates. | |
446 if (identical(this, _globalState.rootContext)) { | |
447 rethrow; | |
448 } | |
449 } | |
450 } finally { | |
451 _isExecutingEvent = false; | |
452 _globalState.currentContext = old; | |
453 if (old != null) old._setGlobals(); | |
454 if (_scheduledControlEvents != null) { | |
455 while (_scheduledControlEvents.isNotEmpty) { | |
456 (_scheduledControlEvents.removeFirst())(); | |
457 } | |
458 } | |
459 } | |
460 return result; | |
461 } | |
462 | |
463 void _setGlobals() { | |
464 JS_SET_CURRENT_ISOLATE(isolateStatics); | |
465 } | |
466 | |
467 /** | |
468 * Handle messages comming in on the control port. | |
469 * | |
470 * These events do not go through the event queue. | |
471 * The `_globalState.currentContext` context is not set to this context | |
472 * during the handling. | |
473 */ | |
474 void handleControlMessage(message) { | |
475 switch (message[0]) { | |
476 case "pause": | |
477 addPause(message[1], message[2]); | |
478 break; | |
479 case "resume": | |
480 removePause(message[1]); | |
481 break; | |
482 case 'add-ondone': | |
483 addDoneListener(message[1]); | |
484 break; | |
485 case 'remove-ondone': | |
486 removeDoneListener(message[1]); | |
487 break; | |
488 case 'set-errors-fatal': | |
489 setErrorsFatal(message[1], message[2]); | |
490 break; | |
491 case "ping": | |
492 handlePing(message[1], message[2]); | |
493 break; | |
494 case "kill": | |
495 handleKill(message[1], message[2]); | |
496 break; | |
497 case "getErrors": | |
498 addErrorListener(message[1]); | |
499 break; | |
500 case "stopErrors": | |
501 removeErrorListener(message[1]); | |
502 break; | |
503 default: | |
504 } | |
505 } | |
506 | |
507 /** Looks up a port registered for this isolate. */ | |
508 RawReceivePortImpl lookup(int portId) => ports[portId]; | |
509 | |
510 void _addRegistration(int portId, RawReceivePortImpl port) { | |
511 if (ports.containsKey(portId)) { | |
512 throw new Exception("Registry: ports must be registered only once."); | |
513 } | |
514 ports[portId] = port; | |
515 } | |
516 | |
517 /** Registers a port on this isolate. */ | |
518 void register(int portId, RawReceivePortImpl port) { | |
519 _addRegistration(portId, port); | |
520 _updateGlobalState(); | |
521 } | |
522 | |
523 /** | |
524 * Registers a weak port on this isolate. | |
525 * | |
526 * The port does not keep the isolate active. | |
527 */ | |
528 void registerWeak(int portId, RawReceivePortImpl port) { | |
529 weakPorts.add(portId); | |
530 _addRegistration(portId, port); | |
531 } | |
532 | |
533 void _updateGlobalState() { | |
534 if (ports.length - weakPorts.length > 0 || isPaused || !initialized) { | |
535 _globalState.isolates[id] = this; // indicate this isolate is active | |
536 } else { | |
537 kill(); | |
538 } | |
539 } | |
540 | |
541 void kill() { | |
542 if (_scheduledControlEvents != null) { | |
543 // Kill all pending events. | |
544 _scheduledControlEvents.clear(); | |
545 } | |
546 // Stop listening on all ports. | |
547 // This should happen before sending events to done handlers, in case | |
548 // we are listening on ourselves. | |
549 // Closes all ports, including control port. | |
550 for (var port in ports.values) { | |
551 port._close(); | |
552 } | |
553 ports.clear(); | |
554 weakPorts.clear(); | |
555 _globalState.isolates.remove(id); // indicate this isolate is not active | |
556 errorPorts.clear(); | |
557 if (doneHandlers != null) { | |
558 for (SendPort port in doneHandlers) { | |
559 port.send(null); | |
560 } | |
561 doneHandlers = null; | |
562 } | |
563 } | |
564 | |
565 /** Unregister a port on this isolate. */ | |
566 void unregister(int portId) { | |
567 ports.remove(portId); | |
568 weakPorts.remove(portId); | |
569 _updateGlobalState(); | |
570 } | |
571 } | |
572 | |
573 /** Represent the event loop on a javascript thread (DOM or worker). */ | |
574 class _EventLoop { | |
575 final Queue<_IsolateEvent> events = new Queue<_IsolateEvent>(); | |
576 | |
577 /// The number of waiting callbacks not controlled by the dart event loop. | |
578 /// | |
579 /// This could be timers or http requests. The worker will only be killed if | |
580 /// this count reaches 0. | |
581 /// Access this by using [enterJsAsync] before starting a JavaScript async | |
582 /// operation and [leaveJsAsync] when the callback has fired. | |
583 int _activeJsAsyncCount = 0; | |
584 | |
585 _EventLoop(); | |
586 | |
587 void enqueue(isolate, fn, msg) { | |
588 events.addLast(new _IsolateEvent(isolate, fn, msg)); | |
589 } | |
590 | |
591 void prequeue(_IsolateEvent event) { | |
592 events.addFirst(event); | |
593 } | |
594 | |
595 _IsolateEvent dequeue() { | |
596 if (events.isEmpty) return null; | |
597 return events.removeFirst(); | |
598 } | |
599 | |
600 void checkOpenReceivePortsFromCommandLine() { | |
601 if (_globalState.rootContext != null | |
602 && _globalState.isolates.containsKey(_globalState.rootContext.id) | |
603 && _globalState.fromCommandLine | |
604 && _globalState.rootContext.ports.isEmpty) { | |
605 // We want to reach here only on the main [_Manager] and only | |
606 // on the command-line. In the browser the isolate might | |
607 // still be alive due to DOM callbacks, but the presumption is | |
608 // that on the command-line, no future events can be injected | |
609 // into the event queue once it's empty. Node has setTimeout | |
610 // so this presumption is incorrect there. We think(?) that | |
611 // in d8 this assumption is valid. | |
612 throw new Exception("Program exited with open ReceivePorts."); | |
613 } | |
614 } | |
615 | |
616 /** Process a single event, if any. */ | |
617 bool runIteration() { | |
618 final event = dequeue(); | |
619 if (event == null) { | |
620 checkOpenReceivePortsFromCommandLine(); | |
621 _globalState.maybeCloseWorker(); | |
622 return false; | |
623 } | |
624 event.process(); | |
625 return true; | |
626 } | |
627 | |
628 /** | |
629 * Runs multiple iterations of the run-loop. If possible, each iteration is | |
630 * run asynchronously. | |
631 */ | |
632 void _runHelper() { | |
633 if (globalWindow != null) { | |
634 // Run each iteration from the browser's top event loop. | |
635 void next() { | |
636 if (!runIteration()) return; | |
637 Timer.run(next); | |
638 } | |
639 next(); | |
640 } else { | |
641 // Run synchronously until no more iterations are available. | |
642 while (runIteration()) {} | |
643 } | |
644 } | |
645 | |
646 /** | |
647 * Call [_runHelper] but ensure that worker exceptions are propragated. | |
648 */ | |
649 void run() { | |
650 if (!_globalState.isWorker) { | |
651 _runHelper(); | |
652 } else { | |
653 try { | |
654 _runHelper(); | |
655 } catch (e, trace) { | |
656 _globalState.mainManager.postMessage(_serializeMessage( | |
657 {'command': 'error', 'msg': '$e\n$trace' })); | |
658 } | |
659 } | |
660 } | |
661 } | |
662 | |
663 /** An event in the top-level event queue. */ | |
664 class _IsolateEvent { | |
665 _IsolateContext isolate; | |
666 Function fn; | |
667 String message; | |
668 | |
669 _IsolateEvent(this.isolate, this.fn, this.message); | |
670 | |
671 void process() { | |
672 if (isolate.isPaused) { | |
673 isolate.delayedEvents.add(this); | |
674 return; | |
675 } | |
676 isolate.eval(fn); | |
677 } | |
678 } | |
679 | |
680 /** A stub for interacting with the main manager. */ | |
681 class _MainManagerStub { | |
682 void postMessage(msg) { | |
683 // "self" is a way to refer to the global context object that | |
684 // works in HTML pages and in Web Workers. It does not work in d8 | |
685 // and Firefox jsshell, because that would have been too easy. | |
686 // | |
687 // See: http://www.w3.org/TR/workers/#the-global-scope | |
688 // and: http://www.w3.org/TR/Window/#dfn-self-attribute | |
689 requiresPreamble(); | |
690 JS("void", r"self.postMessage(#)", msg); | |
691 } | |
692 } | |
693 | |
694 const String _SPAWNED_SIGNAL = "spawned"; | |
695 const String _SPAWN_FAILED_SIGNAL = "spawn failed"; | |
696 | |
697 get globalWindow { | |
698 requiresPreamble(); | |
699 return JS('', "self.window"); | |
700 } | |
701 | |
702 get globalWorker { | |
703 requiresPreamble(); | |
704 return JS('', "self.Worker"); | |
705 } | |
706 bool get globalPostMessageDefined { | |
707 requiresPreamble(); | |
708 return JS('bool', "!!self.postMessage"); | |
709 } | |
710 | |
711 typedef _MainFunction(); | |
712 typedef _MainFunctionArgs(args); | |
713 typedef _MainFunctionArgsMessage(args, message); | |
714 | |
715 /// Note: IsolateNatives depends on _globalState which is only set up correctly | |
716 /// when 'dart:isolate' has been imported. | |
717 class IsolateNatives { | |
718 | |
719 // We set [enableSpawnWorker] to true (not null) when calling isolate | |
720 // primitives that require support for spawning workers. The field starts out | |
721 // by being null, and dart2js' type inference will track if it can have a | |
722 // non-null value. So by testing if this value is not null, we generate code | |
723 // that dart2js knows is dead when worker support isn't needed. | |
724 // TODO(herhut): Initialize this to false when able to track compile-time | |
725 // constants. | |
726 static var enableSpawnWorker; | |
727 | |
728 static String thisScript = computeThisScript(); | |
729 | |
730 /// Associates an ID with a native worker object. | |
731 static final Expando<int> workerIds = new Expando<int>(); | |
732 | |
733 /** | |
734 * The src url for the script tag that loaded this Used to create | |
735 * JavaScript workers. | |
736 */ | |
737 static String computeThisScript() { | |
738 var currentScript = JS('', r'init.currentScript'); | |
739 if (currentScript != null) { | |
740 return JS('String', 'String(#.src)', currentScript); | |
741 } | |
742 if (Primitives.isD8) return computeThisScriptD8(); | |
743 if (Primitives.isJsshell) return computeThisScriptJsshell(); | |
744 // A worker has no script tag - so get an url from a stack-trace. | |
745 if (_globalState.isWorker) return computeThisScriptFromTrace(); | |
746 return null; | |
747 } | |
748 | |
749 static String computeThisScriptJsshell() { | |
750 return JS('String|Null', 'thisFilename()'); | |
751 } | |
752 | |
753 // TODO(ahe): The following is for supporting D8. We should move this code | |
754 // to a helper library that is only loaded when testing on D8. | |
755 static String computeThisScriptD8() => computeThisScriptFromTrace(); | |
756 | |
757 static String computeThisScriptFromTrace() { | |
758 var stack = JS('String|Null', 'new Error().stack'); | |
759 if (stack == null) { | |
760 // According to Internet Explorer documentation, the stack | |
761 // property is not set until the exception is thrown. The stack | |
762 // property was not provided until IE10. | |
763 stack = JS('String|Null', | |
764 '(function() {' | |
765 'try { throw new Error() } catch(e) { return e.stack }' | |
766 '})()'); | |
767 if (stack == null) throw new UnsupportedError('No stack trace'); | |
768 } | |
769 var pattern, matches; | |
770 | |
771 // This pattern matches V8, Chrome, and Internet Explorer stack | |
772 // traces that look like this: | |
773 // Error | |
774 // at methodName (URI:LINE:COLUMN) | |
775 pattern = JS('', | |
776 r'new RegExp("^ *at [^(]*\\((.*):[0-9]*:[0-9]*\\)$", "m")'); | |
777 | |
778 | |
779 matches = JS('JSExtendableArray|Null', '#.match(#)', stack, pattern); | |
780 if (matches != null) return JS('String', '#[1]', matches); | |
781 | |
782 // This pattern matches Firefox stack traces that look like this: | |
783 // methodName@URI:LINE | |
784 pattern = JS('', r'new RegExp("^[^@]*@(.*):[0-9]*$", "m")'); | |
785 | |
786 matches = JS('JSExtendableArray|Null', '#.match(#)', stack, pattern); | |
787 if (matches != null) return JS('String', '#[1]', matches); | |
788 | |
789 throw new UnsupportedError('Cannot extract URI from "$stack"'); | |
790 } | |
791 | |
792 /** | |
793 * Assume that [e] is a browser message event and extract its message data. | |
794 * We don't import the dom explicitly so, when workers are disabled, this | |
795 * library can also run on top of nodejs. | |
796 */ | |
797 static _getEventData(e) => JS("", "#.data", e); | |
798 | |
799 /** | |
800 * Process messages on a worker, either to control the worker instance or to | |
801 * pass messages along to the isolate running in the worker. | |
802 */ | |
803 static void _processWorkerMessage(/* Worker */ sender, e) { | |
804 var msg = _deserializeMessage(_getEventData(e)); | |
805 switch (msg['command']) { | |
806 case 'start': | |
807 _globalState.currentManagerId = msg['id']; | |
808 String functionName = msg['functionName']; | |
809 Function entryPoint = (functionName == null) | |
810 ? _globalState.entry | |
811 : _getJSFunctionFromName(functionName); | |
812 var args = msg['args']; | |
813 var message = _deserializeMessage(msg['msg']); | |
814 var isSpawnUri = msg['isSpawnUri']; | |
815 var startPaused = msg['startPaused']; | |
816 var replyTo = _deserializeMessage(msg['replyTo']); | |
817 var context = new _IsolateContext(); | |
818 _globalState.topEventLoop.enqueue(context, () { | |
819 _startIsolate(entryPoint, args, message, | |
820 isSpawnUri, startPaused, replyTo); | |
821 }, 'worker-start'); | |
822 // Make sure we always have a current context in this worker. | |
823 // TODO(7907): This is currently needed because we're using | |
824 // Timers to implement Futures, and this isolate library | |
825 // implementation uses Futures. We should either stop using | |
826 // Futures in this library, or re-adapt if Futures get a | |
827 // different implementation. | |
828 _globalState.currentContext = context; | |
829 _globalState.topEventLoop.run(); | |
830 break; | |
831 case 'spawn-worker': | |
832 if (enableSpawnWorker != null) handleSpawnWorkerRequest(msg); | |
833 break; | |
834 case 'message': | |
835 SendPort port = msg['port']; | |
836 // If the port has been closed, we ignore the message. | |
837 if (port != null) { | |
838 msg['port'].send(msg['msg']); | |
839 } | |
840 _globalState.topEventLoop.run(); | |
841 break; | |
842 case 'close': | |
843 _globalState.managers.remove(workerIds[sender]); | |
844 JS('void', '#.terminate()', sender); | |
845 _globalState.topEventLoop.run(); | |
846 break; | |
847 case 'log': | |
848 _log(msg['msg']); | |
849 break; | |
850 case 'print': | |
851 if (_globalState.isWorker) { | |
852 _globalState.mainManager.postMessage( | |
853 _serializeMessage({'command': 'print', 'msg': msg})); | |
854 } else { | |
855 print(msg['msg']); | |
856 } | |
857 break; | |
858 case 'error': | |
859 throw msg['msg']; | |
860 } | |
861 } | |
862 | |
863 static handleSpawnWorkerRequest(msg) { | |
864 var replyPort = msg['replyPort']; | |
865 spawn(msg['functionName'], msg['uri'], | |
866 msg['args'], msg['msg'], | |
867 false, msg['isSpawnUri'], msg['startPaused']).then((msg) { | |
868 replyPort.send(msg); | |
869 }, onError: (String errorMessage) { | |
870 replyPort.send([_SPAWN_FAILED_SIGNAL, errorMessage]); | |
871 }); | |
872 } | |
873 | |
874 /** Log a message, forwarding to the main [_Manager] if appropriate. */ | |
875 static _log(msg) { | |
876 if (_globalState.isWorker) { | |
877 _globalState.mainManager.postMessage( | |
878 _serializeMessage({'command': 'log', 'msg': msg })); | |
879 } else { | |
880 try { | |
881 _consoleLog(msg); | |
882 } catch (e, trace) { | |
883 throw new Exception(trace); | |
884 } | |
885 } | |
886 } | |
887 | |
888 static void _consoleLog(msg) { | |
889 requiresPreamble(); | |
890 JS("void", r"self.console.log(#)", msg); | |
891 } | |
892 | |
893 static _getJSFunctionFromName(String functionName) { | |
894 return JS("", "init.globalFunctions[#]()", functionName); | |
895 } | |
896 | |
897 /** | |
898 * Get a string name for the function, if possible. The result for | |
899 * anonymous functions is browser-dependent -- it may be "" or "anonymous" | |
900 * but you should probably not count on this. | |
901 */ | |
902 static String _getJSFunctionName(Function f) { | |
903 return (f is Closure) ? JS("String|Null", r'#.$name', f) : null; | |
904 } | |
905 | |
906 /** Create a new JavaScript object instance given its constructor. */ | |
907 static dynamic _allocate(var ctor) { | |
908 return JS("", "new #()", ctor); | |
909 } | |
910 | |
911 static Future<List> spawnFunction(void topLevelFunction(message), | |
912 var message, | |
913 bool startPaused) { | |
914 IsolateNatives.enableSpawnWorker = true; | |
915 final name = _getJSFunctionName(topLevelFunction); | |
916 if (name == null) { | |
917 throw new UnsupportedError( | |
918 "only top-level functions can be spawned."); | |
919 } | |
920 bool isLight = false; | |
921 bool isSpawnUri = false; | |
922 return spawn(name, null, null, message, isLight, isSpawnUri, startPaused); | |
923 } | |
924 | |
925 static Future<List> spawnUri(Uri uri, List<String> args, var message, | |
926 bool startPaused) { | |
927 IsolateNatives.enableSpawnWorker = true; | |
928 bool isLight = false; | |
929 bool isSpawnUri = true; | |
930 return spawn(null, uri.toString(), args, message, | |
931 isLight, isSpawnUri, startPaused); | |
932 } | |
933 | |
934 // TODO(sigmund): clean up above, after we make the new API the default: | |
935 | |
936 /// If [uri] is `null` it is replaced with the current script. | |
937 static Future<List> spawn(String functionName, String uri, | |
938 List<String> args, message, | |
939 bool isLight, bool isSpawnUri, bool startPaused) { | |
940 // Assume that the compiled version of the Dart file lives just next to the | |
941 // dart file. | |
942 // TODO(floitsch): support precompiled version of dart2js output. | |
943 if (uri != null && uri.endsWith(".dart")) uri += ".js"; | |
944 | |
945 ReceivePort port = new ReceivePort(); | |
946 Completer<List> completer = new Completer(); | |
947 port.first.then((msg) { | |
948 if (msg[0] == _SPAWNED_SIGNAL) { | |
949 completer.complete(msg); | |
950 } else { | |
951 assert(msg[0] == _SPAWN_FAILED_SIGNAL); | |
952 completer.completeError(msg[1]); | |
953 } | |
954 }); | |
955 | |
956 SendPort signalReply = port.sendPort; | |
957 | |
958 if (_globalState.useWorkers && !isLight) { | |
959 _startWorker( | |
960 functionName, uri, args, message, isSpawnUri, startPaused, | |
961 signalReply, (String message) => completer.completeError(message)); | |
962 } else { | |
963 _startNonWorker( | |
964 functionName, uri, args, message, isSpawnUri, startPaused, | |
965 signalReply); | |
966 } | |
967 return completer.future; | |
968 } | |
969 | |
970 static void _startWorker( | |
971 String functionName, String uri, | |
972 List<String> args, message, | |
973 bool isSpawnUri, | |
974 bool startPaused, | |
975 SendPort replyPort, | |
976 void onError(String message)) { | |
977 if (_globalState.isWorker) { | |
978 _globalState.mainManager.postMessage(_serializeMessage({ | |
979 'command': 'spawn-worker', | |
980 'functionName': functionName, | |
981 'args': args, | |
982 'msg': message, | |
983 'uri': uri, | |
984 'isSpawnUri': isSpawnUri, | |
985 'startPaused': startPaused, | |
986 'replyPort': replyPort})); | |
987 } else { | |
988 _spawnWorker(functionName, uri, args, message, | |
989 isSpawnUri, startPaused, replyPort, onError); | |
990 } | |
991 } | |
992 | |
993 static void _startNonWorker( | |
994 String functionName, String uri, | |
995 List<String> args, var message, | |
996 bool isSpawnUri, | |
997 bool startPaused, | |
998 SendPort replyPort) { | |
999 // TODO(eub): support IE9 using an iframe -- Dart issue 1702. | |
1000 if (uri != null) { | |
1001 throw new UnsupportedError( | |
1002 "Currently spawnUri is not supported without web workers."); | |
1003 } | |
1004 message = _serializeMessage(message); | |
1005 args = _serializeMessage(args); // Or just args.toList() ? | |
1006 _globalState.topEventLoop.enqueue(new _IsolateContext(), () { | |
1007 final func = _getJSFunctionFromName(functionName); | |
1008 _startIsolate(func, args, message, isSpawnUri, startPaused, replyPort); | |
1009 }, 'nonworker start'); | |
1010 } | |
1011 | |
1012 static void _startIsolate(Function topLevel, | |
1013 List<String> args, message, | |
1014 bool isSpawnUri, | |
1015 bool startPaused, | |
1016 SendPort replyTo) { | |
1017 _IsolateContext context = JS_CURRENT_ISOLATE_CONTEXT(); | |
1018 Primitives.initializeStatics(context.id); | |
1019 // The isolate's port does not keep the isolate open. | |
1020 replyTo.send([_SPAWNED_SIGNAL, | |
1021 context.controlPort.sendPort, | |
1022 context.pauseCapability, | |
1023 context.terminateCapability]); | |
1024 | |
1025 void runStartFunction() { | |
1026 context.initialized = true; | |
1027 if (!isSpawnUri) { | |
1028 topLevel(message); | |
1029 } else if (topLevel is _MainFunctionArgsMessage) { | |
1030 topLevel(args, message); | |
1031 } else if (topLevel is _MainFunctionArgs) { | |
1032 topLevel(args); | |
1033 } else { | |
1034 topLevel(); | |
1035 } | |
1036 } | |
1037 | |
1038 if (startPaused) { | |
1039 context.addPause(context.pauseCapability, context.pauseCapability); | |
1040 _globalState.topEventLoop.enqueue(context, runStartFunction, | |
1041 'start isolate'); | |
1042 } else { | |
1043 runStartFunction(); | |
1044 } | |
1045 } | |
1046 | |
1047 /** | |
1048 * Spawns an isolate in a worker. [factoryName] is the Javascript constructor | |
1049 * name for the isolate entry point class. | |
1050 */ | |
1051 static void _spawnWorker(functionName, String uri, | |
1052 List<String> args, message, | |
1053 bool isSpawnUri, | |
1054 bool startPaused, | |
1055 SendPort replyPort, | |
1056 void onError(String message)) { | |
1057 if (uri == null) uri = thisScript; | |
1058 final worker = JS('var', 'new Worker(#)', uri); | |
1059 // Trampolines are used when wanting to call a Dart closure from | |
1060 // JavaScript. The helper function DART_CLOSURE_TO_JS only accepts | |
1061 // top-level or static methods, and the trampoline allows us to capture | |
1062 // arguments and values which can be passed to a static method. | |
1063 final onerrorTrampoline = JS( | |
1064 '', | |
1065 ''' | |
1066 (function (f, u, c) { | |
1067 return function(e) { | |
1068 return f(e, u, c) | |
1069 } | |
1070 })(#, #, #)''', | |
1071 DART_CLOSURE_TO_JS(workerOnError), uri, onError); | |
1072 JS('void', '#.onerror = #', worker, onerrorTrampoline); | |
1073 | |
1074 var processWorkerMessageTrampoline = JS( | |
1075 '', | |
1076 """ | |
1077 (function (f, a) { | |
1078 return function (e) { | |
1079 // We can stop listening for errors when the first message is received as | |
1080 // we only listen for messages to determine if the uri was bad. | |
1081 e.onerror = null; | |
1082 return f(a, e); | |
1083 } | |
1084 })(#, #)""", | |
1085 DART_CLOSURE_TO_JS(_processWorkerMessage), | |
1086 worker); | |
1087 JS('void', '#.onmessage = #', worker, processWorkerMessageTrampoline); | |
1088 var workerId = _globalState.nextManagerId++; | |
1089 // We also store the id on the worker itself so that we can unregister it. | |
1090 workerIds[worker] = workerId; | |
1091 _globalState.managers[workerId] = worker; | |
1092 JS('void', '#.postMessage(#)', worker, _serializeMessage({ | |
1093 'command': 'start', | |
1094 'id': workerId, | |
1095 // Note: we serialize replyPort twice because the child worker needs to | |
1096 // first deserialize the worker id, before it can correctly deserialize | |
1097 // the port (port deserialization is sensitive to what is the current | |
1098 // workerId). | |
1099 'replyTo': _serializeMessage(replyPort), | |
1100 'args': args, | |
1101 'msg': _serializeMessage(message), | |
1102 'isSpawnUri': isSpawnUri, | |
1103 'startPaused': startPaused, | |
1104 'functionName': functionName })); | |
1105 } | |
1106 | |
1107 static bool workerOnError( | |
1108 /* Event */ event, | |
1109 String uri, | |
1110 void onError(String message)) { | |
1111 // Attempt to shut up the browser, as the error has been handled. Chrome | |
1112 // ignores this :-( | |
1113 JS('void', '#.preventDefault()', event); | |
1114 String message = JS('String|Null', '#.message', event); | |
1115 if (message == null) { | |
1116 // Some browsers, including Chrome, fail to provide a proper error | |
1117 // event. | |
1118 message = 'Error spawning worker for $uri'; | |
1119 } else { | |
1120 message = 'Error spawning worker for $uri ($message)'; | |
1121 } | |
1122 onError(message); | |
1123 return true; | |
1124 } | |
1125 } | |
1126 | |
1127 /******************************************************** | |
1128 Inserted from lib/isolate/dart2js/ports.dart | |
1129 ********************************************************/ | |
1130 | |
1131 /** Common functionality to all send ports. */ | |
1132 abstract class _BaseSendPort implements SendPort { | |
1133 /** Id for the destination isolate. */ | |
1134 final int _isolateId; | |
1135 | |
1136 const _BaseSendPort(this._isolateId); | |
1137 | |
1138 void _checkReplyTo(SendPort replyTo) { | |
1139 if (replyTo != null | |
1140 && replyTo is! _NativeJsSendPort | |
1141 && replyTo is! _WorkerSendPort) { | |
1142 throw new Exception("SendPort.send: Illegal replyTo port type"); | |
1143 } | |
1144 } | |
1145 | |
1146 void send(var message); | |
1147 bool operator ==(var other); | |
1148 int get hashCode; | |
1149 } | |
1150 | |
1151 /** A send port that delivers messages in-memory via native JavaScript calls. */ | |
1152 class _NativeJsSendPort extends _BaseSendPort implements SendPort { | |
1153 final RawReceivePortImpl _receivePort; | |
1154 | |
1155 const _NativeJsSendPort(this._receivePort, int isolateId) : super(isolateId); | |
1156 | |
1157 void send(var message) { | |
1158 // Check that the isolate still runs and the port is still open | |
1159 final isolate = _globalState.isolates[_isolateId]; | |
1160 if (isolate == null) return; | |
1161 if (_receivePort._isClosed) return; | |
1162 // We force serialization/deserialization as a simple way to ensure | |
1163 // isolate communication restrictions are respected between isolates that | |
1164 // live in the same worker. [_NativeJsSendPort] delivers both messages | |
1165 // from the same worker and messages from other workers. In particular, | |
1166 // messages sent from a worker via a [_WorkerSendPort] are received at | |
1167 // [_processWorkerMessage] and forwarded to a native port. In such cases, | |
1168 // here we'll see [_globalState.currentContext == null]. | |
1169 final shouldSerialize = _globalState.currentContext != null | |
1170 && _globalState.currentContext.id != _isolateId; | |
1171 var msg = message; | |
1172 if (shouldSerialize) { | |
1173 msg = _serializeMessage(msg); | |
1174 } | |
1175 if (isolate.controlPort == _receivePort) { | |
1176 isolate.handleControlMessage(msg); | |
1177 return; | |
1178 } | |
1179 _globalState.topEventLoop.enqueue(isolate, () { | |
1180 if (!_receivePort._isClosed) { | |
1181 if (shouldSerialize) { | |
1182 msg = _deserializeMessage(msg); | |
1183 } | |
1184 _receivePort._add(msg); | |
1185 } | |
1186 }, 'receive $message'); | |
1187 } | |
1188 | |
1189 bool operator ==(var other) => (other is _NativeJsSendPort) && | |
1190 (_receivePort == other._receivePort); | |
1191 | |
1192 int get hashCode => _receivePort._id; | |
1193 } | |
1194 | |
1195 /** A send port that delivers messages via worker.postMessage. */ | |
1196 // TODO(eub): abstract this for iframes. | |
1197 class _WorkerSendPort extends _BaseSendPort implements SendPort { | |
1198 final int _workerId; | |
1199 final int _receivePortId; | |
1200 | |
1201 const _WorkerSendPort(this._workerId, int isolateId, this._receivePortId) | |
1202 : super(isolateId); | |
1203 | |
1204 void send(var message) { | |
1205 final workerMessage = _serializeMessage({ | |
1206 'command': 'message', | |
1207 'port': this, | |
1208 'msg': message}); | |
1209 | |
1210 if (_globalState.isWorker) { | |
1211 // Communication from one worker to another go through the | |
1212 // main worker. | |
1213 _globalState.mainManager.postMessage(workerMessage); | |
1214 } else { | |
1215 // Deliver the message only if the worker is still alive. | |
1216 /* Worker */ var manager = _globalState.managers[_workerId]; | |
1217 if (manager != null) { | |
1218 JS('void', '#.postMessage(#)', manager, workerMessage); | |
1219 } | |
1220 } | |
1221 } | |
1222 | |
1223 bool operator ==(var other) { | |
1224 return (other is _WorkerSendPort) && | |
1225 (_workerId == other._workerId) && | |
1226 (_isolateId == other._isolateId) && | |
1227 (_receivePortId == other._receivePortId); | |
1228 } | |
1229 | |
1230 int get hashCode { | |
1231 // TODO(sigmund): use a standard hash when we get one available in corelib. | |
1232 return (_workerId << 16) ^ (_isolateId << 8) ^ _receivePortId; | |
1233 } | |
1234 } | |
1235 | |
1236 class RawReceivePortImpl implements RawReceivePort { | |
1237 static int _nextFreeId = 1; | |
1238 | |
1239 final int _id; | |
1240 Function _handler; | |
1241 bool _isClosed = false; | |
1242 | |
1243 RawReceivePortImpl(this._handler) : _id = _nextFreeId++ { | |
1244 _globalState.currentContext.register(_id, this); | |
1245 } | |
1246 | |
1247 RawReceivePortImpl.weak(this._handler) : _id = _nextFreeId++ { | |
1248 _globalState.currentContext.registerWeak(_id, this); | |
1249 } | |
1250 | |
1251 // Creates the control port of an isolate. | |
1252 // This is created before the isolate context object itself, | |
1253 // so it cannot access the static _nextFreeId field. | |
1254 RawReceivePortImpl._controlPort() : _handler = null, _id = 0; | |
1255 | |
1256 void set handler(Function newHandler) { | |
1257 _handler = newHandler; | |
1258 } | |
1259 | |
1260 // Close the port without unregistering it. | |
1261 // Used by an isolate context to close all ports when shutting down. | |
1262 void _close() { | |
1263 _isClosed = true; | |
1264 _handler = null; | |
1265 } | |
1266 | |
1267 void close() { | |
1268 if (_isClosed) return; | |
1269 _isClosed = true; | |
1270 _handler = null; | |
1271 _globalState.currentContext.unregister(_id); | |
1272 } | |
1273 | |
1274 void _add(dataEvent) { | |
1275 if (_isClosed) return; | |
1276 _handler(dataEvent); | |
1277 } | |
1278 | |
1279 SendPort get sendPort { | |
1280 return new _NativeJsSendPort(this, _globalState.currentContext.id); | |
1281 } | |
1282 } | |
1283 | |
1284 class ReceivePortImpl extends Stream implements ReceivePort { | |
1285 final RawReceivePort _rawPort; | |
1286 StreamController _controller; | |
1287 | |
1288 ReceivePortImpl() : this.fromRawReceivePort(new RawReceivePortImpl(null)); | |
1289 | |
1290 ReceivePortImpl.weak() | |
1291 : this.fromRawReceivePort(new RawReceivePortImpl.weak(null)); | |
1292 | |
1293 ReceivePortImpl.fromRawReceivePort(this._rawPort) { | |
1294 _controller = new StreamController(onCancel: close, sync: true); | |
1295 _rawPort.handler = _controller.add; | |
1296 } | |
1297 | |
1298 StreamSubscription listen(void onData(var event), | |
1299 {Function onError, | |
1300 void onDone(), | |
1301 bool cancelOnError}) { | |
1302 return _controller.stream.listen(onData, onError: onError, onDone: onDone, | |
1303 cancelOnError: cancelOnError); | |
1304 } | |
1305 | |
1306 void close() { | |
1307 _rawPort.close(); | |
1308 _controller.close(); | |
1309 } | |
1310 | |
1311 SendPort get sendPort => _rawPort.sendPort; | |
1312 } | |
1313 | |
1314 | |
1315 /******************************************************** | |
1316 Inserted from lib/isolate/dart2js/messages.dart | |
1317 ********************************************************/ | |
1318 | |
1319 // Defines message visitors, serialization, and deserialization. | |
1320 | |
1321 /** Serialize [message] (or simulate serialization). */ | |
1322 _serializeMessage(message) { | |
1323 if (_globalState.needSerialization) { | |
1324 return new _JsSerializer().traverse(message); | |
1325 } else { | |
1326 return new _JsCopier().traverse(message); | |
1327 } | |
1328 } | |
1329 | |
1330 /** Deserialize [message] (or simulate deserialization). */ | |
1331 _deserializeMessage(message) { | |
1332 if (_globalState.needSerialization) { | |
1333 return new _JsDeserializer().deserialize(message); | |
1334 } else { | |
1335 // Nothing more to do. | |
1336 return message; | |
1337 } | |
1338 } | |
1339 | |
1340 class _JsSerializer extends _Serializer { | |
1341 | |
1342 _JsSerializer() : super() { _visited = new _JsVisitedMap(); } | |
1343 | |
1344 visitSendPort(SendPort x) { | |
1345 if (x is _NativeJsSendPort) return visitNativeJsSendPort(x); | |
1346 if (x is _WorkerSendPort) return visitWorkerSendPort(x); | |
1347 throw "Illegal underlying port $x"; | |
1348 } | |
1349 | |
1350 visitCapability(Capability x) { | |
1351 if (x is CapabilityImpl) { | |
1352 return ['capability', x._id]; | |
1353 } | |
1354 throw "Capability not serializable: $x"; | |
1355 } | |
1356 | |
1357 visitNativeJsSendPort(_NativeJsSendPort port) { | |
1358 return ['sendport', _globalState.currentManagerId, | |
1359 port._isolateId, port._receivePort._id]; | |
1360 } | |
1361 | |
1362 visitWorkerSendPort(_WorkerSendPort port) { | |
1363 return ['sendport', port._workerId, port._isolateId, port._receivePortId]; | |
1364 } | |
1365 } | |
1366 | |
1367 | |
1368 class _JsCopier extends _Copier { | |
1369 | |
1370 _JsCopier() : super() { _visited = new _JsVisitedMap(); } | |
1371 | |
1372 visitSendPort(SendPort x) { | |
1373 if (x is _NativeJsSendPort) return visitNativeJsSendPort(x); | |
1374 if (x is _WorkerSendPort) return visitWorkerSendPort(x); | |
1375 throw "Illegal underlying port $x"; | |
1376 } | |
1377 | |
1378 visitCapability(Capability x) { | |
1379 if (x is CapabilityImpl) { | |
1380 return new CapabilityImpl._internal(x._id); | |
1381 } | |
1382 throw "Capability not serializable: $x"; | |
1383 } | |
1384 | |
1385 SendPort visitNativeJsSendPort(_NativeJsSendPort port) { | |
1386 return new _NativeJsSendPort(port._receivePort, port._isolateId); | |
1387 } | |
1388 | |
1389 SendPort visitWorkerSendPort(_WorkerSendPort port) { | |
1390 return new _WorkerSendPort( | |
1391 port._workerId, port._isolateId, port._receivePortId); | |
1392 } | |
1393 } | |
1394 | |
1395 class _JsDeserializer extends _Deserializer { | |
1396 | |
1397 SendPort deserializeSendPort(List list) { | |
1398 int managerId = list[1]; | |
1399 int isolateId = list[2]; | |
1400 int receivePortId = list[3]; | |
1401 // If two isolates are in the same manager, we use NativeJsSendPorts to | |
1402 // deliver messages directly without using postMessage. | |
1403 if (managerId == _globalState.currentManagerId) { | |
1404 var isolate = _globalState.isolates[isolateId]; | |
1405 if (isolate == null) return null; // Isolate has been closed. | |
1406 var receivePort = isolate.lookup(receivePortId); | |
1407 if (receivePort == null) return null; // Port has been closed. | |
1408 return new _NativeJsSendPort(receivePort, isolateId); | |
1409 } else { | |
1410 return new _WorkerSendPort(managerId, isolateId, receivePortId); | |
1411 } | |
1412 } | |
1413 | |
1414 Capability deserializeCapability(List list) { | |
1415 return new CapabilityImpl._internal(list[1]); | |
1416 } | |
1417 } | |
1418 | |
1419 class _JsVisitedMap implements _MessageTraverserVisitedMap { | |
1420 List tagged; | |
1421 | |
1422 /** Retrieves any information stored in the native object [object]. */ | |
1423 operator[](var object) { | |
1424 return _getAttachedInfo(object); | |
1425 } | |
1426 | |
1427 /** Injects some information into the native [object]. */ | |
1428 void operator[]=(var object, var info) { | |
1429 tagged.add(object); | |
1430 _setAttachedInfo(object, info); | |
1431 } | |
1432 | |
1433 /** Get ready to rumble. */ | |
1434 void reset() { | |
1435 assert(tagged == null); | |
1436 tagged = new List(); | |
1437 } | |
1438 | |
1439 /** Remove all information injected in the native objects. */ | |
1440 void cleanup() { | |
1441 for (int i = 0, length = tagged.length; i < length; i++) { | |
1442 _clearAttachedInfo(tagged[i]); | |
1443 } | |
1444 tagged = null; | |
1445 } | |
1446 | |
1447 void _clearAttachedInfo(var o) { | |
1448 JS("void", "#['__MessageTraverser__attached_info__'] = #", o, null); | |
1449 } | |
1450 | |
1451 void _setAttachedInfo(var o, var info) { | |
1452 JS("void", "#['__MessageTraverser__attached_info__'] = #", o, info); | |
1453 } | |
1454 | |
1455 _getAttachedInfo(var o) { | |
1456 return JS("", "#['__MessageTraverser__attached_info__']", o); | |
1457 } | |
1458 } | |
1459 | |
1460 // only visible for testing purposes | |
1461 // TODO(sigmund): remove once we can disable privacy for testing (bug #1882) | |
1462 class TestingOnly { | |
1463 static copy(x) { | |
1464 return new _JsCopier().traverse(x); | |
1465 } | |
1466 | |
1467 // only visible for testing purposes | |
1468 static serialize(x) { | |
1469 _Serializer serializer = new _JsSerializer(); | |
1470 _Deserializer deserializer = new _JsDeserializer(); | |
1471 return deserializer.deserialize(serializer.traverse(x)); | |
1472 } | |
1473 } | |
1474 | |
1475 /******************************************************** | |
1476 Inserted from lib/isolate/serialization.dart | |
1477 ********************************************************/ | |
1478 | |
1479 class _MessageTraverserVisitedMap { | |
1480 | |
1481 operator[](var object) => null; | |
1482 void operator[]=(var object, var info) { } | |
1483 | |
1484 void reset() { } | |
1485 void cleanup() { } | |
1486 | |
1487 } | |
1488 | |
1489 /** Abstract visitor for dart objects that can be sent as isolate messages. */ | |
1490 abstract class _MessageTraverser { | |
1491 | |
1492 _MessageTraverserVisitedMap _visited; | |
1493 _MessageTraverser() : _visited = new _MessageTraverserVisitedMap(); | |
1494 | |
1495 /** Visitor's entry point. */ | |
1496 traverse(var x) { | |
1497 if (isPrimitive(x)) return visitPrimitive(x); | |
1498 _visited.reset(); | |
1499 var result; | |
1500 try { | |
1501 result = _dispatch(x); | |
1502 } finally { | |
1503 _visited.cleanup(); | |
1504 } | |
1505 return result; | |
1506 } | |
1507 | |
1508 _dispatch(var x) { | |
1509 // This code likely fails for user classes implementing | |
1510 // SendPort and Capability because it assumes the internal classes. | |
1511 if (isPrimitive(x)) return visitPrimitive(x); | |
1512 if (x is List) return visitList(x); | |
1513 if (x is Map) return visitMap(x); | |
1514 if (x is SendPort) return visitSendPort(x); | |
1515 if (x is Capability) return visitCapability(x); | |
1516 | |
1517 // Overridable fallback. | |
1518 return visitObject(x); | |
1519 } | |
1520 | |
1521 visitPrimitive(x); | |
1522 visitList(List x); | |
1523 visitMap(Map x); | |
1524 visitSendPort(SendPort x); | |
1525 visitCapability(Capability x); | |
1526 | |
1527 visitObject(Object x) { | |
1528 // TODO(floitsch): make this a real exception. (which one)? | |
1529 throw "Message serialization: Illegal value $x passed"; | |
1530 } | |
1531 | |
1532 static bool isPrimitive(x) { | |
1533 return (x == null) || (x is String) || (x is num) || (x is bool); | |
1534 } | |
1535 } | |
1536 | |
1537 | |
1538 /** A visitor that recursively copies a message. */ | |
1539 class _Copier extends _MessageTraverser { | |
1540 | |
1541 visitPrimitive(x) => x; | |
1542 | |
1543 List visitList(List list) { | |
1544 List copy = _visited[list]; | |
1545 if (copy != null) return copy; | |
1546 | |
1547 int len = list.length; | |
1548 | |
1549 // TODO(floitsch): we loose the generic type of the List. | |
1550 copy = new List(len); | |
1551 _visited[list] = copy; | |
1552 for (int i = 0; i < len; i++) { | |
1553 copy[i] = _dispatch(list[i]); | |
1554 } | |
1555 return copy; | |
1556 } | |
1557 | |
1558 Map visitMap(Map map) { | |
1559 Map copy = _visited[map]; | |
1560 if (copy != null) return copy; | |
1561 | |
1562 // TODO(floitsch): we loose the generic type of the map. | |
1563 copy = new Map(); | |
1564 _visited[map] = copy; | |
1565 map.forEach((key, val) { | |
1566 copy[_dispatch(key)] = _dispatch(val); | |
1567 }); | |
1568 return copy; | |
1569 } | |
1570 | |
1571 visitSendPort(SendPort x) => throw new UnimplementedError(); | |
1572 | |
1573 visitCapability(Capability x) => throw new UnimplementedError(); | |
1574 } | |
1575 | |
1576 /** Visitor that serializes a message as a JSON array. */ | |
1577 class _Serializer extends _MessageTraverser { | |
1578 int _nextFreeRefId = 0; | |
1579 | |
1580 visitPrimitive(x) => x; | |
1581 | |
1582 visitList(List list) { | |
1583 int copyId = _visited[list]; | |
1584 if (copyId != null) return ['ref', copyId]; | |
1585 | |
1586 int id = _nextFreeRefId++; | |
1587 _visited[list] = id; | |
1588 var jsArray = _serializeList(list); | |
1589 // TODO(floitsch): we are losing the generic type. | |
1590 return ['list', id, jsArray]; | |
1591 } | |
1592 | |
1593 visitMap(Map map) { | |
1594 int copyId = _visited[map]; | |
1595 if (copyId != null) return ['ref', copyId]; | |
1596 | |
1597 int id = _nextFreeRefId++; | |
1598 _visited[map] = id; | |
1599 var keys = _serializeList(map.keys.toList()); | |
1600 var values = _serializeList(map.values.toList()); | |
1601 // TODO(floitsch): we are losing the generic type. | |
1602 return ['map', id, keys, values]; | |
1603 } | |
1604 | |
1605 _serializeList(List list) { | |
1606 int len = list.length; | |
1607 // Use a growable list because we do not add extra properties on | |
1608 // them. | |
1609 var result = new List()..length = len; | |
1610 for (int i = 0; i < len; i++) { | |
1611 result[i] = _dispatch(list[i]); | |
1612 } | |
1613 return result; | |
1614 } | |
1615 | |
1616 visitSendPort(SendPort x) => throw new UnimplementedError(); | |
1617 | |
1618 visitCapability(Capability x) => throw new UnimplementedError(); | |
1619 } | |
1620 | |
1621 /** Deserializes arrays created with [_Serializer]. */ | |
1622 abstract class _Deserializer { | |
1623 Map<int, dynamic> _deserialized; | |
1624 | |
1625 _Deserializer(); | |
1626 | |
1627 static bool isPrimitive(x) { | |
1628 return (x == null) || (x is String) || (x is num) || (x is bool); | |
1629 } | |
1630 | |
1631 deserialize(x) { | |
1632 if (isPrimitive(x)) return x; | |
1633 // TODO(floitsch): this should be new HashMap<int, dynamic>() | |
1634 _deserialized = new HashMap(); | |
1635 return _deserializeHelper(x); | |
1636 } | |
1637 | |
1638 _deserializeHelper(x) { | |
1639 if (isPrimitive(x)) return x; | |
1640 assert(x is List); | |
1641 switch (x[0]) { | |
1642 case 'ref': return _deserializeRef(x); | |
1643 case 'list': return _deserializeList(x); | |
1644 case 'map': return _deserializeMap(x); | |
1645 case 'sendport': return deserializeSendPort(x); | |
1646 case 'capability': return deserializeCapability(x); | |
1647 default: return deserializeObject(x); | |
1648 } | |
1649 } | |
1650 | |
1651 _deserializeRef(List x) { | |
1652 int id = x[1]; | |
1653 var result = _deserialized[id]; | |
1654 assert(result != null); | |
1655 return result; | |
1656 } | |
1657 | |
1658 List _deserializeList(List x) { | |
1659 int id = x[1]; | |
1660 // We rely on the fact that Dart-lists are directly mapped to Js-arrays. | |
1661 List dartList = x[2]; | |
1662 _deserialized[id] = dartList; | |
1663 int len = dartList.length; | |
1664 for (int i = 0; i < len; i++) { | |
1665 dartList[i] = _deserializeHelper(dartList[i]); | |
1666 } | |
1667 return dartList; | |
1668 } | |
1669 | |
1670 Map _deserializeMap(List x) { | |
1671 Map result = new Map(); | |
1672 int id = x[1]; | |
1673 _deserialized[id] = result; | |
1674 List keys = x[2]; | |
1675 List values = x[3]; | |
1676 int len = keys.length; | |
1677 assert(len == values.length); | |
1678 for (int i = 0; i < len; i++) { | |
1679 var key = _deserializeHelper(keys[i]); | |
1680 var value = _deserializeHelper(values[i]); | |
1681 result[key] = value; | |
1682 } | |
1683 return result; | |
1684 } | |
1685 | |
1686 deserializeSendPort(List x); | |
1687 | |
1688 deserializeCapability(List x); | |
1689 | |
1690 deserializeObject(List x) { | |
1691 // TODO(floitsch): Use real exception (which one?). | |
1692 throw "Unexpected serialized object"; | |
1693 } | |
1694 } | |
1695 | |
1696 class TimerImpl implements Timer { | |
1697 final bool _once; | |
1698 bool _inEventLoop = false; | |
1699 int _handle; | |
1700 | |
1701 TimerImpl(int milliseconds, void callback()) | |
1702 : _once = true { | |
1703 if (milliseconds == 0 && (!hasTimer() || _globalState.isWorker)) { | |
1704 | |
1705 void internalCallback() { | |
1706 _handle = null; | |
1707 callback(); | |
1708 } | |
1709 | |
1710 // Setting _handle to something different from null indicates that the | |
1711 // callback has not been run. Hence, the choice of 1 is arbitrary. | |
1712 _handle = 1; | |
1713 | |
1714 // This makes a dependency between the async library and the | |
1715 // event loop of the isolate library. The compiler makes sure | |
1716 // that the event loop is compiled if [Timer] is used. | |
1717 // TODO(7907): In case of web workers, we need to use the event | |
1718 // loop instead of setTimeout, to make sure the futures get executed in | |
1719 // order. | |
1720 _globalState.topEventLoop.enqueue( | |
1721 _globalState.currentContext, internalCallback, 'timer'); | |
1722 _inEventLoop = true; | |
1723 } else if (hasTimer()) { | |
1724 | |
1725 void internalCallback() { | |
1726 _handle = null; | |
1727 leaveJsAsync(); | |
1728 callback(); | |
1729 } | |
1730 | |
1731 enterJsAsync(); | |
1732 | |
1733 _handle = JS('int', 'self.setTimeout(#, #)', | |
1734 convertDartClosureToJS(internalCallback, 0), | |
1735 milliseconds); | |
1736 } else { | |
1737 assert(milliseconds > 0); | |
1738 throw new UnsupportedError("Timer greater than 0."); | |
1739 } | |
1740 } | |
1741 | |
1742 TimerImpl.periodic(int milliseconds, void callback(Timer timer)) | |
1743 : _once = false { | |
1744 if (hasTimer()) { | |
1745 enterJsAsync(); | |
1746 _handle = JS('int', 'self.setInterval(#, #)', | |
1747 convertDartClosureToJS(() { callback(this); }, 0), | |
1748 milliseconds); | |
1749 } else { | |
1750 throw new UnsupportedError("Periodic timer."); | |
1751 } | |
1752 } | |
1753 | |
1754 void cancel() { | |
1755 if (hasTimer()) { | |
1756 if (_inEventLoop) { | |
1757 throw new UnsupportedError("Timer in event loop cannot be canceled."); | |
1758 } | |
1759 if (_handle == null) return; | |
1760 leaveJsAsync(); | |
1761 if (_once) { | |
1762 JS('void', 'self.clearTimeout(#)', _handle); | |
1763 } else { | |
1764 JS('void', 'self.clearInterval(#)', _handle); | |
1765 } | |
1766 _handle = null; | |
1767 } else { | |
1768 throw new UnsupportedError("Canceling a timer."); | |
1769 } | |
1770 } | |
1771 | |
1772 bool get isActive => _handle != null; | |
1773 } | |
1774 | |
1775 bool hasTimer() { | |
1776 requiresPreamble(); | |
1777 return JS('', 'self.setTimeout') != null; | |
1778 } | |
1779 | |
1780 | |
1781 /** | |
1782 * Implementation class for [Capability]. | |
1783 * | |
1784 * It has the same name to make it harder for users to distinguish. | |
1785 */ | |
1786 class CapabilityImpl implements Capability { | |
1787 /** Internal random secret identifying the capability. */ | |
1788 final int _id; | |
1789 | |
1790 CapabilityImpl() : this._internal(random64()); | |
1791 | |
1792 CapabilityImpl._internal(this._id); | |
1793 | |
1794 int get hashCode { | |
1795 // Thomas Wang 32 bit Mix. | |
1796 // http://www.concentric.net/~Ttwang/tech/inthash.htm | |
1797 // (via https://gist.github.com/badboy/6267743) | |
1798 int hash = _id; | |
1799 hash = (hash >> 0) ^ (hash ~/ 0x100000000); // To 32 bit from ~64. | |
1800 hash = (~hash + (hash << 15)) & 0xFFFFFFFF; | |
1801 hash ^= hash >> 12; | |
1802 hash = (hash * 5) & 0xFFFFFFFF; | |
1803 hash ^= hash >> 4; | |
1804 hash = (hash * 2057) & 0xFFFFFFFF; | |
1805 hash ^= hash >> 16; | |
1806 return hash; | |
1807 } | |
1808 | |
1809 bool operator==(Object other) { | |
1810 if (identical(other, this)) return true; | |
1811 if (other is CapabilityImpl) { | |
1812 return identical(_id, other._id); | |
1813 } | |
1814 return false; | |
1815 } | |
1816 } | |
OLD | NEW |