fzyzcjy / flutter_rust_bridge

Flutter/Dart <-> Rust binding generator, feature-rich, but seamless and simple.
https://fzyzcjy.github.io/flutter_rust_bridge/
MIT License
3.61k stars 254 forks source link

Support concurrent async writes to mutable data in Rust #1917

Closed aran closed 1 week ago

aran commented 2 weeks ago

Describe the bug

It's hard to write correct code with &mut that mutates data that is stored in the Rust layer.

Steps to reproduce

This repro causes #1910 but it also creates PanicExceptions: https://github.com/aran/frb-exc-repro/blob/mut/lib/main.dart

Logs

N/A

Expected behavior

No response

Generated binding code

No response

OS

No response

Version of flutter_rust_bridge_codegen

No response

Flutter info

No response

Version of clang++

No response

Additional context

One hypothesis is that one of the levels of async might be acquiring the FRB-generated RwLock then yielding while still holding it. This allows runtime errors. Another theory is that in the Rust thread pool, multiple Dart-level Futures could be running simultaneously, and if there are both readers and writers of some variable that was &mut and &, they can collide.

The programming model of &mut and & is often going to be a leaky abstraction w.r.t. the RwLock model.

fzyzcjy commented 2 weeks ago

Firstly, I guess it is related to https://github.com/fzyzcjy/flutter_rust_bridge/issues/1883.

One hypothesis is that one of the levels of async might be acquiring the FRB-generated RwLock then yielding while still holding it.

I think so.

Another theory is that in the Rust thread pool, multiple Dart-level Futures could be running simultaneously, and if there are both readers and writers of some variable that was &mut and &, they can collide.

Yes that's also a possibility.

I need to think a bit more about this. The single-thread proposal in #1883 may not work in this specific issue, because we are using async here. If non-async, single-thread is enough to ensure nobody can acquire a lock in-use. But in async, there can easily be some function holding the lock and yielding (e.g. a network request for 1 second).

Do you have any brainstorms about solving this?

aran commented 2 weeks ago

Linking https://github.com/fzyzcjy/flutter_rust_bridge/issues/1910#issuecomment-2093883889 since it's closely related.

With the caveat that I'm about 48hrs into learning about this, at a high level my brainstorm would be that a developer needs to choose a concurrency strategy for each part of their data and stick to it holistically across a whole application for that data. Several different strategies could work but they have different constraints and tradeoffs. FRB could in theory support more than one strategy, but each project would have to stick to a single strategy for each piece of data. Here's a few strategies that should work:

An unwrapped data strategy: