|
3 | 3 | */
|
4 | 4 | package kotlinx.coroutines.time
|
5 | 5 |
|
6 |
| -import kotlinx.coroutines.CoroutineScope |
7 |
| -import kotlinx.coroutines.selects.SelectBuilder |
8 |
| -import java.time.Duration |
9 |
| -import java.time.temporal.ChronoUnit |
| 6 | +import kotlinx.coroutines.* |
| 7 | +import kotlinx.coroutines.selects.* |
| 8 | +import java.time.* |
| 9 | +import java.time.temporal.* |
10 | 10 |
|
11 | 11 | /**
|
12 |
| - * "java.time" adapter method for [kotlinx.coroutines.delay] |
| 12 | + * "java.time" adapter method for [kotlinx.coroutines.delay]. |
13 | 13 | */
|
14 | 14 | public suspend fun delay(duration: Duration) =
|
15 |
| - kotlinx.coroutines.delay(duration.toMillisDelay()) |
| 15 | + kotlinx.coroutines.delay(duration.coerceToMillis()) |
16 | 16 |
|
17 | 17 | /**
|
18 |
| - * "java.time" adapter method for [SelectBuilder.onTimeout] |
| 18 | + * "java.time" adapter method for [SelectBuilder.onTimeout]. |
19 | 19 | */
|
20 | 20 | public fun <R> SelectBuilder<R>.onTimeout(duration: Duration, block: suspend () -> R) =
|
21 |
| - onTimeout(duration.toMillisDelay(), block) |
| 21 | + onTimeout(duration.coerceToMillis(), block) |
22 | 22 |
|
23 | 23 | /**
|
24 |
| - * "java.time" adapter method for [kotlinx.coroutines.withTimeout] |
| 24 | + * "java.time" adapter method for [kotlinx.coroutines.withTimeout]. |
25 | 25 | */
|
26 | 26 | public suspend fun <T> withTimeout(duration: Duration, block: suspend CoroutineScope.() -> T): T =
|
27 |
| - kotlinx.coroutines.withTimeout(duration.toMillisDelay(), block) |
| 27 | + kotlinx.coroutines.withTimeout(duration.coerceToMillis(), block) |
28 | 28 |
|
29 | 29 | /**
|
30 |
| - * "java.time" adapter method for [kotlinx.coroutines.withTimeoutOrNull] |
| 30 | + * "java.time" adapter method for [kotlinx.coroutines.withTimeoutOrNull]. |
31 | 31 | */
|
32 | 32 | public suspend fun <T> withTimeoutOrNull(duration: Duration, block: suspend CoroutineScope.() -> T): T? =
|
33 |
| - kotlinx.coroutines.withTimeoutOrNull(duration.toMillisDelay(), block) |
| 33 | + kotlinx.coroutines.withTimeoutOrNull(duration.coerceToMillis(), block) |
34 | 34 |
|
35 | 35 | /**
|
36 |
| - * Convert the [Duration] to millisecond delay. |
| 36 | + * Coerces the given [Duration] to a millisecond delay. |
| 37 | + * Negative values are coerced to zero, values that cannot |
| 38 | + * be represented in milliseconds as long ("infinite" duration) are coerced to [Long.MAX_VALUE] |
| 39 | + * and durations lesser than a millisecond are coerced to 1 millisecond. |
37 | 40 | *
|
38 |
| - * @return strictly positive duration is coerced to 1..[Long.MAX_VALUE] ms, `0` otherwise. |
| 41 | + * The rationale of coercion: |
| 42 | + * 1) Too large durations typically indicate infinity and Long.MAX_VALUE is the |
| 43 | + * best approximation of infinity we can provide. |
| 44 | + * 2) Coercing too small durations to 1 instead of 0 is crucial for two patterns: |
| 45 | + * - Programming with deadlines and delays |
| 46 | + * - Non-suspending fast-paths (e.g. `withTimeout(1 nanosecond) { 42 }` should not throw) |
39 | 47 | */
|
40 |
| -private fun Duration.toMillisDelay(): Long = |
41 |
| - if (this <= ChronoUnit.MILLIS.duration) { |
42 |
| - if (this <= Duration.ZERO) 0 |
43 |
| - else 1 |
44 |
| - } else { |
45 |
| - // values of Duration.ofMillis(Long.MAX_VALUE) |
46 |
| - val maxSeconds = 9223372036854775 |
47 |
| - val maxNanos = 807000000 |
48 |
| - if (seconds < maxSeconds || seconds == maxSeconds && nano < maxNanos) toMillis() |
49 |
| - else Long.MAX_VALUE |
50 |
| - } |
| 48 | +private fun Duration.coerceToMillis(): Long { |
| 49 | + if (isNegative) return 0 |
| 50 | + if (this <= ChronoUnit.MILLIS.duration) return 1 |
| 51 | + |
| 52 | + // Maximum scalar values of Duration.ofMillis(Long.MAX_VALUE) |
| 53 | + val maxSeconds = 9223372036854775 |
| 54 | + val maxNanos = 807000000 |
| 55 | + return if (seconds < maxSeconds || seconds == maxSeconds && nano < maxNanos) toMillis() |
| 56 | + else Long.MAX_VALUE |
| 57 | +} |
0 commit comments