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

dart:ffi GC finalizers #35770

Closed
dcharkes opened this issue Jan 25, 2019 · 39 comments
Closed

dart:ffi GC finalizers #35770

dcharkes opened this issue Jan 25, 2019 · 39 comments
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-ffi

Comments

@dcharkes
Copy link
Contributor

dcharkes commented Jan 25, 2019

Update 2022:

Available in https://api.dart.dev/stable/2.18.3/dart-ffi/NativeFinalizer-class.html.

Update 2020-06-26:
Finalizers are now available in native code (not yet exposed in Dart):

d.lookupFunction<Void Function(Handle), void Function(Object)>("PassObjectToC");

void attachFinalizer(Object o) => passObjectToC(o);
static void RunFinalizer(void* isolate_callback_data,
                         void* peer) {
  printf("Running finalizer.\n");
}

extern "c" void PassObjectToC(Dart_Handle h) {
  void* peer = 0x0;
  intptr_t size = 8;
  auto finalizable_handle = Dart_NewFinalizableHandle_DL(handle_2, peer, size, RunFinalizer);
}

Use dynamic linking (the _DL suffix) so that the symbols are available in Flutter. See documentation and samples on 7eac9f3 (C code, Dart code).

See more documentation in native_api.h and dart_api_dl.h.

Caveat:

  • The dart optimiser can inline method bodies and fields, which means objects can in some cases be GCed (and their finalizer run) before all methods are done executing. When we expose finalizers in Dart, we will have a solution for this. Workaround: do an ffi call with a Dart_Handle passing that object to native, that will keep it alive until that call (like a reachability fence). Or alternatively use the Dart calling convention to keep the object alive:
    @pragma('vm:never-inline')
    Object reachabilityFence(Object obj) {
      return obj;
    }

==========================================================================================

This issue was originally intended to track exposing finalizers in Dart. (The above code exposes finalizers in C.)

A potential API would be.

/// Return a pointer object that has a finalizer attached to it. When this
/// pointer object is collected by GC the given finalizer is invoked.
///
/// Note: the pointer object passed to the finalizer is not the same as 
/// the pointer object that is returned from [finalizable] - it points
/// to the same memory region but has different identity. 
Pointer<T> finalizable<T>(Pointer<T> p, void finalizer(Pointer<T> ptr)) {

}

However, exposing FFI-specific finalizers in Dart has been superseded by adding finalizers to Dart in general https://github.com/dart-lang/sdk/issues/45455.

@dcharkes dcharkes added this to the Dart VM FFI 1.0 milestone Jan 25, 2019
@kevmoo kevmoo added the area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. label Jan 25, 2019
@dcharkes
Copy link
Contributor Author

dcharkes commented Feb 18, 2019

As discussed with @jonasfj and @mkustermann, maybe it would be better to provide a Pointer to a C function as finalizer, as the GC does currently not support running Dart code for finalization.

class Pointer {
  attachFinalizer(Pointer<NativeFunction<void Function(Pointer)>> finalizerPointer) {

  }
}

@jonasfj
Copy link
Member

jonasfj commented Feb 18, 2019

You might also want to include a sizeHint so we can signal an approximate size of the object to the GC.
Even if I don't know the exact size of the object I'm holding on to, I know if it's approximately 12 bytes, 12kb, or 12mb give or take a few orders of magnitude.

@mit-mit mit-mit removed this from the Dart VM FFI 1.0 milestone Mar 11, 2019
@sjindel-google
Copy link
Contributor

We should implement this in the initial release since so many users seem to need it.

@sjindel-google
Copy link
Contributor

I think we should estimate 3 weeks for this, given that we need:

  • Support for attaching finalizers to Pointer (based on existing finalizer support).
  • Maintaining a reference to the original from derived pointers.
  • Maintaining a reference to the original from external typed data.

@yulingtianxia
Copy link

We should implement this in the initial release since so many users seem to need it.

Yeah, we really need it.

@ds84182
Copy link
Contributor

ds84182 commented Nov 5, 2019

Looking at https://dart-review.googlesource.com/c/sdk/+/123662, I don't think this solves all issues, especially with long lived structures or strings in a static method.

For example, libpng's png_create_read_struct needs the version string of libpng from link-time to ensure that ABI-incompatible changes haven't occurred (https://github.com/glennrp/libpng/blob/libpng16/png.h#L922). Yes, you could also wrap the function with another C library to forward the version correctly, but then you have to make sure that Dart, the auxiliary native library, and libpng are all in sync and are ABI compatible.

I'm just hoping that that CL isn't the final design for pointer finalizers.

@derolf
Copy link

derolf commented Dec 11, 2019

When will this feature be available?

@dcharkes
Copy link
Contributor Author

Somewhere in the next quarters, I cannot give you a precise date.

dart-bot pushed a commit that referenced this issue Dec 12, 2019
Samples for managing native memory without finalizers.

Design: go/dart-ffi-resource-lifetime

Related issue: #35770

Change-Id: I2d0ac1acb65a78db9f57aea3dd5f25b4948ef6d6
Cq-Include-Trybots: luci.dart.try:vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64-try,app-kernel-linux-debug-x64-try,vm-kernel-linux-debug-ia32-try,vm-kernel-win-debug-x64-try,vm-kernel-win-debug-ia32-try,vm-kernel-precomp-linux-debug-x64-try,vm-dartkb-linux-release-x64-abi-try,vm-kernel-precomp-android-release-arm64-try,vm-kernel-asan-linux-release-x64-try,vm-kernel-linux-release-simarm-try,vm-kernel-linux-release-simarm64-try,vm-kernel-precomp-android-release-arm_x64-try,vm-kernel-precomp-obfuscate-linux-release-x64-try,vm-kernel-precomp-mac-release-simarm_x64-try,dart-sdk-linux-try,analyzer-analysis-server-linux-try,analyzer-linux-release-try,front-end-linux-release-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/123662
Commit-Queue: Daco Harkes <dacoharkes@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Reviewed-by: Erik Ernst <eernst@google.com>
dart-bot pushed a commit that referenced this issue Feb 18, 2020
Required for finalizers: #35770

Change-Id: Ic512a4efd81cbd38cd836a8e8ad80464d2a3481f
Cq-Include-Trybots: luci.dart.try:vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64-try,app-kernel-linux-debug-x64-try,vm-kernel-linux-debug-ia32-try,vm-kernel-win-debug-x64-try,vm-kernel-win-debug-ia32-try,vm-kernel-precomp-linux-debug-x64-try,vm-dartkb-linux-release-x64-abi-try,vm-kernel-precomp-android-release-arm64-try,vm-kernel-asan-linux-release-x64-try,vm-kernel-linux-release-simarm-try,vm-kernel-linux-release-simarm64-try,vm-kernel-precomp-android-release-arm_x64-try,vm-kernel-precomp-obfuscate-linux-release-x64-try,dart-sdk-linux-try,analyzer-analysis-server-linux-try,analyzer-linux-release-try,front-end-linux-release-x64-try,vm-kernel-precomp-win-release-x64-try,vm-kernel-mac-debug-x64-try,analyzer-nnbd-linux-release-try,dart2js-nnbd-linux-x64-chrome-try,ddc-nnbd-linux-release-chrome-try,front-end-nnbd-linux-release-x64-try,vm-kernel-nnbd-linux-debug-x64-try,vm-kernel-nnbd-linux-release-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/135906
Reviewed-by: Martin Kustermann <kustermann@google.com>
dart-bot pushed a commit that referenced this issue Mar 26, 2020
The reachability fence keeps a value alive and reachable.

Required for finalizers: #35770

Design: go/dart-ffi-finalizers (See "Premature Cleanup (Single Object)".)

Change-Id: I9742889f0f8d8b15bbcb5dca47f2a4231899dd59
Cq-Include-Trybots: luci.dart.try:vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64-try,app-kernel-linux-debug-x64-try,vm-kernel-linux-debug-ia32-try,vm-kernel-win-debug-x64-try,vm-kernel-win-debug-ia32-try,vm-kernel-precomp-linux-debug-x64-try,vm-dartkb-linux-release-x64-abi-try,vm-kernel-precomp-android-release-arm64-try,vm-kernel-asan-linux-release-x64-try,vm-kernel-linux-release-simarm-try,vm-kernel-linux-release-simarm64-try,vm-kernel-precomp-android-release-arm_x64-try,vm-kernel-precomp-obfuscate-linux-release-x64-try,dart-sdk-linux-try,analyzer-analysis-server-linux-try,analyzer-linux-release-try,front-end-linux-release-x64-try,vm-kernel-precomp-win-release-x64-try,vm-kernel-mac-debug-x64-try,vm-kernel-nnbd-linux-debug-x64-try
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/136188
Reviewed-by: Vyacheslav Egorov <vegorov@google.com>
Reviewed-by: Martin Kustermann <kustermann@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
@darrinps
Copy link

Been over a quarter since the last update. Any news on this front? I believe Mongo, who acquired Realm, would be able to move forward with Realm support for Flutter which many of us want once this issue is resolved.

@drriguz
Copy link

drriguz commented Nov 11, 2020

What's the progress of that? I think this feature might be what I needed. simolus3/drift#835 (comment)

@mraleph
Copy link
Member

mraleph commented Nov 13, 2020

@drriguz The status is given in the issue body itself. See the text after Update 2020-06-26.

@ZhangPingFan
Copy link

Expose finalizers in Dart.

/// Return a pointer object that has a finalizer attached to it. When this
/// pointer object is collected by GC the given finalizer is invoked.
///
/// Note: the pointer object passed to the finalizer is not the same as 
/// the pointer object that is returned from [finalizable] - it points
/// to the same memory region but has different identity. 
Pointer<T> finalizable<T>(Pointer<T> p, void finalizer(Pointer<T> ptr)) {

}

Update 2020-06-26:
Finalizers are now available in native code (not yet exposed in Dart) on Dart master:

d.lookupFunction<Handle Function(Handle),
                 Object Function(Object)>("PassObjectToC");
static void RunFinalizer(void* isolate_callback_data,
                         Dart_WeakPersistentHandle handle,
                         void* peer) {
  printf("Running finalizer for weak handle.\n");
}

extern "c" Dart_Handle PassObjectToC(Dart_Handle h) {
  void* peer = 0x0;
  intptr_t size = 8;
  Dart_NewWeakPersistentHandle_DL(
      handle_2, peer, size, RunFinalizer);
}

Use dynamic linking (the _DL suffix) so that the symbols are available in Flutter. See documentation and samples on 7eac9f3.

See more documentation in native_api.h and dart_api_dl.h.

Caveats:

  • Make weak handles created via Dart_NewWeakPersistentHandle not auto-delete. #42312 Trying to delete the finalizer or getting the object out of it while the object is unreachable will race the GC. See details in issue.
  • The dart optimiser can inline method bodies and fields, which means objects can in some cases be GCed (and their finalizer run) before all methods are done executing. When we expose finalizers in Dart, we will have a solution for this. Workaround: do an ffi call with a Dart_Handle passing that object to native, that will keep it alive.

Supported now ? Is there an example with more details ?

@jonasfj
Copy link
Member

jonasfj commented Jan 25, 2021

Supported now ? Is there an example with more details ?

I'm already using this in package:webcrypto, see webcrypto_dart_dl_attach_finalizer and _attachFinalizerEVP_PKEY, in this case I just use it to call free(void* ptr) for some C pointer when a Dart Object is GC'd.

@vaind
Copy link

vaind commented May 13, 2021

@dcharkes

Regarding the workaround for premature finalization: "Workaround: do an ffi call with a Dart_Handle passing that object to native, that will keep it alive."

Does that apply to any call (e.g. attaching a finalizer in object's constructor), or must such a call happen in any method using the native pointer, after the use of the native pointer - to keep it alive until that point?

@dcharkes
Copy link
Contributor Author

Regarding the workaround for premature finalization: "Workaround: do an ffi call with a Dart_Handle passing that object to native, that will keep it alive."

Does that apply to any call (e.g. attaching a finalizer in object's constructor), or must such a call happen in any method using the native pointer, after the use of the native pointer - to keep it alive until that point?

It works like a reachability fence, it keeps it alive util that point. So yes, at the end of any method using the native pointer.

Side note, we might end up adding finalizers to Dart in which we keep this alive automatically in every method of an object implementing Finalizable. https://github.com/dart-lang/sdk/issues/45455#issuecomment-812172232 So stay tuned for a better solution.

@vaind
Copy link

vaind commented May 17, 2021

Hmm. Is there, by any chance, any way to do that without defining my own native method and distributing it as a (or part of an existing) shared library? (It's a little awkward to do that because of the way I get the libs for all the supported platforms... takes time to get built and releases out...)

I've looked at dart_api/_dl.h for any "useful" function and Dart_IsError came to my attention so something like that should work without being awfully slow (got about 20 million calls/second so I could live with that for the time being). I'm still struggling with how to get the symbols for windows, though... Any suggestion? I hope this isn't too off-topic and could actually help others.

// TODO windows? flutter has `flutter_windows.dll` which should hopefully work
// but is there something for plain Dart? 
final DynamicLibrary _dartLib = DynamicLibrary.process();

/// Must be called after last native pointer call in a method to prevent `this`
/// from being "finalized" preliminarily (before the current method finishes).
final dart_keepalive =
    _dartLib.lookupFunction<Uint8 Function(Handle), int Function(Object)>(
        'Dart_IsError');

@mraleph
Copy link
Member

mraleph commented May 17, 2021

@vaind I think the best you can do right now is the following:

@pragma('vm:never-inline')
Object reachabilityFence(Object obj) {
  return obj;
}

and use this as a replacement for dart_keepalive. That should keep the obj alive (though it will break if we start removing parameters which are never used from functions in AOT compiler, but we don't have any immediate plans to work on something like that AFAIK).

@vaind
Copy link

vaind commented May 17, 2021

Many thanks @mraleph, you saved my day!

@skquo
Copy link

skquo commented May 22, 2021

Update 2020-06-26:
.....

Expose finalizers in Dart.

/// Return a pointer object that has a finalizer attached to it. When this
/// pointer object is collected by GC the given finalizer is invoked.
///
/// Note: the pointer object passed to the finalizer is not the same as 
/// the pointer object that is returned from [finalizable] - it points
/// to the same memory region but has different identity. 
Pointer<T> finalizable<T>(Pointer<T> p, void finalizer(Pointer<T> ptr)) {

}

What does it mean @dcharkes ? Where can we find finalizable?

@bayo-code
Copy link

There's lots of things about this Finalizer API that I don't understand. If you could help me clarify some of these, I'll be grateful...

  1. According to my understanding, a Finalizer is a function we can attach to a Dart handle (in C) that will be called when the object is garbage collected. Is this right?
  2. To create a Finalizer, I have to pass the Dart object itself to native code as a Handle (which is Dart_Handle in C), which will then register a function that Dart can call to "finalize" whatever memory the native code is allocated. However, the Dart_NewPersistentHandle function returns a Dart_PersistentHandle. What am I supposed to do with it, pass it back to the Dart class that called the function registration routine? If that's the case, does that mean that I would not need the initial object (or pointer) that points to native memory and will instead use whatever is returned by the Dart_NewPersistentHandle in Dart?

I'm sorry if these questions sound dumb, it's just that I'm not familiar with the whole Finalizer thing, and I'm trying to make sure that I don't leak memory when I'm working with native code cause I'm creating lots of them

@fei4xu
Copy link

fei4xu commented May 25, 2021

I don't like this design and I'm confused: do I need to change the .c code in order to call it from dart? what if the .c lib is a 3rd party library which I don't have source code ?

@dcharkes
Copy link
Contributor Author

Do I need to change the .c code in order to call it from dart? what if the .c lib is a 3rd party library which I don't have source code ?

You can write a C library that wraps the original library which attaches the finalizers. And yes, that is not ideal. Multiple packages are facing this issue.

That's why we're investigating adding finalizers in Dart-land. (This issue, #35770, is tracking adding FFI-specific Dart finalizers. However, https://github.com/dart-lang/sdk/issues/45455 proposes to add finalizers to Dart in general.)

Until one of those two issues has been resolved, your options are:

  • Finalizers in C.
  • Do memory management in another way. For example with (1) lexical scoping through arena from package:ffi, (2) manual memory management by adding free calls in your code, or (3) providing a release method in your API that users have to call when done. (Of course there are scenarios in which none of these three work, for example when your objects get passed to a framework that does not tell you when it's done using your object.)

@dcharkes
Copy link
Contributor Author

  1. According to my understanding, a Finalizer is a function we can attach to a Dart handle (in C) that will be called when the object is garbage collected. Is this right?

Yes.

  1. To create a Finalizer, I have to pass the Dart object itself to native code as a Handle (which is Dart_Handle in C), which will then register a function that Dart can call to "finalize" whatever memory the native code is allocated. However, the Dart_NewPersistentHandle function returns a Dart_PersistentHandle.

I presume you meant Dart_NewWeakPersistentHandle function returns a Dart_WeakPersistentHandle.

Or Dart_NewFinalizableHandle which returns a Dart_FinalizableHandle.

What am I supposed to do with it, pass it back to the Dart class that called the function registration routine?

Dart_FinalizableHandle and Dart_PersistentHandle are used to remove the finalizer if you would want to remove it.

If you don't ever want to cancel it, you can ignore the return value of Dart_NewFinalizableHandle, because it gets automatically deleted when the object is garbage collected.

You should not disregard a Dart_WeakPersistentHandle that would leak memory. So I suggest you use Dart_FinalizableHandles instead.

If that's the case, does that mean that I would not need the initial object (or pointer) that points to native memory and will instead use whatever is returned by the Dart_NewPersistentHandle in Dart?

I presume you meant Dart_NewWeakPersistentHandle or Dart_NewFinalizablePersistentHandle here.

You can pass the Dart_NewWeakPersistentHandle or Dart_NewFinalizablePersistentHandle back to the Dart class and store it there as a Pointer. If you'd ever want to cancel the finalizer, you would use that Handle the cancel the finalizer.

I'm sorry if these questions sound dumb, it's just that I'm not familiar with the whole Finalizer thing, and I'm trying to make sure that I don't leak memory when I'm working with native code cause I'm creating lots of them

No problemo!

P.S. These kind of questions are not really issues with Dart. So StackOverflow might be a better place for them than making this GitHub thread very long!

@dcharkes
Copy link
Contributor Author

Update 2020-06-26:
.....
Expose finalizers in Dart.

/// Return a pointer object that has a finalizer attached to it. When this
/// pointer object is collected by GC the given finalizer is invoked.
///
/// Note: the pointer object passed to the finalizer is not the same as 
/// the pointer object that is returned from [finalizable] - it points
/// to the same memory region but has different identity. 
Pointer<T> finalizable<T>(Pointer<T> p, void finalizer(Pointer<T> ptr)) {

}

What does it mean @dcharkes ? Where can we find finalizable?

This does not exist. This was a proposed API. That will likely never exist because of the pursuit of adding Dart non-FFI-specific finalizers: #45455.

@dcharkes
Copy link
Contributor Author

Given that C finalizers are available, and Dart finalizers are tracked in #45455, I'm going to lock this issue for now.

If you have questions about how to use the finalizers in C, please post your questions on StackOverflow.

If you want to chime in on the Dart finalizer design discussion, please post in #45455.

If for whatever reason #45455 does not come to be, I'll unlock this discussion again.

@dart-lang dart-lang locked and limited conversation to collaborators May 26, 2021
@vsmenon
Copy link
Member

vsmenon commented Oct 31, 2022

@dcharkes - are we ready to close this issue now?

@dcharkes
Copy link
Contributor Author

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-core-library SDK core library issues (core, async, ...); use area-vm or area-web for platform specific libraries. library-ffi
Projects
None yet
Development

No branches or pull requests