-
Notifications
You must be signed in to change notification settings - Fork 701
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
Add memory management tips to dart:ffi page #3193
Comments
I run into this issue using dart FFI with Go in c-shared mode. Everything works fine on mac where ffi's As workaround was to follow the recommendation shared by @johnpryan and to define an exported function in Go that can be called from dart to free memory. Here is some more info in case it helps someone down the line. In go, export a C function to free a specific pointer. // Free native memory which we allocated in cgo such as en using C.CString.
// This is needed so the caller can safely release the memory allocated by our code.
//export free_pointer
func free_pointer(pointer unsafe.Pointer) {
println("freeing pointer from cgo") // to prove it works
C.free(pointer)
} Then in your dart code, make the ffi function as John explained: late final _cfreePointerPointer = _lookup<NativeFunction<Void Function(Pointer<Int32> a)>>('free_pointer')
.asFunction<void Function(Pointer<Int32> a)>(); And define a dart function to wrap this call and convert the address into a pointer: void _freeCPointer(int address) {
_cfreePointerPointer(Pointer.fromAddress(address));
} Now let's pretend that our Go/cgo code exports a hello function returning a string/char*. //export hello
function hello() *C.char {
return C.CString("hello world!")
} The allocated string won't be garbage collected by Go and therefore needs to be freed by the caller when done with the data. final Pointer<Utf8> cString = _cHello();
final msg = cHello.toDartString();
_freeCPointer(cHello.address);
print(msg); Go should handle the freeing of the pointer and print the debug message we added there. |
Is there a recommended way to track memory leaks using the Dart Dev tools? If I create a simple program that allocates pointers in a loop without freeing them, I see the RSS (Resident Size Set) creep up in the Dart dev tools for the first minute or so, which I think would be expected given pointers are being allocated and never freed. However, it eventually starts to drop and eventually seems to mostly stabilize (with some ups and downs). At first I thought maybe those pointers were eventually getting freed (or reused) somehow, but the amount of memory being used by the process (as shown in Activity Monitor on MacOS) doesn't drop off. It keeps climbing steadily. Here is an example: void main() {
for (var i = 0; i < 1000000000; i++) {
if (i % 1000 == 0) print('iteration: $i');
i.toRadixString(2).toNativeUtf8();
i.toRadixString(3).toNativeUtf8();
i.toRadixString(4).toNativeUtf8();
}
} Here is the memory chart from Dart Dev tools: Note: The solid blue line is really a bunch of individual blue dots from garbage collection. They are just all on top of each other. You can see RSS goes up for a couple minutes until it hits ~1GB, but then drops pretty quickly. After that it goes up and down but never goes above 1GB again. However, after a few minutes of this running I see the process is using 5GB of memory (as shown in Activity Monitor on MacOS). As far as I can tell, there isn't a way to view the memory leaks from native pointers using the dart dev tools. Is this a bug with the Dart Dev tools, or do they not include native memory? |
Hi @Jordan-Nelson, I'm not too familiar with DevTools but with the new You can read and comment on a new proposal/design doc for adding memory leak detection to DevTools here: https://flutter.dev/go/detect-memory-leaks |
The Dart
In order to tell the Dart GC that you're holding on to native memory, one can use the The typical pattern would be to also provide an eager close method for when you'd want to free the native memory early [2]: class MyNativeResource implements Finalizable {
final Pointer<Void> pointer;
bool _closed = false;
MyNativeResource._(this.pointer, {int externalSize}) {
print('pointer $pointer');
freeFinalizer.attach(this, pointer,
externalSize: externalSize, detach: this);
}
factory MyNativeResource() {
const num = 1;
const size = 16;
final pointer = calloc(num, size);
return MyNativeResource._(pointer, externalSize: size);
}
/// Eagerly stop using the native resource. Cancelling the finalizer.
void close() {
_closed = true;
freeFinalizer.detach(this);
free(pointer);
}
void useResource() {
if (_closed) {
throw UnsupportedError('The native resource has already been released');
}
print(pointer.address);
}
} Please note that If you're only implemented in leak-detection, not in freeing the native memory, then you can use cc @polina-c is working on adding leak detection to our dev tools. [1] https://dart-review.googlesource.com/c/sdk/+/236320/21/sdk/lib/ffi/native_finalizer.dart. |
Nice. I think that would be very useful. Looking forward to using it! |
There are two things I am trying to determine. First, does the app/program have a memory leak. Second, what is causing the leak if one exists (which allocation is not freed up). I figured out a simple way to achieve the first goal. This might be obvious to others, but it wasn't to me, so I figured I would share.
|
@mattetti Hello, I've just stumbled into the same problem you had. Do you confirm that the approach you took is still the same you would recommend today? |
@mpl I haven't written any dart code in a while but yes, it's always recommended to free memory in the binary that allocated it. My guess is that the first time around, I got lucky on Mac where Go and Dart might have used the same toolchain allowing freeing from dart to work fine, but it's risky. I know it's cumbersome but even in C++ when providing a dll exporting a function allocating memory, I provide a matching exported function to clear the allocated memory. |
yeah, I experienced the same (that it was working from mac, but not from windows). thanks. |
As someone who didn't know this and had some crashes because of it (especially because it happened to work on WSL but not on Windows), it would be nice to have this documented. Maybe it can also be prevented in-code? FFI functions can return a |
Unfortunately, pointers are not subclassable. In fact, we want to not have an object at runtime at all (just the memory address). See some info here: dart-lang/sdk#49935.
I believe you mean it would turn segfaults into Dart runtime errors. I don't believe we could have compile-time errors catch this? You could write your own abstraction wrapping (As a very off-topic tangent. With WASM-GC and multiple linear memories we also need to keep track of which memory a pointer belongs to. So we might need some kind of abstraction that has a pointer field at some point. cc @askeksa-google) |
If class DartPointer extends Pointer { }
DartPointer allocate(...) { ... }
void free(DartPointer pointer) { ... } then trying to pass a regular
Since this would be a static check, could this still be possible by implementing
Also true, but then I'd have to have the prescience to use my wrapper instead of While on the topic of freeing with
My example is that I have a struct that can be allocated natively or on the Dart side. I have a wrapper class with a bool to keep track of which method was used, and in my |
Maybe if you're only interested in static checks (e.g. you would give every source of
|
Yeah this would be a great feature. Or maybe, in the meantime, it could be implemented in the same way
Okay now I'll admit I'm a little confused. From the top comment on this issue:
...but you're saying it's okay to natively free memory that was allocated from Dart? Then what is the point of keeping track of |
In Wasm, the identity of the accessed memory is a static property of the accessing instruction. Thus, if we add support for accessing multiple Wasm memories through FFI pointers, this will necessarily be a static property of every access. So this will not add any runtime tracking requirements. |
Ah right, so in that case a type argument per linear memory or an extension type per linear memory could work. |
One of my Dart FFI guides discussed the best practices for freeing native-allocated memory using Dart FFI (both for non-Windows and Windows) to ensure code safety. |
extension type DartPointer<T extends NativeType>(Pointer<T> p) implements Pointer<T> { }
DartPointer<T> allocate<T extends NativeType>() => DartPointer(nullptr);
void free(DartPointer p) { } 👀 |
Page: https://dart.dev/guides/libraries/c-interop
@dcharkes just fixed a memory leak in our samples (dart-lang/samples#101). We should probably document this.
We could add some memory management tips, like:
calloc.free()
when freeing memory allocated in Dart (viamalloc
, ortoNativeUtf8
frompackage:ffi
, for example)See also:
The text was updated successfully, but these errors were encountered: