// 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) fun getOwnerAndWeaks(initial: Int): Triple?>, WeakReference>, WeakReference> { val ref = WorkerBoundReference(A(initial)) val refOwner: AtomicReference?> = 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?>, WeakReference>, WeakReference> { val ref = WorkerBoundReference(A(initial)).freeze() val refOwner: AtomicReference?> = 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, 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, Future> { 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> { 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 } data class B2(val b1: WorkerBoundReference) fun createCyclicGarbage(): Triple?>, WeakReference, WeakReference> { val ref1 = WorkerBoundReference(B1()) val ref1Owner: AtomicReference?> = 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?>, WeakReference, WeakReference> { val ref1 = WorkerBoundReference(B1()).freeze() val ref1Owner: AtomicReference?> = 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?>, WeakReference, WeakReference> { val ref1 = WorkerBoundReference(B1()).freeze() val ref1Owner: AtomicReference?> = 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?> fun dispose() { c2.value = null } } data class C2(val c1: AtomicReference>) fun createCyclicGarbageWithAtomicsFrozen(): Triple?>, WeakReference, WeakReference> { 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?>) { 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 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 and schedules C2 removal from the root set GC.collect() // Frees C2, finalizes WorkerBoundReference 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?>, WeakReference, WeakReference> { 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 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 }