// 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. /// Note: the VM concatenates all patch files into a single patch file. This /// file is the first patch in "dart:isolate" which contains all the imports /// used by patches of that library. We plan to change this when we have a /// shared front end and simply use parts. import "dart:_internal" show VMLibraryHooks, patch; import "dart:async" show Completer, Future, Stream, StreamController, StreamSubscription, Timer; import "dart:collection" show HashMap; /// These are the additional parts of this patch library: // part "timer_impl.dart"; @patch class ReceivePort { @patch factory ReceivePort() => new _ReceivePortImpl(); @patch factory ReceivePort.fromRawReceivePort(RawReceivePort rawPort) { return new _ReceivePortImpl.fromRawReceivePort(rawPort); } } @patch class Capability { @patch factory Capability() => new _CapabilityImpl(); } @pragma("vm:entry-point") 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"; } @patch 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. */ @patch factory RawReceivePort([Function handler]) { _RawReceivePortImpl result = new _RawReceivePortImpl(); result.handler = handler; return result; } } 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)); _pendingImmediateCallback = callback; } @pragma("vm:entry-point", "call") 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. @pragma("vm:entry-point", "call") Function _getIsolateScheduleImmediateClosure() { return _isolateScheduleImmediate; } @pragma("vm:entry-point") 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 ****/ _get_id() native "RawReceivePortImpl_get_id"; _get_sendport() native "RawReceivePortImpl_get_sendport"; // Called from the VM to retrieve the handler for a message. @pragma("vm:entry-point", "call") static _lookupHandler(int id) { var result = _handlerMap[id]; return result; } // Called from the VM to dispatch to the handler. @pragma("vm:entry-point", "call") 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. _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 _initHandlerMap() { // TODO(18511): Workaround bad CheckSmi hoisting. var tempMap = new HashMap(); // Collect feedback that not all keys are Smis. tempMap["."] = 1; tempMap["."] = 2; return new HashMap(); } static final Map _handlerMap = _initHandlerMap(); } @pragma("vm:entry-point") class _SendPortImpl implements SendPort { /*--- public interface ---*/ @pragma("vm:entry-point", "call") 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(Null args); typedef _BinaryFunction(Null args, Null 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. */ @pragma("vm:entry-point", "call") void _startMainIsolate(Function entryPoint, List args) { _startIsolate( null, // no parent port entryPoint, args, null, // no message true, // isSpawnUri null, // no control port null); // no capabilities } /** * Returns the _startMainIsolate function. This closurization allows embedders * to setup trampolines to the main function. This workaround can be removed * once support for @pragma("vm:entry_point", "get") as documented in * https://github.com/dart-lang/sdk/issues/35720 lands. */ @pragma("vm:entry-point", "call") Function _getStartMainIsolateFunction() { return _startMainIsolate; } /** * Takes the real entry point as argument and invokes it with the initial * message. */ @pragma("vm:entry-point", "call") void _startIsolate( SendPort parentPort, Function entryPoint, List 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 as dynamic)(args, message); } else if (entryPoint is _UnaryFunction) { (entryPoint as dynamic)(args); } else { entryPoint(); } } else { entryPoint(message); } }; // Make sure the message handler is triggered. port.sendPort.send(null); } @patch class Isolate { static final _currentIsolate = _getCurrentIsolate(); static final _rootUri = _getCurrentRootUri(); @patch static Isolate get current => _currentIsolate; @patch String get debugName => _getDebugName(controlPort); @patch static Future get packageRoot { var hook = VMLibraryHooks.packageRootUriFuture; if (hook == null) { throw new UnsupportedError("Isolate.packageRoot"); } return hook(); } @patch static Future get packageConfig { var hook = VMLibraryHooks.packageConfigUriFuture; if (hook == null) { throw new UnsupportedError("Isolate.packageConfig"); } return hook(); } @patch static Future resolvePackageUri(Uri packageUri) { var hook = VMLibraryHooks.resolvePackageUriFuture; if (hook == null) { throw new UnsupportedError("Isolate.resolvePackageUri"); } return hook(packageUri); } static bool _packageSupported() => (VMLibraryHooks.packageRootUriFuture != null) && (VMLibraryHooks.packageConfigUriFuture != null) && (VMLibraryHooks.resolvePackageUriFuture != null); @patch static Future spawn(void entryPoint(T message), T message, {bool paused: false, bool errorsAreFatal, SendPort onExit, SendPort onError, String debugName}) 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 config settings from the parent isolate, // instead we use the values that were set on the command line. 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, null, packageConfig, debugName); return await _spawnCommon(readyPort); } catch (e, st) { if (readyPort != null) { readyPort.close(); } return await new Future.error(e, st); } } @patch static Future spawnUri(Uri uri, List args, var message, {bool paused: false, SendPort onExit, SendPort onError, bool errorsAreFatal, bool checked, Map environment, Uri packageRoot, Uri packageConfig, bool automaticPackageResolution: false, String debugName}) 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) { // `packages/` directory is no longer supported. Force it null. // TODO(mfairhurst) Should this throw an exception? packageRoot = null; } 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, debugName); return await _spawnCommon(readyPort); } catch (e) { if (readyPort != null) { readyPort.close(); } rethrow; } } static Future _spawnCommon(RawReceivePort readyPort) { Completer completer = new Completer.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; } // TODO(iposva): Cleanup to have only one definition. // These values need to be kept in sync with the class IsolateMessageHandler // in vm/isolate.cc. static const _PAUSE = 1; 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, String debugName) native "Isolate_spawnFunction"; static void _spawnUri( SendPort readyPort, String uri, List args, var message, bool paused, SendPort onExit, SendPort onError, bool errorsAreFatal, bool checked, List environment, String packageRoot, String packageConfig, String debugName) native "Isolate_spawnUri"; static void _sendOOB(port, msg) native "Isolate_sendOOB"; static String _getDebugName(SendPort controlPort) native "Isolate_getDebugName"; @patch 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); } @patch 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); } @patch 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); } @patch 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); } @patch 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); } @patch void kill({int priority: beforeNextEvent}) { var msg = new List(4) ..[0] = 0 // Make room for OOB message type. ..[1] = _KILL ..[2] = terminateCapability ..[3] = priority; _sendOOB(controlPort, msg); } @patch 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); } @patch 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); } @patch 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); } 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) { return null; } } static String _getCurrentRootUriStr() native "Isolate_getCurrentRootUriStr"; }