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

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

Issue 10828411: Re-apply 'Unify dart:isolate.' with fixes for autogenerated dart2js and dartium html files. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Review fixes. 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
« no previous file with comments | « lib/isolate/dart2js/compiler_hooks.dart ('k') | lib/isolate/dart2js/messages.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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._spawn(name, null, false);
56 }
57
58 SendPort _spawnUri(String uri) {
59 return _IsolateNatives._spawn(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 /** Represent the event loop on a javascript thread (DOM or worker). */
221 class _EventLoop {
222 Queue<_IsolateEvent> events;
223
224 _EventLoop() : events = new Queue<_IsolateEvent>();
225
226 void enqueue(isolate, fn, msg) {
227 events.addLast(new _IsolateEvent(isolate, fn, msg));
228 }
229
230 _IsolateEvent dequeue() {
231 if (events.isEmpty()) return null;
232 return events.removeFirst();
233 }
234
235 /** Process a single event, if any. */
236 bool runIteration() {
237 final event = dequeue();
238 if (event == null) {
239 if (_globalState.isWorker) {
240 _globalState.maybeCloseWorker();
241 } else if (_globalState.rootContext != null &&
242 _globalState.isolates.containsKey(
243 _globalState.rootContext.id) &&
244 _globalState.fromCommandLine &&
245 _globalState.rootContext.ports.isEmpty()) {
246 // We want to reach here only on the main [_Manager] and only
247 // on the command-line. In the browser the isolate might
248 // still be alive due to DOM callbacks, but the presumption is
249 // that on the command-line, no future events can be injected
250 // into the event queue once it's empty. Node has setTimeout
251 // so this presumption is incorrect there. We think(?) that
252 // in d8 this assumption is valid.
253 throw new Exception("Program exited with open ReceivePorts.");
254 }
255 return false;
256 }
257 event.process();
258 return true;
259 }
260
261 /**
262 * Runs multiple iterations of the run-loop. If possible, each iteration is
263 * run asynchronously.
264 */
265 void _runHelper() {
266 // [_window] is defined in timer_provider.dart.
267 if (_window != null) {
268 // Run each iteration from the browser's top event loop.
269 void next() {
270 if (!runIteration()) return;
271 _window.setTimeout(next, 0);
272 }
273 next();
274 } else {
275 // Run synchronously until no more iterations are available.
276 while (runIteration()) {}
277 }
278 }
279
280 /**
281 * Call [_runHelper] but ensure that worker exceptions are propragated. Note
282 * this is called from JavaScript (see $wrap_call in corejs.dart).
283 */
284 void run() {
285 if (!_globalState.isWorker) {
286 _runHelper();
287 } else {
288 try {
289 _runHelper();
290 } catch(var e, var trace) {
291 _globalState.mainManager.postMessage(_serializeMessage(
292 {'command': 'error', 'msg': '$e\n$trace' }));
293 }
294 }
295 }
296 }
297
298 /** An event in the top-level event queue. */
299 class _IsolateEvent {
300 _IsolateContext isolate;
301 Function fn;
302 String message;
303
304 _IsolateEvent(this.isolate, this.fn, this.message);
305
306 void process() {
307 isolate.eval(fn);
308 }
309 }
310
311 /** An interface for a stub used to interact with a manager. */
312 interface _ManagerStub {
313 get id();
314 void set id(int i);
315 void set onmessage(Function f);
316 void postMessage(msg);
317 void terminate();
318 }
319
320 /** A stub for interacting with the main manager. */
321 class _MainManagerStub implements _ManagerStub {
322 get id() => 0;
323 void set id(int i) { throw new NotImplementedException(); }
324 void set onmessage(f) {
325 throw new Exception("onmessage should not be set on MainManagerStub");
326 }
327 void postMessage(msg) native @"$globalThis.postMessage(msg);";
328 void terminate() {} // Nothing useful to do here.
329 }
330
331 /**
332 * A stub for interacting with a manager built on a web worker. The
333 * type Worker is also defined in 'dart:dom_deprecated', but we define
334 * it here to avoid introducing a dependency from corelib to dom. This
335 * definition uses a 'hidden' type (* prefix on the native name) to
336 * enforce that the type is defined dynamically only when web workers
337 * are actually available.
338 */
339 class _WorkerStub implements _ManagerStub native "*Worker" {
340 get id() native "return this.id;";
341 void set id(i) native "this.id = i;";
342 void set onmessage(f) native "this.onmessage = f;";
343 void postMessage(msg) native "return this.postMessage(msg);";
344 // terminate() is implemented by Worker.
345 abstract void terminate();
346 }
347
348 final String _SPAWNED_SIGNAL = "spawned";
349
350 class _IsolateNatives {
351
352 /**
353 * The src url for the script tag that loaded this code. Used to create
354 * JavaScript workers.
355 */
356 static String get _thisScript() native @"return $thisScriptUrl";
357
358 /** Starts a new worker with the given URL. */
359 static _WorkerStub _newWorker(url) native "return new Worker(url);";
360
361 /**
362 * Assume that [e] is a browser message event and extract its message data.
363 * We don't import the dom explicitly so, when workers are disabled, this
364 * library can also run on top of nodejs.
365 */
366 static _getEventData(e) native "return e.data";
367
368 /**
369 * Process messages on a worker, either to control the worker instance or to
370 * pass messages along to the isolate running in the worker.
371 */
372 static void _processWorkerMessage(sender, e) {
373 var msg = _deserializeMessage(_getEventData(e));
374 switch (msg['command']) {
375 case 'start':
376 _globalState.currentManagerId = msg['id'];
377 Function entryPoint = _getJSFunctionFromName(msg['functionName']);
378 var replyTo = _deserializeMessage(msg['replyTo']);
379 _globalState.topEventLoop.enqueue(new _IsolateContext(), function() {
380 _startIsolate(entryPoint, replyTo);
381 }, 'worker-start');
382 _globalState.topEventLoop.run();
383 break;
384 case 'spawn-worker':
385 _spawnWorker(msg['functionName'], msg['uri'], msg['replyPort']);
386 break;
387 case 'message':
388 msg['port'].send(msg['msg'], msg['replyTo']);
389 _globalState.topEventLoop.run();
390 break;
391 case 'close':
392 _log("Closing Worker");
393 _globalState.managers.remove(sender.id);
394 sender.terminate();
395 _globalState.topEventLoop.run();
396 break;
397 case 'log':
398 _log(msg['msg']);
399 break;
400 case 'print':
401 if (_globalState.isWorker) {
402 _globalState.mainManager.postMessage(
403 _serializeMessage({'command': 'print', 'msg': msg}));
404 } else {
405 print(msg['msg']);
406 }
407 break;
408 case 'error':
409 throw msg['msg'];
410 }
411 }
412
413 /** Log a message, forwarding to the main [_Manager] if appropriate. */
414 static _log(msg) {
415 if (_globalState.isWorker) {
416 _globalState.mainManager.postMessage(
417 _serializeMessage({'command': 'log', 'msg': msg }));
418 } else {
419 try {
420 _consoleLog(msg);
421 } catch(var e, var trace) {
422 throw new Exception(trace);
423 }
424 }
425 }
426
427 static void _consoleLog(msg) native "\$globalThis.console.log(msg);";
428
429 /**
430 * Extract the constructor of runnable, so it can be allocated in another
431 * isolate.
432 */
433 static Dynamic _getJSConstructor(Isolate runnable) native """
434 return runnable.constructor;
435 """;
436
437 /** Extract the constructor name of a runnable */
438 // TODO(sigmund): find a browser-generic way to support this.
439 static Dynamic _getJSConstructorName(Isolate runnable) native """
440 return runnable.constructor.name;
441 """;
442
443 /** Find a constructor given its name. */
444 static Dynamic _getJSConstructorFromName(String factoryName) native """
445 return \$globalThis[factoryName];
446 """;
447
448 static Dynamic _getJSFunctionFromName(String functionName) native """
449 return \$globalThis[functionName];
450 """;
451
452 /**
453 * Get a string name for the function, if possible. The result for
454 * anonymous functions is browser-dependent -- it may be "" or "anonymous"
455 * but you should probably not count on this.
456 */
457 static String _getJSFunctionName(Function f)
458 native @"return f.$name || (void 0);";
459
460 /** Create a new JavaScript object instance given its constructor. */
461 static Dynamic _allocate(var ctor) native "return new ctor();";
462
463 // TODO(sigmund): clean up above, after we make the new API the default:
464
465 static _spawn(String functionName, String uri, bool isLight) {
466 Completer<SendPort> completer = new Completer<SendPort>();
467 ReceivePort port = new ReceivePort();
468 port.receive((msg, SendPort replyPort) {
469 port.close();
470 assert(msg == _SPAWNED_SIGNAL);
471 completer.complete(replyPort);
472 });
473
474 SendPort signalReply = port.toSendPort();
475
476 if (_globalState.useWorkers && !isLight) {
477 _startWorker(functionName, uri, signalReply);
478 } else {
479 _startNonWorker(functionName, uri, signalReply);
480 }
481 return new _BufferingSendPort(
482 _globalState.currentContext.id, completer.future);
483 }
484
485 static SendPort _startWorker(
486 String functionName, String uri, SendPort replyPort) {
487 if (_globalState.isWorker) {
488 _globalState.mainManager.postMessage(_serializeMessage({
489 'command': 'spawn-worker',
490 'functionName': functionName,
491 'uri': uri,
492 'replyPort': replyPort}));
493 } else {
494 _spawnWorker(functionName, uri, replyPort);
495 }
496 }
497
498 static SendPort _startNonWorker(
499 String functionName, String uri, SendPort replyPort) {
500 // TODO(eub): support IE9 using an iframe -- Dart issue 1702.
501 if (uri != null) throw new UnsupportedOperationException(
502 "Currently spawnUri is not supported without web workers.");
503 _globalState.topEventLoop.enqueue(new _IsolateContext(), function() {
504 final func = _getJSFunctionFromName(functionName);
505 _startIsolate(func, replyPort);
506 }, 'nonworker start');
507 }
508
509 static void _startIsolate(Function topLevel, SendPort replyTo) {
510 _fillStatics(_globalState.currentContext);
511 _lazyPort = new ReceivePort();
512 replyTo.send(_SPAWNED_SIGNAL, port.toSendPort());
513 topLevel();
514 }
515
516 /**
517 * Spawns an isolate in a worker. [factoryName] is the Javascript constructor
518 * name for the isolate entry point class.
519 */
520 static void _spawnWorker(functionName, uri, replyPort) {
521 if (functionName == null) functionName = 'main';
522 if (uri == null) uri = _thisScript;
523 if (!(new Uri.fromString(uri).isAbsolute())) {
524 // The constructor of dom workers requires an absolute URL. If we use a
525 // relative path we will get a DOM exception.
526 String prefix = _thisScript.substring(0, _thisScript.lastIndexOf('/'));
527 uri = "$prefix/$uri";
528 }
529 final worker = _newWorker(uri);
530 worker.onmessage = (e) { _processWorkerMessage(worker, e); };
531 var workerId = _globalState.nextManagerId++;
532 // We also store the id on the worker itself so that we can unregister it.
533 worker.id = workerId;
534 _globalState.managers[workerId] = worker;
535 worker.postMessage(_serializeMessage({
536 'command': 'start',
537 'id': workerId,
538 // Note: we serialize replyPort twice because the child worker needs to
539 // first deserialize the worker id, before it can correctly deserialize
540 // the port (port deserialization is sensitive to what is the current
541 // workerId).
542 'replyTo': _serializeMessage(replyPort),
543 'functionName': functionName }));
544 }
545 }
OLDNEW
« no previous file with comments | « lib/isolate/dart2js/compiler_hooks.dart ('k') | lib/isolate/dart2js/messages.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698