-
Notifications
You must be signed in to change notification settings - Fork 306
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
[Draft] flutter_rust_bridge
2.0: Allow Dart to *own* and manage Rust's opaque objects
#243
Comments
flutter_rust_bridge
2.0: Allow Dart to *own* and manage Rust's opaque objects
flutter_rust_bridge
2.0: Allow Dart to *own* and manage Rust's opaque objectsflutter_rust_bridge
2.0: Allow Dart to *own* and manage Rust's opaque objects
This is what I had in mind for #230, except this approach is more versatile and hopefully robust. Solving this one (and #67) would make this framework truly powerful! The Rust/Dart FFI in larger projects I have seen so far always involved having a server on the Rust side and a client on the Dart side. While that works, it makes for a lot of boilerplate code and general overhead. I can't pretend I understand the internals of |
On the subject of Dart-side GC: When dart-lang/sdk#47772 lands, it should be possible to call Rust when Dart decides to do GC. It looks to me like |
@raphaelrobert Thank you! |
I tried to do a PoC with I think this does all that's needed:
|
@raphaelrobert Great job - that PoC sounds wonderful!
Automatically indeed ;) Look at this:
We can create a Remark about
|
Yes, I saw that native finalizers were already available, but I wasn't sure if they could be used in this case. If they can, all the better! |
I guess yes, at least in Flutter 2.8. They are added only in 2.8 and is not there for 2.5 |
If shekohex/allo-isolate#18 gets merged, we should be one step closer. |
After testing the new In more detail:
To summarize, there are quite a few unknowns and things seem to be in motion on the Dart side. I'm wondering if it makes sense to investigate further at this point or whether it is better to wait for Dart finalizers to become available and look at it again then. |
@raphaelrobert Great insights! Interesting. I do not know it returns a The GC is quite strange. Have you tried to allocate millions of small objects that are not used (referenced) in Dart? By doing so maybe we can trigger a gc.
Good question. At first thought seems that dart finalizer is only porting the C finalizer to Dart; but since it involves lots of code, I guess Dart team is doing something more than that. So maybe Dart finalizer will be great, but I am also not sure... Indeed, even if we have a Dart finalizer, we still have to give Dart a native pointer. Otherwise, we cannot tell Dart how big the external memory is. |
I implemented it and checked the
Just allocating things on the Rust heap without passing them to Dart would not trigger it I think, because the Dart VM doesn't even know about them. It might be different if I did it on the Dart heap, but it's beside the point somehow. |
I mean allocate in Dart, not Rust. e.g. |
I tried that and it worked as expected in the sense that GC kicked in, but only freed stuff on the Dart heap. The finalizers never got called. I brought this up on the Dart SDK issue list btw: dart-lang/sdk#48023 |
@raphaelrobert Ok so I guess it is really a problem of Dart/Flutter's DartCObject's finalizers... |
@raphaelrobert By the way, what about providing some sample code (e.g. a sample github repo), such that me and dart people can have a look (e.g. a bug in the code) |
Sure: https://github.com/raphaelrobert/dart_rust_ffi_poc
|
Forward an interesting remark:
dart-lang/language#1847 (comment) by @dnfield |
More insights by @dnfield: dart-lang/language#1847 (comment)
|
I tried to experiment with external typed data and and GC'ing works as expected: it occurs when allocating new objects, but there is no guarantee it will clean up everything. With all this new information I think neither native pointers nor externally typed data should be the core of the solution. If we want to have opaque pointers (and I still think we should), we need to do that through more code generation. We probably need Dart classes that contain private raw pointers and an explicit destructor to free the memory on the native heap. When native finalizers have landed, we can still use those additionally to free up the native memory but we shouldn't rely on them for all the reasons mentioned above. |
I've made an informal RFC at #293, feel free to discuss it there as well. |
By the way, this repo https://github.com/dart-native/dart_native is implementing bridge for Dart vs ObjC and used fully automatic memory management. I have told him about the discourage from Dart team: dart-native/dart_native#88 |
(Sad) updates:
In short: We should NOT use type
|
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Any progress? |
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. |
Related: |
flutter_rust_bridge is really there! |
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. |
Related discussions: #68 (thanks @gcxfd)
Why
Powerful!
Problems to solve
What data structure to pass around?
Attempt 1: Maybe
Box<Arc<RwLock<T>>>
as a raw pointer?However, can we do that? When raw pointer is passed from dart to multiple rust functions concurrently, multiple rust functions will all rehydrate the same
Arc
object from the raw pointer, so have problems?Attempt 2: Maybe
Box<(AtomicUsize, RwLock<T>)>
as a raw pointer? the AtomicUsize is like a self-managed Arc.Attempt 3: Maybe
Box<RwLock<T>>
is enough? Since when it is used in a Rust function, the Rust function always immutably borrow theRwLock<T>
, and it is Dart who indeed owns it. --> See "How to ensure that the Dart opaque object is still alive before" below. This method is WRONG.How can we create a Dart object with a finalizer, and then pass that object from Rust to Dart?
use
as_native_pointer
of Dart_CObject https://github.com/dart-lang/sdk/blob/5f32f4d7b678dc08d6079664637ad061902c5b69/runtime/include/dart_native_api.h#L88Interestingly, it was added only 4months ago! dart-lang/sdk@bc38783#diff-587868a9cf3a0dd424f04d4d4d30efef5f00661441e094b1e993d20d3847199d
In other words, in Dart 2.14 (Flutter 2.5), https://github.com/dart-lang/sdk/blob/2.14.0/runtime/include/dart_native_api.h does NOT have it.
In Dart 2.15 (Flutter 2.8 - released a few days ago!) https://github.com/dart-lang/sdk/blob/2.15.0/runtime/include/dart_native_api.h It is there now.
How to update the real "external size" of the Rust object to Dart, when using the
Dart_CObject
instead ofDartHandle
?see: dart-lang/sdk#47901
[TODO]
How to know the real "external size" of the Rust object?
It is needed by Dart VM, otherwise vm may not know the memory pressure (I guess?).
For example, if the user's data structure is
struct MyObject {a: i32, b: String, c: Box<Something>}
, how can we know the actual heap size used? NoticeString
andBox
...Attempt 1: Maybe create a trait and enforce user to implement that trait. Say,
pub trait Sizeable { fn memory_size(&self) -> usize }
.However, how to know that size has changed in Rust, and tell Dart the new size whenever it has changed (maybe using Dart_UpdateExternalSize/Dart_UpdateFinalizableExternalSize)?
Attempt 2: Not only have the trait
Sizeable
, but also require the user to manually call flutter_rust_bridge when its memory size changes.Not that friendly...
Attempt 3: Can we automatically generate some code?
Attempt 4: Is there a library to do this?
https://crates.io/crates/deepsize
Related issue: Aeledfyr/deepsize#28
Problem: Can it accurately know things like ndarray? Aeledfyr/deepsize#29
Attempt 5: Can we just use
Attempt 2
and tolerate that the users may not update the size frequently enough?see dart-lang/sdk#47900
Will we face performance problems?
Look at how ObjC makes ref counting, sounds a bit similar.
May not be a problem, since we have to have interior mutability, and have to be thread safe.
How to ensure that the Dart opaque object is still alive when Rust is using that object? Especially, when in async
Serious problem!
So we really need
Arc
to have reference counting, instead of blindly let Dart to own the object. After doing so, we simply do not care when is Dart object deallocated.Do we have concurrecy problems?
Sounds no, since we have
RwLock
.What if, when Dart calls Rust, at the same time, Dart makes a GC and make that pointer invalid?
Code example:
When we are executing (a), what if Dart make a GC and the finalizer of
opaque_pointer
is called, then we are trying to doincrease_arc_count
on an invalid object? (Notice if the finalizer is called and the pointer has rc=1, then it is dropped immediately)Attempt 1: Maybe force the opaque_pointer to be used after the ffi call is done. For example:
[TODO] Seems to have seen some discussions about this before?
The text was updated successfully, but these errors were encountered: