Skip to content

Support concurrent async writes to mutable data in Rust #1917

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

Closed
aran opened this issue May 3, 2024 · 3 comments · Fixed by #1920
Closed

Support concurrent async writes to mutable data in Rust #1917

aran opened this issue May 3, 2024 · 3 comments · Fixed by #1920
Labels
bug Something isn't working

Comments

@aran
Copy link
Contributor

aran commented May 3, 2024

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.

@aran aran added the bug Something isn't working label May 3, 2024
@fzyzcjy
Copy link
Owner

fzyzcjy commented May 4, 2024

Firstly, I guess it is related to #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?

@fzyzcjy fzyzcjy added the awaiting Waiting for responses, PR, further discussions, upstream release, etc label May 4, 2024
@fzyzcjy fzyzcjy changed the title Support async writes to mutable data in Rust Support concurrent async writes to mutable data in Rust May 4, 2024
@aran
Copy link
Contributor Author

aran commented May 4, 2024

Linking #1910 (comment) 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:

  • Unwrapped data—no RwLock/Mutex wrappers
  • Use &mut and & freely
  • Use #[frb(sync)] and async functions freely
  • All code must be run on main thread
  • FRB must be changed to no longer use RwLock for opaque data, and to allow main-thread non-sync functions.

A generated lock strategy:

  • Synchronized data—Uses generated RwLock/Mutex
  • Use &mut and & freely.
  • Never use frb(sync)
  • All code must be run in the web worker thread pool
  • FRB uses try_lock and there is counterintuitive behavior relative to rust since &mut and & no longer have safety enforced by the compiler
  • Or FRB changed to use lock() not try_lock(), in which case there's different counterintuitive behavior since the semantics of &mut and & are changed into runtime lock operations. Deadlocks become possible that would be impossible in Rust only code.

A hand-coded synchronization strategy:

  • Synchronized data—developer uses RwLock/Mutex themselves
  • No &mut or &, only owned data in function calls
  • Never use frb(sync)
  • All code must be run in web worker thread pool
  • Developer chooses their own synchronization operations and takes responsibility for synchronization correctness

Copy link
Contributor

This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new issue.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators May 20, 2024
@fzyzcjy fzyzcjy removed the awaiting Waiting for responses, PR, further discussions, upstream release, etc label Jun 21, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
bug Something isn't working
Projects
None yet
2 participants