Skip to content

Conversation

@mraleph
Copy link
Member

@mraleph mraleph commented Sep 12, 2025

This extracts relevant parts of the bigger proposal into a separate proposal.

The following things have changed:

  • We now include formal memory model: our formalization pretty much follows JavaScripts with some minor tweaks.
  • All atomic operations are consolidates in a single AtomicInt class - which can be constructed as a view on Pointers or TypedData objects. This is a departure from the previous "zoo of extension methods" design. We should discuss if it is actually better.
  • I introduced ScopedThreadLocal class which should address some known problematic usages of static state in core libraries.
  • There are few design points (marked with TODO in the text) which I don't have concrete answers to. We should discuss these.

@lrhn @mkustermann @aam can you take a look?

@aam could you give me the current state of core-library APIs (which already work from IG bound code, which you are fixing and which we will not support). I want to align on what we think is MVP here and include this into the proposal.

@mraleph
Copy link
Member Author

mraleph commented Sep 15, 2025

cc @leafpetersen

```dart
@pragma('vm:deeply-immutable')
final class ScopedThreadLocal<T> {
/// Creates scoped thread local value with the given [initializer] function.
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice, but outside of isolate-group-bound callbacks, in the context of standard isolates, does ScopedThreadLocal instance behave like a static field? Isolates in the end can migrate from one thread to another.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that isolates can't move between threads in synchronous calls and this API is fully synchronous.

Copy link

@aam aam Sep 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will there be something that would prevent this infrastructure from being used in asynchronous context?

 static Future<String> iterableToShortString(...) {
  return toStringVisiting.use((toStringVisitingValue) async {
    await Future.delayed(Duration(seconds: 10));
    ...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use it. But it is not going to do anything useful because the value of the scoped thread local will reset once the synchronous execution completes.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So toStringVisiting can become unbound after await Future.delayed(Duration(seconds: 10)) (in unlikely event) of isolate switching to a different thread? Realistically we are keeping isolate/(vm)thread affinity, so switching should not happen.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not related to moving between threads at all. ScopedThreadLocal are valid within the scope of synchronous execution (hence the name). So if you do stl.use(f) or stl.with(value, f) once synchronous execution of this expression completes stl (e.g. use or with completes) is unbound.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was curious about the case when f is asynchronous. Would that result in ScopedThreadLocal values bound only for initial synchronous part of f execution? In the example above - before await Future.delayed(Duration(seconds: 10));?

Document constructors memory model
@mraleph
Copy link
Member Author

mraleph commented Sep 17, 2025

cc @alexmarkov for memory model feedback

@mraleph
Copy link
Member Author

mraleph commented Oct 14, 2025

Pushed updates based on @aam and @lrhn comments.

PTAL

@mraleph
Copy link
Member Author

mraleph commented Oct 15, 2025

@leafpetersen @lrhn I want to discuss something, which kind of goes against our original agreement on how to scope this feature.

The current state of the proposal is the following: we have trivially shareable objects which are mostly deeply immutable Dart objects and we have shared static fields and captured variables which can only contain trivially shareable objects and are shared between concurrent threads of execution.

This setup obviously contains shared mutable state in a very round-about way: closures. We all know very well that closures and objects are isomorphic, in a sense that you can build an object system out of closures using captured variables to store object state.

As such I think we are standing on a fork in the road.

We can choose to close the loophole: require that only final variables can be annotated as shared. That means neither shared statics nor shared captured variables can be mutated. Proceed with the original plan.

We can choose to open the floodgate: forgo the trivial shareability restriction altogether and allow shared mutable Dart state.

Our current plan is very defensive: gradually remove isolation guarantee, see how people use that and then possibly remove the isolate entirely. Maybe intermediate step does not make much sense anyway - because it does not solve sufficiently enough user challenges (it is solely focused on native interop and requires users to move all of their data into the native heap if they want to use multicore capabilities). Maybe we should take the whole step instead of taking half step.

WDYT?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants