-
Notifications
You must be signed in to change notification settings - Fork 50
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
Memory management for blocks #233
Comments
Blocks have a dispose_helper function that we can use to detect that the block is destroyed. Function pointer blocks just need to free their Block struct. Closure blocks need to additionally deregister their closure. |
I'm not really sure how to handle the closure case. Dart closures get registered in a static map from int ID to function, and then we put that ID in an ObjC block. I can attach a native finalizer to clean up the block, but I can't make it delete the function from the map, because doing so requires running Dart code. Another option I've considered is replacing the global function map with an Expando or something, so that the function reference is only weakly held. Then the question is what the key of the Expando is. It can't be the block pointer, because that's explicitly disallowed. I could make it some sort of Dart object (maybe the ObjCBlock wrapper object), but then the issue becomes what do we stick in the native block, since (afaik) we can't put a reference to a Dart object in a native struct field. Any ideas? |
It would also not be useful. You could lose track of the pointer in Dart, but still hold on to the block in ObjectiveC, in which case the ObjectiveC could try to call the closure after the Dart GC has collected it. How about passing the closure as |
So far I haven't had to write/generate any ObjectiveC or C code. Is there a way of getting the handles and persistent handle in Dart? Can I call those functions using FFI? |
Nice.
You could try looking up |
There's a lot of moving parts here, so I'm writing down my plan. There are 3 kinds of block, and they have different memory management requirements: blocks that come from ObjC code, blocks that are constructed in Dart from C function pointers, and blocks constructed in Dart from Dart functions. There are 3 stages to this, in increasing order of difficulty, and decreasing order of importance (so maybe we can skip the last step):
|
Let me see if I grasped this correctly.
I think we might be able to simplify the design a bit:
Or does the
Do we need to support copying of these blocks? Or can we disallow that? If we need to support it, we need to allocate a new persistent handle to the closure on copying. That way the closure will only get GCed when all blocks referring to it from native are deleted.
Correct, it needs to be called from a thread where at least an isolate group is. (It doesn't have to be a mutator thread, so delete persistent handle can be called from native finalizers.) A workaround for this would be to send the handle through a native port (to an isolate in the isolate group, but probably easiest to do the same isolate) to Dart and let Dart call You probably need a similar trick for the callbacks themselves, if they can be called from any thread. But for those you would also need to setup a port the other way, so you can communicate the return value back. |
Oh yeah, that should work. Thanks.
Not sure, I need to test it. I don't know what will happen if we leave that function as null.
Yeah, since this is already a problem for callbacks, I was planning to ignore it for now. Just flagging it as a potential issue. |
FWIW: We do the same for C bindings. It is the users' responsibility to use native ports instead of passing in a |
Ran some tests this morning. For reference, the copy and dispose function fields on the Block look like this: void (*copy_helper)(void *dst, void *src);
void (*dispose_helper)(void *src); Findings:
As for why the original is not ref counted, but the copy is, I looked at the memory layout of the blocks, and these were the differences:
No matter what combination of those factors I try, I can't get the runtime to ref count the Dart constructed blocks. I also tried dumping the bytes before the copy, and setting the same bytes before the Dart constructed object, in case there were some more undocumented flags there, but nothing worked. This is a problem because if release doesn't do anything then we can't free the block's memory. Something else that's interesting is that copying the copy gives you back the same pointer (and increments the copy's ref count). So the best way to do this might be to construct the fake Dart Block, then make a copy of it (by sending a "copy" message or using |
New simpler design, based on my investigation, and Daco's suggestions:
|
There's an implementation detail that the dispose function can't just be struct Block {
void *isa;
int flags;
int reserved;
void* invoke;
void* descriptor;
void* target;
};
void disposeDartBlock(void* ptr) {
Dart_DeletePersistentHandle(((Block*)ptr)->target);
} This tiny amount of C would significantly complicate the workflow for developers (and complicate our APIs), so I'm really hesitant to go down this route. If we do chose to do this, it should at least be behind a config flag, because most use cases will have a small/bounded number of closures, so won't really care if they're never cleaned up. For now, unless we come up with another approach, I'm just going to do the other parts, and leak these Dart functions. |
FWIW, If we have the same usage pattern in ObjectiveC, a fixed number of blocks for a program, representing the number of static functions we're calling, it's probably fine to leak the memory. If we keep allocating blocks during the run of the program, it would be problematic.
This is indeed the case at the moment.
sgtm |
Right now we leak all the block pointers.
The text was updated successfully, but these errors were encountered: