Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(811)

Side by Side Diff: lib/isolate/frog/isolateimpl.dart

Issue 10843007: Cleanup isolate library after frog removal. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 8 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 /**
6 * Concepts used here:
7 *
8 * "manager" - A manager contains one or more isolates, schedules their
9 * execution, and performs other plumbing on their behalf. The isolate
10 * present at the creation of the manager is designated as its "root isolate".
11 * A manager may, for example, be implemented on a web Worker.
12 *
13 * [_Manager] - State present within a manager (exactly once, as a global).
14 *
15 * [_ManagerStub] - A handle held within one manager that allows interaction
16 * with another manager. A target manager may be addressed by zero or more
17 * [_ManagerStub]s.
18 *
19 */
20
21 /**
22 * A native object that is shared across isolates. This object is visible to all
23 * isolates running under the same manager (either UI or background web worker).
24 *
25 * This is code that is intended to 'escape' the isolate boundaries in order to
26 * implement the semantics of isolates in JavaScript. Without this we would have
27 * been forced to implement more code (including the top-level event loop) in
28 * JavaScript itself.
29 */
30 // TODO(eub, sigmund): move the "manager" to be entirely in JS.
31 // Running any Dart code outside the context of an isolate gives it
32 // the change to break the isolate abstraction.
33 _Manager get _globalState() native "return \$globalState;";
34 set _globalState(_Manager val) native "\$globalState = val;";
35
36 void _fillStatics(context) native @"""
37 $globals = context.isolateStatics;
38 $static_init();
39 """;
40
41 ReceivePort _lazyPort;
42 ReceivePort get _port() {
43 if (_lazyPort === null) {
44 _lazyPort = new ReceivePort();
45 }
46 return _lazyPort;
47 }
48
49 SendPort _spawnFunction(void topLevelFunction()) {
50 final name = _IsolateNatives._getJSFunctionName(topLevelFunction);
51 if (name == null) {
52 throw new UnsupportedOperationException(
53 "only top-level functions can be spawned.");
54 }
55 return _IsolateNatives._spawn2(name, null, false);
56 }
57
58 SendPort _spawnUri(String uri) {
59 return _IsolateNatives._spawn2(null, uri, false);
60 }
61
62 /** State associated with the current manager. See [globalState]. */
63 // TODO(sigmund): split in multiple classes: global, thread, main-worker states?
64 class _Manager {
65
66 /** Next available isolate id within this [_Manager]. */
67 int nextIsolateId = 0;
68
69 /** id assigned to this [_Manager]. */
70 int currentManagerId = 0;
71
72 /**
73 * Next available manager id. Only used by the main manager to assign a unique
74 * id to each manager created by it.
75 */
76 int nextManagerId = 1;
77
78 /** Context for the currently running [Isolate]. */
79 _IsolateContext currentContext = null;
80
81 /** Context for the root [Isolate] that first run in this [_Manager]. */
82 _IsolateContext rootContext = null;
83
84 /** The top-level event loop. */
85 _EventLoop topEventLoop;
86
87 /** Whether this program is running from the command line. */
88 bool fromCommandLine;
89
90 /** Whether this [_Manager] is running as a web worker. */
91 bool isWorker;
92
93 /** Whether we support spawning web workers. */
94 bool supportsWorkers;
95
96 /**
97 * Whether to use web workers when implementing isolates. Set to false for
98 * debugging/testing.
99 */
100 bool get useWorkers() => supportsWorkers;
101
102 /**
103 * Whether to use the web-worker JSON-based message serialization protocol. By
104 * default this is only used with web workers. For debugging, you can force
105 * using this protocol by changing this field value to [true].
106 */
107 bool get needSerialization() => useWorkers;
108
109 /**
110 * Registry of isolates. Isolates must be registered if, and only if, receive
111 * ports are alive. Normally no open receive-ports means that the isolate is
112 * dead, but DOM callbacks could resurrect it.
113 */
114 Map<int, _IsolateContext> isolates;
115
116 /** Reference to the main [_Manager]. Null in the main [_Manager] itself. */
117 _ManagerStub mainManager;
118
119 /** Registry of active [_ManagerStub]s. Only used in the main [_Manager]. */
120 Map<int, _ManagerStub> managers;
121
122 _Manager() {
123 _nativeDetectEnvironment();
124 topEventLoop = new _EventLoop();
125 isolates = new Map<int, _IsolateContext>();
126 managers = new Map<int, _ManagerStub>();
127 if (isWorker) { // "if we are not the main manager ourself" is the intent.
128 mainManager = new _MainManagerStub();
129 _nativeInitWorkerMessageHandler();
130 }
131 }
132
133 void _nativeDetectEnvironment() native @"""
134 this.isWorker = $isWorker;
135 this.supportsWorkers = $supportsWorkers;
136 this.fromCommandLine = typeof(window) == 'undefined';
137 """;
138
139 void _nativeInitWorkerMessageHandler() native @"""
140 $globalThis.onmessage = function (e) {
141 _IsolateNatives._processWorkerMessage(this.mainManager, e);
142 }
143 """;
144 /*: TODO: check that _processWorkerMessage is not discarded while treeshaking.
145 """ {
146 _IsolateNatives._processWorkerMessage(null, null);
147 }
148 */
149
150
151 /** Close the worker running this code if all isolates are done. */
152 void maybeCloseWorker() {
153 if (isolates.isEmpty()) {
154 mainManager.postMessage(_serializeMessage({'command': 'close'}));
155 }
156 }
157 }
158
159 /** Context information tracked for each isolate. */
160 class _IsolateContext {
161 /** Current isolate id. */
162 int id;
163
164 /** Registry of receive ports currently active on this isolate. */
165 Map<int, ReceivePort> ports;
166
167 /** Holds isolate globals (statics and top-level properties). */
168 var isolateStatics; // native object containing all globals of an isolate.
169
170 _IsolateContext() {
171 id = _globalState.nextIsolateId++;
172 ports = new Map<int, ReceivePort>();
173 initGlobals();
174 }
175
176 // these are filled lazily the first time the isolate starts running.
177 void initGlobals() native @'$initGlobals(this);';
178
179 /**
180 * Run [code] in the context of the isolate represented by [this]. Note this
181 * is called from JavaScript (see $wrap_call in corejs.dart).
182 */
183 Dynamic eval(Function code) {
184 var old = _globalState.currentContext;
185 _globalState.currentContext = this;
186 this._setGlobals();
187 var result = null;
188 try {
189 result = code();
190 } finally {
191 _globalState.currentContext = old;
192 if (old != null) old._setGlobals();
193 }
194 return result;
195 }
196
197 void _setGlobals() native @'$setGlobals(this);';
198
199 /** Lookup a port registered for this isolate. */
200 ReceivePort lookup(int portId) => ports[portId];
201
202 /** Register a port on this isolate. */
203 void register(int portId, ReceivePort port) {
204 if (ports.containsKey(portId)) {
205 throw new Exception("Registry: ports must be registered only once.");
206 }
207 ports[portId] = port;
208 _globalState.isolates[id] = this; // indicate this isolate is active
209 }
210
211 /** Unregister a port on this isolate. */
212 void unregister(int portId) {
213 ports.remove(portId);
214 if (ports.isEmpty()) {
215 _globalState.isolates.remove(id); // indicate this isolate is not active
216 }
217 }
218 }
219
220 // We don't want to import the DOM library just because of window.setTimeout,
221 // so we reconstruct the Window class here. The only conflict that could happen
222 // with the other DOMWindow class would be because of subclasses.
223 // Currently, none of the two Dart classes have subclasses.
224 typedef void _TimeoutHandler();
225 class _Window native "@*DOMWindow" {
226 int setTimeout(_TimeoutHandler handler, int timeout) native;
227 }
228 _Window get _window() native
229 """return typeof window != 'undefined' ? window : (void 0);""";
230
231 /** Represent the event loop on a javascript thread (DOM or worker). */
232 class _EventLoop {
233 Queue<_IsolateEvent> events;
234
235 _EventLoop() : events = new Queue<_IsolateEvent>();
236
237 void enqueue(isolate, fn, msg) {
238 events.addLast(new _IsolateEvent(isolate, fn, msg));
239 }
240
241 _IsolateEvent dequeue() {
242 if (events.isEmpty()) return null;
243 return events.removeFirst();
244 }
245
246 /** Process a single event, if any. */
247 bool runIteration() {
248 final event = dequeue();
249 if (event == null) {
250 if (_globalState.isWorker) {
251 _globalState.maybeCloseWorker();
252 } else if (_globalState.rootContext != null &&
253 _globalState.isolates.containsKey(
254 _globalState.rootContext.id) &&
255 _globalState.fromCommandLine &&
256 _globalState.rootContext.ports.isEmpty()) {
257 // We want to reach here only on the main [_Manager] and only
258 // on the command-line. In the browser the isolate might
259 // still be alive due to DOM callbacks, but the presumption is
260 // that on the command-line, no future events can be injected
261 // into the event queue once it's empty. Node has setTimeout
262 // so this presumption is incorrect there. We think(?) that
263 // in d8 this assumption is valid.
264 throw new Exception("Program exited with open ReceivePorts.");
265 }
266 return false;
267 }
268 event.process();
269 return true;
270 }
271
272 /**
273 * Runs multiple iterations of the run-loop. If possible, each iteration is
274 * run asynchronously.
275 */
276 void _runHelper() {
277 if (_window != null) {
278 // Run each iteration from the browser's top event loop.
279 void next() {
280 if (!runIteration()) return;
281 _window.setTimeout(next, 0);
282 }
283 next();
284 } else {
285 // Run synchronously until no more iterations are available.
286 while (runIteration()) {}
287 }
288 }
289
290 /**
291 * Call [_runHelper] but ensure that worker exceptions are propragated. Note
292 * this is called from JavaScript (see $wrap_call in corejs.dart).
293 */
294 void run() {
295 if (!_globalState.isWorker) {
296 _runHelper();
297 } else {
298 try {
299 _runHelper();
300 } catch(var e, var trace) {
301 _globalState.mainManager.postMessage(_serializeMessage(
302 {'command': 'error', 'msg': '$e\n$trace' }));
303 }
304 }
305 }
306 }
307
308 /** An event in the top-level event queue. */
309 class _IsolateEvent {
310 _IsolateContext isolate;
311 Function fn;
312 String message;
313
314 _IsolateEvent(this.isolate, this.fn, this.message);
315
316 void process() {
317 isolate.eval(fn);
318 }
319 }
320
321 /** An interface for a stub used to interact with a manager. */
322 interface _ManagerStub {
323 get id();
324 void set id(int i);
325 void set onmessage(Function f);
326 void postMessage(msg);
327 void terminate();
328 }
329
330 /** A stub for interacting with the main manager. */
331 class _MainManagerStub implements _ManagerStub {
332 get id() => 0;
333 void set id(int i) { throw new NotImplementedException(); }
334 void set onmessage(f) {
335 throw new Exception("onmessage should not be set on MainManagerStub");
336 }
337 void postMessage(msg) native @"$globalThis.postMessage(msg);";
338 void terminate() {} // Nothing useful to do here.
339 }
340
341 /**
342 * A stub for interacting with a manager built on a web worker. The
343 * type Worker is also defined in 'dart:dom_deprecated', but we define
344 * it here to avoid introducing a dependency from corelib to dom. This
345 * definition uses a 'hidden' type (* prefix on the native name) to
346 * enforce that the type is defined dynamically only when web workers
347 * are actually available.
348 */
349 class _WorkerStub implements _ManagerStub native "*Worker" {
350 get id() native "return this.id;";
351 void set id(i) native "this.id = i;";
352 void set onmessage(f) native "this.onmessage = f;";
353 void postMessage(msg) native "return this.postMessage(msg);";
354 // terminate() is implemented by Worker.
355 abstract void terminate();
356 }
357
358 final String _SPAWNED_SIGNAL = "spawned";
359
360 class _IsolateNatives {
361
362 /** JavaScript-specific implementation to spawn an isolate. */
363 static Future<SendPort> spawn(Isolate isolate, bool isLight) {
364 Completer<SendPort> completer = new Completer<SendPort>();
365 ReceivePort port = new ReceivePort();
366 port.receive((msg, SendPort replyPort) {
367 port.close();
368 assert(msg == _SPAWNED_SIGNAL);
369 completer.complete(replyPort);
370 });
371
372 // TODO(floitsch): throw exception if isolate's class doesn't have a
373 // default constructor.
374 if (_globalState.useWorkers && !isLight) {
375 _startWorker(isolate, port.toSendPort());
376 } else {
377 _startNonWorker(isolate, port.toSendPort());
378 }
379
380 return completer.future;
381 }
382
383 static SendPort _startWorker(Isolate runnable, SendPort replyPort) {
384 var factoryName = _getJSConstructorName(runnable);
385 if (_globalState.isWorker) {
386 _globalState.mainManager.postMessage(_serializeMessage({
387 'command': 'spawn-worker',
388 'factoryName': factoryName,
389 'replyPort': _serializeMessage(replyPort)}));
390 } else {
391 _spawnWorker(factoryName, _serializeMessage(replyPort));
392 }
393 }
394
395 /**
396 * The src url for the script tag that loaded this code. Used to create
397 * JavaScript workers.
398 */
399 static String get _thisScript() native @"return $thisScriptUrl";
400
401 /** Starts a new worker with the given URL. */
402 static _WorkerStub _newWorker(url) native "return new Worker(url);";
403
404 /**
405 * Spawns an isolate in a worker. [factoryName] is the Javascript constructor
406 * name for the isolate entry point class.
407 */
408 static void _spawnWorker(factoryName, serializedReplyPort) {
409 final worker = _newWorker(_thisScript);
410 worker.onmessage = (e) { _processWorkerMessage(worker, e); };
411 var workerId = _globalState.nextManagerId++;
412 // We also store the id on the worker itself so that we can unregister it.
413 worker.id = workerId;
414 _globalState.managers[workerId] = worker;
415 worker.postMessage(_serializeMessage({
416 'command': 'start',
417 'id': workerId,
418 'replyTo': serializedReplyPort,
419 'factoryName': factoryName }));
420 }
421
422 /**
423 * Assume that [e] is a browser message event and extract its message data.
424 * We don't import the dom explicitly so, when workers are disabled, this
425 * library can also run on top of nodejs.
426 */
427 static _getEventData(e) native "return e.data";
428
429 /**
430 * Process messages on a worker, either to control the worker instance or to
431 * pass messages along to the isolate running in the worker.
432 */
433 static void _processWorkerMessage(sender, e) {
434 var msg = _deserializeMessage(_getEventData(e));
435 switch (msg['command']) {
436 // TODO(sigmund): delete after we migrate to the new API
437 case 'start':
438 _globalState.currentManagerId = msg['id'];
439 var runnerObject =
440 _allocate(_getJSConstructorFromName(msg['factoryName']));
441 var serializedReplyTo = msg['replyTo'];
442 _globalState.topEventLoop.enqueue(new _IsolateContext(), function() {
443 var replyTo = _deserializeMessage(serializedReplyTo);
444 _startIsolate(runnerObject, replyTo);
445 }, 'worker-start');
446 _globalState.topEventLoop.run();
447 break;
448 case 'start2':
449 _globalState.currentManagerId = msg['id'];
450 Function entryPoint = _getJSFunctionFromName(msg['functionName']);
451 var replyTo = _deserializeMessage(msg['replyTo']);
452 _globalState.topEventLoop.enqueue(new _IsolateContext(), function() {
453 _startIsolate2(entryPoint, replyTo);
454 }, 'worker-start');
455 _globalState.topEventLoop.run();
456 break;
457 // TODO(sigmund): delete after we migrate to the new API
458 case 'spawn-worker':
459 _spawnWorker(msg['factoryName'], msg['replyPort']);
460 break;
461 case 'spawn-worker2':
462 _spawnWorker2(msg['functionName'], msg['uri'], msg['replyPort']);
463 break;
464 case 'message':
465 msg['port'].send(msg['msg'], msg['replyTo']);
466 _globalState.topEventLoop.run();
467 break;
468 case 'close':
469 _log("Closing Worker");
470 _globalState.managers.remove(sender.id);
471 sender.terminate();
472 _globalState.topEventLoop.run();
473 break;
474 case 'log':
475 _log(msg['msg']);
476 break;
477 case 'print':
478 if (_globalState.isWorker) {
479 _globalState.mainManager.postMessage(
480 _serializeMessage({'command': 'print', 'msg': msg}));
481 } else {
482 print(msg['msg']);
483 }
484 break;
485 case 'error':
486 throw msg['msg'];
487 }
488 }
489
490 /** Log a message, forwarding to the main [_Manager] if appropriate. */
491 static _log(msg) {
492 if (_globalState.isWorker) {
493 _globalState.mainManager.postMessage(
494 _serializeMessage({'command': 'log', 'msg': msg }));
495 } else {
496 try {
497 _consoleLog(msg);
498 } catch(var e, var trace) {
499 throw new Exception(trace);
500 }
501 }
502 }
503
504 static void _consoleLog(msg) native "\$globalThis.console.log(msg);";
505
506
507 /**
508 * Extract the constructor of runnable, so it can be allocated in another
509 * isolate.
510 */
511 static Dynamic _getJSConstructor(Isolate runnable) native """
512 return runnable.constructor;
513 """;
514
515 /** Extract the constructor name of a runnable */
516 // TODO(sigmund): find a browser-generic way to support this.
517 static Dynamic _getJSConstructorName(Isolate runnable) native """
518 return runnable.constructor.name;
519 """;
520
521 /** Find a constructor given its name. */
522 static Dynamic _getJSConstructorFromName(String factoryName) native """
523 return \$globalThis[factoryName];
524 """;
525
526 static Dynamic _getJSFunctionFromName(String functionName) native """
527 return \$globalThis[functionName];
528 """;
529
530 /**
531 * Get a string name for the function, if possible. The result for
532 * anonymous functions is browser-dependent -- it may be "" or "anonymous"
533 * but you should probably not count on this.
534 */
535 static String _getJSFunctionName(Function f)
536 native @"return f.$name || (void 0);";
537
538 /** Create a new JavaScript object instance given its constructor. */
539 static Dynamic _allocate(var ctor) native "return new ctor();";
540
541 /** Starts a non-worker isolate. */
542 static SendPort _startNonWorker(Isolate runnable, SendPort replyTo) {
543 // Spawn a new isolate and create the receive port in it.
544 final spawned = new _IsolateContext();
545
546 // Instead of just running the provided runnable, we create a
547 // new cloned instance of it with a fresh state in the spawned
548 // isolate. This way, we do not get cross-isolate references
549 // through the runnable.
550 final ctor = _getJSConstructor(runnable);
551 _globalState.topEventLoop.enqueue(spawned, function() {
552 _startIsolate(_allocate(ctor), replyTo);
553 }, 'nonworker start');
554 }
555
556 /** Given a ready-to-start runnable, start running it. */
557 static void _startIsolate(Isolate isolate, SendPort replyTo) {
558 _fillStatics(_globalState.currentContext);
559 ReceivePort port = new ReceivePort();
560 replyTo.send(_SPAWNED_SIGNAL, port.toSendPort());
561 isolate._run(port);
562 }
563
564 // TODO(sigmund): clean up above, after we make the new API the default:
565
566 static _spawn2(String functionName, String uri, bool isLight) {
567 Completer<SendPort> completer = new Completer<SendPort>();
568 ReceivePort port = new ReceivePort();
569 port.receive((msg, SendPort replyPort) {
570 port.close();
571 assert(msg == _SPAWNED_SIGNAL);
572 completer.complete(replyPort);
573 });
574
575 SendPort signalReply = port.toSendPort();
576
577 if (_globalState.useWorkers && !isLight) {
578 _startWorker2(functionName, uri, signalReply);
579 } else {
580 _startNonWorker2(functionName, uri, signalReply);
581 }
582 return new _BufferingSendPort(
583 _globalState.currentContext.id, completer.future);
584 }
585
586 static SendPort _startWorker2(
587 String functionName, String uri, SendPort replyPort) {
588 if (_globalState.isWorker) {
589 _globalState.mainManager.postMessage(_serializeMessage({
590 'command': 'spawn-worker2',
591 'functionName': functionName,
592 'uri': uri,
593 'replyPort': replyPort}));
594 } else {
595 _spawnWorker2(functionName, uri, replyPort);
596 }
597 }
598
599 static SendPort _startNonWorker2(
600 String functionName, String uri, SendPort replyPort) {
601 // TODO(eub): support IE9 using an iframe -- Dart issue 1702.
602 if (uri != null) throw new UnsupportedOperationException(
603 "Currently spawnUri is not supported without web workers.");
604 _globalState.topEventLoop.enqueue(new _IsolateContext(), function() {
605 final func = _getJSFunctionFromName(functionName);
606 _startIsolate2(func, replyPort);
607 }, 'nonworker start');
608 }
609
610 static void _startIsolate2(Function topLevel, SendPort replyTo) {
611 _fillStatics(_globalState.currentContext);
612 _lazyPort = new ReceivePort();
613 replyTo.send(_SPAWNED_SIGNAL, port.toSendPort());
614 topLevel();
615 }
616
617 /**
618 * Spawns an isolate in a worker. [factoryName] is the Javascript constructor
619 * name for the isolate entry point class.
620 */
621 static void _spawnWorker2(functionName, uri, replyPort) {
622 if (functionName == null) functionName = 'main';
623 if (uri == null) uri = _thisScript;
624 if (!(new Uri.fromString(uri).isAbsolute())) {
625 // The constructor of dom workers requires an absolute URL. If we use a
626 // relative path we will get a DOM exception.
627 String prefix = _thisScript.substring(0, _thisScript.lastIndexOf('/'));
628 uri = "$prefix/$uri";
629 }
630 final worker = _newWorker(uri);
631 worker.onmessage = (e) { _processWorkerMessage(worker, e); };
632 var workerId = _globalState.nextManagerId++;
633 // We also store the id on the worker itself so that we can unregister it.
634 worker.id = workerId;
635 _globalState.managers[workerId] = worker;
636 worker.postMessage(_serializeMessage({
637 'command': 'start2',
638 'id': workerId,
639 // Note: we serialize replyPort twice because the child worker needs to
640 // first deserialize the worker id, before it can correctly deserialize
641 // the port (port deserialization is sensitive to what is the current
642 // workerId).
643 'replyTo': _serializeMessage(replyPort),
644 'functionName': functionName }));
645 }
646 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698