From 1f5377f13114cf88a4ce50dbe452c1e8b498c272 Mon Sep 17 00:00:00 2001 From: Guflly <145608489+Guflly@users.noreply.github.com> Date: Sat, 1 Aug 2026 14:54:23 -0700 Subject: [PATCH] Close native locks with Synchronizable --- .../touchlab/stately/concurrency/Functions.kt | 19 ++++++++-- .../stately/concurrency/SynchronizableTest.kt | 37 +++++++++++++++++++ 2 files changed, 53 insertions(+), 3 deletions(-) create mode 100644 stately-concurrency/src/nativeTest/kotlin/co/touchlab/stately/concurrency/SynchronizableTest.kt diff --git a/stately-concurrency/src/nativeMain/kotlin/co/touchlab/stately/concurrency/Functions.kt b/stately-concurrency/src/nativeMain/kotlin/co/touchlab/stately/concurrency/Functions.kt index 2fdafe1..02f49b1 100644 --- a/stately-concurrency/src/nativeMain/kotlin/co/touchlab/stately/concurrency/Functions.kt +++ b/stately-concurrency/src/nativeMain/kotlin/co/touchlab/stately/concurrency/Functions.kt @@ -2,10 +2,23 @@ package co.touchlab.stately.concurrency -actual open class Synchronizable(private val _lock: Lock) { - actual constructor() : this(Lock()) +import kotlin.experimental.ExperimentalNativeApi +import kotlin.native.ref.createCleaner - fun runSynchronized(block: () -> R): R = _lock.withLock(block) +actual open class Synchronizable internal constructor(private val resource: SynchronizationResource) { + actual constructor() : this(SynchronizationResource()) + + @Suppress("unused") + @OptIn(ExperimentalNativeApi::class) + private val cleaner = createCleaner(resource, SynchronizationResource::close) + + fun runSynchronized(block: () -> R): R = resource.lock.withLock(block) +} + +internal open class SynchronizationResource(internal val lock: Lock = Lock()) { + internal open fun close() { + lock.close() + } } actual inline fun Synchronizable.synchronize(noinline block: () -> R): R = runSynchronized(block) diff --git a/stately-concurrency/src/nativeTest/kotlin/co/touchlab/stately/concurrency/SynchronizableTest.kt b/stately-concurrency/src/nativeTest/kotlin/co/touchlab/stately/concurrency/SynchronizableTest.kt new file mode 100644 index 0000000..01f3ca2 --- /dev/null +++ b/stately-concurrency/src/nativeTest/kotlin/co/touchlab/stately/concurrency/SynchronizableTest.kt @@ -0,0 +1,37 @@ +package co.touchlab.stately.concurrency + +import kotlin.native.runtime.GC +import kotlin.native.runtime.NativeRuntimeApi +import kotlin.test.Test +import kotlin.test.assertEquals + +@OptIn(NativeRuntimeApi::class) +class SynchronizableTest { + @Test + fun closesLockWhenCollected() { + val closeCount = AtomicInt(0) + val resource = object : SynchronizationResource() { + override fun close() { + super.close() + closeCount.incrementAndGet() + } + } + + useResource(resource) + + repeat(100) { + GC.collect() + if (closeCount.get() == 1) { + GC.collect() + assertEquals(1, closeCount.get()) + return + } + } + + assertEquals(1, closeCount.get()) + } + + private fun useResource(resource: SynchronizationResource) { + Synchronizable(resource).synchronize {} + } +}