Skip to content

Commit 607f893

Browse files
committed
withTimeoutOrNull returns null only when it did timeout itself
Fixed #67
1 parent 2113d1c commit 607f893

File tree

2 files changed

+42
-7
lines changed

2 files changed

+42
-7
lines changed

kotlinx-coroutines-core/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ internal fun scheduledExecutorShutdownNowAndRelease() {
7272
* The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
7373
* cancellable suspending function inside the block throws [CancellationException], so normally that exception,
7474
* if uncaught, also gets thrown by `withTimeout` as a result.
75-
* However, the code in the block can suppresses [CancellationException].
75+
* However, the code in the block can suppress [CancellationException].
7676
*
7777
* The sibling function that does not throw exception on timeout is [withTimeoutOrNull].
7878
* Note, that timeout action can be specified for [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
@@ -107,19 +107,19 @@ private class TimeoutExceptionCoroutine<in T>(
107107
private val cont: Continuation<T>
108108
) : JobSupport(active = true), Runnable, Continuation<T> {
109109
override val context: CoroutineContext = cont.context + this // mix in this Job into the context
110-
override fun run() { cancel(TimeoutException(time, unit)) }
110+
override fun run() { cancel(TimeoutException(time, unit, this)) }
111111
override fun resume(value: T) { cont.resumeDirect(value) }
112112
override fun resumeWithException(exception: Throwable) { cont.resumeDirectWithException(exception) }
113113
}
114114

115115
/**
116116
* Runs a given suspending block of code inside a coroutine with a specified timeout and returns
117-
* `null` if timeout was exceeded.
117+
* `null` if this timeout was exceeded.
118118
*
119119
* The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
120120
* cancellable suspending function inside the block throws [CancellationException]. Normally that exception,
121121
* if uncaught by the block, gets converted into the `null` result of `withTimeoutOrNull`.
122-
* However, the code in the block can suppresses [CancellationException].
122+
* However, the code in the block can suppress [CancellationException].
123123
*
124124
* The sibling function that throws exception on timeout is [withTimeout].
125125
* Note, that timeout action can be specified for [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
@@ -158,14 +158,18 @@ private class TimeoutNullCoroutine<in T>(
158158
private val cont: Continuation<T?>
159159
) : JobSupport(active = true), Runnable, Continuation<T> {
160160
override val context: CoroutineContext = cont.context + this // mix in this Job into the context
161-
override fun run() { cancel(TimeoutException(time, unit)) }
161+
override fun run() { cancel(TimeoutException(time, unit, this)) }
162162
override fun resume(value: T) { cont.resumeDirect(value) }
163163
override fun resumeWithException(exception: Throwable) {
164164
// suppress inner timeout exception and replace it with null
165-
if (exception is TimeoutException)
165+
if (exception is TimeoutException && exception.coroutine === this)
166166
cont.resumeDirect(null) else
167167
cont.resumeDirectWithException(exception)
168168
}
169169
}
170170

171-
private class TimeoutException(time: Long, unit: TimeUnit) : CancellationException("Timed out waiting for $time $unit")
171+
private class TimeoutException(
172+
time: Long,
173+
unit: TimeUnit,
174+
@JvmField val coroutine: Job
175+
) : CancellationException("Timed out waiting for $time $unit")

kotlinx-coroutines-core/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,35 @@ class WithTimeoutOrNullTest : TestBase() {
107107
assertThat(result, IsNull())
108108
finish(2)
109109
}
110+
111+
@Test(expected = CancellationException::class)
112+
fun testInnerTimeoutTest() = runBlocking {
113+
withTimeoutOrNull(200) {
114+
withTimeout(100) {
115+
while (true) {
116+
yield()
117+
}
118+
}
119+
expectUnreached() // will timeout
120+
}
121+
expectUnreached() // will timeout
122+
}
123+
124+
@Test
125+
fun testOuterTimeoutTest() = runBlocking {
126+
var counter = 0
127+
val result = withTimeoutOrNull(250) {
128+
while (true) {
129+
val inner = withTimeoutOrNull(100) {
130+
while (true) {
131+
yield()
132+
}
133+
}
134+
assertThat(inner, IsNull())
135+
counter++
136+
}
137+
}
138+
assertThat(result, IsNull())
139+
assertThat(counter, IsEqual(2))
140+
}
110141
}

0 commit comments

Comments
 (0)