Skip to content

Commit 6843648

Browse files
qwwdfsadelizarov
andauthored
Release intercepted SafeCollector when onCompletion block is done (#2323)
* Do not use invokeSafely in onCompletion Co-authored-by: Roman Elizarov <[email protected]>
1 parent 9587590 commit 6843648

File tree

3 files changed

+51
-2
lines changed

3 files changed

+51
-2
lines changed

kotlinx-coroutines-core/common/src/flow/operators/Emitters.kt

+6-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,12 @@ public fun <T> Flow<T>.onCompletion(
158158
throw e
159159
}
160160
// Normal completion
161-
SafeCollector(this, currentCoroutineContext()).invokeSafely(action, null)
161+
val sc = SafeCollector(this, currentCoroutineContext())
162+
try {
163+
sc.action(null)
164+
} finally {
165+
sc.releaseIntercepted()
166+
}
162167
}
163168

164169
/**

kotlinx-coroutines-core/jvm/src/flow/internal/SafeCollector.kt

-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ internal actual class SafeCollector<T> actual constructor(
5555
*/
5656
override suspend fun emit(value: T) {
5757
return suspendCoroutineUninterceptedOrReturn sc@{ uCont ->
58-
// Update information about caller for stackwalking
5958
try {
6059
emit(uCont, value)
6160
} catch (e: Throwable) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*
2+
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines.flow
6+
7+
import kotlinx.coroutines.*
8+
import org.junit.Test
9+
import kotlin.coroutines.*
10+
import kotlin.test.*
11+
12+
class OnCompletionInterceptedReleaseTest : TestBase() {
13+
@Test
14+
fun testLeak() = runTest {
15+
expect(1)
16+
var cont: Continuation<Unit>? = null
17+
val interceptor = CountingInterceptor()
18+
val job = launch(interceptor, start = CoroutineStart.UNDISPATCHED) {
19+
emptyFlow<Int>()
20+
.onCompletion { emit(1) }
21+
.collect { value ->
22+
expect(2)
23+
assertEquals(1, value)
24+
suspendCoroutine { cont = it }
25+
}
26+
}
27+
cont!!.resume(Unit)
28+
assertTrue(job.isCompleted)
29+
assertEquals(interceptor.intercepted, interceptor.released)
30+
finish(3)
31+
}
32+
33+
class CountingInterceptor : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
34+
var intercepted = 0
35+
var released = 0
36+
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
37+
intercepted++
38+
return Continuation(continuation.context) { continuation.resumeWith(it) }
39+
}
40+
41+
override fun releaseInterceptedContinuation(continuation: Continuation<*>) {
42+
released++
43+
}
44+
}
45+
}

0 commit comments

Comments
 (0)