Is there any way to register some code to be execu...
# kotlin-native
b
Is there any way to register some code to be executed when K/N object is being garbage collected (like java's finalize)? Trying to figure out how to properly dispose of the cinterop pointers that my wrapper classes own.
Maybe there's some interface I can implement like AutoCloseable that would indicate the gc that the class needs cleanup on gc?
l
You can use a cleaner. It takes in a reference (important that the reference is not
this
) to a variable, and when the variable is GC’ed, it runs a lambda.
b
Promising! Do you have any links on that?
Also, why can't the reference be this?
l
More details for createCleaner
b
Thanks
k
Whoa I had no idea this was a thing.
l
The lambda is run when the cleaner gets GC’ed. By passing in
this
, you’re creating a reference to the
this
object, which also holds the cleaner, so it can’t be GC’ed.
b
I can work around that with factories. Thanks, this helps a ton!
l
You shouldn’t need a factory. Simple example
Copy code
class FooFromCWrapper(val foo: CPointer<CFoo>) {
    //val cleaner = createCleaner(foo) { c_lib_free(foo) } //will leak memory
    val cleaner = createCleaner(foo) { c_lib_free(it) } //won't leak
}
b
tenor_gif3226045315463139326.gif
j
Tangentially related, but finalizers are deprecated for removal and will be gone soon. The mechanism people should be using in Java/on the JVM is also called a `Cleaner`: https://docs.oracle.com/en/java/javase/19/docs/api/java.base/java/lang/ref/Cleaner.html
l
Actually, the code I posted is wrong, and will leak memory. Replace c_lib_free(foo) with c_lib_free(it).
Cleaners are a bit dangerous, for the same reason my code will leak memory. The lambda shouldn’t capture anything. Not sure if there’s a lint check for this, or something like how staticCFunction will error if you capture. Only reference the argument as the value provided by the lambda, never anything directly from the class.
b
Well I'm only cleaning a single cpointer so just have to be vigilant I guess.
Plus I'm generating this code so there's only one place I can ever mess up
l
You can also have multiple cleaners if needed. I’d do one per pointer to be safe. Just write a test that allocates a bunch in a loop and make sure it doesn’t run out of memory.
j
Rather than multiple cleaners, at times I've encapsulated the pointers that need freeing on GC in an object within the wrapper. E.g.:
Copy code
class CWrapper(
    val foo: CPointer<CFoo>,
    val bar: CPointer<CBar>
) {
    val memory = object {
        val foo = foo
        val bar = bar
    }

    val cleaner = createCleaner(memory) {
        c_lib_free(it.foo)
        c_lib_free(it.bar)
    }
}
This can also work if the pointer is a
var
and might be freed and replaced within the lifecycle of the wrapper and just need to ensure the last
var
set is freed on GC.
l
Never thought to do that. I’d be a bit more worried about accidentally capturing.
I may have to start practicing that style, though. Makes it much more clear what’s cinteropped and what’s not. That’s sometimes a challenge when I need to check on what needs to be freed vs not.
j
Yeah, definitely always need to double check the captured variables in the cleaner lambda. A lint check for that would be great: only use the
it
reference within the cleaner. I like that it does make it explicit any memory that needs freeing should be kept in that one place.
l
I agree. I have one class that has way too many properties (but it has to be high-performance and memory locality seems to have an impact). Whenever we have a memory leak, I have to look through each one and see if it’s a Kotlin or C managed variable, and if it’s C, I have to check where it’s freed.
I would imagine creating an object allocates separate memory. Wonder if there’s a way to have it act like some sort of namespace.
j
That example does store each pointer twice. Alternatively you could remove the property variables from the constructor and always access the values from the memory subobject.
b
Or just have inline val properties that delegate to container object?
Same api from the outside
But extra work inside
j
If they're public (or internal) properties, that does make the API better for external callers.
b
I think in recent kotlin versions you can even do stuff like this
Copy code
val foo by memory::foo
l
My main concern would be access time. It now has to go through an extra object, and that object won’t necessarily be local in memory. Typically, when I have to use cinterop, there’s some performance constraints, but that may just be my experience.
j
That's a good point. I haven't done any performance benchmarking. It'd be interesting to see if there is a performance hit.
l
In theory, the compiler could have a lowering of some sort that desugars it to fields (maybe with name mangling). That would be nice to see, even if just an annotation.