Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bridging Dart Exceptions with Isolates: Addressing the Incompatibility #59608

Open
stephane-archer opened this issue Nov 26, 2024 · 5 comments
Open
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. library-isolate type-enhancement A request for a change that isn't a bug

Comments

@stephane-archer
Copy link

When exploring the Dart standard library, it becomes evident that exceptions are the primary mechanism for handling errors. This approach works seamlessly in most scenarios—until you introduce isolates into the equation. Isolates rely on values to handle errors, creating a dissonance between the two paradigms.

This mismatch forces developers to write wrapper functions to convert exceptions into value-based errors to make them compatible with isolates. This process can be tedious and introduces unnecessary complexity, especially when working with existing Dart codebases.

A potential improvement would be enabling isolates to propagate exceptions back to the sender. This enhancement could significantly reduce the friction caused by this inconsistency, allowing for a more harmonious interaction between isolates and Dart's standard error-handling mechanisms.

Currently, this disconnect makes it cumbersome to leverage isolates effectively within Dart, particularly in projects that depend on exception-driven error handling.

@dart-github-bot
Copy link
Collaborator

Summary: Dart isolates don't directly support exception propagation, requiring manual conversion to values. This adds complexity when bridging isolates and exception-based error handling.

@dart-github-bot dart-github-bot added area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. triage-automation See https://github.com/dart-lang/ecosystem/tree/main/pkgs/sdk_triage_bot. type-enhancement A request for a change that isn't a bug labels Nov 26, 2024
@lrhn
Copy link
Member

lrhn commented Nov 26, 2024

Isolates rely on values to handle errors, creating a dissonance between the two paradigms.

Isolates rely on sending objects (errors are objects too) to communicate anything between different isolates.
Exceptions are control flow based. you throw them and they unwind the control flow stack until they are handled, or until reaching the top level where they too are treated as objects. (Asynchronous errors are also just objects until they are re-raised using an await.)
Different isolates do not share any control flow, so they can't communicate using thrown objects. So, yes, isolates communicate objects, by necessity.

What do you suggest to do?

The current Isolate.run does communicate errors back as asynchronous errors in the returned future.
To do that, it's set up to terminate the isolate on the first uncaught error, because it can only report one error.

It's definitely possible to run a computation that returns a Stream in a different isolate and communicate back the events and errors. (The biggest question is whether to use messages or Isolate.pause/Isolate.kill for push-back, the rest is basic message shuffling.)

Which operations, or primitives, are you missing?

@lrhn lrhn added library-isolate and removed triage-automation See https://github.com/dart-lang/ecosystem/tree/main/pkgs/sdk_triage_bot. labels Nov 26, 2024
@stephane-archer
Copy link
Author

@lrhn I have limited knowledge of Isolate so I might be doing something wrong.

I have a function Fibonacci that can throw errors.
I have a list of numbers that are all messages for Fibonacci
I want all of these computations to run in parallel so on different Isolte

List<Future> futures;
for (var message in messages) {
   var future = compute(Fibonacci, message);
   futures.add(future);
}
try {
results = Future.wait(futures)
} catch (e) {
    print("I lost everything if one fail!") 
}

from my understanding if one of the Fibonacci calls throw I lose everything
I don't know any good way to await multiple futures and have a list of results and a list of failures on the other side.

My really ugly solution is wrapping Fibonacci into:

(int result, String error) FibonachiWithoutException(int message) {
try {
     return Fibonachi(message), null
} catch(e) {
   return 0, e.toString();
}
}

so I have a list of results and I can check if it's an error or a value (really golang style)

removing the need for FibonachiWithoutException is what I want

@lrhn
Copy link
Member

lrhn commented Nov 27, 2024

This doesn't seem to be about isolates, just how to combine multiple synchronous results.

You have a number of futures, where some may contain errors, and you want to combine those into a single result, somehow, that represents all of the results.

If you change await Future.wait(futures) to await futures.wait then you're a big step closer. The .wait extension operation on a List<Future<T>> will return a Future<List<T>> with all the results if nothing throws, or will otherwise throw a ParallelWaitError<List<T?>, List<AsyncError?>> which will contain the results split into errors and values (with a null at a position if the result of that future is in the other list).

Then you have all the results, and can decide how you want to continue with those.

A more direct and Dart-like version of the Go-style would be to use the Result class from package:async, and the Result.capture function.
Instead of collecting the computed futures in a list, add Result.capture(future) to the list instead. Then do await results.wait/await Further.wait(results), shouldn't matter which since those Future<Result>s will never throw, and you can then go through the list and ask each Result whether it's a value or an error.

@stephane-archer
Copy link
Author

@lrhn thanks for your great answer!

This doesn't seem to be about isolates, just how to combine multiple synchronous results.

await compute(Fibonacci, message); would result in no parallelism regarding computing values.
And from my understanding, because of exceptions, you should always await at the same time, all the future you have access to.

Because of this, I came up with this solution, but it's more about, "How to compute multiple values in parallel using Dart Isolate?"

I don't care about having all the results all at once, I would much prefer to get their results as soon as they are finished (with some clue of which message has been sent to them preferably) so I can show a progress indication for example or move to the next steps for those fast to compute values.

Do you see any more relevant way to compute multiple values in parallel?
I imagine some streams might be useful.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-vm Use area-vm for VM related issues, including code coverage, and the AOT and JIT backends. library-isolate type-enhancement A request for a change that isn't a bug
Projects
None yet
Development

No branches or pull requests

3 participants