• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
<lambda>null2  * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3  */
4 @file:OptIn(ExperimentalContracts::class)
5 
6 package kotlinx.coroutines
7 
8 import kotlinx.coroutines.internal.*
9 import kotlinx.coroutines.intrinsics.*
10 import kotlinx.coroutines.selects.*
11 import kotlin.contracts.*
12 import kotlin.coroutines.*
13 import kotlin.coroutines.intrinsics.*
14 import kotlin.jvm.*
15 import kotlin.time.*
16 import kotlin.time.Duration.Companion.milliseconds
17 
18 /**
19  * Runs a given suspending [block] of code inside a coroutine with a specified [timeout][timeMillis] and throws
20  * a [TimeoutCancellationException] if the timeout was exceeded.
21  * If the given [timeMillis] is non-positive, [TimeoutCancellationException] is thrown immediately.
22  *
23  * The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
24  * the cancellable suspending function inside the block throws a [TimeoutCancellationException].
25  *
26  * The sibling function that does not throw an exception on timeout is [withTimeoutOrNull].
27  * Note that the timeout action can be specified for a [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
28  *
29  * **The timeout event is asynchronous with respect to the code running in the block** and may happen at any time,
30  * even right before the return from inside the timeout [block]. Keep this in mind if you open or acquire some
31  * resource inside the [block] that needs closing or release outside the block.
32  * See the
33  * [Asynchronous timeout and resources][https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html#asynchronous-timeout-and-resources]
34  * section of the coroutines guide for details.
35  *
36  * > Implementation note: how the time is tracked exactly is an implementation detail of the context's [CoroutineDispatcher].
37  *
38  * @param timeMillis timeout time in milliseconds.
39  */
40 public suspend fun <T> withTimeout(timeMillis: Long, block: suspend CoroutineScope.() -> T): T {
41     contract {
42         callsInPlace(block, InvocationKind.EXACTLY_ONCE)
43     }
44     if (timeMillis <= 0L) throw TimeoutCancellationException("Timed out immediately")
45     return suspendCoroutineUninterceptedOrReturn { uCont ->
46         setupTimeout(TimeoutCoroutine(timeMillis, uCont), block)
47     }
48 }
49 
50 /**
51  * Runs a given suspending [block] of code inside a coroutine with the specified [timeout] and throws
52  * a [TimeoutCancellationException] if the timeout was exceeded.
53  * If the given [timeout] is non-positive, [TimeoutCancellationException] is thrown immediately.
54  *
55  * The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
56  * the cancellable suspending function inside the block throws a [TimeoutCancellationException].
57  *
58  * The sibling function that does not throw an exception on timeout is [withTimeoutOrNull].
59  * Note that the timeout action can be specified for a [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
60  *
61  * **The timeout event is asynchronous with respect to the code running in the block** and may happen at any time,
62  * even right before the return from inside the timeout [block]. Keep this in mind if you open or acquire some
63  * resource inside the [block] that needs closing or release outside the block.
64  * See the
65  * [Asynchronous timeout and resources][https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html#asynchronous-timeout-and-resources]
66  * section of the coroutines guide for details.
67  *
68  * > Implementation note: how the time is tracked exactly is an implementation detail of the context's [CoroutineDispatcher].
69  */
withTimeoutnull70 public suspend fun <T> withTimeout(timeout: Duration, block: suspend CoroutineScope.() -> T): T {
71     contract {
72         callsInPlace(block, InvocationKind.EXACTLY_ONCE)
73     }
74     return withTimeout(timeout.toDelayMillis(), block)
75 }
76 
77 /**
78  * Runs a given suspending block of code inside a coroutine with a specified [timeout][timeMillis] and returns
79  * `null` if this timeout was exceeded.
80  * If the given [timeMillis] is non-positive, `null` is returned immediately.
81  *
82  * The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
83  * cancellable suspending function inside the block throws a [TimeoutCancellationException].
84  *
85  * The sibling function that throws an exception on timeout is [withTimeout].
86  * Note that the timeout action can be specified for a [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
87  *
88  * **The timeout event is asynchronous with respect to the code running in the block** and may happen at any time,
89  * even right before the return from inside the timeout [block]. Keep this in mind if you open or acquire some
90  * resource inside the [block] that needs closing or release outside the block.
91  * See the
92  * [Asynchronous timeout and resources][https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html#asynchronous-timeout-and-resources]
93  * section of the coroutines guide for details.
94  *
95  * > Implementation note: how the time is tracked exactly is an implementation detail of the context's [CoroutineDispatcher].
96  *
97  * @param timeMillis timeout time in milliseconds.
98  */
withTimeoutOrNullnull99 public suspend fun <T> withTimeoutOrNull(timeMillis: Long, block: suspend CoroutineScope.() -> T): T? {
100     if (timeMillis <= 0L) return null
101 
102     var coroutine: TimeoutCoroutine<T?, T?>? = null
103     try {
104         return suspendCoroutineUninterceptedOrReturn { uCont ->
105             val timeoutCoroutine = TimeoutCoroutine(timeMillis, uCont)
106             coroutine = timeoutCoroutine
107             setupTimeout<T?, T?>(timeoutCoroutine, block)
108         }
109     } catch (e: TimeoutCancellationException) {
110         // Return null if it's our exception, otherwise propagate it upstream (e.g. in case of nested withTimeouts)
111         if (e.coroutine === coroutine) {
112             return null
113         }
114         throw e
115     }
116 }
117 
118 /**
119  * Runs a given suspending block of code inside a coroutine with the specified [timeout] and returns
120  * `null` if this timeout was exceeded.
121  * If the given [timeout] is non-positive, `null` is returned immediately.
122  *
123  * The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of
124  * cancellable suspending function inside the block throws a [TimeoutCancellationException].
125  *
126  * The sibling function that throws an exception on timeout is [withTimeout].
127  * Note that the timeout action can be specified for a [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause.
128  *
129  * **The timeout event is asynchronous with respect to the code running in the block** and may happen at any time,
130  * even right before the return from inside the timeout [block]. Keep this in mind if you open or acquire some
131  * resource inside the [block] that needs closing or release outside the block.
132  * See the
133  * [Asynchronous timeout and resources][https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html#asynchronous-timeout-and-resources]
134  * section of the coroutines guide for details.
135  *
136  * > Implementation note: how the time is tracked exactly is an implementation detail of the context's [CoroutineDispatcher].
137  */
withTimeoutOrNullnull138 public suspend fun <T> withTimeoutOrNull(timeout: Duration, block: suspend CoroutineScope.() -> T): T? =
139     withTimeoutOrNull(timeout.toDelayMillis(), block)
140 
141 private fun <U, T : U> setupTimeout(
142     coroutine: TimeoutCoroutine<U, T>,
143     block: suspend CoroutineScope.() -> T
144 ): Any? {
145     // schedule cancellation of this coroutine on time
146     val cont = coroutine.uCont
147     val context = cont.context
148     coroutine.disposeOnCompletion(context.delay.invokeOnTimeout(coroutine.time, coroutine, coroutine.context))
149     // restart the block using a new coroutine with a new job,
150     // however, start it undispatched, because we already are in the proper context
151     return coroutine.startUndispatchedOrReturnIgnoreTimeout(coroutine, block)
152 }
153 
154 private class TimeoutCoroutine<U, in T : U>(
155     @JvmField val time: Long,
156     uCont: Continuation<U> // unintercepted continuation
157 ) : ScopeCoroutine<T>(uCont.context, uCont), Runnable {
runnull158     override fun run() {
159         cancelCoroutine(TimeoutCancellationException(time, context.delay, this))
160     }
161 
nameStringnull162     override fun nameString(): String =
163         "${super.nameString()}(timeMillis=$time)"
164 }
165 
166 /**
167  * This exception is thrown by [withTimeout] to indicate timeout.
168  */
169 public class TimeoutCancellationException internal constructor(
170     message: String,
171     @JvmField @Transient internal val coroutine: Job?
172 ) : CancellationException(message), CopyableThrowable<TimeoutCancellationException> {
173     /**
174      * Creates a timeout exception with the given message.
175      * This constructor is needed for exception stack-traces recovery.
176      */
177     internal constructor(message: String) : this(message, null)
178 
179     // message is never null in fact
180     override fun createCopy(): TimeoutCancellationException =
181         TimeoutCancellationException(message ?: "", coroutine).also { it.initCause(this) }
182 }
183 
TimeoutCancellationExceptionnull184 internal fun TimeoutCancellationException(
185     time: Long,
186     delay: Delay,
187     coroutine: Job
188 ) : TimeoutCancellationException {
189     val message = (delay as? DelayWithTimeoutDiagnostics)?.timeoutMessage(time.milliseconds)
190         ?: "Timed out waiting for $time ms"
191     return TimeoutCancellationException(message, coroutine)
192 }
193