diff --git a/CHANGELOG.md b/CHANGELOG.md index cb298a32059..ce5d29af581 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -78,10 +78,6 @@ Add a new `allowLegacyUnsafeRenegotiation` poperty to `SecurityContext`, which allows TLS renegotiation for client secure sockets. -#### `dart:isolate` - -- Add `Isolate.run` to run a function in a new isolate. - ### Tools #### Dart command line diff --git a/sdk/lib/isolate/isolate.dart b/sdk/lib/isolate/isolate.dart index d1ff9fc9921..8098a0f463f 100644 --- a/sdk/lib/isolate/isolate.dart +++ b/sdk/lib/isolate/isolate.dart @@ -144,113 +144,6 @@ class Isolate { /// inspect the isolate and see uncaught errors or when it terminates. Isolate(this.controlPort, {this.pauseCapability, this.terminateCapability}); - /// Runs [computation] in a new isolate and returns the result. - /// - /// ```dart - /// int slowFib(int n) => - /// n <= 1 ? 1 : slowFib(n - 1) + slowFib(n - 2); - /// - /// // Compute without blocking current isolate. - /// var fib40 = await Isolate.run(() => slowFib(40)); - /// ``` - /// - /// If [computation] is asynchronous (returns a `Future`) then - /// that future is awaited in the new isolate, completing the entire - /// asynchronous computation, before returning the result. - /// - /// ```dart - /// int slowFib(int n) => - /// n <= 1 ? 1 : slowFib(n - 1) + slowFib(n - 2); - /// Stream fibStream() async* { - /// for (var i = 0;; i++) yield slowFib(i); - /// } - /// - /// // Returns `Future`. - /// var fib40 = await Isolate.run(() => fibStream().elementAt(40)); - /// ``` - /// - /// If [computation] throws, the isolate is terminated and this - /// function throws the same error. - /// - /// ```dart import:convert - /// Future eventualError() async { - /// await Future.delayed(const Duration(seconds: 1)); - /// throw StateError("In a bad state!"); - /// } - /// - /// try { - /// await Isolate.run(eventualError); - /// } on StateError catch (e, s) { - /// print(e.message); // In a bad state! - /// print(LineSplitter.split("$s").first); // Contains "eventualError" - /// } - /// ``` - /// Any uncaught asynchronous errors will terminate the computation as well, - /// but will be reported as a [RemoteError] because [addErrorListener] - /// does not provide the original error object. - /// - /// The result is sent using [exit], which means it's sent to this - /// isolate without copying. - /// - /// The [computation] function and its result (or error) must be - /// sendable between isolates. - /// - /// The [debugName] is only used to name the new isolate for debugging. - @Since("2.17") - static Future run(FutureOr computation(), {String? debugName}) { - var result = Completer(); - var resultPort = RawReceivePort(); - resultPort.handler = (response) { - resultPort.close(); - if (response == null) { - // onExit handler message, isolate terminated without sending result. - result.completeError( - RemoteError("Computation ended without result", ""), - StackTrace.empty); - return; - } - var list = response as List; - if (list.length == 2) { - var remoteError = list[0]; - var remoteStack = list[1]; - if (remoteStack is StackTrace) { - // Typed error. - result.completeError(remoteError!, remoteStack); - } else { - // onError handler message, uncaught async error. - // Both values are strings, so calling `toString` is efficient. - var error = - RemoteError(remoteError.toString(), remoteStack.toString()); - result.completeError(error, error.stackTrace); - } - } else { - assert(list.length == 1); - result.complete(list[0] as R); - } - }; - try { - Isolate.spawn(_RemoteRunner._remoteExecute, - _RemoteRunner(computation, resultPort.sendPort), - onError: resultPort.sendPort, - onExit: resultPort.sendPort, - errorsAreFatal: true, - debugName: debugName) - .then((_) {}, onError: (error, stack) { - // Sending the computation failed asynchronously. - // Do not expect a response, report the error asynchronously. - resultPort.close(); - result.completeError(error, stack); - }); - } on Object { - // Sending the computation failed synchronously. - // This is not expected to happen, but if it does, - // the synchronous error is respected and rethrown synchronously. - resultPort.close(); - rethrow; - } - return result.future; - } - /// An [Isolate] object representing the current isolate. /// /// The current isolate for code using [current] @@ -905,62 +798,3 @@ abstract class TransferableTypedData { /// transferable bytes, even if the calls occur in different isolates. ByteBuffer materialize(); } - -/// Parameter object used by [Isolate.run]. -/// -/// The [_remoteExecute] function is run in a new isolate with a -/// [_RemoteRunner] object as argument. -class _RemoteRunner { - /// User computation to run. - final FutureOr Function() computation; - - /// Port to send isolate computation result on. - /// - /// Only one object is ever sent on this port. - /// If the value is `null`, it is sent by the isolate's "on-exit" handler - /// when the isolate terminates without otherwise sending value. - /// If the value is a list with one element, - /// then it is the result value of the computation. - /// Otherwise it is a list with two elements representing an error. - /// If the error is sent by the isolate's "on-error" uncaught error handler, - /// then the list contains two strings. This also terminates the isolate. - /// If sent manually by this class, after capturing the error, - /// the list contains one non-`null` [Object] and one [StackTrace]. - final SendPort resultPort; - - _RemoteRunner(this.computation, this.resultPort); - - /// Run in a new isolate to get the result of [computation]. - /// - /// The result is sent back on [resultPort] as a single-element list. - /// A two-element list sent on the same port is an error result. - /// When sent by this function, it's always an object and a [StackTrace]. - /// (The same port listens on uncaught errors from the isolate, which - /// sends two-element lists containing [String]s instead). - static void _remoteExecute(_RemoteRunner runner) { - runner._run(); - } - - void _run() async { - R result; - try { - var potentiallyAsyncResult = computation(); - if (potentiallyAsyncResult is Future) { - result = await potentiallyAsyncResult; - } else { - result = potentiallyAsyncResult; - } - } catch (e, s) { - // If sending fails, the error becomes an uncaught error. - Isolate.exit(resultPort, _list2(e, s)); - } - Isolate.exit(resultPort, _list1(result)); - } - - /// Helper function to create a one-element non-growable list. - static List _list1(Object? value) => List.filled(1, value); - - /// Helper function to create a two-element non-growable list. - static List _list2(Object? value1, Object? value2) => - List.filled(2, value1)..[1] = value2; -} diff --git a/tests/lib/isolate/isolate_run_test.dart b/tests/lib/isolate/isolate_run_test.dart deleted file mode 100644 index b598029bb35..00000000000 --- a/tests/lib/isolate/isolate_run_test.dart +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) 2022, 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. - -import 'dart:isolate'; -import 'dart:async'; -import 'package:async_helper/async_helper.dart'; -import 'package:expect/expect.dart'; - -void main() async { - asyncStart(); - // Sending result back. - await testValue(); - await testAsyncValue(); - // Sending error from computation back. - await testError(); - await testAsyncError(); - // Sending uncaught async error back. - await testUncaughtError(); - // Not sending anything back before isolate dies. - await testIsolateHangs(); - await testIsolateKilled(); - await testIsolateExits(); - // Failing to start. - await testInvalidMessage(); - asyncEnd(); -} - -final StackTrace stack = StackTrace.fromString("Known Stacktrace"); -final ArgumentError error = ArgumentError.value(42, "name"); - -var variable = 0; - -Future testValue() async { - var value = await Isolate.run(() { - variable = 1; // Changed in other isolate! - Expect.equals(1, variable); - return 42; - }); - Expect.equals(42, value); - Expect.equals(0, variable); -} - -Future testAsyncValue() async { - var value = await Isolate.run(() async { - variable = 1; - return 42; - }); - Expect.equals(42, value); - Expect.equals(0, variable); -} - -Future testError() async { - var e = await asyncExpectThrows(Isolate.run(() { - variable = 1; - Error.throwWithStackTrace(error, stack); - })); - Expect.equals(42, e.invalidValue); - Expect.equals("name", e.name); - Expect.equals(0, variable); -} - -Future testAsyncError() async { - var e = await asyncExpectThrows(Isolate.run(() async { - variable = 1; - Error.throwWithStackTrace(error, stack); - })); - Expect.equals(42, e.invalidValue); - Expect.equals("name", e.name); - Expect.equals(0, variable); -} - -Future testUncaughtError() async { - var e = await asyncExpectThrows(Isolate.run(() async { - variable = 1; - unawaited(Future.error(error, stack)); // Uncaught error - await Completer().future; // Never completes. - return -1; - })); - - Expect.type(e); - Expect.equals(error.toString(), e.toString()); - Expect.equals(0, variable); -} - -Future testIsolateHangs() async { - var e = await asyncExpectThrows(Isolate.run(() async { - variable = 1; - await Completer().future; // Never completes. - // Isolate should end while hanging here, because its event loop is empty. - })); - Expect.type(e); - Expect.equals("Computation ended without result", e.toString()); - Expect.equals(0, variable); -} - -Future testIsolateKilled() async { - var e = await asyncExpectThrows(Isolate.run(() async { - variable = 1; - Isolate.current.kill(); // Send kill request. - await Completer().future; // Never completes. - // Isolate should get killed while hanging here. - })); - Expect.type(e); - Expect.equals("Computation ended without result", e.toString()); - Expect.equals(0, variable); -} - -Future testIsolateExits() async { - var e = await asyncExpectThrows(Isolate.run(() async { - variable = 1; - Isolate.exit(); // Dies here without sending anything back. - })); - Expect.type(e); - Expect.equals("Computation ended without result", e.toString()); - Expect.equals(0, variable); -} - -Future testInvalidMessage() async { - // Regression test for http://dartbug.com/48516 - var unsendable = RawReceivePort(); - await asyncExpectThrows(Isolate.run(() => unsendable)); - unsendable.close(); - // Test should not hang. -} diff --git a/tests/lib_2/isolate/isolate_run_test.dart b/tests/lib_2/isolate/isolate_run_test.dart deleted file mode 100644 index cad8d04c85a..00000000000 --- a/tests/lib_2/isolate/isolate_run_test.dart +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) 2022, 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. - -// @dart = 2.9 - -import 'dart:isolate'; -import 'dart:async'; -import 'package:async_helper/async_helper.dart'; -import 'package:expect/expect.dart'; - -void main() async { - asyncStart(); - // Sending result back. - await testValue(); - await testAsyncValue(); - // Sending error from computation back. - await testError(); - await testAsyncError(); - // Sending uncaught async error back. - await testUncaughtError(); - // Not sending anything back before isolate dies. - await testIsolateHangs(); - await testIsolateKilled(); - await testIsolateExits(); - // Failing to start. - await testInvalidMessage(); - asyncEnd(); -} - -final StackTrace stack = StackTrace.fromString("Known Stacktrace"); -final ArgumentError error = ArgumentError.value(42, "name"); - -var variable = 0; - -Future testValue() async { - var value = await Isolate.run(() { - variable = 1; // Changed in other isolate! - Expect.equals(1, variable); - return 42; - }); - Expect.equals(42, value); - Expect.equals(0, variable); -} - -Future testAsyncValue() async { - var value = await Isolate.run(() async { - variable = 1; - return 42; - }); - Expect.equals(42, value); - Expect.equals(0, variable); -} - -Future testError() async { - var e = await asyncExpectThrows(Isolate.run(() { - variable = 1; - Error.throwWithStackTrace(error, stack); - })); - Expect.equals(42, e.invalidValue); - Expect.equals("name", e.name); - Expect.equals(0, variable); -} - -Future testAsyncError() async { - var e = await asyncExpectThrows(Isolate.run(() async { - variable = 1; - Error.throwWithStackTrace(error, stack); - })); - Expect.equals(42, e.invalidValue); - Expect.equals("name", e.name); - Expect.equals(0, variable); -} - -Future testUncaughtError() async { - var e = await asyncExpectThrows(Isolate.run(() async { - variable = 1; - unawaited(Future.error(error, stack)); // Uncaught error - await Completer().future; // Never completes. - return -1; - })); - - Expect.type(e); - Expect.equals(error.toString(), e.toString()); - Expect.equals(0, variable); -} - -Future testIsolateHangs() async { - var e = await asyncExpectThrows(Isolate.run(() async { - variable = 1; - await Completer().future; // Never completes. - // Isolate should end while hanging here, because its event loop is empty. - })); - Expect.type(e); - Expect.equals("Computation ended without result", e.toString()); - Expect.equals(0, variable); -} - -Future testIsolateKilled() async { - var e = await asyncExpectThrows(Isolate.run(() async { - variable = 1; - Isolate.current.kill(); // Send kill request. - await Completer().future; // Never completes. - // Isolate should get killed while hanging here. - })); - Expect.type(e); - Expect.equals("Computation ended without result", e.toString()); - Expect.equals(0, variable); -} - -Future testIsolateExits() async { - var e = await asyncExpectThrows(Isolate.run(() async { - variable = 1; - Isolate.exit(); // Dies here without sending anything back. - })); - Expect.type(e); - Expect.equals("Computation ended without result", e.toString()); - Expect.equals(0, variable); -} - -Future testInvalidMessage() async { - // Regression test for http://dartbug.com/48516 - var unsendable = RawReceivePort(); - await asyncExpectThrows(Isolate.run(() => unsendable)); - unsendable.close(); - // Test should not hang. -}