Skip to content

Commit 8b8a0f8

Browse files
committed
fix(jvm): make job cancellation hashcode not fail after deserialization
1 parent 6c6df2b commit 8b8a0f8

File tree

2 files changed

+29
-2
lines changed

2 files changed

+29
-2
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,6 @@ internal actual class JobCancellationException public actual constructor(
6161
override fun equals(other: Any?): Boolean =
6262
other === this ||
6363
other is JobCancellationException && other.message == message && other.job == job && other.cause == cause
64-
override fun hashCode(): Int =
65-
(message!!.hashCode() * 31 + job.hashCode()) * 31 + (cause?.hashCode() ?: 0)
64+
override fun hashCode(): Int = /* since job is transient it is indeed nullable after deserialization */
65+
(message!!.hashCode() * 31 + (job?.hashCode() ?: 0)) * 31 + (cause?.hashCode() ?: 0)
6666
}

kotlinx-coroutines-core/jvm/test/JobCancellationExceptionSerializerTest.kt

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,31 @@ class JobCancellationExceptionSerializerTest : TestBase() {
3636
finish(4)
3737
}
3838
}
39+
40+
@Test
41+
fun testHashCodeAfterDeserialization() = runTest {
42+
try {
43+
coroutineScope {
44+
val job = launch {
45+
hang {}
46+
}
47+
throw JobCancellationException(
48+
message = "Job Cancelled",
49+
job = job,
50+
cause = null,
51+
)
52+
}
53+
} catch (e: Throwable) {
54+
val outputStream = ByteArrayOutputStream()
55+
ObjectOutputStream(outputStream).use {
56+
it.writeObject(e)
57+
}
58+
val deserializedException =
59+
ObjectInputStream(outputStream.toByteArray().inputStream()).use {
60+
it.readObject() as JobCancellationException
61+
}
62+
// verify hashCode does not fail even though Job is transient
63+
assert(deserializedException.hashCode() != 0)
64+
}
65+
}
3966
}

0 commit comments

Comments
 (0)