/* * Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors. * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file. */ @file:OptIn(kotlin.experimental.ExperimentalNativeApi::class) import kt56402.* import kotlin.concurrent.AtomicInt import kotlin.native.concurrent.* import kotlin.native.internal.test.testLauncherEntryPoint import kotlin.system.exitProcess import kotlin.test.* import kotlinx.cinterop.* class LockedSet { private val lock = AtomicInt(0) private val impl = mutableSetOf() private inline fun locked(f: () -> R): R { while (!lock.compareAndSet(0, 1)) {} try { return f() } finally { lock.value = 0 } } fun add(id: T) = locked { assertFalse(id in impl) impl.add(id) } fun remove(id: T) = locked { assertTrue(id in impl) impl.remove(id) } operator fun contains(id: T) = locked { id in impl } } class OnDestroyHookSub(onDestroy: (ULong) -> Unit) : OnDestroyHook(onDestroy) val aliveObjectIds = LockedSet() fun alloc(ctor: ((ULong) -> Unit) -> ULong): ULong = autoreleasepool { val id = ctor { aliveObjectIds.remove(it) } aliveObjectIds.add(id) id } fun waitDestruction(id: ULong) { assertTrue(isMainThread()) // Make sure the finalizers are not run on the main thread even for STMS. withWorker { execute(TransferMode.SAFE, {}) { kotlin.native.internal.GC.collect() }.result } while (true) { spin() if (!(id in aliveObjectIds)) { break } } } @Test fun testOnMainThread() { assertTrue(isMainThread()) val id = alloc { onDestroy -> OnDestroyHook { assertTrue(isMainThread()) onDestroy(it) }.identity() } waitDestruction(id) } @Test fun testOnSecondaryThread() { val id = withWorker { execute(TransferMode.SAFE, {}) { assertFalse(isMainThread()) alloc { onDestroy -> OnDestroyHook { assertFalse(isMainThread()) onDestroy(it) }.identity() } }.result } waitDestruction(id) } @Test fun testSubOnMainThread() { assertTrue(isMainThread()) val id = alloc { onDestroy -> OnDestroyHookSub { assertTrue(isMainThread()) onDestroy(it) }.identity() } waitDestruction(id) } @Test fun testSubOnSecondaryThread() { val id = withWorker { execute(TransferMode.SAFE, {}) { assertFalse(isMainThread()) alloc { onDestroy -> OnDestroyHookSub { assertFalse(isMainThread()) onDestroy(it) }.identity() } }.result } waitDestruction(id) } @Test fun testGlobalOnMainThread() { assertTrue(isMainThread()) val id = alloc { onDestroy -> val obj = newGlobal { assertTrue(isMainThread()) onDestroy(it) }!! clearGlobal() obj.identity() } waitDestruction(id) } @Test fun testGlobalOnSecondaryThread() { val id = withWorker { execute(TransferMode.SAFE, {}) { assertFalse(isMainThread()) alloc { onDestroy -> val obj = newGlobal { assertFalse(isMainThread()) onDestroy(it) }!! clearGlobal() obj.identity() } }.result } waitDestruction(id) } @Test fun testProtocolOnMainThread() { assertTrue(isMainThread()) val id = alloc { onDestroy -> newOnDestroyHook { assertTrue(isMainThread()) onDestroy(it) }!!.identity() } waitDestruction(id) } @Test fun testProtocolOnSecondaryThread() { val id = withWorker { execute(TransferMode.SAFE, {}) { assertFalse(isMainThread()) alloc { onDestroy -> newOnDestroyHook { assertFalse(isMainThread()) onDestroy(it) }!!.identity() } }.result } waitDestruction(id) } fun main(args: Array) = startApp { val exitCode = testLauncherEntryPoint(args) if (exitCode != 0) { exitProcess(exitCode) } }