Skip to content

Commit 988eb26

Browse files
committed
Implement withTimeoutOrNull via withTimeout to avoid timing bugs and races.
Remove deprecated API Fixes #498
1 parent 44f52b8 commit 988eb26

File tree

3 files changed

+45
-36
lines changed

3 files changed

+45
-36
lines changed

binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt

-4
Original file line numberDiff line numberDiff line change
@@ -416,14 +416,10 @@ public final class kotlinx/coroutines/experimental/RunnableKt {
416416

417417
public final class kotlinx/coroutines/experimental/ScheduledKt {
418418
public static final fun withTimeout (ILkotlin/jvm/functions/Function2;Lkotlin/coroutines/experimental/Continuation;)Ljava/lang/Object;
419-
public static final synthetic fun withTimeout (JLjava/util/concurrent/TimeUnit;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/experimental/Continuation;)Ljava/lang/Object;
420419
public static final fun withTimeout (JLjava/util/concurrent/TimeUnit;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/experimental/Continuation;)Ljava/lang/Object;
421-
public static synthetic fun withTimeout$default (JLjava/util/concurrent/TimeUnit;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/experimental/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
422420
public static synthetic fun withTimeout$default (JLjava/util/concurrent/TimeUnit;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/experimental/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
423421
public static final fun withTimeoutOrNull (ILkotlin/jvm/functions/Function2;Lkotlin/coroutines/experimental/Continuation;)Ljava/lang/Object;
424-
public static final synthetic fun withTimeoutOrNull (JLjava/util/concurrent/TimeUnit;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/experimental/Continuation;)Ljava/lang/Object;
425422
public static final fun withTimeoutOrNull (JLjava/util/concurrent/TimeUnit;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/experimental/Continuation;)Ljava/lang/Object;
426-
public static synthetic fun withTimeoutOrNull$default (JLjava/util/concurrent/TimeUnit;Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/experimental/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
427423
public static synthetic fun withTimeoutOrNull$default (JLjava/util/concurrent/TimeUnit;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/experimental/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
428424
}
429425

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

+14-32
Original file line numberDiff line numberDiff line change
@@ -69,13 +69,6 @@ private fun <U, T: U> setupTimeout(
6969
return coroutine.startUndispatchedOrReturn(coroutine, block)
7070
}
7171

72-
/**
73-
* @suppress **Deprecated**: for binary compatibility only
74-
*/
75-
@Deprecated("for binary compatibility only", level=DeprecationLevel.HIDDEN)
76-
public suspend fun <T> withTimeout(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS, block: suspend () -> T): T =
77-
withTimeout(time, unit) { block() }
78-
7972
private open class TimeoutCoroutine<U, in T: U>(
8073
@JvmField val time: Long,
8174
@JvmField val unit: TimeUnit,
@@ -140,32 +133,21 @@ public suspend fun <T> withTimeoutOrNull(time: Int, block: suspend CoroutineScop
140133
*/
141134
public suspend fun <T> withTimeoutOrNull(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS, block: suspend CoroutineScope.() -> T): T? {
142135
if (time <= 0L) return null
143-
return suspendCoroutineUninterceptedOrReturn { uCont ->
144-
setupTimeout(TimeoutOrNullCoroutine(time, unit, uCont), block)
145-
}
146-
}
147-
148-
/**
149-
* @suppress **Deprecated**: for binary compatibility only
150-
*/
151-
@Deprecated("for binary compatibility only", level=DeprecationLevel.HIDDEN)
152-
public suspend fun <T> withTimeoutOrNull(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS, block: suspend () -> T): T? =
153-
withTimeoutOrNull(time, unit) { block() }
154136

155-
private class TimeoutOrNullCoroutine<T>(
156-
time: Long,
157-
unit: TimeUnit,
158-
uCont: Continuation<T?> // unintercepted continuation
159-
) : TimeoutCoroutine<T?, T>(time, unit, uCont) {
160-
@Suppress("UNCHECKED_CAST")
161-
internal override fun onCompletionInternal(state: Any?, mode: Int) {
162-
if (state is CompletedExceptionally) {
163-
val exception = state.cause
164-
if (exception is TimeoutCancellationException && exception.coroutine === this)
165-
uCont.resumeUninterceptedMode(null, mode) else
166-
uCont.resumeUninterceptedWithExceptionMode(exception, mode)
167-
} else
168-
uCont.resumeUninterceptedMode(state as T, mode)
137+
var coroutine: TimeoutCoroutine<T?, T?>? = null
138+
try {
139+
return suspendCoroutineUninterceptedOrReturn { uCont ->
140+
TimeoutCoroutine(time, unit, uCont).let {
141+
coroutine = it
142+
setupTimeout<T?, T?>(it, block)
143+
}
144+
}
145+
} catch (e: TimeoutCancellationException) {
146+
// Return null iff it's our exception, otherwise propagate it upstream (e.g. in case of nested withTimeouts)
147+
if (e.coroutine === coroutine) {
148+
return null
149+
}
150+
throw e
169151
}
170152
}
171153

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

+31
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
package kotlinx.coroutines.experimental
99

10+
import kotlinx.coroutines.experimental.channels.*
1011
import kotlin.coroutines.experimental.*
1112
import kotlin.test.*
1213

@@ -81,6 +82,23 @@ class WithTimeoutOrNullTest : TestBase() {
8182
finish(2)
8283
}
8384

85+
@Test
86+
fun testSmallTimeout() = runTest {
87+
val channel = Channel<Int>(1)
88+
val value = withTimeoutOrNull(1) {
89+
channel.receive()
90+
}
91+
92+
assertNull(value)
93+
}
94+
95+
@Test
96+
fun testThrowException() = runTest(expected = {it is AssertionError}) {
97+
withTimeoutOrNull(Long.MAX_VALUE) {
98+
throw AssertionError()
99+
}
100+
}
101+
84102
@Test
85103
fun testInnerTimeoutTest() = runTest(
86104
expected = { it is CancellationException }
@@ -96,6 +114,19 @@ class WithTimeoutOrNullTest : TestBase() {
96114
expectUnreached() // will timeout
97115
}
98116

117+
@Test
118+
fun testNestedTimeout() = runTest(expected = { it is TimeoutCancellationException }) {
119+
withTimeoutOrNull(Long.MAX_VALUE) {
120+
// Exception from this withTimeout is not suppressed by withTimeoutOrNull
121+
withTimeout(10) {
122+
delay(Long.MAX_VALUE)
123+
1
124+
}
125+
}
126+
127+
expectUnreached()
128+
}
129+
99130
@Test
100131
fun testOuterTimeoutTest() = runTest {
101132
var counter = 0

0 commit comments

Comments
 (0)