1
1
package ru.kontur.kinfra.commons.time
2
2
3
3
import kotlinx.coroutines.CoroutineScope
4
- import kotlinx.coroutines.TimeoutCancellationException
5
4
import kotlinx.coroutines.time.withTimeout
5
+ import kotlinx.coroutines.time.withTimeoutOrNull
6
6
import kotlinx.coroutines.withContext
7
7
import ru.kontur.kinfra.commons.time.MonotonicInstant.Companion.now
8
8
import java.time.Duration
9
9
import java.time.temporal.ChronoUnit
10
+ import java.util.*
11
+ import java.util.concurrent.TimeoutException
12
+ import kotlin.contracts.ExperimentalContracts
13
+ import kotlin.contracts.InvocationKind
14
+ import kotlin.contracts.contract
10
15
import kotlin.coroutines.CoroutineContext
11
16
import kotlin.coroutines.coroutineContext
12
17
@@ -96,18 +101,31 @@ public class Deadline private constructor(
96
101
97
102
/* *
98
103
* Runs a given suspending [block] of code inside a coroutine with a specified [deadline][Deadline]
99
- * and throws a [TimeoutCancellationException ] when the deadline passes.
104
+ * and throws a [TimeoutException ] when the deadline passes.
100
105
*
101
106
* If current deadline is less than the specified one, it will be used instead.
102
107
*/
108
+ @OptIn(ExperimentalContracts ::class )
103
109
public suspend fun <R > withDeadline (deadline : Deadline , block : suspend CoroutineScope .() -> R ): R {
110
+ contract {
111
+ callsInPlace(block, InvocationKind .EXACTLY_ONCE )
112
+ }
113
+
104
114
val currentDeadline = coroutineContext[Deadline ]
105
115
val newDeadline = currentDeadline
106
116
?.let { minOf(it, deadline) }
107
117
? : deadline
108
118
109
- return withTimeout(newDeadline.timeLeft()) {
110
- withContext(newDeadline, block)
119
+ // withTimeout is not used because of https://github.com/Kotlin/kotlinx.coroutines/issues/1374
120
+ val timeout = newDeadline.timeLeft()
121
+ val result = withTimeoutOrNull(timeout) {
122
+ Optional .ofNullable(withContext(newDeadline, block))
123
+ }
124
+ if (result != null ) {
125
+ // "unchecked" cast to nullable type
126
+ return result.orElse(null )
127
+ } else {
128
+ throw TimeoutException (" Timed out waiting for ${timeout.toMillis()} ms" )
111
129
}
112
130
}
113
131
@@ -117,7 +135,12 @@ public suspend fun <R> withDeadline(deadline: Deadline, block: suspend Coroutine
117
135
* @see withDeadline
118
136
* @see Deadline.after
119
137
*/
138
+ @OptIn(ExperimentalContracts ::class )
120
139
public suspend fun <R > withDeadlineAfter (timeout : Duration , block : suspend CoroutineScope .() -> R ): R {
140
+ contract {
141
+ callsInPlace(block, InvocationKind .EXACTLY_ONCE )
142
+ }
143
+
121
144
val deadline = Deadline .after(timeout)
122
145
return withDeadline(deadline, block)
123
146
}
0 commit comments