f4e8ae5191
* @ExperimentalForeignApi for all declarations that operate on
unmanaged memory (i.e. pointers and references)
* @BetaInteropApi for the rest of the interoperability declarations,
namely Swift/CInterop-specific interfaces and convenience-functions
### Implementation details
* Some typealiases are not marked explicitly because it crashes the compiler,
yet their experimentality is properly propagated
* License header is adjusted where it previously had been existing
* Deprecated with ERROR interop declarations that are deprecated for more than
two years are removed
* WASM target interop declarations are deprecated
* Deliberately make Boolean.toByte and Byte.toBoolean foreign-experimental to scare
people away
^KT-57728 fixed
Merge-request: KT-MR-9788
Merged-by: Vsevolod Tolstopyatov <qwwdfsad@gmail.com>
301 lines
8.1 KiB
Kotlin
301 lines
8.1 KiB
Kotlin
/*
|
|
* Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
|
* that can be found in the LICENSE file.
|
|
*/
|
|
@file:OptIn(ExperimentalStdlibApi::class, FreezingIsDeprecated::class,
|
|
kotlin.native.runtime.NativeRuntimeApi::class, kotlinx.cinterop.ExperimentalForeignApi::class)
|
|
|
|
package runtime.basic.cleaner_basic
|
|
|
|
import kotlin.test.*
|
|
|
|
import kotlin.native.internal.*
|
|
import kotlin.native.concurrent.*
|
|
import kotlin.native.ref.WeakReference
|
|
import kotlin.native.ref.Cleaner
|
|
import kotlin.native.ref.createCleaner
|
|
import kotlin.native.runtime.GC
|
|
|
|
class AtomicBoolean(initialValue: Boolean) {
|
|
private val impl = AtomicInt(if (initialValue) 1 else 0)
|
|
|
|
init {
|
|
freeze()
|
|
}
|
|
|
|
public var value: Boolean
|
|
get() = impl.value != 0
|
|
set(new) { impl.value = if (new) 1 else 0 }
|
|
}
|
|
|
|
class FunBox(private val impl: () -> Unit) {
|
|
fun call() {
|
|
impl()
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun testCleanerLambda() {
|
|
val called = AtomicBoolean(false);
|
|
var funBoxWeak: WeakReference<FunBox>? = null
|
|
var cleanerWeak: WeakReference<Cleaner>? = null
|
|
{
|
|
val cleaner = {
|
|
val funBox = FunBox { called.value = true }.freeze()
|
|
funBoxWeak = WeakReference(funBox)
|
|
createCleaner(funBox) { it.call() }
|
|
}()
|
|
GC.collect() // Make sure local funBox reference is gone
|
|
cleanerWeak = WeakReference(cleaner)
|
|
assertFalse(called.value)
|
|
}()
|
|
|
|
GC.collect()
|
|
performGCOnCleanerWorker()
|
|
|
|
assertNull(cleanerWeak!!.value)
|
|
assertTrue(called.value)
|
|
assertNull(funBoxWeak!!.value)
|
|
}
|
|
|
|
@Test
|
|
fun testCleanerNonSharedLambda() {
|
|
// Only for experimental MM.
|
|
if (Platform.memoryModel != MemoryModel.EXPERIMENTAL) {
|
|
return
|
|
}
|
|
val called = AtomicBoolean(false);
|
|
var funBoxWeak: WeakReference<FunBox>? = null
|
|
var cleanerWeak: WeakReference<Cleaner>? = null
|
|
{
|
|
val cleaner = {
|
|
val funBox = FunBox { called.value = true }
|
|
funBoxWeak = WeakReference(funBox)
|
|
createCleaner(funBox) { it.call() }
|
|
}()
|
|
GC.collect() // Make sure local funBox reference is gone
|
|
cleanerWeak = WeakReference(cleaner)
|
|
assertFalse(called.value)
|
|
}()
|
|
|
|
GC.collect()
|
|
performGCOnCleanerWorker()
|
|
|
|
assertNull(cleanerWeak!!.value)
|
|
assertTrue(called.value)
|
|
assertNull(funBoxWeak!!.value)
|
|
}
|
|
|
|
@Test
|
|
fun testCleanerAnonymousFunction() {
|
|
val called = AtomicBoolean(false);
|
|
var funBoxWeak: WeakReference<FunBox>? = null
|
|
var cleanerWeak: WeakReference<Cleaner>? = null
|
|
{
|
|
val cleaner = {
|
|
val funBox = FunBox { called.value = true }.freeze()
|
|
funBoxWeak = WeakReference(funBox)
|
|
createCleaner(funBox, fun (it: FunBox) { it.call() })
|
|
}()
|
|
GC.collect() // Make sure local funBox reference is gone
|
|
cleanerWeak = WeakReference(cleaner)
|
|
assertFalse(called.value)
|
|
}()
|
|
|
|
GC.collect()
|
|
performGCOnCleanerWorker()
|
|
|
|
assertNull(cleanerWeak!!.value)
|
|
assertTrue(called.value)
|
|
assertNull(funBoxWeak!!.value)
|
|
}
|
|
|
|
@Test
|
|
fun testCleanerNonSharedAnonymousFunction() {
|
|
// Only for experimental MM.
|
|
if (Platform.memoryModel != MemoryModel.EXPERIMENTAL) {
|
|
return
|
|
}
|
|
val called = AtomicBoolean(false);
|
|
var funBoxWeak: WeakReference<FunBox>? = null
|
|
var cleanerWeak: WeakReference<Cleaner>? = null
|
|
{
|
|
val cleaner = {
|
|
val funBox = FunBox { called.value = true }
|
|
funBoxWeak = WeakReference(funBox)
|
|
createCleaner(funBox, fun (it: FunBox) { it.call() })
|
|
}()
|
|
GC.collect() // Make sure local funBox reference is gone
|
|
cleanerWeak = WeakReference(cleaner)
|
|
assertFalse(called.value)
|
|
}()
|
|
|
|
GC.collect()
|
|
performGCOnCleanerWorker()
|
|
|
|
assertNull(cleanerWeak!!.value)
|
|
assertTrue(called.value)
|
|
assertNull(funBoxWeak!!.value)
|
|
}
|
|
|
|
@Test
|
|
fun testCleanerFunctionReference() {
|
|
val called = AtomicBoolean(false);
|
|
var funBoxWeak: WeakReference<FunBox>? = null
|
|
var cleanerWeak: WeakReference<Cleaner>? = null
|
|
{
|
|
val cleaner = {
|
|
val funBox = FunBox { called.value = true }.freeze()
|
|
funBoxWeak = WeakReference(funBox)
|
|
createCleaner(funBox, FunBox::call)
|
|
}()
|
|
GC.collect() // Make sure local funBox reference is gone
|
|
cleanerWeak = WeakReference(cleaner)
|
|
assertFalse(called.value)
|
|
}()
|
|
|
|
GC.collect()
|
|
performGCOnCleanerWorker()
|
|
|
|
assertNull(cleanerWeak!!.value)
|
|
assertTrue(called.value)
|
|
assertNull(funBoxWeak!!.value)
|
|
}
|
|
|
|
@Test
|
|
fun testCleanerNonSharedFunctionReference() {
|
|
// Only for experimental MM.
|
|
if (Platform.memoryModel != MemoryModel.EXPERIMENTAL) {
|
|
return
|
|
}
|
|
val called = AtomicBoolean(false);
|
|
var funBoxWeak: WeakReference<FunBox>? = null
|
|
var cleanerWeak: WeakReference<Cleaner>? = null
|
|
{
|
|
val cleaner = {
|
|
val funBox = FunBox { called.value = true }
|
|
funBoxWeak = WeakReference(funBox)
|
|
createCleaner(funBox, FunBox::call)
|
|
}()
|
|
GC.collect() // Make sure local funBox reference is gone
|
|
cleanerWeak = WeakReference(cleaner)
|
|
assertFalse(called.value)
|
|
}()
|
|
|
|
GC.collect()
|
|
performGCOnCleanerWorker()
|
|
|
|
assertNull(cleanerWeak!!.value)
|
|
assertTrue(called.value)
|
|
assertNull(funBoxWeak!!.value)
|
|
}
|
|
|
|
@Test
|
|
fun testCleanerFailWithNonShareableArgument() {
|
|
// Only for legacy MM.
|
|
if (Platform.memoryModel == MemoryModel.EXPERIMENTAL) {
|
|
return
|
|
}
|
|
val funBox = FunBox {}
|
|
assertFailsWith<IllegalArgumentException> {
|
|
createCleaner(funBox) {}
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun testCleanerCleansWithoutGC() {
|
|
val called = AtomicBoolean(false);
|
|
var funBoxWeak: WeakReference<FunBox>? = null
|
|
var cleanerWeak: WeakReference<Cleaner>? = null
|
|
{
|
|
val cleaner = {
|
|
val funBox = FunBox { called.value = true }.freeze()
|
|
funBoxWeak = WeakReference(funBox)
|
|
createCleaner(funBox) { it.call() }
|
|
}()
|
|
GC.collect() // Make sure local funBox reference is gone
|
|
cleaner.freeze()
|
|
cleanerWeak = WeakReference(cleaner)
|
|
assertFalse(called.value)
|
|
}()
|
|
|
|
GC.collect()
|
|
|
|
assertNull(cleanerWeak!!.value)
|
|
|
|
waitCleanerWorker()
|
|
|
|
assertTrue(called.value)
|
|
|
|
// Only for legacy MM.
|
|
if (Platform.memoryModel != MemoryModel.EXPERIMENTAL) {
|
|
// If this fails, GC has somehow ran on the cleaners worker.
|
|
assertNotNull(funBoxWeak!!.value)
|
|
}
|
|
}
|
|
|
|
val globalInt = AtomicInt(0)
|
|
|
|
@Test
|
|
fun testCleanerWithInt() {
|
|
var cleanerWeak: WeakReference<Cleaner>? = null
|
|
{
|
|
val cleaner = createCleaner(42) {
|
|
globalInt.value = it
|
|
}.freeze()
|
|
cleanerWeak = WeakReference(cleaner)
|
|
assertEquals(0, globalInt.value)
|
|
}()
|
|
|
|
GC.collect()
|
|
performGCOnCleanerWorker()
|
|
|
|
assertNull(cleanerWeak!!.value)
|
|
assertEquals(42, globalInt.value)
|
|
}
|
|
|
|
val globalPtr = AtomicNativePtr(NativePtr.NULL)
|
|
|
|
@Test
|
|
fun testCleanerWithNativePtr() {
|
|
var cleanerWeak: WeakReference<Cleaner>? = null
|
|
{
|
|
val cleaner = createCleaner(NativePtr.NULL + 42L) {
|
|
globalPtr.value = it
|
|
}
|
|
cleanerWeak = WeakReference(cleaner)
|
|
assertEquals(NativePtr.NULL, globalPtr.value)
|
|
}()
|
|
|
|
GC.collect()
|
|
performGCOnCleanerWorker()
|
|
|
|
assertNull(cleanerWeak!!.value)
|
|
assertEquals(NativePtr.NULL + 42L, globalPtr.value)
|
|
}
|
|
|
|
@Test
|
|
fun testCleanerWithException() {
|
|
val called = AtomicBoolean(false);
|
|
var funBoxWeak: WeakReference<FunBox>? = null
|
|
var cleanerWeak: WeakReference<Cleaner>? = null
|
|
{
|
|
val funBox = FunBox { called.value = true }.freeze()
|
|
funBoxWeak = WeakReference(funBox)
|
|
val cleaner = createCleaner(funBox) {
|
|
it.call()
|
|
error("Cleaner block failed")
|
|
}
|
|
cleanerWeak = WeakReference(cleaner)
|
|
}()
|
|
|
|
GC.collect()
|
|
performGCOnCleanerWorker()
|
|
|
|
assertNull(cleanerWeak!!.value)
|
|
// Cleaners block started executing.
|
|
assertTrue(called.value)
|
|
// Even though the block failed, the captured funBox is freed.
|
|
assertNull(funBoxWeak!!.value)
|
|
}
|