Skip to content

Commit 9ec881e

Browse files
committed
Dispose waiting node when Deferred.await is cancelled
This is part of the problem that is shown in issue #893. The other part of the problem (what happens during race) is addressed by PR #896 Fixes #893
1 parent 4327b21 commit 9ec881e

File tree

2 files changed

+56
-1
lines changed

2 files changed

+56
-1
lines changed

common/kotlinx-coroutines-core-common/src/JobSupport.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1106,7 +1106,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
11061106
*/
11071107
val cont = AwaitContinuation(uCont.intercepted(), this)
11081108
cont.initCancellability()
1109-
invokeOnCompletion(ResumeAwaitOnCompletion(this, cont).asHandler)
1109+
cont.disposeOnCancellation(invokeOnCompletion(ResumeAwaitOnCompletion(this, cont).asHandler))
11101110
cont.getResult()
11111111
}
11121112

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
package kotlinx.coroutines
6+
7+
import org.junit.*
8+
9+
class CancelledAwaitStressTest : TestBase() {
10+
private val n = 1000 * stressTestMultiplier
11+
12+
/**
13+
* Tests that memory does not leak from cancelled [Deferred.await]
14+
*/
15+
@Test
16+
fun testCancelledAwait() = runTest {
17+
val d = async {
18+
delay(Long.MAX_VALUE)
19+
}
20+
repeat(n) {
21+
val waiter = launch(start = CoroutineStart.UNDISPATCHED) {
22+
val a = ByteArray(10000000) // allocate 10M of memory here
23+
d.await()
24+
keepMe(a) // make sure it is kept in state machine
25+
}
26+
waiter.cancel() // cancel await
27+
yield() // complete the waiter job, release its memory
28+
}
29+
d.cancel() // done test
30+
}
31+
32+
/**
33+
* Tests that memory does not leak from cancelled [Job.join]
34+
*/
35+
@Test
36+
fun testCancelledJoin() = runTest {
37+
val j = launch {
38+
delay(Long.MAX_VALUE)
39+
}
40+
repeat(n) {
41+
val joiner = launch(start = CoroutineStart.UNDISPATCHED) {
42+
val a = ByteArray(10000000) // allocate 10M of memory here
43+
j.join()
44+
keepMe(a) // make sure it is kept in state machine
45+
}
46+
joiner.cancel() // cancel join
47+
yield() // complete the joiner job, release its memory
48+
}
49+
j.cancel() // done test
50+
}
51+
52+
private fun keepMe(a: ByteArray) {
53+
// does nothing, makes sure the variable is kept in state-machine
54+
}
55+
}

0 commit comments

Comments
 (0)