Skip to content

Commit 541a9b6

Browse files
committed
Job unwraps all instances of CancellationException
* This makes exception handling fully consistent, since CancellationException is considered "normal", never goes to logs and does not cause parent cancellation. * Solves exception transparency of withTimeout/withTimeoutOrNull -- exception throws from inside gets rethrown outside * Makes sure that closed/cancelled producer that throws ClosedSendChannelException is not considered to be failed
1 parent 852fae5 commit 541a9b6

File tree

7 files changed

+32
-86
lines changed

7 files changed

+32
-86
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, ChildJob, Sel
247247
* Note that it's only happening when both parent and child throw exception simultaneously.
248248
*/
249249
var rootCause = exceptions[0]
250-
if (rootCause is JobCancellationException) {
250+
if (rootCause is CancellationException) {
251251
val cause = unwrap(rootCause)
252252
rootCause = if (cause !== null) {
253253
cause
@@ -275,7 +275,7 @@ internal open class JobSupport constructor(active: Boolean) : Job, ChildJob, Sel
275275
}
276276

277277
private tailrec fun unwrap(exception: Throwable): Throwable? =
278-
if (exception is JobCancellationException) {
278+
if (exception is CancellationException) {
279279
val cause = exception.cause
280280
if (cause !== null) unwrap(cause) else null
281281
} else {

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

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,6 @@ import kotlin.coroutines.experimental.intrinsics.*
1616
*
1717
* The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
1818
* cancellable suspending function inside the block throws [TimeoutCancellationException].
19-
* Even if the code in the block suppresses [TimeoutCancellationException], it
20-
* is still thrown by `withTimeout` invocation.
2119
*
2220
* The sibling function that does not throw exception on timeout is [withTimeoutOrNull].
2321
* Note, that timeout action can be specified for [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
@@ -40,8 +38,6 @@ public suspend fun <T> withTimeout(timeMillis: Long, block: suspend CoroutineSco
4038
*
4139
* The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
4240
* cancellable suspending function inside the block throws [TimeoutCancellationException].
43-
* **NB**: this method is exception-unsafe. Even if the code in the block suppresses [TimeoutCancellationException], this
44-
* invocation of `withTimeoutOrNull` still returns `null` and thrown exception will be ignored.
4541
*
4642
* The sibling function that throws exception on timeout is [withTimeout].
4743
* Note, that timeout action can be specified for [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.

common/kotlinx-coroutines-core-common/test/WithTimeoutOrNullTest.kt

Lines changed: 15 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -190,19 +190,24 @@ class WithTimeoutOrNullTest : TestBase() {
190190
@Test
191191
fun testSuppressExceptionWithAnotherException() = runTest {
192192
expect(1)
193-
val value = withTimeoutOrNull(100) {
194-
expect(2)
195-
try {
196-
delay(1000)
197-
} catch (e: CancellationException) {
198-
finish(3)
199-
throw TestException()
193+
try {
194+
withTimeoutOrNull(100) {
195+
expect(2)
196+
try {
197+
delay(1000)
198+
} catch (e: CancellationException) {
199+
expect(3)
200+
throw TestException()
201+
}
202+
expectUnreached()
203+
"OK"
200204
}
201205
expectUnreached()
202-
"OK"
203-
}
206+
} catch (e: TestException) {
207+
// catches TestException
208+
finish(4)
204209

205-
assertNull(value)
210+
}
206211
}
207212

208213
private class TestException : Exception()

common/kotlinx-coroutines-core-common/test/WithTimeoutTest.kt

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -147,21 +147,24 @@ class WithTimeoutTest : TestBase() {
147147
}
148148

149149
@Test
150-
fun testSuppressExceptionWithAnotherException() = runTest(expected = { it is TimeoutCancellationException }) {
150+
fun testSuppressExceptionWithAnotherException() = runTest{
151151
expect(1)
152-
withTimeout(100) {
153-
expect(2)
154-
try {
155-
delay(1000)
156-
} catch (e: CancellationException) {
157-
finish(3)
158-
throw TestException()
152+
try {
153+
withTimeout(100) {
154+
expect(2)
155+
try {
156+
delay(1000)
157+
} catch (e: CancellationException) {
158+
expect(3)
159+
throw TestException()
160+
}
161+
expectUnreached()
162+
"OK"
159163
}
160164
expectUnreached()
161-
"OK"
165+
} catch (e: TestException) {
166+
finish(4)
162167
}
163-
164-
expectUnreached()
165168
}
166169

167170
private class TestException : Exception()

common/kotlinx-coroutines-core-common/test/channels/ProduceTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ class ProduceTest : TestBase() {
3838
send(2) // will get cancelled
3939
} catch (e: Throwable) {
4040
finish(7)
41-
check(e is JobCancellationException && e.job == coroutineContext[Job])
41+
check(e is ClosedSendChannelException)
4242
throw e
4343
}
4444
expectUnreached()

core/kotlinx-coroutines-core/test/WithTimeoutJvmTest.kt

Lines changed: 0 additions & 36 deletions
This file was deleted.

core/kotlinx-coroutines-core/test/WithTimeoutOrNullJvmTest.kt

Lines changed: 0 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,28 +8,6 @@ import java.io.*
88
import kotlin.test.*
99

1010
class WithTimeoutOrNullJvmTest : TestBase() {
11-
12-
13-
@Test
14-
fun testCancellationSuppression() = runTest {
15-
16-
expect(1)
17-
val value = withTimeoutOrNull(100) {
18-
expect(2)
19-
try {
20-
delay(1000)
21-
} catch (e: CancellationException) {
22-
expect(3)
23-
throw IOException()
24-
}
25-
expectUnreached()
26-
"OK"
27-
}
28-
29-
assertNull(value)
30-
finish(4)
31-
}
32-
3311
@Test
3412
fun testOuterTimeoutFiredBeforeInner() = runTest {
3513
val result = withTimeoutOrNull(100) {

0 commit comments

Comments
 (0)