Skip to content

Commit dfe6f06

Browse files
authored
Ensure that all coroutines throwables in core are serializable (#3337)
* Ensure that all coroutines throwables in core are serializable Fixes #3328
1 parent 2ee99e2 commit dfe6f06

File tree

6 files changed

+107
-5
lines changed

6 files changed

+107
-5
lines changed

integration-testing/build.gradle

+18-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,16 @@ dependencies {
2323
}
2424

2525
sourceSets {
26+
withGuavaTest {
27+
kotlin
28+
compileClasspath += sourceSets.test.runtimeClasspath
29+
runtimeClasspath += sourceSets.test.runtimeClasspath
30+
31+
dependencies {
32+
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
33+
implementation 'com.google.guava:guava:31.1-jre'
34+
}
35+
}
2636
mavenTest {
2737
kotlin
2838
compileClasspath += sourceSets.test.runtimeClasspath
@@ -60,6 +70,13 @@ compileDebugAgentTestKotlin {
6070
}
6171
}
6272

73+
task withGuavaTest(type: Test) {
74+
environment "version", coroutines_version
75+
def sourceSet = sourceSets.withGuavaTest
76+
testClassesDirs = sourceSet.output.classesDirs
77+
classpath = sourceSet.runtimeClasspath
78+
}
79+
6380
task mavenTest(type: Test) {
6481
environment "version", coroutines_version
6582
def sourceSet = sourceSets.mavenTest
@@ -89,5 +106,5 @@ compileTestKotlin {
89106
}
90107

91108
check {
92-
dependsOn([mavenTest, debugAgentTest, coreAgentTest, 'smokeTest:build'])
109+
dependsOn([withGuavaTest, mavenTest, debugAgentTest, coreAgentTest, 'smokeTest:build'])
93110
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2016-2022 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 com.google.common.reflect.*
8+
import kotlinx.coroutines.*
9+
import org.junit.Test
10+
import kotlin.test.*
11+
12+
class ListAllCoroutineThrowableSubclassesTest {
13+
14+
/*
15+
* These are all the known throwables in kotlinx.coroutines.
16+
* If you add one, this test will fail to make
17+
* you ensure your exception type is java.io.Serializable.
18+
*
19+
* We do not have means to check it automatically, so checks are delegated to humans.
20+
*
21+
* See #3328 for serialization rationale.
22+
*/
23+
private val knownThrowables = setOf(
24+
"kotlinx.coroutines.TimeoutCancellationException",
25+
"kotlinx.coroutines.JobCancellationException",
26+
"kotlinx.coroutines.internal.UndeliveredElementException",
27+
"kotlinx.coroutines.CompletionHandlerException",
28+
"kotlinx.coroutines.DiagnosticCoroutineContextException",
29+
"kotlinx.coroutines.CoroutinesInternalError",
30+
"kotlinx.coroutines.channels.ClosedSendChannelException",
31+
"kotlinx.coroutines.channels.ClosedReceiveChannelException",
32+
"kotlinx.coroutines.flow.internal.ChildCancelledException",
33+
"kotlinx.coroutines.flow.internal.AbortFlowException",
34+
)
35+
36+
@Test
37+
fun testThrowableSubclassesAreSerializable() {
38+
val classes = ClassPath.from(this.javaClass.classLoader)
39+
.getTopLevelClassesRecursive("kotlinx.coroutines");
40+
val throwables = classes.filter { Throwable::class.java.isAssignableFrom(it.load()) }.map { it.toString() }
41+
assertEquals(knownThrowables.sorted(), throwables.sorted())
42+
}
43+
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ private class TimeoutCoroutine<U, in T: U>(
163163
*/
164164
public class TimeoutCancellationException internal constructor(
165165
message: String,
166-
@JvmField internal val coroutine: Job?
166+
@JvmField @Transient internal val coroutine: Job?
167167
) : CancellationException(message), CopyableThrowable<TimeoutCancellationException> {
168168
/**
169169
* Creates a timeout exception with the given message.
@@ -173,7 +173,7 @@ public class TimeoutCancellationException internal constructor(
173173
internal constructor(message: String) : this(message, null)
174174

175175
// message is never null in fact
176-
override fun createCopy(): TimeoutCancellationException? =
176+
override fun createCopy(): TimeoutCancellationException =
177177
TimeoutCancellationException(message ?: "", coroutine).also { it.initCause(this) }
178178
}
179179

kotlinx-coroutines-core/jvm/src/Exceptions.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public actual fun CancellationException(message: String?, cause: Throwable?) : C
2929
internal actual class JobCancellationException public actual constructor(
3030
message: String,
3131
cause: Throwable?,
32-
@JvmField internal actual val job: Job
32+
@JvmField @Transient internal actual val job: Job
3333
) : CancellationException(message), CopyableThrowable<JobCancellationException> {
3434

3535
init {

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import kotlinx.coroutines.*
88
import kotlinx.coroutines.flow.*
99

1010
internal actual class AbortFlowException actual constructor(
11-
actual val owner: FlowCollector<*>
11+
@JvmField @Transient actual val owner: FlowCollector<*>
1212
) : CancellationException("Flow was aborted, no more elements needed") {
1313

1414
override fun fillInStackTrace(): Throwable {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/*
2+
* Copyright 2016-2022 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+
import java.io.*
9+
10+
11+
@Suppress("BlockingMethodInNonBlockingContext")
12+
class JobCancellationExceptionSerializerTest : TestBase() {
13+
14+
@Test
15+
fun testSerialization() = runTest {
16+
try {
17+
coroutineScope {
18+
expect(1)
19+
20+
launch {
21+
expect(2)
22+
try {
23+
hang {}
24+
} catch (e: CancellationException) {
25+
throw RuntimeException("RE2", e)
26+
}
27+
}
28+
29+
launch {
30+
expect(3)
31+
throw RuntimeException("RE1")
32+
}
33+
}
34+
} catch (e: Throwable) {
35+
// Should not fail
36+
ObjectOutputStream(ByteArrayOutputStream()).use {
37+
it.writeObject(e)
38+
}
39+
finish(4)
40+
}
41+
}
42+
}

0 commit comments

Comments
 (0)