| 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 * A native object that is shared across isolates. This object is visible to all | 6 * A native object that is shared across isolates. This object is visible to all |
| 7 * isolates running on the same worker (either UI or background web worker). | 7 * isolates running on the same worker (either UI or background web worker). |
| 8 * | 8 * |
| 9 * This is code that is intended to 'escape' the isolate boundaries in order to | 9 * This is code that is intended to 'escape' the isolate boundaries in order to |
| 10 * implement the semantics of isolates in JavaScript. Without this we would have | 10 * implement the semantics of isolates in JavaScript. Without this we would have |
| 11 * been forced to implement more code (including the top-level event loop) in | 11 * been forced to implement more code (including the top-level event loop) in |
| 12 * JavaScript itself. | 12 * JavaScript itself. |
| 13 */ | 13 */ |
| 14 GlobalState get globalState() native "return \$globalState;"; | 14 _GlobalState get _globalState() native "return \$globalState;"; |
| 15 set globalState(GlobalState val) native "\$globalState = val;"; | 15 set _globalState(_GlobalState val) native "\$globalState = val;"; |
| 16 | 16 |
| 17 void fillStatics(context) native @""" | 17 void _fillStatics(context) native @""" |
| 18 $globals = context.isolateStatics; | 18 $globals = context.isolateStatics; |
| 19 $static_init(); | 19 $static_init(); |
| 20 """; | 20 """; |
| 21 | 21 |
| 22 /** Global state associated with the current worker. See [globalState]. */ | 22 /** Global state associated with the current worker. See [globalState]. */ |
| 23 // TODO(sigmund): split in multiple classes: global, thread, main-worker states? | 23 // TODO(sigmund): split in multiple classes: global, thread, main-worker states? |
| 24 class GlobalState { | 24 class _GlobalState { |
| 25 | 25 |
| 26 /** Next available isolate id. */ | 26 /** Next available isolate id. */ |
| 27 int nextIsolateId = 0; | 27 int nextIsolateId = 0; |
| 28 | 28 |
| 29 /** Worker id associated with this worker. */ | 29 /** Worker id associated with this worker. */ |
| 30 int currentWorkerId = 0; | 30 int currentWorkerId = 0; |
| 31 | 31 |
| 32 /** | 32 /** |
| 33 * Next available worker id. Only used by the main worker to assign a unique | 33 * Next available worker id. Only used by the main worker to assign a unique |
| 34 * id to each worker created by it. | 34 * id to each worker created by it. |
| 35 */ | 35 */ |
| 36 int nextWorkerId = 1; | 36 int nextWorkerId = 1; |
| 37 | 37 |
| 38 /** Context for the currently running [Isolate]. */ | 38 /** Context for the currently running [Isolate]. */ |
| 39 IsolateContext currentContext = null; | 39 _IsolateContext currentContext = null; |
| 40 | 40 |
| 41 /** Context for the root [Isolate] that first run in this worker. */ | 41 /** Context for the root [Isolate] that first run in this worker. */ |
| 42 IsolateContext rootContext = null; | 42 _IsolateContext rootContext = null; |
| 43 | 43 |
| 44 /** The top-level event loop. */ | 44 /** The top-level event loop. */ |
| 45 EventLoop topEventLoop; | 45 _EventLoop topEventLoop; |
| 46 | 46 |
| 47 /** Whether this program is running in a background worker. */ | 47 /** Whether this program is running in a background worker. */ |
| 48 bool isWorker; | 48 bool isWorker; |
| 49 | 49 |
| 50 /** Whether this program is running in a UI worker. */ | 50 /** Whether this program is running in a UI worker. */ |
| 51 bool inWindow; | 51 bool inWindow; |
| 52 | 52 |
| 53 /** Whether we support spawning workers. */ | 53 /** Whether we support spawning workers. */ |
| 54 bool supportsWorkers; | 54 bool supportsWorkers; |
| 55 | 55 |
| 56 /** | 56 /** |
| 57 * Whether to use web workers when implementing isolates. Set to false for | 57 * Whether to use web workers when implementing isolates. Set to false for |
| 58 * debugging/testing. | 58 * debugging/testing. |
| 59 */ | 59 */ |
| 60 bool get useWorkers() => supportsWorkers; | 60 bool get useWorkers() => supportsWorkers; |
| 61 | 61 |
| 62 /** | 62 /** |
| 63 * Whether to use the web-worker JSON-based message serialization protocol. By | 63 * Whether to use the web-worker JSON-based message serialization protocol. By |
| 64 * default this is only used with web workers. For debugging, you can force | 64 * default this is only used with web workers. For debugging, you can force |
| 65 * using this protocol by changing this field value to [true]. | 65 * using this protocol by changing this field value to [true]. |
| 66 */ | 66 */ |
| 67 bool get needSerialization() => useWorkers; | 67 bool get needSerialization() => useWorkers; |
| 68 | 68 |
| 69 /** | 69 /** |
| 70 * Registry of isolates. Isolates must be registered if, and only if, receive | 70 * Registry of isolates. Isolates must be registered if, and only if, receive |
| 71 * ports are alive. Normally no open receive-ports means that the isolate is | 71 * ports are alive. Normally no open receive-ports means that the isolate is |
| 72 * dead, but DOM callbacks could resurrect it. | 72 * dead, but DOM callbacks could resurrect it. |
| 73 */ | 73 */ |
| 74 Map<int, IsolateContext> isolates; | 74 Map<int, _IsolateContext> isolates; |
| 75 | 75 |
| 76 /** Reference to the main worker. */ | 76 /** Reference to the main worker. */ |
| 77 MainWorker mainWorker; | 77 _MainWorker mainWorker; |
| 78 | 78 |
| 79 /** Registry of active workers. Only used in the main worker. */ | 79 /** Registry of active workers. Only used in the main worker. */ |
| 80 Map<int, Dynamic> workers; | 80 Map<int, Dynamic> workers; |
| 81 | 81 |
| 82 GlobalState() { | 82 _GlobalState() { |
| 83 topEventLoop = new EventLoop(); | 83 topEventLoop = new _EventLoop(); |
| 84 isolates = {}; | 84 isolates = {}; |
| 85 workers = {}; | 85 workers = {}; |
| 86 mainWorker = new MainWorker(); | 86 mainWorker = new _MainWorker(); |
| 87 _nativeInit(); | 87 _nativeInit(); |
| 88 } | 88 } |
| 89 | 89 |
| 90 void _nativeInit() native @""" | 90 void _nativeInit() native @""" |
| 91 this.isWorker = typeof ($globalThis['importScripts']) != 'undefined'; | 91 this.isWorker = typeof ($globalThis['importScripts']) != 'undefined'; |
| 92 this.inWindow = typeof(window) !== 'undefined'; | 92 this.inWindow = typeof(window) !== 'undefined'; |
| 93 this.supportsWorkers = this.isWorker || | 93 this.supportsWorkers = this.isWorker || |
| 94 ((typeof $globalThis['Worker']) != 'undefined'); | 94 ((typeof $globalThis['Worker']) != 'undefined'); |
| 95 | 95 if (this.isWorker) { |
| 96 // if workers are supported, treat this as a main worker: | 96 $globalThis.onmessage = function (e) { |
| 97 if (this.supportsWorkers) { | |
| 98 $globalThis.onmessage = function(e) { | |
| 99 _IsolateNatives._processWorkerMessage(this.mainWorker, e); | 97 _IsolateNatives._processWorkerMessage(this.mainWorker, e); |
| 100 }; | 98 }; |
| 101 } | 99 } |
| 102 """ { | 100 """ { |
| 103 // Declare that the native code has a dependency on this fn. | 101 // Declare that the native code has a dependency on this fn. |
| 104 _IsolateNatives._processWorkerMessage(null, null); | 102 _IsolateNatives._processWorkerMessage(null, null); |
| 105 } | 103 } |
| 106 | 104 |
| 107 /** | 105 /** |
| 108 * Close the worker running this code, called when there is nothing else to | 106 * Close the worker running this code, called when there is nothing else to |
| 109 * run. | 107 * run. |
| 110 */ | 108 */ |
| 111 void closeWorker() { | 109 void closeWorker() { |
| 112 if (isWorker) { | 110 if (isWorker) { |
| 113 if (!isolates.isEmpty()) return; | 111 if (!isolates.isEmpty()) return; |
| 114 mainWorker.postMessage( | 112 mainWorker.postMessage( |
| 115 _serializeMessage({'command': 'close'})); | 113 _serializeMessage({'command': 'close'})); |
| 116 } else if (isolates.containsKey(rootContext.id) && workers.isEmpty() && | 114 } else if (isolates.containsKey(rootContext.id) && workers.isEmpty() && |
| 117 !supportsWorkers && !inWindow) { | 115 !supportsWorkers && !inWindow) { |
| 118 // This should only trigger when running on the command-line. | 116 // This should only trigger when running on the command-line. |
| 119 // We don't want this check to execute in the browser where the isolate | 117 // We don't want this check to execute in the browser where the isolate |
| 120 // might still be alive due to DOM callbacks. | 118 // might still be alive due to DOM callbacks. |
| 121 throw new Exception("Program exited with open ReceivePorts."); | 119 throw new Exception("Program exited with open ReceivePorts."); |
| 122 } | 120 } |
| 123 } | 121 } |
| 124 } | 122 } |
| 125 | 123 |
| 126 /** Context information tracked for each isolate. */ | 124 /** Context information tracked for each isolate. */ |
| 127 class IsolateContext { | 125 class _IsolateContext { |
| 128 /** Current isolate id. */ | 126 /** Current isolate id. */ |
| 129 int id; | 127 int id; |
| 130 | 128 |
| 131 /** Registry of receive ports currently active on this isolate. */ | 129 /** Registry of receive ports currently active on this isolate. */ |
| 132 Map<int, ReceivePort> ports; | 130 Map<int, ReceivePort> ports; |
| 133 | 131 |
| 134 /** Holds isolate globals (statics and top-level properties). */ | 132 /** Holds isolate globals (statics and top-level properties). */ |
| 135 var isolateStatics; // native object containing all globals of an isolate. | 133 var isolateStatics; // native object containing all globals of an isolate. |
| 136 | 134 |
| 137 IsolateContext() { | 135 _IsolateContext() { |
| 138 id = globalState.nextIsolateId++; | 136 id = _globalState.nextIsolateId++; |
| 139 ports = {}; | 137 ports = {}; |
| 140 initGlobals(); | 138 initGlobals(); |
| 141 } | 139 } |
| 142 | 140 |
| 143 // these are filled lazily the first time the isolate starts running. | 141 // these are filled lazily the first time the isolate starts running. |
| 144 void initGlobals() native 'this.isolateStatics = {};'; | 142 void initGlobals() native 'this.isolateStatics = {};'; |
| 145 | 143 |
| 146 /** | 144 /** |
| 147 * Run [code] in the context of the isolate represented by [this]. Note this | 145 * Run [code] in the context of the isolate represented by [this]. Note this |
| 148 * is called from JavaScript (see $wrap_call in corejs.dart). | 146 * is called from JavaScript (see $wrap_call in corejs.dart). |
| 149 */ | 147 */ |
| 150 void eval(Function code) { | 148 void eval(Function code) { |
| 151 var old = globalState.currentContext; | 149 var old = _globalState.currentContext; |
| 152 globalState.currentContext = this; | 150 _globalState.currentContext = this; |
| 153 this._setGlobals(); | 151 this._setGlobals(); |
| 154 var result = null; | 152 var result = null; |
| 155 try { | 153 try { |
| 156 result = code(); | 154 result = code(); |
| 157 } finally { | 155 } finally { |
| 158 globalState.currentContext = old; | 156 _globalState.currentContext = old; |
| 159 if (old != null) old._setGlobals(); | 157 if (old != null) old._setGlobals(); |
| 160 } | 158 } |
| 161 return result; | 159 return result; |
| 162 } | 160 } |
| 163 | 161 |
| 164 void _setGlobals() native @'$globals = this.isolateStatics;'; | 162 void _setGlobals() native @'$globals = this.isolateStatics;'; |
| 165 | 163 |
| 166 /** Lookup a port registered for this isolate. */ | 164 /** Lookup a port registered for this isolate. */ |
| 167 ReceivePort lookup(int id) => ports[id]; | 165 ReceivePort lookup(int id) => ports[id]; |
| 168 | 166 |
| 169 /** Register a port on this isolate. */ | 167 /** Register a port on this isolate. */ |
| 170 void register(int portId, ReceivePort port) { | 168 void register(int portId, ReceivePort port) { |
| 171 if (ports.containsKey(portId)) { | 169 if (ports.containsKey(portId)) { |
| 172 throw new Exception("Registry: ports must be registered only once."); | 170 throw new Exception("Registry: ports must be registered only once."); |
| 173 } | 171 } |
| 174 ports[portId] = port; | 172 ports[portId] = port; |
| 175 globalState.isolates[id] = this; // indicate this isolate is active | 173 _globalState.isolates[id] = this; // indicate this isolate is active |
| 176 } | 174 } |
| 177 | 175 |
| 178 /** Unregister a port on this isolate. */ | 176 /** Unregister a port on this isolate. */ |
| 179 void unregister(int portId) { | 177 void unregister(int portId) { |
| 180 ports.remove(portId); | 178 ports.remove(portId); |
| 181 if (ports.isEmpty()) { | 179 if (ports.isEmpty()) { |
| 182 globalState.isolates.remove(id); // indicate this isolate is not active | 180 _globalState.isolates.remove(id); // indicate this isolate is not active |
| 183 } | 181 } |
| 184 } | 182 } |
| 185 } | 183 } |
| 186 | 184 |
| 187 | 185 |
| 188 /** Represent the event loop on a javascript thread (DOM or worker). */ | 186 /** Represent the event loop on a javascript thread (DOM or worker). */ |
| 189 class EventLoop { | 187 class _EventLoop { |
| 190 Queue<IsolateEvent> events; | 188 Queue<_IsolateEvent> events; |
| 191 | 189 |
| 192 EventLoop() : events = new Queue<IsolateEvent>(); | 190 _EventLoop() : events = new Queue<_IsolateEvent>(); |
| 193 | 191 |
| 194 void enqueue(isolate, fn, msg) { | 192 void enqueue(isolate, fn, msg) { |
| 195 events.addLast(new IsolateEvent(isolate, fn, msg)); | 193 events.addLast(new _IsolateEvent(isolate, fn, msg)); |
| 196 } | 194 } |
| 197 | 195 |
| 198 IsolateEvent dequeue() { | 196 _IsolateEvent dequeue() { |
| 199 if (events.isEmpty()) return null; | 197 if (events.isEmpty()) return null; |
| 200 return events.removeFirst(); | 198 return events.removeFirst(); |
| 201 } | 199 } |
| 202 | 200 |
| 203 /** Process a single event, if any. */ | 201 /** Process a single event, if any. */ |
| 204 bool runIteration() { | 202 bool runIteration() { |
| 205 final event = dequeue(); | 203 final event = dequeue(); |
| 206 if (event == null) { | 204 if (event == null) { |
| 207 globalState.closeWorker(); | 205 _globalState.closeWorker(); |
| 208 return false; | 206 return false; |
| 209 } | 207 } |
| 210 event.process(); | 208 event.process(); |
| 211 return true; | 209 return true; |
| 212 } | 210 } |
| 213 | 211 |
| 214 /** Function equivalent to [:window.setTimeout:] when available, or null. */ | 212 /** Function equivalent to [:window.setTimeout:] when available, or null. */ |
| 215 static Function _wrapSetTimeout() native """ | 213 static Function _wrapSetTimeout() native """ |
| 216 return typeof window != 'undefined' ? | 214 return typeof window != 'undefined' ? |
| 217 function(a, b) { window.setTimeout(a, b); } : undefined; | 215 function(a, b) { window.setTimeout(a, b); } : undefined; |
| (...skipping 16 matching lines...) Expand all Loading... |
| 234 // Run synchronously until no more iterations are available. | 232 // Run synchronously until no more iterations are available. |
| 235 while (runIteration()) {} | 233 while (runIteration()) {} |
| 236 } | 234 } |
| 237 } | 235 } |
| 238 | 236 |
| 239 /** | 237 /** |
| 240 * Call [_runHelper] but ensure that worker exceptions are propragated. Note | 238 * Call [_runHelper] but ensure that worker exceptions are propragated. Note |
| 241 * this is called from JavaScript (see $wrap_call in corejs.dart). | 239 * this is called from JavaScript (see $wrap_call in corejs.dart). |
| 242 */ | 240 */ |
| 243 void run() { | 241 void run() { |
| 244 if (!globalState.isWorker) { | 242 if (!_globalState.isWorker) { |
| 245 _runHelper(); | 243 _runHelper(); |
| 246 } else { | 244 } else { |
| 247 try { | 245 try { |
| 248 _runHelper(); | 246 _runHelper(); |
| 249 } catch(var e, var trace) { | 247 } catch(var e, var trace) { |
| 250 globalState.mainWorker.postMessage(_serializeMessage( | 248 _globalState.mainWorker.postMessage(_serializeMessage( |
| 251 {'command': 'error', 'msg': '$e\n$trace' })); | 249 {'command': 'error', 'msg': '$e\n$trace' })); |
| 252 } | 250 } |
| 253 } | 251 } |
| 254 } | 252 } |
| 255 } | 253 } |
| 256 | 254 |
| 257 /** An event in the top-level event queue. */ | 255 /** An event in the top-level event queue. */ |
| 258 class IsolateEvent { | 256 class _IsolateEvent { |
| 259 IsolateContext isolate; | 257 _IsolateContext isolate; |
| 260 Function fn; | 258 Function fn; |
| 261 String message; | 259 String message; |
| 262 | 260 |
| 263 IsolateEvent(this.isolate, this.fn, this.message); | 261 _IsolateEvent(this.isolate, this.fn, this.message); |
| 264 | 262 |
| 265 void process() { | 263 void process() { |
| 266 isolate.eval(fn); | 264 isolate.eval(fn); |
| 267 } | 265 } |
| 268 } | 266 } |
| 269 | 267 |
| 270 | 268 |
| 271 /** Default worker. */ | 269 /** Default worker. */ |
| 272 class MainWorker { | 270 class _MainWorker { |
| 273 int id = 0; | 271 int id = 0; |
| 274 void postMessage(msg) native "return \$globalThis.postMessage(msg);"; | 272 void postMessage(msg) native @"$globalThis.postMessage(msg);"; |
| 275 void set onmessage(f) native "\$globalThis.onmessage = f;"; | |
| 276 void terminate() {} | 273 void terminate() {} |
| 277 } | 274 } |
| 278 | 275 |
| 279 /** | 276 /** |
| 280 * A web worker. This type is also defined in 'dart:dom', but we define it here | 277 * A web worker. This type is also defined in 'dart:dom', but we define it here |
| 281 * to avoid introducing a dependency from corelib to dom. This definition uses a | 278 * to avoid introducing a dependency from corelib to dom. This definition uses a |
| 282 * 'hidden' type (* prefix on the native name) to enforce that the type is | 279 * 'hidden' type (* prefix on the native name) to enforce that the type is |
| 283 * defined dynamically only when web workers are actually available. | 280 * defined dynamically only when web workers are actually available. |
| 284 */ | 281 */ |
| 285 class _Worker native "*Worker" { | 282 class _Worker native "*Worker" { |
| (...skipping 11 matching lines...) Expand all Loading... |
| 297 static Future<SendPort> spawn(Isolate isolate, bool isLight) { | 294 static Future<SendPort> spawn(Isolate isolate, bool isLight) { |
| 298 Completer<SendPort> completer = new Completer<SendPort>(); | 295 Completer<SendPort> completer = new Completer<SendPort>(); |
| 299 ReceivePort port = new ReceivePort.singleShot(); | 296 ReceivePort port = new ReceivePort.singleShot(); |
| 300 port.receive((msg, SendPort replyPort) { | 297 port.receive((msg, SendPort replyPort) { |
| 301 assert(msg == _SPAWNED_SIGNAL); | 298 assert(msg == _SPAWNED_SIGNAL); |
| 302 completer.complete(replyPort); | 299 completer.complete(replyPort); |
| 303 }); | 300 }); |
| 304 | 301 |
| 305 // TODO(floitsch): throw exception if isolate's class doesn't have a | 302 // TODO(floitsch): throw exception if isolate's class doesn't have a |
| 306 // default constructor. | 303 // default constructor. |
| 307 if (globalState.useWorkers && !isLight) { | 304 if (_globalState.useWorkers && !isLight) { |
| 308 _startWorker(isolate, port.toSendPort()); | 305 _startWorker(isolate, port.toSendPort()); |
| 309 } else { | 306 } else { |
| 310 _startNonWorker(isolate, port.toSendPort()); | 307 _startNonWorker(isolate, port.toSendPort()); |
| 311 } | 308 } |
| 312 | 309 |
| 313 return completer.future; | 310 return completer.future; |
| 314 } | 311 } |
| 315 | 312 |
| 316 static SendPort _startWorker(Isolate runnable, SendPort replyPort) { | 313 static SendPort _startWorker(Isolate runnable, SendPort replyPort) { |
| 317 var factoryName = _getJSConstructorName(runnable); | 314 var factoryName = _getJSConstructorName(runnable); |
| 318 if (globalState.isWorker) { | 315 if (_globalState.isWorker) { |
| 319 globalState.mainWorker.postMessage(_serializeMessage({ | 316 _globalState.mainWorker.postMessage(_serializeMessage({ |
| 320 'command': 'spawn-worker', | 317 'command': 'spawn-worker', |
| 321 'factoryName': factoryName, | 318 'factoryName': factoryName, |
| 322 'replyPort': _serializeMessage(replyPort)})); | 319 'replyPort': _serializeMessage(replyPort)})); |
| 323 } else { | 320 } else { |
| 324 _spawnWorker(factoryName, _serializeMessage(replyPort)); | 321 _spawnWorker(factoryName, _serializeMessage(replyPort)); |
| 325 } | 322 } |
| 326 } | 323 } |
| 327 | 324 |
| 328 /** | 325 /** |
| 329 * The src url for the script tag that loaded this code. Used to create | 326 * The src url for the script tag that loaded this code. Used to create |
| (...skipping 31 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 361 /** Starts a new worker with the given URL. */ | 358 /** Starts a new worker with the given URL. */ |
| 362 static _Worker _newWorker(url) native "return new Worker(url);"; | 359 static _Worker _newWorker(url) native "return new Worker(url);"; |
| 363 | 360 |
| 364 /** | 361 /** |
| 365 * Spawns an isolate in a worker. [factoryName] is the Javascript constructor | 362 * Spawns an isolate in a worker. [factoryName] is the Javascript constructor |
| 366 * name for the isolate entry point class. | 363 * name for the isolate entry point class. |
| 367 */ | 364 */ |
| 368 static void _spawnWorker(factoryName, serializedReplyPort) { | 365 static void _spawnWorker(factoryName, serializedReplyPort) { |
| 369 final worker = _newWorker(_thisScript); | 366 final worker = _newWorker(_thisScript); |
| 370 worker.onmessage = (e) { _processWorkerMessage(worker, e); }; | 367 worker.onmessage = (e) { _processWorkerMessage(worker, e); }; |
| 371 var workerId = globalState.nextWorkerId++; | 368 var workerId = _globalState.nextWorkerId++; |
| 372 // We also store the id on the worker itself so that we can unregister it. | 369 // We also store the id on the worker itself so that we can unregister it. |
| 373 worker.id = workerId; | 370 worker.id = workerId; |
| 374 globalState.workers[workerId] = worker; | 371 _globalState.workers[workerId] = worker; |
| 375 worker.postMessage(_serializeMessage({ | 372 worker.postMessage(_serializeMessage({ |
| 376 'command': 'start', | 373 'command': 'start', |
| 377 'id': workerId, | 374 'id': workerId, |
| 378 'replyTo': serializedReplyPort, | 375 'replyTo': serializedReplyPort, |
| 379 'factoryName': factoryName })); | 376 'factoryName': factoryName })); |
| 380 } | 377 } |
| 381 | 378 |
| 382 /** | 379 /** |
| 383 * Assume that [e] is a browser message event and extract its message data. | 380 * Assume that [e] is a browser message event and extract its message data. |
| 384 * We don't import the dom explicitly so, when workers are disabled, this | 381 * We don't import the dom explicitly so, when workers are disabled, this |
| 385 * library can also run on top of nodejs. | 382 * library can also run on top of nodejs. |
| 386 */ | 383 */ |
| 387 static _getEventData(e) native "return e.data"; | 384 static _getEventData(e) native "return e.data"; |
| 388 | 385 |
| 389 /** | 386 /** |
| 390 * Process messages on a worker, either to control the worker instance or to | 387 * Process messages on a worker, either to control the worker instance or to |
| 391 * pass messages along to the isolate running in the worker. | 388 * pass messages along to the isolate running in the worker. |
| 392 */ | 389 */ |
| 393 static void _processWorkerMessage(sender, e) { | 390 static void _processWorkerMessage(sender, e) { |
| 394 var msg = _deserializeMessage(_getEventData(e)); | 391 var msg = _deserializeMessage(_getEventData(e)); |
| 395 switch (msg['command']) { | 392 switch (msg['command']) { |
| 396 // TODO(sigmund): delete after we migrate to the new API | 393 // TODO(sigmund): delete after we migrate to the new API |
| 397 case 'start': | 394 case 'start': |
| 398 globalState.currentWorkerId = msg['id']; | 395 _globalState.currentWorkerId = msg['id']; |
| 399 var runnerObject = | 396 var runnerObject = |
| 400 _allocate(_getJSConstructorFromName(msg['factoryName'])); | 397 _allocate(_getJSConstructorFromName(msg['factoryName'])); |
| 401 var serializedReplyTo = msg['replyTo']; | 398 var serializedReplyTo = msg['replyTo']; |
| 402 globalState.topEventLoop.enqueue(new IsolateContext(), function() { | 399 _globalState.topEventLoop.enqueue(new _IsolateContext(), function() { |
| 403 var replyTo = _deserializeMessage(serializedReplyTo); | 400 var replyTo = _deserializeMessage(serializedReplyTo); |
| 404 _startIsolate(runnerObject, replyTo); | 401 _startIsolate(runnerObject, replyTo); |
| 405 }, 'worker-start'); | 402 }, 'worker-start'); |
| 406 globalState.topEventLoop.run(); | 403 _globalState.topEventLoop.run(); |
| 407 break; | 404 break; |
| 408 case 'start2': | 405 case 'start2': |
| 409 globalState.currentWorkerId = msg['id']; | 406 _globalState.currentWorkerId = msg['id']; |
| 410 Function entryPoint = _getJSFunctionFromName(msg['functionName']); | 407 Function entryPoint = _getJSFunctionFromName(msg['functionName']); |
| 411 var replyTo = _deserializeMessage(msg['replyTo']); | 408 var replyTo = _deserializeMessage(msg['replyTo']); |
| 412 globalState.topEventLoop.enqueue(new IsolateContext(), function() { | 409 _globalState.topEventLoop.enqueue(new _IsolateContext(), function() { |
| 413 _startIsolate2(entryPoint, replyTo); | 410 _startIsolate2(entryPoint, replyTo); |
| 414 }, 'worker-start'); | 411 }, 'worker-start'); |
| 415 globalState.topEventLoop.run(); | 412 _globalState.topEventLoop.run(); |
| 416 break; | 413 break; |
| 417 // TODO(sigmund): delete after we migrate to the new API | 414 // TODO(sigmund): delete after we migrate to the new API |
| 418 case 'spawn-worker': | 415 case 'spawn-worker': |
| 419 _spawnWorker(msg['factoryName'], msg['replyPort']); | 416 _spawnWorker(msg['factoryName'], msg['replyPort']); |
| 420 break; | 417 break; |
| 421 case 'spawn-worker2': | 418 case 'spawn-worker2': |
| 422 _spawnWorker2(msg['functionName'], msg['uri'], msg['replyPort']); | 419 _spawnWorker2(msg['functionName'], msg['uri'], msg['replyPort']); |
| 423 break; | 420 break; |
| 424 case 'message': | 421 case 'message': |
| 425 msg['port'].send(msg['msg'], msg['replyTo']); | 422 msg['port'].send(msg['msg'], msg['replyTo']); |
| 426 globalState.topEventLoop.run(); | 423 _globalState.topEventLoop.run(); |
| 427 break; | 424 break; |
| 428 case 'close': | 425 case 'close': |
| 429 _log("Closing Worker"); | 426 _log("Closing Worker"); |
| 430 globalState.workers.remove(sender.id); | 427 _globalState.workers.remove(sender.id); |
| 431 sender.terminate(); | 428 sender.terminate(); |
| 432 globalState.topEventLoop.run(); | 429 _globalState.topEventLoop.run(); |
| 433 break; | 430 break; |
| 434 case 'log': | 431 case 'log': |
| 435 _log(msg['msg']); | 432 _log(msg['msg']); |
| 436 break; | 433 break; |
| 437 case 'print': | 434 case 'print': |
| 438 if (globalState.isWorker) { | 435 if (_globalState.isWorker) { |
| 439 globalState.mainWorker.postMessage( | 436 _globalState.mainWorker.postMessage( |
| 440 _serializeMessage({'command': 'print', 'msg': msg})); | 437 _serializeMessage({'command': 'print', 'msg': msg})); |
| 441 } else { | 438 } else { |
| 442 print(msg['msg']); | 439 print(msg['msg']); |
| 443 } | 440 } |
| 444 break; | 441 break; |
| 445 case 'error': | 442 case 'error': |
| 446 throw msg['msg']; | 443 throw msg['msg']; |
| 447 } | 444 } |
| 448 } | 445 } |
| 449 | 446 |
| 450 /** Log a message, forwarding to the main worker if appropriate. */ | 447 /** Log a message, forwarding to the main worker if appropriate. */ |
| 451 static _log(msg) { | 448 static _log(msg) { |
| 452 if (globalState.isWorker) { | 449 if (_globalState.isWorker) { |
| 453 globalState.mainWorker.postMessage( | 450 _globalState.mainWorker.postMessage( |
| 454 _serializeMessage({'command': 'log', 'msg': msg })); | 451 _serializeMessage({'command': 'log', 'msg': msg })); |
| 455 } else { | 452 } else { |
| 456 try { | 453 try { |
| 457 _consoleLog(msg); | 454 _consoleLog(msg); |
| 458 } catch(e, trace) { | 455 } catch(e, trace) { |
| 459 throw new Exception(trace); | 456 throw new Exception(trace); |
| 460 } | 457 } |
| 461 } | 458 } |
| 462 } | 459 } |
| 463 | 460 |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 515 return f.name || null; | 512 return f.name || null; |
| 516 } | 513 } |
| 517 """; | 514 """; |
| 518 | 515 |
| 519 /** Create a new JavaScript object instance given its constructor. */ | 516 /** Create a new JavaScript object instance given its constructor. */ |
| 520 static var _allocate(var ctor) native "return new ctor();"; | 517 static var _allocate(var ctor) native "return new ctor();"; |
| 521 | 518 |
| 522 /** Starts a non-worker isolate. */ | 519 /** Starts a non-worker isolate. */ |
| 523 static SendPort _startNonWorker(Isolate runnable, SendPort replyTo) { | 520 static SendPort _startNonWorker(Isolate runnable, SendPort replyTo) { |
| 524 // Spawn a new isolate and create the receive port in it. | 521 // Spawn a new isolate and create the receive port in it. |
| 525 final spawned = new IsolateContext(); | 522 final spawned = new _IsolateContext(); |
| 526 | 523 |
| 527 // Instead of just running the provided runnable, we create a | 524 // Instead of just running the provided runnable, we create a |
| 528 // new cloned instance of it with a fresh state in the spawned | 525 // new cloned instance of it with a fresh state in the spawned |
| 529 // isolate. This way, we do not get cross-isolate references | 526 // isolate. This way, we do not get cross-isolate references |
| 530 // through the runnable. | 527 // through the runnable. |
| 531 final ctor = _getJSConstructor(runnable); | 528 final ctor = _getJSConstructor(runnable); |
| 532 globalState.topEventLoop.enqueue(spawned, function() { | 529 _globalState.topEventLoop.enqueue(spawned, function() { |
| 533 _startIsolate(_allocate(ctor), replyTo); | 530 _startIsolate(_allocate(ctor), replyTo); |
| 534 }, 'nonworker start'); | 531 }, 'nonworker start'); |
| 535 } | 532 } |
| 536 | 533 |
| 537 /** Given a ready-to-start runnable, start running it. */ | 534 /** Given a ready-to-start runnable, start running it. */ |
| 538 static void _startIsolate(Isolate isolate, SendPort replyTo) { | 535 static void _startIsolate(Isolate isolate, SendPort replyTo) { |
| 539 fillStatics(globalState.currentContext); | 536 _fillStatics(_globalState.currentContext); |
| 540 ReceivePort port = new ReceivePort(); | 537 ReceivePort port = new ReceivePort(); |
| 541 replyTo.send(_SPAWNED_SIGNAL, port.toSendPort()); | 538 replyTo.send(_SPAWNED_SIGNAL, port.toSendPort()); |
| 542 isolate._run(port); | 539 isolate._run(port); |
| 543 } | 540 } |
| 544 | 541 |
| 545 // TODO(sigmund): clean up above, after we make the new API the default: | 542 // TODO(sigmund): clean up above, after we make the new API the default: |
| 546 | 543 |
| 547 static _spawn2(String functionName, String uri, bool isLight) { | 544 static _spawn2(String functionName, String uri, bool isLight) { |
| 548 Completer<SendPort> completer = new Completer<SendPort>(); | 545 Completer<SendPort> completer = new Completer<SendPort>(); |
| 549 ReceivePort port = new ReceivePort.singleShot(); | 546 ReceivePort port = new ReceivePort.singleShot(); |
| 550 port.receive((msg, SendPort replyPort) { | 547 port.receive((msg, SendPort replyPort) { |
| 551 assert(msg == _SPAWNED_SIGNAL); | 548 assert(msg == _SPAWNED_SIGNAL); |
| 552 completer.complete(replyPort); | 549 completer.complete(replyPort); |
| 553 }); | 550 }); |
| 554 | 551 |
| 555 SendPort signalReply = port.toSendPort(); | 552 SendPort signalReply = port.toSendPort(); |
| 556 | 553 |
| 557 if (globalState.useWorkers && !isLight) { | 554 if (_globalState.useWorkers && !isLight) { |
| 558 _startWorker2(functionName, uri, signalReply); | 555 _startWorker2(functionName, uri, signalReply); |
| 559 } else { | 556 } else { |
| 560 _startNonWorker2(functionName, uri, signalReply); | 557 _startNonWorker2(functionName, uri, signalReply); |
| 561 } | 558 } |
| 562 return new _BufferingSendPort( | 559 return new _BufferingSendPort( |
| 563 globalState.currentContext.id, completer.future); | 560 _globalState.currentContext.id, completer.future); |
| 564 } | 561 } |
| 565 | 562 |
| 566 static SendPort _startWorker2( | 563 static SendPort _startWorker2( |
| 567 String functionName, String uri, SendPort replyPort) { | 564 String functionName, String uri, SendPort replyPort) { |
| 568 if (globalState.isWorker) { | 565 if (_globalState.isWorker) { |
| 569 globalState.mainWorker.postMessage(_serializeMessage({ | 566 _globalState.mainWorker.postMessage(_serializeMessage({ |
| 570 'command': 'spawn-worker2', | 567 'command': 'spawn-worker2', |
| 571 'functionName': functionName, | 568 'functionName': functionName, |
| 572 'uri': uri, | 569 'uri': uri, |
| 573 'replyPort': replyPort})); | 570 'replyPort': replyPort})); |
| 574 } else { | 571 } else { |
| 575 _spawnWorker2(functionName, uri, replyPort); | 572 _spawnWorker2(functionName, uri, replyPort); |
| 576 } | 573 } |
| 577 } | 574 } |
| 578 | 575 |
| 579 static SendPort _startNonWorker2( | 576 static SendPort _startNonWorker2( |
| 580 String functionName, String uri, SendPort replyPort) { | 577 String functionName, String uri, SendPort replyPort) { |
| 581 // TODO(eub): support IE9 using an iframe -- Dart issue 1702. | 578 // TODO(eub): support IE9 using an iframe -- Dart issue 1702. |
| 582 if (uri != null) throw new UnsupportedOperationException( | 579 if (uri != null) throw new UnsupportedOperationException( |
| 583 "Currently spawnUri is not supported without web workers."); | 580 "Currently spawnUri is not supported without web workers."); |
| 584 globalState.topEventLoop.enqueue(new IsolateContext(), function() { | 581 _globalState.topEventLoop.enqueue(new _IsolateContext(), function() { |
| 585 final func = _getJSFunctionFromName(functionName); | 582 final func = _getJSFunctionFromName(functionName); |
| 586 _startIsolate2(func, replyPort); | 583 _startIsolate2(func, replyPort); |
| 587 }, 'nonworker start'); | 584 }, 'nonworker start'); |
| 588 } | 585 } |
| 589 | 586 |
| 590 static void _startIsolate2(Function topLevel, SendPort replyTo) { | 587 static void _startIsolate2(Function topLevel, SendPort replyTo) { |
| 591 fillStatics(globalState.currentContext); | 588 _fillStatics(_globalState.currentContext); |
| 592 _port = new ReceivePort(); | 589 _port = new ReceivePort(); |
| 593 replyTo.send(_SPAWNED_SIGNAL, port.toSendPort()); | 590 replyTo.send(_SPAWNED_SIGNAL, port.toSendPort()); |
| 594 topLevel(); | 591 topLevel(); |
| 595 } | 592 } |
| 596 | 593 |
| 597 /** | 594 /** |
| 598 * Spawns an isolate in a worker. [factoryName] is the Javascript constructor | 595 * Spawns an isolate in a worker. [factoryName] is the Javascript constructor |
| 599 * name for the isolate entry point class. | 596 * name for the isolate entry point class. |
| 600 */ | 597 */ |
| 601 static void _spawnWorker2(functionName, uri, replyPort) { | 598 static void _spawnWorker2(functionName, uri, replyPort) { |
| 602 if (functionName == null) functionName = 'main'; | 599 if (functionName == null) functionName = 'main'; |
| 603 if (uri == null) uri = _thisScript; | 600 if (uri == null) uri = _thisScript; |
| 604 if (!(new Uri.fromString(uri).isAbsolute())) { | 601 if (!(new Uri.fromString(uri).isAbsolute())) { |
| 605 // The constructor of dom workers requires an absolute URL. If we use a | 602 // The constructor of dom workers requires an absolute URL. If we use a |
| 606 // relative path we will get a DOM exception. | 603 // relative path we will get a DOM exception. |
| 607 String prefix = _thisScript.substring(0, _thisScript.lastIndexOf('/')); | 604 String prefix = _thisScript.substring(0, _thisScript.lastIndexOf('/')); |
| 608 uri = "$prefix/$uri"; | 605 uri = "$prefix/$uri"; |
| 609 } | 606 } |
| 610 final worker = _newWorker(uri); | 607 final worker = _newWorker(uri); |
| 611 worker.onmessage = (e) { _processWorkerMessage(worker, e); }; | 608 worker.onmessage = (e) { _processWorkerMessage(worker, e); }; |
| 612 var workerId = globalState.nextWorkerId++; | 609 var workerId = _globalState.nextWorkerId++; |
| 613 // We also store the id on the worker itself so that we can unregister it. | 610 // We also store the id on the worker itself so that we can unregister it. |
| 614 worker.id = workerId; | 611 worker.id = workerId; |
| 615 globalState.workers[workerId] = worker; | 612 _globalState.workers[workerId] = worker; |
| 616 worker.postMessage(_serializeMessage({ | 613 worker.postMessage(_serializeMessage({ |
| 617 'command': 'start2', | 614 'command': 'start2', |
| 618 'id': workerId, | 615 'id': workerId, |
| 619 // Note: we serialize replyPort twice because the child worker needs to | 616 // Note: we serialize replyPort twice because the child worker needs to |
| 620 // first deserialize the worker id, before it can correctly deserialize | 617 // first deserialize the worker id, before it can correctly deserialize |
| 621 // the port (port deserialization is sensitive to what is the current | 618 // the port (port deserialization is sensitive to what is the current |
| 622 // workerId). | 619 // workerId). |
| 623 'replyTo': _serializeMessage(replyPort), | 620 'replyTo': _serializeMessage(replyPort), |
| 624 'functionName': functionName })); | 621 'functionName': functionName })); |
| 625 } | 622 } |
| 626 } | 623 } |
| OLD | NEW |