340 lines
10 KiB
Kotlin
Vendored
340 lines
10 KiB
Kotlin
Vendored
// DISABLE_NATIVE: gcType=NOOP
|
|
// FREE_COMPILER_ARGS: -opt-in=kotlin.experimental.ExperimentalNativeApi,kotlin.native.runtime.NativeRuntimeApi,kotlin.native.FreezingIsDeprecated
|
|
|
|
import kotlin.test.*
|
|
|
|
import kotlin.concurrent.AtomicInt
|
|
import kotlin.concurrent.AtomicReference
|
|
import kotlin.native.concurrent.*
|
|
import kotlin.native.*
|
|
import kotlin.native.ref.WeakReference
|
|
import kotlin.native.runtime.GC
|
|
|
|
class A(var a: Int)
|
|
|
|
class Wrapper(val ref: WorkerBoundReference<A>)
|
|
|
|
fun getOwnerAndWeaks(initial: Int): Triple<AtomicReference<WorkerBoundReference<A>?>, WeakReference<WorkerBoundReference<A>>, WeakReference<A>> {
|
|
val ref = WorkerBoundReference(A(initial))
|
|
val refOwner: AtomicReference<WorkerBoundReference<A>?> = AtomicReference(ref)
|
|
val refWeak = WeakReference(ref)
|
|
val refValueWeak = WeakReference(ref.value)
|
|
|
|
return Triple(refOwner, refWeak, refValueWeak)
|
|
}
|
|
|
|
@Test
|
|
fun testCollect() {
|
|
val (refOwner, refWeak, refValueWeak) = getOwnerAndWeaks(3)
|
|
|
|
refOwner.value = null
|
|
GC.collect()
|
|
|
|
// Last reference to WorkerBoundReference is gone, so it and it's referent are destroyed.
|
|
assertNull(refWeak.value)
|
|
assertNull(refValueWeak.value)
|
|
}
|
|
|
|
fun getOwnerAndWeaksFrozen(initial: Int): Triple<AtomicReference<WorkerBoundReference<A>?>, WeakReference<WorkerBoundReference<A>>, WeakReference<A>> {
|
|
val ref = WorkerBoundReference(A(initial)).freeze()
|
|
val refOwner: AtomicReference<WorkerBoundReference<A>?> = AtomicReference(ref)
|
|
val refWeak = WeakReference(ref)
|
|
val refValueWeak = WeakReference(ref.value)
|
|
|
|
return Triple(refOwner, refWeak, refValueWeak)
|
|
}
|
|
|
|
@Test
|
|
fun testCollectFrozen() {
|
|
val (refOwner, refWeak, refValueWeak) = getOwnerAndWeaksFrozen(3)
|
|
|
|
refOwner.value = null
|
|
if (Platform.memoryModel == MemoryModel.EXPERIMENTAL) {
|
|
// This runs the finalizer on the WorkerBoundReference<A>, which schedules removing A from the root set
|
|
GC.collect()
|
|
// This actually frees A
|
|
GC.collect()
|
|
} else {
|
|
GC.collect()
|
|
}
|
|
|
|
// Last reference to WorkerBoundReference is gone, so it and it's referent are destroyed.
|
|
assertNull(refWeak.value)
|
|
assertNull(refValueWeak.value)
|
|
}
|
|
|
|
fun collectInWorkerFrozen(worker: Worker, semaphore: AtomicInt): Pair<WeakReference<A>, Future<Unit>> {
|
|
val (refOwner, _, refValueWeak) = getOwnerAndWeaksFrozen(3)
|
|
|
|
val future = worker.execute(TransferMode.SAFE, { Pair(refOwner, semaphore) }) { (refOwner, semaphore) ->
|
|
semaphore.incrementAndGet()
|
|
while (semaphore.value < 2) {
|
|
}
|
|
|
|
refOwner.value = null
|
|
GC.collect()
|
|
}
|
|
|
|
while (semaphore.value < 1) {
|
|
}
|
|
// At this point worker is spinning on semaphore. refOwner still contains reference to
|
|
// WorkerBoundReference, so referent is kept alive.
|
|
GC.collect()
|
|
assertNotNull(refValueWeak.value)
|
|
|
|
return Pair(refValueWeak, future)
|
|
}
|
|
|
|
@Test
|
|
fun testCollectInWorkerFrozen() {
|
|
val semaphore: AtomicInt = AtomicInt(0)
|
|
|
|
val worker = Worker.start()
|
|
|
|
val (refValueWeak, future) = collectInWorkerFrozen(worker, semaphore)
|
|
semaphore.incrementAndGet()
|
|
future.result
|
|
|
|
// At this point WorkerBoundReference no longer has a reference, so it's referent is destroyed.
|
|
GC.collect()
|
|
assertNull(refValueWeak.value)
|
|
|
|
worker.requestTermination().result
|
|
}
|
|
|
|
fun doNotCollectInWorkerFrozen(worker: Worker, semaphore: AtomicInt): Future<WorkerBoundReference<A>> {
|
|
val ref = WorkerBoundReference(A(3)).freeze()
|
|
|
|
return worker.execute(TransferMode.SAFE, { Pair(ref, semaphore) }) { (ref, semaphore) ->
|
|
semaphore.incrementAndGet()
|
|
while (semaphore.value < 2) {
|
|
}
|
|
|
|
GC.collect()
|
|
ref
|
|
}
|
|
}
|
|
|
|
@Test
|
|
fun testDoNotCollectInWorkerFrozen() {
|
|
val semaphore: AtomicInt = AtomicInt(0)
|
|
|
|
val worker = Worker.start()
|
|
|
|
val future = doNotCollectInWorkerFrozen(worker, semaphore)
|
|
while (semaphore.value < 1) {
|
|
}
|
|
GC.collect()
|
|
semaphore.incrementAndGet()
|
|
|
|
val value = future.result
|
|
assertEquals(3, value.value.a)
|
|
worker.requestTermination().result
|
|
}
|
|
|
|
class B1 {
|
|
lateinit var b2: WorkerBoundReference<B2>
|
|
}
|
|
|
|
data class B2(val b1: WorkerBoundReference<B1>)
|
|
|
|
fun createCyclicGarbage(): Triple<AtomicReference<WorkerBoundReference<B1>?>, WeakReference<B1>, WeakReference<B2>> {
|
|
val ref1 = WorkerBoundReference(B1())
|
|
val ref1Owner: AtomicReference<WorkerBoundReference<B1>?> = AtomicReference(ref1)
|
|
val ref1Weak = WeakReference(ref1.value)
|
|
|
|
val ref2 = WorkerBoundReference(B2(ref1))
|
|
val ref2Weak = WeakReference(ref2.value)
|
|
|
|
ref1.value.b2 = ref2
|
|
|
|
return Triple(ref1Owner, ref1Weak, ref2Weak)
|
|
}
|
|
|
|
@Test
|
|
fun collectCyclicGarbage() {
|
|
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbage()
|
|
|
|
ref1Owner.value = null
|
|
GC.collect()
|
|
|
|
assertNull(ref1Weak.value)
|
|
assertNull(ref2Weak.value)
|
|
}
|
|
|
|
fun createCyclicGarbageFrozen(): Triple<AtomicReference<WorkerBoundReference<B1>?>, WeakReference<B1>, WeakReference<B2>> {
|
|
val ref1 = WorkerBoundReference(B1()).freeze()
|
|
val ref1Owner: AtomicReference<WorkerBoundReference<B1>?> = AtomicReference(ref1)
|
|
val ref1Weak = WeakReference(ref1.value)
|
|
|
|
val ref2 = WorkerBoundReference(B2(ref1)).freeze()
|
|
val ref2Weak = WeakReference(ref2.value)
|
|
|
|
ref1.value.b2 = ref2
|
|
|
|
return Triple(ref1Owner, ref1Weak, ref2Weak)
|
|
}
|
|
|
|
@Test
|
|
fun doesNotCollectCyclicGarbageFrozen() {
|
|
if (!Platform.isFreezingEnabled) return
|
|
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbageFrozen()
|
|
|
|
ref1Owner.value = null
|
|
GC.collect()
|
|
|
|
// If these asserts fail, that means WorkerBoundReference managed to clean up cyclic garbage all by itself.
|
|
assertNotNull(ref1Weak.value)
|
|
assertNotNull(ref2Weak.value)
|
|
}
|
|
|
|
fun createCrossThreadCyclicGarbageFrozen(
|
|
worker: Worker
|
|
): Triple<AtomicReference<WorkerBoundReference<B1>?>, WeakReference<B1>, WeakReference<B2>> {
|
|
val ref1 = WorkerBoundReference(B1()).freeze()
|
|
val ref1Owner: AtomicReference<WorkerBoundReference<B1>?> = AtomicReference(ref1)
|
|
val ref1Weak = WeakReference(ref1.value)
|
|
|
|
val future = worker.execute(TransferMode.SAFE, { ref1 }) { ref1 ->
|
|
val ref2 = WorkerBoundReference(B2(ref1)).freeze()
|
|
Pair(ref2, WeakReference(ref2.value))
|
|
}
|
|
val (ref2, ref2Weak) = future.result
|
|
|
|
ref1.value.b2 = ref2
|
|
|
|
return Triple(ref1Owner, ref1Weak, ref2Weak)
|
|
}
|
|
|
|
@Test
|
|
fun doesNotCollectCrossThreadCyclicGarbageFrozen() {
|
|
if (!Platform.isFreezingEnabled) return
|
|
val worker = Worker.start()
|
|
|
|
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbageFrozen(worker)
|
|
|
|
ref1Owner.value = null
|
|
GC.collect()
|
|
worker.execute(TransferMode.SAFE, {}) { GC.collect() }.result
|
|
|
|
// If these asserts fail, that means WorkerBoundReference managed to clean up cyclic garbage all by itself.
|
|
assertNotNull(ref1Weak.value)
|
|
assertNotNull(ref2Weak.value)
|
|
|
|
worker.requestTermination().result
|
|
}
|
|
|
|
class C1 {
|
|
lateinit var c2: AtomicReference<WorkerBoundReference<C2>?>
|
|
|
|
fun dispose() {
|
|
c2.value = null
|
|
}
|
|
}
|
|
|
|
data class C2(val c1: AtomicReference<WorkerBoundReference<C1>>)
|
|
|
|
fun createCyclicGarbageWithAtomicsFrozen(): Triple<AtomicReference<WorkerBoundReference<C1>?>, WeakReference<C1>, WeakReference<C2>> {
|
|
val ref1 = WorkerBoundReference(C1()).freeze()
|
|
val ref1Weak = WeakReference(ref1.value)
|
|
|
|
val ref2 = WorkerBoundReference(C2(AtomicReference(ref1))).freeze()
|
|
val ref2Weak = WeakReference(ref2.value)
|
|
|
|
ref1.value.c2 = AtomicReference(ref2)
|
|
|
|
return Triple(AtomicReference(ref1), ref1Weak, ref2Weak)
|
|
}
|
|
|
|
fun dispose(refOwner: AtomicReference<WorkerBoundReference<C1>?>) {
|
|
refOwner.value!!.value.dispose()
|
|
refOwner.value = null
|
|
}
|
|
|
|
@Test
|
|
fun doesNotCollectCyclicGarbageWithAtomicsFrozen() {
|
|
if (!Platform.isFreezingEnabled) return
|
|
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbageWithAtomicsFrozen()
|
|
|
|
ref1Owner.value = null
|
|
GC.collect()
|
|
|
|
// If these asserts fail, that means AtomicReference<WorkerBoundReference> managed to clean up cyclic garbage all by itself.
|
|
assertNotNull(ref1Weak.value)
|
|
assertNotNull(ref2Weak.value)
|
|
}
|
|
|
|
@Test
|
|
fun collectCyclicGarbageWithAtomicsFrozen() {
|
|
val (ref1Owner, ref1Weak, ref2Weak) = createCyclicGarbageWithAtomicsFrozen()
|
|
|
|
dispose(ref1Owner)
|
|
if (Platform.memoryModel == MemoryModel.EXPERIMENTAL) {
|
|
// Finalizes WorkerBoundReference<C2> and schedules C2 removal from the root set
|
|
GC.collect()
|
|
// Frees C2, finalizes WorkerBoundReference<C1> and schedules C1 removal from the root set
|
|
GC.collect()
|
|
// Frees C1
|
|
GC.collect()
|
|
} else {
|
|
GC.collect()
|
|
}
|
|
|
|
assertNull(ref1Weak.value)
|
|
assertNull(ref2Weak.value)
|
|
}
|
|
|
|
fun createCrossThreadCyclicGarbageWithAtomicsFrozen(
|
|
worker: Worker
|
|
): Triple<AtomicReference<WorkerBoundReference<C1>?>, WeakReference<C1>, WeakReference<C2>> {
|
|
val ref1 = WorkerBoundReference(C1()).freeze()
|
|
val ref1Weak = WeakReference(ref1.value)
|
|
|
|
val future = worker.execute(TransferMode.SAFE, { ref1 }) { ref1 ->
|
|
val ref2 = WorkerBoundReference(C2(AtomicReference(ref1))).freeze()
|
|
Pair(ref2, WeakReference(ref2.value))
|
|
}
|
|
val (ref2, ref2Weak) = future.result
|
|
|
|
ref1.value.c2 = AtomicReference(ref2)
|
|
|
|
return Triple(AtomicReference(ref1), ref1Weak, ref2Weak)
|
|
}
|
|
|
|
@Test
|
|
fun doesNotCollectCrossThreadCyclicGarbageWithAtomicsFrozen() {
|
|
if (!Platform.isFreezingEnabled) return
|
|
val worker = Worker.start()
|
|
|
|
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbageWithAtomicsFrozen(worker)
|
|
|
|
ref1Owner.value = null
|
|
GC.collect()
|
|
worker.execute(TransferMode.SAFE, {}) { GC.collect() }.result
|
|
|
|
// If these asserts fail, that means AtomicReference<WorkerBoundReference> managed to clean up cyclic garbage all by itself.
|
|
assertNotNull(ref1Weak.value)
|
|
assertNotNull(ref2Weak.value)
|
|
|
|
worker.requestTermination().result
|
|
}
|
|
|
|
@Test
|
|
fun collectCrossThreadCyclicGarbageWithAtomicsFrozen() {
|
|
val worker = Worker.start()
|
|
|
|
val (ref1Owner, ref1Weak, ref2Weak) = createCrossThreadCyclicGarbageWithAtomicsFrozen(worker)
|
|
|
|
dispose(ref1Owner)
|
|
// This marks C2 as gone on the main thread
|
|
GC.collect()
|
|
// This cleans up all the references from the worker thread and destroys C2, but C1 is still alive.
|
|
worker.execute(TransferMode.SAFE, {}) { GC.collect() }.result
|
|
// And this finally destroys C1
|
|
GC.collect()
|
|
|
|
assertNull(ref1Weak.value)
|
|
assertNull(ref2Weak.value)
|
|
|
|
worker.requestTermination().result
|
|
} |