https://kotlinlang.org logo
Title
b

Big Chungus

03/14/2023, 5:07 PM
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

Landry Norris

03/14/2023, 5:10 PM
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

Big Chungus

03/14/2023, 5:10 PM
Promising! Do you have any links on that?
Also, why can't the reference be this?
l

Landry Norris

03/14/2023, 5:11 PM
More details for createCleaner
b

Big Chungus

03/14/2023, 5:11 PM
Thanks
k

kevin.cianfarini

03/14/2023, 5:11 PM
Whoa I had no idea this was a thing.
l

Landry Norris

03/14/2023, 5:13 PM
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

Big Chungus

03/14/2023, 5:14 PM
I can work around that with factories. Thanks, this helps a ton!
l

Landry Norris

03/14/2023, 5:18 PM
You shouldn’t need a factory. Simple example
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

Big Chungus

03/14/2023, 5:19 PM
tenor_gif3226045315463139326.gif
j

jw

03/14/2023, 5:19 PM
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

Landry Norris

03/14/2023, 5:19 PM
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

Big Chungus

03/14/2023, 5:22 PM
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

Landry Norris

03/14/2023, 5:23 PM
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

Jeff Lockhart

03/14/2023, 5:51 PM
Rather than multiple cleaners, at times I've encapsulated the pointers that need freeing on GC in an object within the wrapper. E.g.:
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

Landry Norris

03/14/2023, 5:52 PM
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

Jeff Lockhart

03/14/2023, 5:55 PM
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

Landry Norris

03/14/2023, 5:57 PM
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

Jeff Lockhart

03/14/2023, 6:01 PM
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

Big Chungus

03/14/2023, 6:02 PM
Or just have inline val properties that delegate to container object?
Same api from the outside
But extra work inside
j

Jeff Lockhart

03/14/2023, 6:04 PM
If they're public (or internal) properties, that does make the API better for external callers.
b

Big Chungus

03/14/2023, 6:06 PM
I think in recent kotlin versions you can even do stuff like this
val foo by memory::foo
l

Landry Norris

03/14/2023, 6:10 PM
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

Jeff Lockhart

03/14/2023, 6:11 PM
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

Landry Norris

03/14/2023, 6:12 PM
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.