Skip to content

Commit c7c2779

Browse files
committed
Make tests both more robust and more depressing to look at
1 parent 4c69d1c commit c7c2779

File tree

2 files changed

+15
-12
lines changed

2 files changed

+15
-12
lines changed

kotlinx-coroutines-core/jvm/src/CoroutineContext.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,8 @@ internal actual class UndispatchedCoroutine<in T>actual constructor (
185185
* `withContext` for the sake of logging, MDC, tracing etc., meaning that there exists thousands of
186186
* undispatched coroutines.
187187
* Each access to Java's [ThreadLocal] leaves a footprint in the corresponding Thread's `ThreadLocalMap`
188-
* that is cleared automatically as soon as the associated thread-local (-> UndispatchedCoroutine) is garbage collected.
188+
* that is cleared automatically as soon as the associated thread-local (-> UndispatchedCoroutine) is garbage collected
189+
* when either the corresponding thread is GC'ed or it cleans up its stale entries on other TL accesses.
189190
* When such coroutines are promoted to old generation, `ThreadLocalMap`s become bloated and an arbitrary accesses to thread locals
190191
* start to consume significant amount of CPU because these maps are open-addressed and cleaned up incrementally on each access.
191192
* (You can read more about this effect as "GC nepotism").

kotlinx-coroutines-core/jvm/test/ThreadLocalsLeaksTest.kt

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,42 +35,45 @@ class ThreadLocalCustomContinuationInterceptorTest : TestBase() {
3535
override fun equals(other: Any?) = false
3636
}
3737

38-
@Test
38+
@Test(timeout = 20_000L)
3939
fun testDefaultDispatcherNoSuspension() = ensureCoroutineContextGCed(Dispatchers.Default, suspend = false)
4040

41-
@Test
41+
@Test(timeout = 20_000L)
4242
fun testDefaultDispatcher() = ensureCoroutineContextGCed(Dispatchers.Default, suspend = true)
4343

4444

45-
@Test
45+
@Test(timeout = 20_000L)
4646
fun testNonCoroutineDispatcher() = ensureCoroutineContextGCed(
4747
CustomContinuationInterceptor(Dispatchers.Default),
4848
suspend = true
4949
)
5050

51-
@Test
51+
@Test(timeout = 20_000L)
5252
fun testNonCoroutineDispatcherSuspension() = ensureCoroutineContextGCed(
5353
CustomContinuationInterceptor(Dispatchers.Default),
5454
suspend = false
5555
)
5656

5757
// Note asymmetric equals codepath never goes through the undispatched withContext, thus the separate test case
5858

59-
@Test
59+
@Test(timeout = 20_000L)
6060
fun testNonCoroutineDispatcherAsymmetricEquals() =
6161
ensureCoroutineContextGCed(
6262
CustomNeverEqualContinuationInterceptor(Dispatchers.Default),
6363
suspend = true
6464
)
6565

66-
@Test
66+
@Test(timeout = 20_000L)
6767
fun testNonCoroutineDispatcherAsymmetricEqualsSuspension() =
6868
ensureCoroutineContextGCed(
6969
CustomNeverEqualContinuationInterceptor(Dispatchers.Default),
7070
suspend = false
7171
)
7272

7373

74+
@Volatile
75+
private var letThatSinkIn: Any = "What is my purpose? To frag the garbage collctor"
76+
7477
private fun ensureCoroutineContextGCed(coroutineContext: CoroutineContext, suspend: Boolean) {
7578
runTest {
7679
lateinit var ref: WeakReference<CoroutineName>
@@ -85,11 +88,10 @@ class ThreadLocalCustomContinuationInterceptorTest : TestBase() {
8588
}
8689
job.join()
8790

88-
// Twice is enough to ensure
89-
System.gc()
90-
System.gc()
91-
assertNull(ref.get())
91+
while (ref.get() != null) {
92+
System.gc()
93+
letThatSinkIn = LongArray(1024 * 1024)
94+
}
9295
}
9396
}
94-
9597
}

0 commit comments

Comments
 (0)