Skip to content

Commit d40add7

Browse files
committed
Demonstrate TestCoroutineScheduler leaking canceled delayed jobs
1 parent 6cd4ad3 commit d40add7

File tree

1 file changed

+58
-0
lines changed

1 file changed

+58
-0
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import kotlinx.coroutines.*
2+
import kotlinx.coroutines.test.*
3+
import java.lang.ref.*
4+
import kotlin.test.*
5+
6+
class TestCoroutineSchedulerJvmTest {
7+
8+
/**
9+
* This test demonstrates a bug in the TestCoroutineScheduler implementation: canceled delayed
10+
* jobs are held in memory by TestCoroutineScheduler until the TestCoroutineScheduler current
11+
* time has moved past the job's planned time, even though said job has long been canceled.
12+
*
13+
* Canceled jobs should instead be immediately removed from TestCoroutineScheduler#events.
14+
*/
15+
@Test
16+
fun testCancellationLeakInTestCoroutineScheduler() = runTest {
17+
lateinit var weakRef: WeakReference<*>
18+
val delayedLeakingJob = launch {
19+
val leakingObject = Any()
20+
weakRef = WeakReference(leakingObject)
21+
// This delay prevents the job from finishing.
22+
delay(3)
23+
// This is never called as job is canceled before we get here. However this code
24+
// holds a reference to leakingObject, preventing it from becoming weakly reachable
25+
// until the job itself is weakly reachable.
26+
println(leakingObject)
27+
}
28+
29+
// Start running the job and hit delay.
30+
advanceTimeBy(1)
31+
32+
// At this point, delayedLeakingJob is still in the queue and holding on to leakingObject.
33+
System.gc()
34+
assertNotNull(weakRef.get())
35+
36+
// We're canceling the job, and now expect leakingObject to become weakly reachable.
37+
delayedLeakingJob.cancel()
38+
39+
// Surprise: the canceled job is not weakly reachable! TestCoroutineScheduler is holding
40+
// on to it in its TestCoroutineScheduler#events queue.
41+
System.gc()
42+
assertNotNull(weakRef.get())
43+
44+
// Let's move time forward without going over the delay yet.
45+
advanceTimeBy(1)
46+
47+
// Still not weakly reachable.
48+
System.gc()
49+
assertNotNull(weakRef.get())
50+
51+
// Now we move past the delay
52+
advanceTimeBy(2)
53+
54+
// The job is finally weakly reachable and leakingObject is released.
55+
System.gc()
56+
assertNull(weakRef.get())
57+
}
58+
}

0 commit comments

Comments
 (0)