| // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| /** |
| * Concurrent programming using _isolates_: |
| * independent workers that are similar to threads |
| * but don't share memory, |
| * communicating only via messages. |
| * |
| * To use this library in your code: |
| * |
| * import 'dart:isolate'; |
| */ |
| library dart.isolate; |
| |
| import "dart:async"; |
| import "dart:collection" show HashMap; |
| import "dart:_internal" hide Symbol; |
| |
| part "capability.dart"; |
| |
| /** |
| * Thrown when an isolate cannot be created. |
| */ |
| class IsolateSpawnException implements Exception { |
| /** Error message reported by the spawn operation. */ |
| final String message; |
| IsolateSpawnException(this.message); |
| String toString() => "IsolateSpawnException: $message"; |
| } |
| |
| /** |
| * An isolated Dart execution context. |
| * |
| * All Dart code runs in an isolate, and code can access classes and values |
| * only from the same isolate. Different isolates can communicate by sending |
| * values through ports (see [ReceivePort], [SendPort]). |
| * |
| * An `Isolate` object is a reference to an isolate, usually different from |
| * the current isolate. |
| * It represents, and can be used control, the other isolate. |
| * |
| * When spawning a new isolate, the spawning isolate receives an `Isolate` |
| * object representing the new isolate when the spawn operation succeeds. |
| * |
| * Isolates run code in its own event loop, and each event may run smaller tasks |
| * in a nested microtask queue. |
| * |
| * An `Isolate` object allows other isolates to control the event loop |
| * of the isolate that it represents, and to inspect the isolate, |
| * for example by pausing the isolate or by getting events when the isolate |
| * has an uncaught error. |
| * |
| * The [controlPort] identifies and gives access to controlling the isolate, |
| * and the [pauseCapability] and [terminateCapability] guard access |
| * to some control operations. |
| * For example, calling [pause] on an `Isolate` object created without a |
| * [pauseCapability], has no effect. |
| * |
| * The `Isolate` object provided by a spawn operation will have the |
| * control port and capabilities needed to control the isolate. |
| * New isolate objects can be created without some of these capabilities |
| * if necessary, using the [Isolate.Isolate] constructor. |
| * |
| * An `Isolate` object cannot be sent over a `SendPort`, but the control port |
| * and capabilities can be sent, and can be used to create a new functioning |
| * `Isolate` object in the receiving port's isolate. |
| */ |
| class Isolate { |
| /** Argument to `ping` and `kill`: Ask for immediate action. */ |
| static const int IMMEDIATE = 0; |
| /** Argument to `ping` and `kill`: Ask for action before the next event. */ |
| static const int BEFORE_NEXT_EVENT = 1; |
| |
| /** |
| * Control port used to send control messages to the isolate. |
| * |
| * The control port identifies the isolate. |
| * |
| * An `Isolate` object allows sending control messages |
| * through the control port. |
| * |
| * Some control messages require a specific capability to be passed along |
| * with the message (see [pauseCapability] and [terminateCapability]), |
| * otherwise the message is ignored by the isolate. |
| */ |
| final SendPort controlPort; |
| |
| /** |
| * Capability granting the ability to pause the isolate. |
| * |
| * This capability is required by [pause]. |
| * If the capability is `null`, or if it is not the correct pause capability |
| * of the isolate identified by [controlPort], |
| * then calls to [pause] will have no effect. |
| * |
| * If the isolate is spawned in a paused state, use this capability as |
| * argument to the [resume] method in order to resume the paused isolate. |
| */ |
| final Capability pauseCapability; |
| |
| /** |
| * Capability granting the ability to terminate the isolate. |
| * |
| * This capability is required by [kill] and [setErrorsFatal]. |
| * If the capability is `null`, or if it is not the correct termination |
| * capability of the isolate identified by [controlPort], |
| * then calls to those methods will have no effect. |
| */ |
| final Capability terminateCapability; |
| |
| /** |
| * Create a new [Isolate] object with a restricted set of capabilities. |
| * |
| * The port should be a control port for an isolate, as taken from |
| * another `Isolate` object. |
| * |
| * The capabilities should be the subset of the capabilities that are |
| * available to the original isolate. |
| * Capabilities of an isolate are locked to that isolate, and have no effect |
| * anywhere else, so the capabilities should come from the same isolate as |
| * the control port. |
| * |
| * Can also be used to create an [Isolate] object from a control port, and |
| * any available capabilities, that have been sent through a [SendPort]. |
| * |
| * Example: |
| * ```dart |
| * Isolate isolate = findSomeIsolate(); |
| * Isolate restrictedIsolate = new Isolate(isolate.controlPort); |
| * untrustedCode(restrictedIsolate); |
| * ``` |
| * This example creates a new `Isolate` object that cannot be used to |
| * pause or terminate the isolate. All the untrusted code can do is to |
| * inspect the isolate and see uncaught errors or when it terminates. |
| */ |
| Isolate(this.controlPort, {this.pauseCapability, this.terminateCapability}); |
| |
| /** |
| * Return an [Isolate] object representing the current isolate. |
| * |
| * The current isolate for code using [current] |
| * is the isolate running the code. |
| * |
| * The isolate object provides the capabilities required to inspect, |
| * pause or kill the isolate, and allows granting these capabilities |
| * to others. |
| * |
| * It is possible to pause the current isolate, but doing so *without* |
| * first passing the ability to resume it again to another isolate, |
| * is a sure way to hang your program. |
| */ |
| static Isolate get current => _currentIsolate; |
| |
| /** |
| * Returns the package root of the current isolate, if any. |
| * |
| * If the isolate is using a [packageConfig] or the isolate has not been |
| * setup for package resolution, this getter returns `null`, otherwise it |
| * returns the package root - a directory that package URIs are resolved |
| * against. |
| */ |
| static Future<Uri> get packageRoot { |
| var hook = VMLibraryHooks.packageRootUriFuture; |
| if (hook == null) { |
| throw new UnsupportedError("Isolate.packageRoot"); |
| } |
| return hook(); |
| } |
| |
| /** |
| * Returns the package root of the current isolate, if any. |
| * |
| * If the isolate is using a [packageRoot] or the isolate has not been |
| * setup for package resolution, this getter returns `null`, otherwise it |
| * returns the package config URI. |
| */ |
| static Future<Uri> get packageConfig { |
| var hook = VMLibraryHooks.packageConfigUriFuture; |
| if (hook == null) { |
| throw new UnsupportedError("Isolate.packageConfig"); |
| } |
| return hook(); |
| } |
| |
| /** |
| * Maps a package: URI to a non-package Uri. |
| * |
| * If there is no valid mapping from the package: URI in the current |
| * isolate, then this call returns `null`. Non-package: URIs are |
| * returned unmodified. |
| */ |
| static Future<Uri> resolvePackageUri(Uri packageUri) { |
| var hook = VMLibraryHooks.resolvePackageUriFuture; |
| if (hook == null) { |
| throw new UnsupportedError("Isolate.resolvePackageUri"); |
| } |
| return hook(packageUri); |
| } |
| |
| /** |
| * Creates and spawns an isolate that shares the same code as the current |
| * isolate. |
| * |
| * The argument [entryPoint] specifies the initial function to call |
| * in the spawned isolate. |
| * The entry-point function is invoked in the new isolate with [message] |
| * as the only argument. |
| * |
| * The function must be a top-level function or a static method |
| * that can be called with a single argument, |
| * that is, a compile-time constant function value |
| * which accepts at least one positional parameter |
| * and has at most one required positional parameter. |
| * The function may accept any number of optional parameters, |
| * as long as it *can* be called with just a single argument. |
| * The function must not be the value of a function expression |
| * or an instance method tear-off. |
| * |
| * Usually the initial [message] contains a [SendPort] so |
| * that the spawner and spawnee can communicate with each other. |
| * |
| * If the [paused] parameter is set to `true`, |
| * the isolate will start up in a paused state, |
| * just before calling the [entryPoint] function with the [message], |
| * as if by an initial call of `isolate.pause(isolate.pauseCapability)`. |
| * To resume the isolate, call `isolate.resume(isolate.pauseCapability)`. |
| * |
| * If the [errorAreFatal], [onExit] and/or [onError] parameters are provided, |
| * the isolate will act as if, respectively, [setErrorsFatal], |
| * [addOnExitListener] and [addErrorListener] were called with the |
| * corresponding parameter and was processed before the isolate starts |
| * running. |
| * |
| * If [errorsAreFatal] is omitted, the platform may choose a default behavior |
| * or inherit the current isolate's behavior. |
| * |
| * You can also call the [setErrorsFatal], [addOnExitListener] and |
| * [addErrorListener] methods on the returned isolate, but unless the |
| * isolate was started as [paused], it may already have terminated |
| * before those methods can complete. |
| * |
| * Returns a future which will complete with an [Isolate] instance if the |
| * spawning succeeded. It will complete with an error otherwise. |
| */ |
| static Future<Isolate> spawn(void entryPoint(message), var message, |
| {bool paused: false, |
| bool errorsAreFatal, |
| SendPort onExit, |
| SendPort onError}) async { |
| // `paused` isn't handled yet. |
| RawReceivePort readyPort; |
| try { |
| // Check for the type of `entryPoint` on the spawning isolate to make |
| // error-handling easier. |
| if (entryPoint is! _UnaryFunction) { |
| throw new ArgumentError(entryPoint); |
| } |
| // The VM will invoke [_startIsolate] with entryPoint as argument. |
| readyPort = new RawReceivePort(); |
| |
| // We do not inherit the package root or package config settings |
| // from the parent isolate, instead we use the values that were |
| // set on the command line. |
| var packageRoot = VMLibraryHooks.packageRootString; |
| var packageConfig = VMLibraryHooks.packageConfigString; |
| var script = VMLibraryHooks.platformScript; |
| if (script == null) { |
| // We do not have enough information to support spawning the new |
| // isolate. |
| throw new UnsupportedError("Isolate.spawn"); |
| } |
| if (script.scheme == "package") { |
| script = await Isolate.resolvePackageUri(script); |
| } |
| |
| _spawnFunction(readyPort.sendPort, script.toString(), entryPoint, message, |
| paused, errorsAreFatal, onExit, onError, packageRoot, packageConfig); |
| return await _spawnCommon(readyPort); |
| } catch (e, st) { |
| if (readyPort != null) { |
| readyPort.close(); |
| } |
| return await new Future<Isolate>.error(e, st); |
| } |
| } |
| |
| /** |
| * Creates and spawns an isolate that runs the code from the library with |
| * the specified URI. |
| * |
| * The isolate starts executing the top-level `main` function of the library |
| * with the given URI. |
| * |
| * The target `main` must be callable with zero, one or two arguments. |
| * Examples: |
| * |
| * * `main()` |
| * * `main(args)` |
| * * `main(args, message)` |
| * |
| * When present, the parameter `args` is set to the provided [args] list. |
| * When present, the parameter `message` is set to the initial [message]. |
| * |
| * If the [paused] parameter is set to `true`, |
| * the isolate will start up in a paused state, |
| * as if by an initial call of `isolate.pause(isolate.pauseCapability)`. |
| * To resume the isolate, call `isolate.resume(isolate.pauseCapability)`. |
| * |
| * If the [errorAreFatal], [onExit] and/or [onError] parameters are provided, |
| * the isolate will act as if, respectively, [setErrorsFatal], |
| * [addOnExitListener] and [addErrorListener] were called with the |
| * corresponding parameter and was processed before the isolate starts |
| * running. |
| * |
| * You can also call the [setErrorsFatal], [addOnExitListener] and |
| * [addErrorListener] methods on the returned isolate, but unless the |
| * isolate was started as [paused], it may already have terminated |
| * before those methods can complete. |
| * |
| * If the [checked] parameter is set to `true` or `false`, |
| * the new isolate will run code in checked mode, |
| * respectively in production mode, if possible. |
| * If the parameter is omitted, the new isolate will inherit the |
| * value from the current isolate. |
| * |
| * It may not always be possible to honor the `checked` parameter. |
| * If the isolate code was pre-compiled, it may not be possible to change |
| * the checked mode setting dynamically. |
| * In that case, the `checked` parameter is ignored. |
| * |
| * WARNING: The [checked] parameter is not implemented on all platforms yet. |
| * |
| * If the [packageRoot] parameter is provided, it is used to find the location |
| * of package sources in the spawned isolate. |
| * |
| * The `packageRoot` URI must be a "file" or "http"/"https" URI that specifies |
| * a directory. If it doesn't end in a slash, one will be added before |
| * using the URI, and any query or fragment parts are ignored. |
| * Package imports (like `"package:foo/bar.dart"`) in the new isolate are |
| * resolved against this location, as by |
| * `packageRoot.resolve("foo/bar.dart")`. |
| * |
| * If the [packageConfig] parameter is provided, then it is used to find the |
| * location of a package resolution configuration file for the spawned |
| * isolate. |
| * |
| * If the [automaticPackageResolution] parameter is provided, then the |
| * location of the package sources in the spawned isolate is automatically |
| * determined. |
| * |
| * The [environment] is a mapping from strings to strings which the |
| * spawned isolate uses when looking up [String.fromEnvironment] values. |
| * The system may add its own entries to environment as well. |
| * If `environment` is omitted, the spawned isolate has the same environment |
| * declarations as the spawning isolate. |
| * |
| * WARNING: The [environment] parameter is not implemented on all |
| * platforms yet. |
| * |
| * Returns a future that will complete with an [Isolate] instance if the |
| * spawning succeeded. It will complete with an error otherwise. |
| */ |
| static Future<Isolate> spawnUri(Uri uri, List<String> args, var message, |
| {bool paused: false, |
| SendPort onExit, |
| SendPort onError, |
| bool errorsAreFatal, |
| bool checked, |
| Map<String, String> environment, |
| Uri packageRoot, |
| Uri packageConfig, |
| bool automaticPackageResolution: false}) async { |
| RawReceivePort readyPort; |
| if (environment != null) { |
| throw new UnimplementedError("environment"); |
| } |
| |
| // Verify that no mutually exclusive arguments have been passed. |
| if (automaticPackageResolution) { |
| if (packageRoot != null) { |
| throw new ArgumentError("Cannot simultaneously request " |
| "automaticPackageResolution and specify a" |
| "packageRoot."); |
| } |
| if (packageConfig != null) { |
| throw new ArgumentError("Cannot simultaneously request " |
| "automaticPackageResolution and specify a" |
| "packageConfig."); |
| } |
| } else { |
| if ((packageRoot != null) && (packageConfig != null)) { |
| throw new ArgumentError("Cannot simultaneously specify a " |
| "packageRoot and a packageConfig."); |
| } |
| } |
| try { |
| // Resolve the uri against the current isolate's root Uri first. |
| var spawnedUri = _rootUri.resolveUri(uri); |
| |
| // Inherit this isolate's package resolution setup if not overridden. |
| if (!automaticPackageResolution && |
| (packageRoot == null) && |
| (packageConfig == null)) { |
| if (Isolate._packageSupported()) { |
| packageRoot = await Isolate.packageRoot; |
| packageConfig = await Isolate.packageConfig; |
| } |
| } |
| |
| // Ensure to resolve package: URIs being handed in as parameters. |
| if (packageRoot != null) { |
| // Avoid calling resolvePackageUri if not stricly necessary in case |
| // the API is not supported. |
| if (packageRoot.scheme == "package") { |
| packageRoot = await Isolate.resolvePackageUri(packageRoot); |
| } |
| } else if (packageConfig != null) { |
| // Avoid calling resolvePackageUri if not strictly necessary in case |
| // the API is not supported. |
| if (packageConfig.scheme == "package") { |
| packageConfig = await Isolate.resolvePackageUri(packageConfig); |
| } |
| } |
| |
| // The VM will invoke [_startIsolate] and not `main`. |
| readyPort = new RawReceivePort(); |
| var packageRootString = packageRoot?.toString(); |
| var packageConfigString = packageConfig?.toString(); |
| |
| _spawnUri( |
| readyPort.sendPort, |
| spawnedUri.toString(), |
| args, |
| message, |
| paused, |
| onExit, |
| onError, |
| errorsAreFatal, |
| checked, |
| null, |
| /* environment */ |
| packageRootString, |
| packageConfigString); |
| return await _spawnCommon(readyPort); |
| } catch (e, st) { |
| if (readyPort != null) { |
| readyPort.close(); |
| } |
| rethrow; |
| } |
| } |
| |
| /** |
| * Requests the isolate to pause. |
| * |
| * When the isolate receives the pause command, it stops |
| * processing events from the event loop queue. |
| * It may still add new events to the queue in response to, e.g., timers |
| * or receive-port messages. When the isolate is resumed, |
| * it starts handling the already enqueued events. |
| * |
| * The pause request is sent through the isolate's command port, |
| * which bypasses the receiving isolate's event loop. |
| * The pause takes effect when it is received, pausing the event loop |
| * as it is at that time. |
| * |
| * The [resumeCapability] is used to identity the pause, |
| * and must be used again to end the pause using [resume]. |
| * If [resumeCapability] is omitted, a new capability object is created |
| * and used instead. |
| * |
| * If an isolate is paused more than once using the same capability, |
| * only one resume with that capability is needed to end the pause. |
| * |
| * If an isolate is paused using more than one capability, |
| * each pause must be individually ended before the isolate resumes. |
| * |
| * Returns the capability that must be used to end the pause. |
| * This is either [resumeCapability], or a new capability when |
| * [resumeCapability] is omitted. |
| * |
| * If [pauseCapability] is `null`, or it's not the pause capability |
| * of the isolate identified by [controlPort], |
| * the pause request is ignored by the receiving isolate. |
| */ |
| Capability pause([Capability resumeCapability]) { |
| resumeCapability ??= new Capability(); |
| _pause(resumeCapability); |
| return resumeCapability; |
| } |
| |
| /** Internal implementation of [pause]. */ |
| void _pause(Capability resumeCapability) { |
| var msg = new List(4) |
| ..[0] = 0 // Make room for OOB message type. |
| ..[1] = _PAUSE |
| ..[2] = pauseCapability |
| ..[3] = resumeCapability; |
| _sendOOB(controlPort, msg); |
| } |
| |
| /** |
| * Resumes a paused isolate. |
| * |
| * Sends a message to an isolate requesting that it ends a pause |
| * that was previously requested. |
| * |
| * When all active pause requests have been cancelled, the isolate |
| * will continue processing events and handling normal messages. |
| * |
| * If the [resumeCapability] is not one that has previously been used |
| * to pause the isolate, or it has already been used to resume from |
| * that pause, the resume call has no effect. |
| */ |
| void resume(Capability resumeCapability) { |
| var msg = new List(4) |
| ..[0] = 0 // Make room for OOB message type. |
| ..[1] = _RESUME |
| ..[2] = pauseCapability |
| ..[3] = resumeCapability; |
| _sendOOB(controlPort, msg); |
| } |
| |
| /** |
| * Requests an exist message on [responsePort] when the isolate terminates. |
| * |
| * The isolate will send [response] as a message on [responsePort] as the last |
| * thing before it terminates. It will run no further code after the message |
| * has been sent. |
| * |
| * Adding the same port more than once will only cause it to receive one exit |
| * message, using the last response value that was added, |
| * and it only needs to be removed once using [removeOnExitListener]. |
| * |
| * If the isolate has terminated before it can receive this request, |
| * no exit message will be sent. |
| * |
| * The [response] object must follow the same restrictions as enforced by |
| * [SendPort.send]. |
| * It is recommended to only use simple values that can be sent to all |
| * isolates, like `null`, booleans, numbers or strings. |
| * |
| * Since isolates run concurrently, it's possible for it to exit before the |
| * exit listener is established, and in that case no response will be |
| * sent on [responsePort]. |
| * To avoid this, either use the corresponding parameter to the spawn |
| * function, or start the isolate paused, add the listener and |
| * then resume the isolate. |
| */ |
| /* TODO(lrn): Can we do better? Can the system recognize this message and |
| * send a reply if the receiving isolate is dead? |
| */ |
| void addOnExitListener(SendPort responsePort, {Object response}) { |
| var msg = new List(4) |
| ..[0] = 0 // Make room for OOB message type. |
| ..[1] = _ADD_EXIT |
| ..[2] = responsePort |
| ..[3] = response; |
| _sendOOB(controlPort, msg); |
| } |
| |
| /** |
| * Stop listening for exit messages from the isolate. |
| * |
| * Requests for the isolate to not send exit messages on [responsePort]. |
| * If the isolate isn't expecting to send exit messages on [responsePort], |
| * because the port hasn't been added using [addOnExitListener], |
| * or because it has already been removed, the request is ignored. |
| * |
| * If the same port has been passed via [addOnExitListener] more than once, |
| * only one call to `removeOnExitListener` is needed to stop it from receiving |
| * exit messagees. |
| * |
| * Closing the receive port at the end of the send port will not stop the |
| * isolate from sending exit messages, they are just going to be lost. |
| * |
| * An exit message may still be sent if the isolate terminates |
| * before this request is received and processed. |
| */ |
| void removeOnExitListener(SendPort responsePort) { |
| var msg = new List(3) |
| ..[0] = 0 // Make room for OOB message type. |
| ..[1] = _DEL_EXIT |
| ..[2] = responsePort; |
| _sendOOB(controlPort, msg); |
| } |
| |
| /** |
| * Set whether uncaught errors will terminate the isolate. |
| * |
| * If errors are fatal, any uncaught error will terminate the isolate |
| * event loop and shut down the isolate. |
| * |
| * This call requires the [terminateCapability] for the isolate. |
| * If the capability is absent or incorrect, no change is made. |
| * |
| * Since isolates run concurrently, it's possible for it to exit due to an |
| * error before errors are set non-fatal. |
| * To avoid this, either use the corresponding parameter to the spawn |
| * function, or start the isolate paused, set errors non-fatal and |
| * then resume the isolate. |
| */ |
| void setErrorsFatal(bool errorsAreFatal) { |
| var msg = new List(4) |
| ..[0] = 0 // Make room for OOB message type. |
| ..[1] = _ERROR_FATAL |
| ..[2] = terminateCapability |
| ..[3] = errorsAreFatal; |
| _sendOOB(controlPort, msg); |
| } |
| |
| /** |
| * Requests the isolate to shut down. |
| * |
| * The isolate is requested to terminate itself. |
| * The [priority] argument specifies when this must happen. |
| * |
| * The [priority], when provided, must be one of [IMMEDIATE] or |
| * [BEFORE_NEXT_EVENT] (the default). |
| * The shutdown is performed at different times depending on the priority: |
| * |
| * * `IMMEDIATE`: The isolate shuts down as soon as possible. |
| * Control messages are handled in order, so all previously sent control |
| * events from this isolate will all have been processed. |
| * The shutdown should happen no later than if sent with |
| * `BEFORE_NEXT_EVENT`. |
| * It may happen earlier if the system has a way to shut down cleanly |
| * at an earlier time, even during the execution of another event. |
| * * `BEFORE_NEXT_EVENT`: The shutdown is scheduled for the next time |
| * control returns to the event loop of the receiving isolate, |
| * after the current event, and any already scheduled control events, |
| * are completed. |
| * |
| * If [terminateCapability] is `null`, or it's not the terminate capability |
| * of the isolate identified by [controlPort], |
| * the kill request is ignored by the receiving isolate. |
| */ |
| void kill({int priority: BEFORE_NEXT_EVENT}) { |
| var msg = new List(4) |
| ..[0] = 0 // Make room for OOB message type. |
| ..[1] = _KILL |
| ..[2] = terminateCapability |
| ..[3] = priority; |
| _sendOOB(controlPort, msg); |
| } |
| |
| /** |
| * Request that the isolate send [response] on the [responsePort]. |
| * |
| * The [response] object must follow the same restrictions as enforced by |
| * [SendPort.send]. |
| * It is recommended to only use simple values that can be sent to all |
| * isolates, like `null`, booleans, numbers or strings. |
| * |
| * If the isolate is alive, it will eventually send `response` |
| * (defaulting to `null`) on the response port. |
| * |
| * The [priority] must be one of [IMMEDIATE] or [BEFORE_NEXT_EVENT]. |
| * The response is sent at different times depending on the ping type: |
| * |
| * * `IMMEDIATE`: The isolate responds as soon as it receives the |
| * control message. This is after any previous control message |
| * from the same isolate has been received and processed, |
| * but may be during execution of another event. |
| * * `BEFORE_NEXT_EVENT`: The response is scheduled for the next time |
| * control returns to the event loop of the receiving isolate, |
| * after the current event, and any already scheduled control events, |
| * are completed. |
| */ |
| void ping(SendPort responsePort, {Object response, int priority: IMMEDIATE}) { |
| var msg = new List(5) |
| ..[0] = 0 // Make room for OOM message type. |
| ..[1] = _PING |
| ..[2] = responsePort |
| ..[3] = priority |
| ..[4] = response; |
| _sendOOB(controlPort, msg); |
| } |
| |
| /** |
| * Requests that uncaught errors of the isolate are sent back to [port]. |
| * |
| * The errors are sent back as two elements lists. |
| * The first element is a `String` representation of the error, usually |
| * created by calling `toString` on the error. |
| * The second element is a `String` representation of an accompanying |
| * stack trace, or `null` if no stack trace was provided. |
| * To convert this back to a [StackTrace] object, use [StackTrace.fromString]. |
| * |
| * Listening using the same port more than once does nothing. |
| * A port will only receive each error once, |
| * and will only need to be removed once using [removeErrorListener]. |
| * |
| * Since isolates run concurrently, it's possible for it to exit before the |
| * error listener is established. To avoid this, start the isolate paused, |
| * add the listener and then resume the isolate. |
| */ |
| void addErrorListener(SendPort port) { |
| var msg = new List(3) |
| ..[0] = 0 // Make room for OOB message type. |
| ..[1] = _ADD_ERROR |
| ..[2] = port; |
| _sendOOB(controlPort, msg); |
| } |
| |
| /** |
| * Stop listening for uncaught errors from the isolate. |
| * |
| * Requests for the isolate to not send uncaught errors on [responsePort]. |
| * If the isolate isn't expecting to send uncaught errors on [responsePort], |
| * because the port hasn't been added using [addErrorListener], |
| * or because it has already been removed, the request is ignored. |
| * |
| * If the same port has been passed via [addErrorListener] more than once, |
| * only one call to `removeErrorListener` is needed to stop it from receiving |
| * unaught errors. |
| * |
| * Closing the receive port at the end of the send port will not stop the |
| * isolate from sending uncaught errors, they are just going to be lost. |
| * |
| * Uncaught errors message may still be sent by the isolate |
| * until this request is received and processed. |
| */ |
| void removeErrorListener(SendPort port) { |
| var msg = new List(3) |
| ..[0] = 0 // Make room for OOB message type. |
| ..[1] = _DEL_ERROR |
| ..[2] = port; |
| _sendOOB(controlPort, msg); |
| } |
| |
| /** |
| * Returns a broadcast stream of uncaught errors from the isolate. |
| * |
| * Each error is provided as an error event on the stream. |
| * |
| * The actual error object and stackTraces will not necessarily |
| * be the same object types as in the actual isolate, but they will |
| * always have the same [Object.toString] result. |
| * |
| * This stream is based on [addErrorListener] and [removeErrorListener]. |
| */ |
| Stream get errors { |
| StreamController controller; |
| RawReceivePort port; |
| void handleError(message) { |
| String errorDescription = message[0]; |
| String stackDescription = message[1]; |
| var error = new RemoteError(errorDescription, stackDescription); |
| controller.addError(error, error.stackTrace); |
| } |
| |
| controller = new StreamController.broadcast( |
| sync: true, |
| onListen: () { |
| port = new RawReceivePort(handleError); |
| this.addErrorListener(port.sendPort); |
| }, |
| onCancel: () { |
| this.removeErrorListener(port.sendPort); |
| port.close(); |
| port = null; |
| }); |
| return controller.stream; |
| } |
| |
| static const _PAUSE = 1; |
| |
| static Future<Isolate> _spawnCommon(RawReceivePort readyPort) { |
| Completer completer = new Completer<Isolate>.sync(); |
| readyPort.handler = (readyMessage) { |
| readyPort.close(); |
| if (readyMessage is List && readyMessage.length == 2) { |
| SendPort controlPort = readyMessage[0]; |
| List capabilities = readyMessage[1]; |
| completer.complete(new Isolate(controlPort, |
| pauseCapability: capabilities[0], |
| terminateCapability: capabilities[1])); |
| } else if (readyMessage is String) { |
| // We encountered an error while starting the new isolate. |
| completer.completeError(new IsolateSpawnException( |
| 'Unable to spawn isolate: ${readyMessage}')); |
| } else { |
| // This shouldn't happen. |
| completer.completeError(new IsolateSpawnException( |
| "Internal error: unexpected format for ready message: " |
| "'${readyMessage}'")); |
| } |
| }; |
| return completer.future; |
| } |
| |
| static final _currentIsolate = _getCurrentIsolate(); |
| |
| static const _RESUME = 2; |
| |
| static const _PING = 3; |
| |
| static const _KILL = 4; |
| |
| static const _ADD_EXIT = 5; |
| |
| static const _DEL_EXIT = 6; |
| |
| static const _ADD_ERROR = 7; |
| |
| static const _DEL_ERROR = 8; |
| |
| static const _ERROR_FATAL = 9; |
| |
| static void _spawnFunction( |
| SendPort readyPort, |
| String uri, |
| Function topLevelFunction, |
| var message, |
| bool paused, |
| bool errorsAreFatal, |
| SendPort onExit, |
| SendPort onError, |
| String packageRoot, |
| String packageConfig) native "Isolate_spawnFunction"; |
| |
| static void _spawnUri( |
| SendPort readyPort, |
| String uri, |
| List<String> args, |
| var message, |
| bool paused, |
| SendPort onExit, |
| SendPort onError, |
| bool errorsAreFatal, |
| bool checked, |
| List environment, |
| String packageRoot, |
| String packageConfig) native "Isolate_spawnUri"; |
| |
| static void _sendOOB(port, msg) native "Isolate_sendOOB"; |
| |
| static Isolate _getCurrentIsolate() { |
| List portAndCapabilities = _getPortAndCapabilitiesOfCurrentIsolate(); |
| return new Isolate(portAndCapabilities[0], |
| pauseCapability: portAndCapabilities[1], |
| terminateCapability: portAndCapabilities[2]); |
| } |
| |
| static List _getPortAndCapabilitiesOfCurrentIsolate() |
| native "Isolate_getPortAndCapabilitiesOfCurrentIsolate"; |
| |
| static Uri _getCurrentRootUri() { |
| try { |
| return Uri.parse(_getCurrentRootUriStr()); |
| } catch (e, s) { |
| return null; |
| } |
| } |
| |
| static String _getCurrentRootUriStr() native "Isolate_getCurrentRootUriStr"; |
| |
| static final _rootUri = _getCurrentRootUri(); |
| |
| static bool _packageSupported() => |
| (VMLibraryHooks.packageRootUriFuture != null) && |
| (VMLibraryHooks.packageConfigUriFuture != null) && |
| (VMLibraryHooks.resolvePackageUriFuture != null); |
| } |
| |
| /** |
| * Sends messages to its [ReceivePort]s. |
| * |
| * [SendPort]s are created from [ReceivePort]s. Any message sent through |
| * a [SendPort] is delivered to its corresponding [ReceivePort]. There might be |
| * many [SendPort]s for the same [ReceivePort]. |
| * |
| * [SendPort]s can be transmitted to other isolates, and they preserve equality |
| * when sent. |
| */ |
| abstract class SendPort implements Capability { |
| /** |
| * Sends an asynchronous [message] through this send port, to its |
| * corresponding `ReceivePort`. |
| * |
| * The content of [message] can be: primitive values (null, num, bool, double, |
| * String), instances of [SendPort], and lists and maps whose elements are any |
| * of these. List and maps are also allowed to be cyclic. |
| * |
| * In the special circumstances when two isolates share the same code and are |
| * running in the same process (e.g. isolates created via [Isolate.spawn]), it |
| * is also possible to send object instances (which would be copied in the |
| * process). This is currently only supported by the dartvm. For now, the |
| * dart2js compiler only supports the restricted messages described above. |
| * |
| * The send happens immediately and doesn't block. The corresponding receive |
| * port can receive the message as soon as its isolate's event loop is ready |
| * to deliver it, independently of what the sending isolate is doing. |
| */ |
| void send(var message); |
| |
| /** |
| * Tests whether [other] is a [SendPort] pointing to the same |
| * [ReceivePort] as this one. |
| */ |
| bool operator ==(var other); |
| |
| /** |
| * Returns an immutable hash code for this send port that is |
| * consistent with the == operator. |
| */ |
| int get hashCode; |
| } |
| |
| /** |
| * Together with [SendPort], the only means of communication between isolates. |
| * |
| * [ReceivePort]s have a `sendPort` getter which returns a [SendPort]. |
| * Any message that is sent through this [SendPort] |
| * is delivered to the [ReceivePort] it has been created from. There, the |
| * message is dispatched to the `ReceivePort`'s listener. |
| * |
| * A [ReceivePort] is a non-broadcast stream. This means that it buffers |
| * incoming messages until a listener is registered. Only one listener can |
| * receive messages. See [Stream.asBroadcastStream] for transforming the port |
| * to a broadcast stream. |
| * |
| * A [ReceivePort] may have many [SendPort]s. |
| */ |
| abstract class ReceivePort implements Stream { |
| /** |
| * Opens a long-lived port for receiving messages. |
| * |
| * A [ReceivePort] is a non-broadcast stream. This means that it buffers |
| * incoming messages until a listener is registered. Only one listener can |
| * receive messages. See [Stream.asBroadcastStream] for transforming the port |
| * to a broadcast stream. |
| * |
| * A receive port is closed by canceling its subscription. |
| */ |
| factory ReceivePort() = _ReceivePortImpl; |
| |
| /** |
| * Creates a [ReceivePort] from a [RawReceivePort]. |
| * |
| * The handler of the given [rawPort] is overwritten during the construction |
| * of the result. |
| */ |
| factory ReceivePort.fromRawReceivePort(RawReceivePort rawPort) = |
| _ReceivePortImpl.fromRawReceivePort; |
| |
| /** |
| * Inherited from [Stream]. |
| * |
| * Note that [onError] and [cancelOnError] are ignored since a ReceivePort |
| * will never receive an error. |
| * |
| * The [onDone] handler will be called when the stream closes. |
| * The stream closes when [close] is called. |
| */ |
| StreamSubscription listen(void onData(var message), |
| {Function onError, void onDone(), bool cancelOnError}); |
| |
| /** |
| * Closes `this`. |
| * |
| * If the stream has not been canceled yet, adds a close-event to the event |
| * queue and discards any further incoming messages. |
| * |
| * If the stream has already been canceled this method has no effect. |
| */ |
| void close(); |
| |
| /** |
| * Returns a [SendPort] that sends to this receive port. |
| */ |
| SendPort get sendPort; |
| } |
| |
| abstract class RawReceivePort { |
| /** |
| * Opens a long-lived port for receiving messages. |
| * |
| * A [RawReceivePort] is low level and does not work with [Zone]s. It |
| * can not be paused. The data-handler must be set before the first |
| * event is received. |
| */ |
| factory RawReceivePort([void handler(event)]) { |
| _RawReceivePortImpl result = new _RawReceivePortImpl(); |
| result.handler = handler; |
| return result; |
| } |
| |
| /** |
| * Sets the handler that is invoked for every incoming message. |
| * |
| * The handler is invoked in the root-zone ([Zone.ROOT]). |
| */ |
| void set handler(Function newHandler); |
| |
| /** |
| * Closes the port. |
| * |
| * After a call to this method any incoming message is silently dropped. |
| */ |
| void close(); |
| |
| /** |
| * Returns a [SendPort] that sends to this raw receive port. |
| */ |
| SendPort get sendPort; |
| } |
| |
| /** |
| * Description of an error from another isolate. |
| * |
| * This error has the same `toString()` and `stackTrace.toString()` behavior |
| * as the original error, but has no other features of the original error. |
| */ |
| class RemoteError implements Error { |
| final String _description; |
| final StackTrace stackTrace; |
| RemoteError(String description, String stackDescription) |
| : _description = description, |
| stackTrace = new StackTrace.fromString(stackDescription); |
| String toString() => _description; |
| } |
| |
| class _CapabilityImpl implements Capability { |
| factory _CapabilityImpl() native "CapabilityImpl_factory"; |
| |
| bool operator ==(var other) { |
| return (other is _CapabilityImpl) && _equals(other); |
| } |
| |
| int get hashCode { |
| return _get_hashcode(); |
| } |
| |
| _equals(other) native "CapabilityImpl_equals"; |
| _get_hashcode() native "CapabilityImpl_get_hashcode"; |
| } |
| class _ReceivePortImpl extends Stream implements ReceivePort { |
| _ReceivePortImpl() : this.fromRawReceivePort(new RawReceivePort()); |
| |
| _ReceivePortImpl.fromRawReceivePort(this._rawPort) { |
| _controller = new StreamController(onCancel: close, sync: true); |
| _rawPort.handler = _controller.add; |
| } |
| |
| SendPort get sendPort { |
| return _rawPort.sendPort; |
| } |
| |
| StreamSubscription listen(void onData(var message), |
| {Function onError, void onDone(), bool cancelOnError}) { |
| return _controller.stream.listen(onData, |
| onError: onError, onDone: onDone, cancelOnError: cancelOnError); |
| } |
| |
| close() { |
| _rawPort.close(); |
| _controller.close(); |
| } |
| |
| final RawReceivePort _rawPort; |
| StreamController _controller; |
| } |
| typedef void _ImmediateCallback(); |
| /// The callback that has been registered through `scheduleImmediate`. |
| _ImmediateCallback _pendingImmediateCallback; |
| /// The closure that should be used as scheduleImmediateClosure, when the VM |
| /// is responsible for the event loop. |
| void _isolateScheduleImmediate(void callback()) { |
| assert(_pendingImmediateCallback == null); |
| _pendingImmediateCallback = callback; |
| } |
| void _runPendingImmediateCallback() { |
| if (_pendingImmediateCallback != null) { |
| var callback = _pendingImmediateCallback; |
| _pendingImmediateCallback = null; |
| callback(); |
| } |
| } |
| _ImmediateCallback _removePendingImmediateCallback() { |
| var callback = _pendingImmediateCallback; |
| _pendingImmediateCallback = null; |
| return callback; |
| } |
| /// The embedder can execute this function to get hold of |
| /// [_isolateScheduleImmediate] above. |
| Function _getIsolateScheduleImmediateClosure() { |
| return _isolateScheduleImmediate; |
| } |
| class _RawReceivePortImpl implements RawReceivePort { |
| factory _RawReceivePortImpl() native "RawReceivePortImpl_factory"; |
| |
| close() { |
| // Close the port and remove it from the handler map. |
| _handlerMap.remove(this._closeInternal()); |
| } |
| |
| SendPort get sendPort { |
| return _get_sendport(); |
| } |
| |
| bool operator ==(var other) { |
| return (other is _RawReceivePortImpl) && |
| (this._get_id() == other._get_id()); |
| } |
| |
| int get hashCode { |
| return sendPort.hashCode; |
| } |
| |
| /**** Internal implementation details ****/ |
| int _get_id() native "RawReceivePortImpl_get_id"; |
| SendPort _get_sendport() native "RawReceivePortImpl_get_sendport"; |
| |
| // Called from the VM to retrieve the handler for a message. |
| static _lookupHandler(int id) { |
| var result = _handlerMap[id]; |
| return result; |
| } |
| |
| // Called from the VM to dispatch to the handler. |
| static void _handleMessage(Function handler, var message) { |
| // TODO(floitsch): this relies on the fact that any exception aborts the |
| // VM. Once we have non-fatal global exceptions we need to catch errors |
| // so that we can run the immediate callbacks. |
| handler(message); |
| _runPendingImmediateCallback(); |
| } |
| |
| // Call into the VM to close the VM maintained mappings. |
| int _closeInternal() native "RawReceivePortImpl_closeInternal"; |
| |
| void set handler(Function value) { |
| _handlerMap[this._get_id()] = value; |
| } |
| |
| // TODO(iposva): Ideally keep this map in the VM. |
| // id to handler mapping. |
| static Map<int, Function> _initHandlerMap() { |
| // TODO(18511): Workaround bad CheckSmi hoisting. |
| return new HashMap<int, Function>(); |
| } |
| |
| static final Map<int, Function> _handlerMap = _initHandlerMap(); |
| } |
| class _SendPortImpl implements SendPort { |
| /*--- public interface ---*/ |
| void send(var message) { |
| _sendInternal(message); |
| } |
| |
| bool operator ==(var other) { |
| return (other is _SendPortImpl) && (this._get_id() == other._get_id()); |
| } |
| |
| int get hashCode { |
| return _get_hashcode(); |
| } |
| |
| /*--- private implementation ---*/ |
| _get_id() native "SendPortImpl_get_id"; |
| _get_hashcode() native "SendPortImpl_get_hashcode"; |
| |
| // Forward the implementation of sending messages to the VM. |
| void _sendInternal(var message) native "SendPortImpl_sendInternal_"; |
| } |
| typedef _NullaryFunction(); |
| typedef _UnaryFunction(args); |
| typedef _BinaryFunction(args, message); |
| /** |
| * Takes the real entry point as argument and invokes it with the |
| * initial message. Defers execution of the entry point until the |
| * isolate is in the message loop. |
| */ |
| void _startMainIsolate(Function entryPoint, List<String> args) { |
| _startIsolate( |
| null, // no parent port |
| entryPoint, |
| args, |
| null, // no message |
| true, // isSpawnUri |
| null, // no control port |
| null); // no capabilities |
| } |
| /** |
| * Takes the real entry point as argument and invokes it with the initial |
| * message. |
| */ |
| void _startIsolate( |
| SendPort parentPort, |
| Function entryPoint, |
| List<String> args, |
| var message, |
| bool isSpawnUri, |
| RawReceivePort controlPort, |
| List capabilities) { |
| // The control port (aka the main isolate port) does not handle any messages. |
| if (controlPort != null) { |
| controlPort.handler = (_) {}; // Nobody home on the control port. |
| } |
| |
| if (parentPort != null) { |
| // Build a message to our parent isolate providing access to the |
| // current isolate's control port and capabilities. |
| // |
| // TODO(floitsch): Send an error message if we can't find the entry point. |
| var readyMessage = new List(2); |
| readyMessage[0] = controlPort.sendPort; |
| readyMessage[1] = capabilities; |
| |
| // Out of an excess of paranoia we clear the capabilities from the |
| // stack. Not really necessary. |
| capabilities = null; |
| parentPort.send(readyMessage); |
| } |
| assert(capabilities == null); |
| |
| // Delay all user code handling to the next run of the message loop. This |
| // allows us to intercept certain conditions in the event dispatch, such as |
| // starting in paused state. |
| RawReceivePort port = new RawReceivePort(); |
| port.handler = (_) { |
| port.close(); |
| |
| if (isSpawnUri) { |
| if (entryPoint is _BinaryFunction) { |
| entryPoint(args, message); |
| } else if (entryPoint is _UnaryFunction) { |
| entryPoint(args); |
| } else { |
| entryPoint(); |
| } |
| } else { |
| entryPoint(message); |
| } |
| }; |
| // Make sure the message handler is triggered. |
| port.sendPort.send(null); |
| } |
| class _TimerHeap { |
| List<_Timer> _list; |
| int _used = 0; |
| |
| _TimerHeap([int initSize = 7]) : _list = new List<_Timer>(initSize); |
| |
| bool get isEmpty => _used == 0; |
| |
| _Timer get first => _list[0]; |
| |
| bool isFirst(_Timer timer) => timer._indexOrNext == 0; |
| |
| void add(_Timer timer) { |
| if (_used == _list.length) { |
| _resize(); |
| } |
| timer._indexOrNext = _used++; |
| _list[timer._indexOrNext] = timer; |
| _bubbleUp(timer); |
| } |
| |
| _Timer removeFirst() { |
| var f = first; |
| remove(f); |
| return f; |
| } |
| |
| void remove(_Timer timer) { |
| _used--; |
| if (isEmpty) { |
| _list[0] = null; |
| timer._indexOrNext = null; |
| return; |
| } |
| var last = _list[_used]; |
| if (!identical(last, timer)) { |
| last._indexOrNext = timer._indexOrNext; |
| _list[last._indexOrNext] = last; |
| if (last._compareTo(timer) < 0) { |
| _bubbleUp(last); |
| } else { |
| _bubbleDown(last); |
| } |
| } |
| _list[_used] = null; |
| timer._indexOrNext = null; |
| } |
| |
| void _resize() { |
| var newList = new List(_list.length * 2 + 1); |
| newList.setRange(0, _used, _list); |
| _list = newList; |
| } |
| |
| void _bubbleUp(_Timer timer) { |
| while (!isFirst(timer)) { |
| Timer parent = _parent(timer); |
| if (timer._compareTo(parent) < 0) { |
| _swap(timer, parent); |
| } else { |
| break; |
| } |
| } |
| } |
| |
| void _bubbleDown(_Timer timer) { |
| while (true) { |
| int leftIndex = _leftChildIndex(timer._indexOrNext); |
| int rightIndex = _rightChildIndex(timer._indexOrNext); |
| _Timer newest = timer; |
| if (leftIndex < _used && _list[leftIndex]._compareTo(newest) < 0) { |
| newest = _list[leftIndex]; |
| } |
| if (rightIndex < _used && _list[rightIndex]._compareTo(newest) < 0) { |
| newest = _list[rightIndex]; |
| } |
| if (identical(newest, timer)) { |
| // We are where we should be, break. |
| break; |
| } |
| _swap(newest, timer); |
| } |
| } |
| |
| void _swap(_Timer first, _Timer second) { |
| int tmp = first._indexOrNext; |
| first._indexOrNext = second._indexOrNext; |
| second._indexOrNext = tmp; |
| _list[first._indexOrNext] = first; |
| _list[second._indexOrNext] = second; |
| } |
| |
| Timer _parent(_Timer timer) => _list[_parentIndex(timer._indexOrNext)]; |
| Timer _leftChild(_Timer timer) => _list[_leftChildIndex(timer._indexOrNext)]; |
| Timer _rightChild(_Timer timer) => |
| _list[_rightChildIndex(timer._indexOrNext)]; |
| |
| static int _parentIndex(int index) => (index - 1) ~/ 2; |
| static int _leftChildIndex(int index) => 2 * index + 1; |
| static int _rightChildIndex(int index) => 2 * index + 2; |
| } |
| class _Timer implements Timer { |
| // Cancels the timer in the event handler. |
| static const _NO_TIMER = -1; |
| |
| // We distinguish what kind of message arrived based on the value being sent. |
| static const _ZERO_EVENT = 1; |
| static const _TIMEOUT_EVENT = null; |
| |
| // Timers are ordered by wakeup time. Timers with a timeout value of > 0 do |
| // end up on the TimerHeap. Timers with a timeout of 0 are queued in a list. |
| static _TimerHeap _heap = new _TimerHeap(); |
| static _Timer _firstZeroTimer; |
| static _Timer _lastZeroTimer; |
| |
| // We use an id to be able to sort timers with the same expiration time. |
| // ids are recycled after ID_MASK enqueues or when the timer queue is empty. |
| static const _ID_MASK = 0x1fffffff; |
| static int _idCount = 0; |
| |
| static RawReceivePort _receivePort; |
| static SendPort _sendPort; |
| static int _scheduledWakeupTime; |
| |
| static bool _handlingCallbacks = false; |
| |
| Function _callback; // Closure to call when timer fires. null if canceled. |
| int _wakeupTime; // Expiration time. |
| final int _milliSeconds; // Duration specified at creation. |
| final bool _repeating; // Indicates periodic timers. |
| var _indexOrNext; // Index if part of the TimerHeap, link otherwise. |
| int _id; // Incrementing id to enable sorting of timers with same expiry. |
| |
| // Get the next available id. We accept collisions and reordering when the |
| // _idCount overflows and the timers expire at the same millisecond. |
| static int _nextId() { |
| var result = _idCount; |
| _idCount = (_idCount + 1) & _ID_MASK; |
| return result; |
| } |
| |
| _Timer._internal( |
| this._callback, this._wakeupTime, this._milliSeconds, this._repeating) |
| : _id = _nextId(); |
| |
| static Timer _createTimer( |
| void callback(Timer timer), int milliSeconds, bool repeating) { |
| // Negative timeouts are treated as if 0 timeout. |
| if (milliSeconds < 0) { |
| milliSeconds = 0; |
| } |
| // Add one because DateTime.now() is assumed to round down |
| // to nearest millisecond, not up, so that time + duration is before |
| // duration milliseconds from now. Using microsecond timers like |
| // Stopwatch allows detecting that the timer fires early. |
| int now = VMLibraryHooks.timerMillisecondClock(); |
| int wakeupTime = (milliSeconds == 0) ? now : (now + 1 + milliSeconds); |
| |
| _Timer timer = |
| new _Timer._internal(callback, wakeupTime, milliSeconds, repeating); |
| // Enqueue this newly created timer in the appropriate structure and |
| // notify if necessary. |
| timer._enqueue(); |
| return timer; |
| } |
| |
| factory _Timer(int milliSeconds, void callback(Timer timer)) { |
| return _createTimer(callback, milliSeconds, false); |
| } |
| |
| factory _Timer.periodic(int milliSeconds, void callback(Timer timer)) { |
| return _createTimer(callback, milliSeconds, true); |
| } |
| |
| bool get _isInHeap => _indexOrNext is int; |
| |
| int _compareTo(_Timer other) { |
| int c = _wakeupTime - other._wakeupTime; |
| if (c != 0) return c; |
| return _id - other._id; |
| } |
| |
| bool get isActive => _callback != null; |
| |
| // Cancels a set timer. The timer is removed from the timer heap if it is a |
| // non-zero timer. Zero timers are kept in the list as they need to consume |
| // the corresponding pending message. |
| void cancel() { |
| _callback = null; |
| // Only heap timers are really removed. Zero timers need to consume their |
| // corresponding wakeup message so they are left in the queue. |
| if (!_isInHeap) return; |
| bool update = _heap.isFirst(this); |
| _heap.remove(this); |
| if (update) { |
| _notifyEventHandler(); |
| } |
| } |
| |
| void _advanceWakeupTime() { |
| // Recalculate the next wakeup time. For repeating timers with a 0 timeout |
| // the next wakeup time is now. |
| _id = _nextId(); |
| if (_milliSeconds > 0) { |
| _wakeupTime += _milliSeconds; |
| } else { |
| _wakeupTime = VMLibraryHooks.timerMillisecondClock(); |
| } |
| } |
| |
| // Adds a timer to the heap or timer list. Timers with the same wakeup time |
| // are enqueued in order and notified in FIFO order. |
| void _enqueue() { |
| if (_milliSeconds == 0) { |
| if (_firstZeroTimer == null) { |
| _lastZeroTimer = this; |
| _firstZeroTimer = this; |
| } else { |
| _lastZeroTimer._indexOrNext = this; |
| _lastZeroTimer = this; |
| } |
| // Every zero timer gets its own event. |
| _notifyZeroHandler(); |
| } else { |
| _heap.add(this); |
| if (_heap.isFirst(this)) { |
| _notifyEventHandler(); |
| } |
| } |
| } |
| |
| // Enqeue one message for each zero timer. To be able to distinguish from |
| // EventHandler messages we send a _ZERO_EVENT instead of a _TIMEOUT_EVENT. |
| static void _notifyZeroHandler() { |
| if (_sendPort == null) { |
| _createTimerHandler(); |
| } |
| _sendPort.send(_ZERO_EVENT); |
| } |
| |
| // Handle the notification of a zero timer. Make sure to also execute non-zero |
| // timers with a lower expiration time. |
| static List _queueFromZeroEvent() { |
| var pendingTimers = new List(); |
| assert(_firstZeroTimer != null); |
| // Collect pending timers from the timer heap that have an expiration prior |
| // to the currently notified zero timer. |
| var timer; |
| while (!_heap.isEmpty && (_heap.first._compareTo(_firstZeroTimer) < 0)) { |
| timer = _heap.removeFirst(); |
| pendingTimers.add(timer); |
| } |
| // Append the first zero timer to the pending timers. |
| timer = _firstZeroTimer; |
| _firstZeroTimer = timer._indexOrNext; |
| timer._indexOrNext = null; |
| pendingTimers.add(timer); |
| return pendingTimers; |
| } |
| |
| static void _notifyEventHandler() { |
| if (_handlingCallbacks) { |
| // While we are already handling callbacks we will not notify the event |
| // handler. _handleTimeout will call _notifyEventHandler once all pending |
| // timers are processed. |
| return; |
| } |
| |
| // If there are no pending timers. Close down the receive port. |
| if ((_firstZeroTimer == null) && _heap.isEmpty) { |
| // No pending timers: Close the receive port and let the event handler |
| // know. |
| if (_sendPort != null) { |
| _cancelWakeup(); |
| _shutdownTimerHandler(); |
| } |
| return; |
| } else if (_heap.isEmpty) { |
| // Only zero timers are left. Cancel any scheduled wakeups. |
| _cancelWakeup(); |
| return; |
| } |
| |
| // Only send a message if the requested wakeup time differs from the |
| // already scheduled wakeup time. |
| var wakeupTime = _heap.first._wakeupTime; |
| if ((_scheduledWakeupTime == null) || |
| (wakeupTime != _scheduledWakeupTime)) { |
| _scheduleWakeup(wakeupTime); |
| } |
| } |
| |
| static List _queueFromTimeoutEvent() { |
| var pendingTimers = new List(); |
| if (_firstZeroTimer != null) { |
| // Collect pending timers from the timer heap that have an expiration |
| // prior to the next zero timer. |
| // By definition the first zero timer has been scheduled before the |
| // current time, meaning all timers which are "less than" the first zero |
| // timer are expired. The first zero timer will be dispatched when its |
| // corresponding message is delivered. |
| var timer; |
| while (!_heap.isEmpty && (_heap.first._compareTo(_firstZeroTimer) < 0)) { |
| timer = _heap.removeFirst(); |
| pendingTimers.add(timer); |
| } |
| } else { |
| // Collect pending timers from the timer heap which have expired at this |
| // time. |
| var currentTime = VMLibraryHooks.timerMillisecondClock(); |
| var timer; |
| while (!_heap.isEmpty && (_heap.first._wakeupTime <= currentTime)) { |
| timer = _heap.removeFirst(); |
| pendingTimers.add(timer); |
| } |
| } |
| return pendingTimers; |
| } |
| |
| static void _runTimers(List pendingTimers) { |
| // If there are no pending timers currently reset the id space before we |
| // have a chance to enqueue new timers. |
| if (_heap.isEmpty && (_firstZeroTimer == null)) { |
| _idCount = 0; |
| } |
| |
| // Fast exit if no pending timers. |
| if (pendingTimers.length == 0) { |
| return; |
| } |
| |
| // Trigger all of the pending timers. New timers added as part of the |
| // callbacks will be enqueued now and notified in the next spin at the |
| // earliest. |
| _handlingCallbacks = true; |
| try { |
| for (var i = 0; i < pendingTimers.length; i++) { |
| // Next pending timer. |
| var timer = pendingTimers[i]; |
| timer._indexOrNext = null; |
| |
| // One of the timers in the pending_timers list can cancel |
| // one of the later timers which will set the callback to |
| // null. Or the pending zero timer has been canceled earlier. |
| if (timer._callback != null) { |
| var callback = timer._callback; |
| if (!timer._repeating) { |
| // Mark timer as inactive. |
| timer._callback = null; |
| } |
| callback(timer); |
| // Re-insert repeating timer if not canceled. |
| if (timer._repeating && (timer._callback != null)) { |
| timer._advanceWakeupTime(); |
| timer._enqueue(); |
| } |
| // Execute pending micro tasks. |
| var immediateCallback = _removePendingImmediateCallback(); |
| if (immediateCallback != null) { |
| immediateCallback(); |
| } |
| } |
| } |
| } finally { |
| _handlingCallbacks = false; |
| } |
| } |
| |
| static void _handleMessage(msg) { |
| var pendingTimers; |
| if (msg == _ZERO_EVENT) { |
| pendingTimers = _queueFromZeroEvent(); |
| assert(pendingTimers.length > 0); |
| } else { |
| assert(msg == _TIMEOUT_EVENT); |
| _scheduledWakeupTime = null; // Consumed the last scheduled wakeup now. |
| pendingTimers = _queueFromTimeoutEvent(); |
| } |
| _runTimers(pendingTimers); |
| // Notify the event handler or shutdown the port if no more pending |
| // timers are present. |
| _notifyEventHandler(); |
| } |
| |
| // Tell the event handler to wake this isolate at a specific time. |
| static void _scheduleWakeup(int wakeupTime) { |
| if (_sendPort == null) { |
| _createTimerHandler(); |
| } |
| VMLibraryHooks.eventHandlerSendData(null, _sendPort, wakeupTime); |
| _scheduledWakeupTime = wakeupTime; |
| } |
| |
| // Cancel pending wakeups in the event handler. |
| static void _cancelWakeup() { |
| assert(_sendPort != null); |
| VMLibraryHooks.eventHandlerSendData(null, _sendPort, _NO_TIMER); |
| _scheduledWakeupTime = null; |
| } |
| |
| // Create a receive port and register a message handler for the timer |
| // events. |
| static void _createTimerHandler() { |
| assert(_receivePort == null); |
| assert(_sendPort == null); |
| _receivePort = new RawReceivePort(_handleMessage); |
| _sendPort = _receivePort.sendPort; |
| _scheduledWakeupTime = null; |
| } |
| |
| static void _shutdownTimerHandler() { |
| _receivePort.close(); |
| _receivePort = null; |
| _sendPort = null; |
| _scheduledWakeupTime = null; |
| } |
| |
| // The Timer factory registered with the dart:async library by the embedder. |
| static Timer _factory( |
| int milliSeconds, void callback(Timer timer), bool repeating) { |
| if (repeating) { |
| return new _Timer.periodic(milliSeconds, callback); |
| } |
| return new _Timer(milliSeconds, callback); |
| } |
| } |
| _setupHooks() { |
| VMLibraryHooks.timerFactory = _Timer._factory; |
| } |