withTimeoutOrNull returns null only when it did timeout itself
Fixed #67
diff --git a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt
index c58c233..774dd3a 100644
--- a/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt
+++ b/kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt
@@ -72,7 +72,7 @@
* The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
* cancellable suspending function inside the block throws [CancellationException], so normally that exception,
* if uncaught, also gets thrown by `withTimeout` as a result.
- * However, the code in the block can suppresses [CancellationException].
+ * However, the code in the block can suppress [CancellationException].
*
* The sibling function that does not throw exception on timeout is [withTimeoutOrNull].
* Note, that timeout action can be specified for [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
@@ -107,19 +107,19 @@
private val cont: Continuation<T>
) : JobSupport(active = true), Runnable, Continuation<T> {
override val context: CoroutineContext = cont.context + this // mix in this Job into the context
- override fun run() { cancel(TimeoutException(time, unit)) }
+ override fun run() { cancel(TimeoutException(time, unit, this)) }
override fun resume(value: T) { cont.resumeDirect(value) }
override fun resumeWithException(exception: Throwable) { cont.resumeDirectWithException(exception) }
}
/**
* Runs a given suspending block of code inside a coroutine with a specified timeout and returns
- * `null` if timeout was exceeded.
+ * `null` if this timeout was exceeded.
*
* The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
* cancellable suspending function inside the block throws [CancellationException]. Normally that exception,
* if uncaught by the block, gets converted into the `null` result of `withTimeoutOrNull`.
- * However, the code in the block can suppresses [CancellationException].
+ * However, the code in the block can suppress [CancellationException].
*
* The sibling function that throws exception on timeout is [withTimeout].
* Note, that timeout action can be specified for [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
@@ -158,14 +158,18 @@
private val cont: Continuation<T?>
) : JobSupport(active = true), Runnable, Continuation<T> {
override val context: CoroutineContext = cont.context + this // mix in this Job into the context
- override fun run() { cancel(TimeoutException(time, unit)) }
+ override fun run() { cancel(TimeoutException(time, unit, this)) }
override fun resume(value: T) { cont.resumeDirect(value) }
override fun resumeWithException(exception: Throwable) {
// suppress inner timeout exception and replace it with null
- if (exception is TimeoutException)
+ if (exception is TimeoutException && exception.coroutine === this)
cont.resumeDirect(null) else
cont.resumeDirectWithException(exception)
}
}
-private class TimeoutException(time: Long, unit: TimeUnit) : CancellationException("Timed out waiting for $time $unit")
\ No newline at end of file
+private class TimeoutException(
+ time: Long,
+ unit: TimeUnit,
+ @JvmField val coroutine: Job
+) : CancellationException("Timed out waiting for $time $unit")
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt b/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt
index 84109ac..13b11e9 100644
--- a/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt
+++ b/kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt
@@ -107,4 +107,35 @@
assertThat(result, IsNull())
finish(2)
}
+
+ @Test(expected = CancellationException::class)
+ fun testInnerTimeoutTest() = runBlocking {
+ withTimeoutOrNull(200) {
+ withTimeout(100) {
+ while (true) {
+ yield()
+ }
+ }
+ expectUnreached() // will timeout
+ }
+ expectUnreached() // will timeout
+ }
+
+ @Test
+ fun testOuterTimeoutTest() = runBlocking {
+ var counter = 0
+ val result = withTimeoutOrNull(250) {
+ while (true) {
+ val inner = withTimeoutOrNull(100) {
+ while (true) {
+ yield()
+ }
+ }
+ assertThat(inner, IsNull())
+ counter++
+ }
+ }
+ assertThat(result, IsNull())
+ assertThat(counter, IsEqual(2))
+ }
}
\ No newline at end of file