Skip to content

Commit 6e66695

Browse files
authored
Fix leak of subscription reference in closed ArrayBroadcastChannel (#1885)
When ArrayBroadcastChannel was closed it was still retaining a reference to its subscription (even if that subscription was cancelled) while, in fact, either closing a broadcast channel or cancelling subscription should remove the reference. This is no problem with ConflatedBroadcastChannel, but it is added to the test for completeness.
1 parent 52dcddc commit 6e66695

File tree

2 files changed

+37
-1
lines changed

2 files changed

+37
-1
lines changed

kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -218,13 +218,15 @@ internal class ArrayBroadcastChannel<E>(
218218
override val isBufferAlwaysFull: Boolean get() = error("Should not be used")
219219
override val isBufferFull: Boolean get() = error("Should not be used")
220220

221-
override fun onCancelIdempotent(wasClosed: Boolean) {
221+
override fun close(cause: Throwable?): Boolean {
222+
val wasClosed = super.close(cause)
222223
if (wasClosed) {
223224
broadcastChannel.updateHead(removeSub = this)
224225
subLock.withLock {
225226
subHead = broadcastChannel.tail
226227
}
227228
}
229+
return wasClosed
228230
}
229231

230232
// returns true if subHead was updated and broadcast channel's head must be checked
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package kotlinx.coroutines.channels
2+
3+
import kotlinx.coroutines.*
4+
import org.junit.Test
5+
import kotlin.test.*
6+
7+
class BroadcastChannelLeakTest : TestBase() {
8+
@Test
9+
fun testArrayBroadcastChannelSubscriptionLeak() {
10+
checkLeak { ArrayBroadcastChannel(1) }
11+
}
12+
13+
@Test
14+
fun testConflatedBroadcastChannelSubscriptionLeak() {
15+
checkLeak { ConflatedBroadcastChannel() }
16+
}
17+
18+
enum class TestKind { BROADCAST_CLOSE, SUB_CANCEL, BOTH }
19+
20+
private fun checkLeak(factory: () -> BroadcastChannel<String>) = runTest {
21+
for (kind in TestKind.values()) {
22+
val broadcast = factory()
23+
val sub = broadcast.openSubscription()
24+
broadcast.send("OK")
25+
assertEquals("OK", sub.receive())
26+
// now close broadcast
27+
if (kind != TestKind.SUB_CANCEL) broadcast.close()
28+
// and then cancel subscription
29+
if (kind != TestKind.BROADCAST_CLOSE) sub.cancel()
30+
// subscription should not be reachable from the channel anymore
31+
FieldWalker.assertReachableCount(0, broadcast) { it === sub }
32+
}
33+
}
34+
}

0 commit comments

Comments
 (0)