Skip to content

Commit

Permalink
Kotlin multiplatform leaking memory (#4037)
Browse files Browse the repository at this point in the history
* Add deinit for KMP iOS and JVM targets

* Add deinit for JS target

* Add deinit for JS target

* Fix JVM native name

* Reuse one thread on JVM

---------

Co-authored-by: satoshiotomakan <[email protected]>
  • Loading branch information
vcoolish and satoshiotomakan authored Oct 1, 2024
1 parent b4221b4 commit 00a8744
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 0 deletions.
9 changes: 9 additions & 0 deletions codegen/lib/templates/kotlin/android_class.erb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ actual class <%= entity.name %> private constructor(

init {
if (nativeHandle == 0L) throw IllegalArgumentException()
<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%>
GenericPhantomReference.register(this, nativeHandle, ::delete)
<% end -%>
}
<%# Constructors -%>
<%- constructors.each do |constructor| -%>
Expand Down Expand Up @@ -52,6 +55,12 @@ actual class <%= entity.name %> private constructor(
@JvmStatic
@JvmName("createFromNative")
private fun createFromNative(nativeHandle: Long) = <%= entity.name %>(nativeHandle)

<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%>
@JvmStatic
@JvmName("delete")
private external fun delete(handle: Long)
<%- end -%>
<%- constructors.each do |constructor| -%>

@JvmStatic
Expand Down
6 changes: 6 additions & 0 deletions codegen/lib/templates/kotlin/ios_class.erb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ import kotlinx.cinterop.CPointer
actual class <%= entity.name %> constructor(
val pointer: CPointer<TW<%= entity.name %>>,
) {
<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%>
@OptIn(ExperimentalStdlibApi::class)
private val cleaner = kotlin.native.internal.createCleaner(pointer) { ptr ->
TW<%= entity.name %>Delete(ptr)
}
<% end -%>
<%# Constructors -%>
<%- constructors.each do |constructor| -%>
Expand Down
3 changes: 3 additions & 0 deletions codegen/lib/templates/kotlin/js_accessors_class.erb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ external interface Js<%= entity.name %> {
<%- entity.properties.each do |property| -%>
fun <%= KotlinHelper.fix_name(WasmCppHelper.format_name(property.name)) %>()<%= KotlinHelper.js_return_type(property.return_type) %>
<%- end -%>
<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%>
fun delete()
<% end -%>
<% entity.methods.each do |method| -%>
<% next if method.name == "Delete" -%>
fun <%= KotlinHelper.fix_name(WasmCppHelper.format_name(method.name)) %>(<%= KotlinHelper.js_parameters(method.parameters.drop(1)) %>)<%= KotlinHelper.js_return_type(method.return_type) %>
Expand Down
15 changes: 15 additions & 0 deletions codegen/lib/templates/kotlin/js_class.erb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,21 @@
actual class <%= entity.name %> constructor(
val jsValue: Js<%= entity.name %>,
) {
<% unless entity.methods.select{ |x| x.name == "Delete" }.empty? -%>
private val finalizationRegistry =
js(
"""
new FinalizationRegistry(function(heldValue) {
heldValue.delete();
})
"""
)

init {
finalizationRegistry.register(this, jsValue)
}
<% end -%>
<%# Constructors -%>
<%- constructors.each do |constructor| -%>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.trustwallet.core

import java.lang.ref.PhantomReference
import java.lang.ref.ReferenceQueue

internal class GenericPhantomReference private constructor(
referent: Any,
private val handle: Long,
private val onDelete: (Long) -> Unit,
) : PhantomReference<Any>(referent, queue) {

companion object {
private val references: MutableSet<GenericPhantomReference> = HashSet()
private val queue: ReferenceQueue<Any> = ReferenceQueue()

init {
Thread {
try {
doDeletes()
} catch (e: InterruptedException) {
Thread.currentThread().interrupt()
}
}.apply {
name = "WCFinalizingDaemon"
isDaemon = true
priority = Thread.NORM_PRIORITY
start()
}
}

fun register(
referent: Any,
handle: Long,
onDelete: (Long) -> Unit,
) {
references.add(GenericPhantomReference(referent, handle, onDelete))
}

private fun doDeletes() {
while (true) {
val ref = queue.remove() as GenericPhantomReference
ref.onDelete(ref.handle)
references.remove(ref)
}
}
}
}

0 comments on commit 00a8744

Please sign in to comment.