Roberto Leinardi
12/07/2024, 6:46 PMmemScoped
for scoped memory management, but I’m looking for something closer to Java's `Arena.ofAuto()`: a GC-managed memory arena where allocations are automatically cleaned up by the runtime without relying on scope-based disposal.
Is anything like this available in K/N 2.1? If not, do you have suggestions for an alternative approach to handle such cases? Thanks!ephemient
12/07/2024, 7:34 PMval arena = Arena()
// then after you're done
arena.clear()
?
or to be closer to the Java version
@OptIn(ExperimentalNativeApi::class)
val arena = createCleaner(Arena()) {
it.clear()
}
Roberto Leinardi
12/07/2024, 8:09 PMRoberto Leinardi
12/09/2024, 10:04 AMGdkRectangle
, passes it to the primary constructor, and sets up a cleaner to free the allocation when the Rectangle
object is garbage-collected.
2. Ensure the cleaner runs only after the Rectangle
instance is no longer in use.
Here’s the solution I’ve come up with:
public class Rectangle(
val pointer: CPointer<GdkRectangle>,
private val cleaner: Cleaner? = null
) {
public constructor() : this(
run {
val arena = Arena()
val ptr = arena.alloc<GdkRectangle>().ptr
val c = createCleaner(arena) { it.clear() }
ptr to c
}
)
private constructor(pair: Pair<CPointer<GdkRectangle>, Cleaner>) : this(
pointer = pair.first,
cleaner = pair.second
)
}
I don’t keep a reference to the Arena
, only to the Cleaner
. It seems to behave as expected, where the cleaner is run when the Rectangle
is garbage-collected.
Does this approach seem correct to you? Is there anything I’m overlooking with this design?Oleg Yukhnevich
12/09/2024, 10:15 AMpublic class Rectangle {
private val pointer: CPointer<GdkRectangle>
private val cleaner: Cleaner
init {
val arena = Arena()
pointer = arena.alloc<GdkRectangle>().ptr
cleaner = createCleaner(arena, Arena::clear)
}
}
Or probably you don't even need to use Arena
as you are allocating just single object, so probably nativeHeap
should be enough:
public class Rectangle {
private val pointer: CPointer<GdkRectangle> = nativeHeap.alloc<GdkRectangle>().ptr
private val cleaner: Cleaner = createCleaner(pointer, nativeHeap::free)
}
Minor suggestion from my experience with K/N cleaner: it's might be better to use function references instead of lambdas so that it will be less error-prone as lambda passed to createCleaner
should not catch any variable which is declared inside enclosing class or otherwise it will be never GC-ed (as written in createCleaner
kdoc comment)Roberto Leinardi
12/09/2024, 11:56 AMcreateCleaner
! I really appreciate it.
However, my requirements involve distinguishing between two cases:
1. When the class allocates its own GdkRectangle
(via the no-arg constructor), it should free the memory when the class is garbage-collected.
2. When the class is initialized with an externally provided pointer (via a constructor), it should not attempt to free the memory, as it’s managed externally.
If I get it right, in your init
block solution, the allocation and cleaner setup would always run, even when an external pointer is provided. Similarly, with the nativeHeap
approach, the class would attempt to free the externally managed pointer, which I want to avoid.
Let me know if I’ve misunderstood anything in your solution! And if you have some other idea on how to handle this differently from what I got so far. Thanks again for your input! 🙏Oleg Yukhnevich
12/09/2024, 12:10 PMdefault
constructor, but setup this initialization logic just in two separate constructors:
public class Rectangle {
private val pointer: CPointer<GdkRectangle>
private val cleaner: Cleaner?
public constructor() {
this.pointer = nativeHeap.alloc<GdkRectangle>().ptr
this.cleaner = createCleaner(pointer, nativeHeap::free)
}
public constructor(pointer: CPointer<GdkRectangle>) {
this.pointer = pointer
this.cleaner = null
}
init { /* shared initialization if needed */ }
}
It looks more similar to your initial code, but without the pair handling stuff and so both constructors expli
Though (a matter of taste), I would probably will go here with private default constructor (as in your initial code) and two functions (with logic inside) in companion object like Rectagle.wrap(pointer)
and Rectangle.allocate()
- just to add a bit of clarity on use-site that those are two different semantics:
public class Rectangle private constructor(
private val pointer: CPointer<GdkRectangle>,
private val cleaner: Cleaner?,
) {
public companion object {
public fun allocate(): Rectangle {
val pointer = nativeHeap.alloc<GdkRectangle>().ptr
return Rectangle(
pointer = pointer,
cleaner = createCleaner(pointer, nativeHeap::free)
)
}
public fun wrap(pointer: CPointer<GdkRectangle>): Rectangle {
return Rectangle(pointer, null)
}
}
}
Roberto Leinardi
12/09/2024, 12:14 PM