From 459282c45c288cfd427e6b18daaa0cb2a0288b10 Mon Sep 17 00:00:00 2001 From: Pierre-Yves Ricau Date: Tue, 9 Aug 2022 16:21:14 -0700 Subject: [PATCH] Demonstrate TestCoroutineScheduler leaking canceled delayed jobs --- .../jvm/test/TestCoroutineSchedulerJvmTest.kt | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 kotlinx-coroutines-test/jvm/test/TestCoroutineSchedulerJvmTest.kt diff --git a/kotlinx-coroutines-test/jvm/test/TestCoroutineSchedulerJvmTest.kt b/kotlinx-coroutines-test/jvm/test/TestCoroutineSchedulerJvmTest.kt new file mode 100644 index 0000000000..805e0143a0 --- /dev/null +++ b/kotlinx-coroutines-test/jvm/test/TestCoroutineSchedulerJvmTest.kt @@ -0,0 +1,58 @@ +import kotlinx.coroutines.* +import kotlinx.coroutines.test.* +import java.lang.ref.* +import kotlin.test.* + +class TestCoroutineSchedulerJvmTest { + + /** + * This test demonstrates a bug in the TestCoroutineScheduler implementation: canceled delayed + * jobs are held in memory by TestCoroutineScheduler until the TestCoroutineScheduler current + * time has moved past the job's planned time, even though said job has long been canceled. + * + * Canceled jobs should instead be immediately removed from TestCoroutineScheduler#events. + */ + @Test + fun testCancellationLeakInTestCoroutineScheduler() = runTest { + lateinit var weakRef: WeakReference<*> + val delayedLeakingJob = launch { + val leakingObject = Any() + weakRef = WeakReference(leakingObject) + // This delay prevents the job from finishing. + delay(3) + // This is never called as job is canceled before we get here. However this code + // holds a reference to leakingObject, preventing it from becoming weakly reachable + // until the job itself is weakly reachable. + println(leakingObject) + } + + // Start running the job and hit delay. + advanceTimeBy(1) + + // At this point, delayedLeakingJob is still in the queue and holding on to leakingObject. + System.gc() + assertNotNull(weakRef.get()) + + // We're canceling the job, and now expect leakingObject to become weakly reachable. + delayedLeakingJob.cancel() + + // Surprise: the canceled job is not weakly reachable! TestCoroutineScheduler is holding + // on to it in its TestCoroutineScheduler#events queue. + System.gc() + assertNotNull(weakRef.get()) + + // Let's move time forward without going over the delay yet. + advanceTimeBy(1) + + // Still not weakly reachable. + System.gc() + assertNotNull(weakRef.get()) + + // Now we move past the delay + advanceTimeBy(2) + + // The job is finally weakly reachable and leakingObject is released. + System.gc() + assertNull(weakRef.get()) + } +} \ No newline at end of file