Skip to content

Commit 993c192

Browse files
authored
Cleanup lazy coroutines that have been cancelled but not yet garbage … (#2315)
Cleanup lazy coroutines that have been cancelled but not yet garbage collected Fixes #2294
1 parent 20ad25f commit 993c192

File tree

2 files changed

+45
-1
lines changed

2 files changed

+45
-1
lines changed

kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt

+22-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,11 @@ internal object DebugProbesImpl {
156156
// Stable ordering of coroutines by their sequence number
157157
.sortedBy { it.info.sequenceNumber }
158158
// Leave in the dump only the coroutines that were not collected while we were dumping them
159-
.mapNotNull { owner -> owner.info.context?.let { context -> create(owner, context) } }
159+
.mapNotNull { owner ->
160+
// Fuse map and filter into one operation to save an inline
161+
if (owner.isFinished()) null
162+
else owner.info.context?.let { context -> create(owner, context) }
163+
}
160164
}
161165

162166
/*
@@ -183,10 +187,27 @@ internal object DebugProbesImpl {
183187
dumpCoroutinesSynchronized(out)
184188
}
185189

190+
/*
191+
* Filters out coroutines that do not call probeCoroutineCompleted,
192+
* are completed, but not yet garbage collected.
193+
*
194+
* Typically, we intercept completion of the coroutine so it invokes "probeCoroutineCompleted",
195+
* but it's not the case for lazy coroutines that get cancelled before start.
196+
*/
197+
private fun CoroutineOwner<*>.isFinished(): Boolean {
198+
// Guarded by lock
199+
val job = info.context?.get(Job) ?: return false
200+
if (!job.isCompleted) return false
201+
capturedCoroutinesMap.remove(this) // Clean it up by the way
202+
return true
203+
}
204+
186205
private fun dumpCoroutinesSynchronized(out: PrintStream): Unit = coroutineStateLock.write {
187206
check(isInstalled) { "Debug probes are not installed" }
188207
out.print("Coroutines dump ${dateFormat.format(System.currentTimeMillis())}")
189208
capturedCoroutines
209+
.asSequence()
210+
.filter { !it.isFinished() }
190211
.sortedBy { it.info.sequenceNumber }
191212
.forEach { owner ->
192213
val info = owner.info
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
package kotlinx.coroutines.debug
5+
6+
import kotlinx.coroutines.*
7+
import org.junit.Test
8+
import kotlin.test.*
9+
10+
class LazyCoroutineTest : DebugTestBase() {
11+
12+
@Test
13+
fun testLazyCompletedCoroutine() = runTest {
14+
val job = launch(start = CoroutineStart.LAZY) {}
15+
job.invokeOnCompletion { expect(2) }
16+
expect(1)
17+
job.cancelAndJoin()
18+
expect(3)
19+
assertEquals(1, DebugProbes.dumpCoroutinesInfo().size) // Outer runBlocking
20+
verifyPartialDump(1, "BlockingCoroutine{Active}")
21+
finish(4)
22+
}
23+
}

0 commit comments

Comments
 (0)