| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 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 | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 /** | 5 /** |
| 6 * Concepts used here: | 6 * Concepts used here: |
| 7 * | 7 * |
| 8 * "manager" - A manager contains one or more isolates, schedules their | 8 * "manager" - A manager contains one or more isolates, schedules their |
| 9 * execution, and performs other plumbing on their behalf. The isolate | 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". | 10 * present at the creation of the manager is designated as its "root isolate". |
| (...skipping 34 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 45 } | 45 } |
| 46 return _lazyPort; | 46 return _lazyPort; |
| 47 } | 47 } |
| 48 | 48 |
| 49 SendPort _spawnFunction(void topLevelFunction()) { | 49 SendPort _spawnFunction(void topLevelFunction()) { |
| 50 final name = _IsolateNatives._getJSFunctionName(topLevelFunction); | 50 final name = _IsolateNatives._getJSFunctionName(topLevelFunction); |
| 51 if (name == null) { | 51 if (name == null) { |
| 52 throw new UnsupportedOperationException( | 52 throw new UnsupportedOperationException( |
| 53 "only top-level functions can be spawned."); | 53 "only top-level functions can be spawned."); |
| 54 } | 54 } |
| 55 return _IsolateNatives._spawn2(name, null, false); | 55 return _IsolateNatives._spawn(name, null, false); |
| 56 } | 56 } |
| 57 | 57 |
| 58 SendPort _spawnUri(String uri) { | 58 SendPort _spawnUri(String uri) { |
| 59 return _IsolateNatives._spawn2(null, uri, false); | 59 return _IsolateNatives._spawn(null, uri, false); |
| 60 } | 60 } |
| 61 | 61 |
| 62 /** State associated with the current manager. See [globalState]. */ | 62 /** State associated with the current manager. See [globalState]. */ |
| 63 // TODO(sigmund): split in multiple classes: global, thread, main-worker states? | 63 // TODO(sigmund): split in multiple classes: global, thread, main-worker states? |
| 64 class _Manager { | 64 class _Manager { |
| 65 | 65 |
| 66 /** Next available isolate id within this [_Manager]. */ | 66 /** Next available isolate id within this [_Manager]. */ |
| 67 int nextIsolateId = 0; | 67 int nextIsolateId = 0; |
| 68 | 68 |
| 69 /** id assigned to this [_Manager]. */ | 69 /** id assigned to this [_Manager]. */ |
| (...skipping 282 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 352 void set onmessage(f) native "this.onmessage = f;"; | 352 void set onmessage(f) native "this.onmessage = f;"; |
| 353 void postMessage(msg) native "return this.postMessage(msg);"; | 353 void postMessage(msg) native "return this.postMessage(msg);"; |
| 354 // terminate() is implemented by Worker. | 354 // terminate() is implemented by Worker. |
| 355 abstract void terminate(); | 355 abstract void terminate(); |
| 356 } | 356 } |
| 357 | 357 |
| 358 final String _SPAWNED_SIGNAL = "spawned"; | 358 final String _SPAWNED_SIGNAL = "spawned"; |
| 359 | 359 |
| 360 class _IsolateNatives { | 360 class _IsolateNatives { |
| 361 | 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 /** | 362 /** |
| 396 * The src url for the script tag that loaded this code. Used to create | 363 * The src url for the script tag that loaded this code. Used to create |
| 397 * JavaScript workers. | 364 * JavaScript workers. |
| 398 */ | 365 */ |
| 399 static String get _thisScript() native @"return $thisScriptUrl"; | 366 static String get _thisScript() native @"return $thisScriptUrl"; |
| 400 | 367 |
| 401 /** Starts a new worker with the given URL. */ | 368 /** Starts a new worker with the given URL. */ |
| 402 static _WorkerStub _newWorker(url) native "return new Worker(url);"; | 369 static _WorkerStub _newWorker(url) native "return new Worker(url);"; |
| 403 | 370 |
| 404 /** | 371 /** |
| 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. | 372 * 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 | 373 * We don't import the dom explicitly so, when workers are disabled, this |
| 425 * library can also run on top of nodejs. | 374 * library can also run on top of nodejs. |
| 426 */ | 375 */ |
| 427 static _getEventData(e) native "return e.data"; | 376 static _getEventData(e) native "return e.data"; |
| 428 | 377 |
| 429 /** | 378 /** |
| 430 * Process messages on a worker, either to control the worker instance or to | 379 * Process messages on a worker, either to control the worker instance or to |
| 431 * pass messages along to the isolate running in the worker. | 380 * pass messages along to the isolate running in the worker. |
| 432 */ | 381 */ |
| 433 static void _processWorkerMessage(sender, e) { | 382 static void _processWorkerMessage(sender, e) { |
| 434 var msg = _deserializeMessage(_getEventData(e)); | 383 var msg = _deserializeMessage(_getEventData(e)); |
| 435 switch (msg['command']) { | 384 switch (msg['command']) { |
| 436 // TODO(sigmund): delete after we migrate to the new API | |
| 437 case 'start': | 385 case 'start': |
| 438 _globalState.currentManagerId = msg['id']; | 386 _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']); | 387 Function entryPoint = _getJSFunctionFromName(msg['functionName']); |
| 451 var replyTo = _deserializeMessage(msg['replyTo']); | 388 var replyTo = _deserializeMessage(msg['replyTo']); |
| 452 _globalState.topEventLoop.enqueue(new _IsolateContext(), function() { | 389 _globalState.topEventLoop.enqueue(new _IsolateContext(), function() { |
| 453 _startIsolate2(entryPoint, replyTo); | 390 _startIsolate(entryPoint, replyTo); |
| 454 }, 'worker-start'); | 391 }, 'worker-start'); |
| 455 _globalState.topEventLoop.run(); | 392 _globalState.topEventLoop.run(); |
| 456 break; | 393 break; |
| 457 // TODO(sigmund): delete after we migrate to the new API | |
| 458 case 'spawn-worker': | 394 case 'spawn-worker': |
| 459 _spawnWorker(msg['factoryName'], msg['replyPort']); | 395 _spawnWorker(msg['functionName'], msg['uri'], msg['replyPort']); |
| 460 break; | |
| 461 case 'spawn-worker2': | |
| 462 _spawnWorker2(msg['functionName'], msg['uri'], msg['replyPort']); | |
| 463 break; | 396 break; |
| 464 case 'message': | 397 case 'message': |
| 465 msg['port'].send(msg['msg'], msg['replyTo']); | 398 msg['port'].send(msg['msg'], msg['replyTo']); |
| 466 _globalState.topEventLoop.run(); | 399 _globalState.topEventLoop.run(); |
| 467 break; | 400 break; |
| 468 case 'close': | 401 case 'close': |
| 469 _log("Closing Worker"); | 402 _log("Closing Worker"); |
| 470 _globalState.managers.remove(sender.id); | 403 _globalState.managers.remove(sender.id); |
| 471 sender.terminate(); | 404 sender.terminate(); |
| 472 _globalState.topEventLoop.run(); | 405 _globalState.topEventLoop.run(); |
| (...skipping 23 matching lines...) Expand all Loading... |
| 496 try { | 429 try { |
| 497 _consoleLog(msg); | 430 _consoleLog(msg); |
| 498 } catch(var e, var trace) { | 431 } catch(var e, var trace) { |
| 499 throw new Exception(trace); | 432 throw new Exception(trace); |
| 500 } | 433 } |
| 501 } | 434 } |
| 502 } | 435 } |
| 503 | 436 |
| 504 static void _consoleLog(msg) native "\$globalThis.console.log(msg);"; | 437 static void _consoleLog(msg) native "\$globalThis.console.log(msg);"; |
| 505 | 438 |
| 506 | |
| 507 /** | 439 /** |
| 508 * Extract the constructor of runnable, so it can be allocated in another | 440 * Extract the constructor of runnable, so it can be allocated in another |
| 509 * isolate. | 441 * isolate. |
| 510 */ | 442 */ |
| 511 static Dynamic _getJSConstructor(Isolate runnable) native """ | 443 static Dynamic _getJSConstructor(Isolate runnable) native """ |
| 512 return runnable.constructor; | 444 return runnable.constructor; |
| 513 """; | 445 """; |
| 514 | 446 |
| 515 /** Extract the constructor name of a runnable */ | 447 /** Extract the constructor name of a runnable */ |
| 516 // TODO(sigmund): find a browser-generic way to support this. | 448 // TODO(sigmund): find a browser-generic way to support this. |
| (...skipping 14 matching lines...) Expand all Loading... |
| 531 * Get a string name for the function, if possible. The result for | 463 * Get a string name for the function, if possible. The result for |
| 532 * anonymous functions is browser-dependent -- it may be "" or "anonymous" | 464 * anonymous functions is browser-dependent -- it may be "" or "anonymous" |
| 533 * but you should probably not count on this. | 465 * but you should probably not count on this. |
| 534 */ | 466 */ |
| 535 static String _getJSFunctionName(Function f) | 467 static String _getJSFunctionName(Function f) |
| 536 native @"return f.$name || (void 0);"; | 468 native @"return f.$name || (void 0);"; |
| 537 | 469 |
| 538 /** Create a new JavaScript object instance given its constructor. */ | 470 /** Create a new JavaScript object instance given its constructor. */ |
| 539 static Dynamic _allocate(var ctor) native "return new ctor();"; | 471 static Dynamic _allocate(var ctor) native "return new ctor();"; |
| 540 | 472 |
| 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: | 473 // TODO(sigmund): clean up above, after we make the new API the default: |
| 565 | 474 |
| 566 static _spawn2(String functionName, String uri, bool isLight) { | 475 static _spawn(String functionName, String uri, bool isLight) { |
| 567 Completer<SendPort> completer = new Completer<SendPort>(); | 476 Completer<SendPort> completer = new Completer<SendPort>(); |
| 568 ReceivePort port = new ReceivePort(); | 477 ReceivePort port = new ReceivePort(); |
| 569 port.receive((msg, SendPort replyPort) { | 478 port.receive((msg, SendPort replyPort) { |
| 570 port.close(); | 479 port.close(); |
| 571 assert(msg == _SPAWNED_SIGNAL); | 480 assert(msg == _SPAWNED_SIGNAL); |
| 572 completer.complete(replyPort); | 481 completer.complete(replyPort); |
| 573 }); | 482 }); |
| 574 | 483 |
| 575 SendPort signalReply = port.toSendPort(); | 484 SendPort signalReply = port.toSendPort(); |
| 576 | 485 |
| 577 if (_globalState.useWorkers && !isLight) { | 486 if (_globalState.useWorkers && !isLight) { |
| 578 _startWorker2(functionName, uri, signalReply); | 487 _startWorker(functionName, uri, signalReply); |
| 579 } else { | 488 } else { |
| 580 _startNonWorker2(functionName, uri, signalReply); | 489 _startNonWorker(functionName, uri, signalReply); |
| 581 } | 490 } |
| 582 return new _BufferingSendPort( | 491 return new _BufferingSendPort( |
| 583 _globalState.currentContext.id, completer.future); | 492 _globalState.currentContext.id, completer.future); |
| 584 } | 493 } |
| 585 | 494 |
| 586 static SendPort _startWorker2( | 495 static SendPort _startWorker( |
| 587 String functionName, String uri, SendPort replyPort) { | 496 String functionName, String uri, SendPort replyPort) { |
| 588 if (_globalState.isWorker) { | 497 if (_globalState.isWorker) { |
| 589 _globalState.mainManager.postMessage(_serializeMessage({ | 498 _globalState.mainManager.postMessage(_serializeMessage({ |
| 590 'command': 'spawn-worker2', | 499 'command': 'spawn-worker', |
| 591 'functionName': functionName, | 500 'functionName': functionName, |
| 592 'uri': uri, | 501 'uri': uri, |
| 593 'replyPort': replyPort})); | 502 'replyPort': replyPort})); |
| 594 } else { | 503 } else { |
| 595 _spawnWorker2(functionName, uri, replyPort); | 504 _spawnWorker(functionName, uri, replyPort); |
| 596 } | 505 } |
| 597 } | 506 } |
| 598 | 507 |
| 599 static SendPort _startNonWorker2( | 508 static SendPort _startNonWorker( |
| 600 String functionName, String uri, SendPort replyPort) { | 509 String functionName, String uri, SendPort replyPort) { |
| 601 // TODO(eub): support IE9 using an iframe -- Dart issue 1702. | 510 // TODO(eub): support IE9 using an iframe -- Dart issue 1702. |
| 602 if (uri != null) throw new UnsupportedOperationException( | 511 if (uri != null) throw new UnsupportedOperationException( |
| 603 "Currently spawnUri is not supported without web workers."); | 512 "Currently spawnUri is not supported without web workers."); |
| 604 _globalState.topEventLoop.enqueue(new _IsolateContext(), function() { | 513 _globalState.topEventLoop.enqueue(new _IsolateContext(), function() { |
| 605 final func = _getJSFunctionFromName(functionName); | 514 final func = _getJSFunctionFromName(functionName); |
| 606 _startIsolate2(func, replyPort); | 515 _startIsolate(func, replyPort); |
| 607 }, 'nonworker start'); | 516 }, 'nonworker start'); |
| 608 } | 517 } |
| 609 | 518 |
| 610 static void _startIsolate2(Function topLevel, SendPort replyTo) { | 519 static void _startIsolate(Function topLevel, SendPort replyTo) { |
| 611 _fillStatics(_globalState.currentContext); | 520 _fillStatics(_globalState.currentContext); |
| 612 _lazyPort = new ReceivePort(); | 521 _lazyPort = new ReceivePort(); |
| 613 replyTo.send(_SPAWNED_SIGNAL, port.toSendPort()); | 522 replyTo.send(_SPAWNED_SIGNAL, port.toSendPort()); |
| 614 topLevel(); | 523 topLevel(); |
| 615 } | 524 } |
| 616 | 525 |
| 617 /** | 526 /** |
| 618 * Spawns an isolate in a worker. [factoryName] is the Javascript constructor | 527 * Spawns an isolate in a worker. [factoryName] is the Javascript constructor |
| 619 * name for the isolate entry point class. | 528 * name for the isolate entry point class. |
| 620 */ | 529 */ |
| 621 static void _spawnWorker2(functionName, uri, replyPort) { | 530 static void _spawnWorker(functionName, uri, replyPort) { |
| 622 if (functionName == null) functionName = 'main'; | 531 if (functionName == null) functionName = 'main'; |
| 623 if (uri == null) uri = _thisScript; | 532 if (uri == null) uri = _thisScript; |
| 624 if (!(new Uri.fromString(uri).isAbsolute())) { | 533 if (!(new Uri.fromString(uri).isAbsolute())) { |
| 625 // The constructor of dom workers requires an absolute URL. If we use a | 534 // The constructor of dom workers requires an absolute URL. If we use a |
| 626 // relative path we will get a DOM exception. | 535 // relative path we will get a DOM exception. |
| 627 String prefix = _thisScript.substring(0, _thisScript.lastIndexOf('/')); | 536 String prefix = _thisScript.substring(0, _thisScript.lastIndexOf('/')); |
| 628 uri = "$prefix/$uri"; | 537 uri = "$prefix/$uri"; |
| 629 } | 538 } |
| 630 final worker = _newWorker(uri); | 539 final worker = _newWorker(uri); |
| 631 worker.onmessage = (e) { _processWorkerMessage(worker, e); }; | 540 worker.onmessage = (e) { _processWorkerMessage(worker, e); }; |
| 632 var workerId = _globalState.nextManagerId++; | 541 var workerId = _globalState.nextManagerId++; |
| 633 // We also store the id on the worker itself so that we can unregister it. | 542 // We also store the id on the worker itself so that we can unregister it. |
| 634 worker.id = workerId; | 543 worker.id = workerId; |
| 635 _globalState.managers[workerId] = worker; | 544 _globalState.managers[workerId] = worker; |
| 636 worker.postMessage(_serializeMessage({ | 545 worker.postMessage(_serializeMessage({ |
| 637 'command': 'start2', | 546 'command': 'start', |
| 638 'id': workerId, | 547 'id': workerId, |
| 639 // Note: we serialize replyPort twice because the child worker needs to | 548 // Note: we serialize replyPort twice because the child worker needs to |
| 640 // first deserialize the worker id, before it can correctly deserialize | 549 // first deserialize the worker id, before it can correctly deserialize |
| 641 // the port (port deserialization is sensitive to what is the current | 550 // the port (port deserialization is sensitive to what is the current |
| 642 // workerId). | 551 // workerId). |
| 643 'replyTo': _serializeMessage(replyPort), | 552 'replyTo': _serializeMessage(replyPort), |
| 644 'functionName': functionName })); | 553 'functionName': functionName })); |
| 645 } | 554 } |
| 646 } | 555 } |
| OLD | NEW |