Allow negative timeouts in delay, withTimeout and onTimeout on JVM
Fixes #310
diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt
index 25775d1..bd9ec4a 100644
--- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt
+++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Delay.kt
@@ -35,7 +35,6 @@
* immediately resumes with [CancellationException].
*/
suspend fun delay(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS) {
- require(time >= 0) { "Delay time $time cannot be negative" }
if (time <= 0) return // don't delay
return suspendCancellableCoroutine { scheduleResumeAfterDelay(time, unit, it) }
}
@@ -99,7 +98,6 @@
* @param unit time unit.
*/
public suspend fun delay(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS) {
- require(time >= 0) { "Delay time $time cannot be negative" }
if (time <= 0) return // don't delay
return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
cont.context.delay.scheduleResumeAfterDelay(time, unit, cont)
diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt
index 93a522e..e06323d 100644
--- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt
+++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/Scheduled.kt
@@ -61,7 +61,6 @@
* @param unit timeout unit (milliseconds by default)
*/
public suspend fun <T> withTimeout(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS, block: suspend CoroutineScope.() -> T): T {
- require(time >= 0) { "Timeout time $time cannot be negative" }
if (time <= 0L) throw CancellationException("Timed out immediately")
return suspendCoroutineOrReturn { cont: Continuation<T> ->
setupTimeout(TimeoutCoroutine(time, unit, cont), block)
@@ -151,7 +150,6 @@
* @param unit timeout unit (milliseconds by default)
*/
public suspend fun <T> withTimeoutOrNull(time: Long, unit: TimeUnit = TimeUnit.MILLISECONDS, block: suspend CoroutineScope.() -> T): T? {
- require(time >= 0) { "Timeout time $time cannot be negative" }
if (time <= 0L) return null
return suspendCoroutineOrReturn { cont: Continuation<T?> ->
setupTimeout(TimeoutOrNullCoroutine(time, unit, cont), block)
diff --git a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt
index 85ba53c..b7f61ce 100644
--- a/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt
+++ b/common/kotlinx-coroutines-core-common/src/main/kotlin/kotlinx/coroutines/experimental/selects/Select.kt
@@ -52,6 +52,7 @@
/**
* Clause that selects the given [block] after a specified timeout passes.
+ * If timeout is negative or zero, [block] is selected immediately.
*
* @param time timeout time
* @param unit timeout unit (milliseconds by default)
@@ -416,8 +417,7 @@
}
override fun onTimeout(time: Long, unit: TimeUnit, block: suspend () -> R) {
- require(time >= 0) { "Timeout time $time cannot be negative" }
- if (time == 0L) {
+ if (time <= 0L) {
if (trySelect(null))
block.startCoroutineUndispatched(completion)
return
diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt
index 69b9bd4..5116d12 100644
--- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt
+++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutOrNullTest.kt
@@ -192,4 +192,18 @@
}
private class TestException : Exception()
+
+ @Test
+ fun testNegativeTimeout() = runTest {
+ expect(1)
+ var result = withTimeoutOrNull(-1) {
+ expectUnreached()
+ }
+ assertNull(result)
+ result = withTimeoutOrNull(0) {
+ expectUnreached()
+ }
+ assertNull(result)
+ finish(2)
+ }
}
\ No newline at end of file
diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt
index 1aa2368..220a12b 100644
--- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt
+++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/WithTimeoutTest.kt
@@ -179,5 +179,19 @@
}
private class TestException : Exception()
+
+ @Test
+ fun testNegativeTimeout() = runTest {
+ expect(1)
+ try {
+ withTimeout(-1) {
+ expectUnreached()
+ "OK"
+ }
+ } catch (e: CancellationException) {
+ assertEquals("Timed out immediately", e.message)
+ finish(2)
+ }
+ }
}
diff --git a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt
index 26bb748..10ad327 100644
--- a/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt
+++ b/common/kotlinx-coroutines-core-common/src/test/kotlin/kotlinx/coroutines/experimental/selects/SelectTimeoutTest.kt
@@ -20,6 +20,7 @@
import kotlin.test.*
class SelectTimeoutTest : TestBase() {
+
@Test
fun testBasic() = runTest {
expect(1)
@@ -40,4 +41,62 @@
assertEquals("OK", result)
finish(3)
}
-}
\ No newline at end of file
+
+ @Test
+ fun testZeroTimeout() = runTest {
+ expect(1)
+ val result = select<String> {
+ onTimeout(1000) {
+ expectUnreached()
+ "FAIL"
+ }
+ onTimeout(0) {
+ expect(2)
+ "OK"
+ }
+ }
+ assertEquals("OK", result)
+ finish(3)
+ }
+
+ @Test
+ fun testNegativeTimeout() = runTest {
+ expect(1)
+ val result = select<String> {
+ onTimeout(1000) {
+ expectUnreached()
+ "FAIL"
+ }
+ onTimeout(-10) {
+ expect(2)
+ "OK"
+ }
+ }
+ assertEquals("OK", result)
+ finish(3)
+ }
+
+ @Test
+ fun testUnbiasedNegativeTimeout() = runTest {
+ val counters = intArrayOf(0, 0, 0)
+ val iterations =10_000
+ for (i in 0..iterations) {
+ val result = selectUnbiased<Int> {
+ onTimeout(-10) {
+ 0
+ }
+ onTimeout(0) {
+ 1
+ }
+ onTimeout(10) {
+ expectUnreached()
+ 2
+ }
+ }
+ ++counters[result]
+ }
+ assertEquals(0, counters[2])
+ assertTrue { counters[0] > iterations / 4 }
+ assertTrue { counters[1] > iterations / 4 }
+ }
+}