Roman Elizarov | a7db8ec | 2017-12-21 22:45:12 +0300 | [diff] [blame] | 1 | /* |
| 2 | * Copyright 2016-2017 JetBrains s.r.o. |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 17 | @file:JvmMultifileClass |
| 18 | @file:JvmName("BuildersKt") |
Roman Elizarov | f161c9f | 2018-02-07 23:27:10 +0300 | [diff] [blame] | 19 | |
Roman Elizarov | e1c0b65 | 2017-12-01 14:02:57 +0300 | [diff] [blame] | 20 | package kotlinx.coroutines.experimental |
| 21 | |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 22 | import kotlinx.coroutines.experimental.internalAnnotations.* |
Roman Elizarov | 23fb728 | 2018-01-24 23:09:42 +0300 | [diff] [blame] | 23 | import kotlinx.coroutines.experimental.intrinsics.* |
Roman Elizarov | 9d5abcd | 2017-12-21 16:54:30 +0300 | [diff] [blame] | 24 | import kotlin.coroutines.experimental.* |
Roman Elizarov | 23fb728 | 2018-01-24 23:09:42 +0300 | [diff] [blame] | 25 | import kotlin.coroutines.experimental.intrinsics.* |
Roman Elizarov | e1c0b65 | 2017-12-01 14:02:57 +0300 | [diff] [blame] | 26 | |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 27 | // --------------- basic coroutine builders --------------- |
| 28 | |
Roman Elizarov | e1c0b65 | 2017-12-01 14:02:57 +0300 | [diff] [blame] | 29 | /** |
| 30 | * Launches new coroutine without blocking current thread and returns a reference to the coroutine as a [Job]. |
| 31 | * The coroutine is cancelled when the resulting job is [cancelled][Job.cancel]. |
| 32 | * |
| 33 | * The [context] for the new coroutine can be explicitly specified. |
| 34 | * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`. |
Roman Elizarov | 9fe5f46 | 2018-02-21 19:05:52 +0300 | [diff] [blame] | 35 | * The [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/coroutine-context.html) |
| 36 | * of the parent coroutine may be used, |
Roman Elizarov | e1c0b65 | 2017-12-01 14:02:57 +0300 | [diff] [blame] | 37 | * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. |
| 38 | * The parent job may be also explicitly specified using [parent] parameter. |
| 39 | * |
| 40 | * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used. |
| 41 | * |
| 42 | * By default, the coroutine is immediately scheduled for execution. |
| 43 | * Other options can be specified via `start` parameter. See [CoroutineStart] for details. |
| 44 | * An optional [start] parameter can be set to [CoroutineStart.LAZY] to start coroutine _lazily_. In this case, |
| 45 | * the coroutine [Job] is created in _new_ state. It can be explicitly started with [start][Job.start] function |
| 46 | * and will be started implicitly on the first invocation of [join][Job.join]. |
| 47 | * |
| 48 | * Uncaught exceptions in this coroutine cancel parent job in the context by default |
| 49 | * (unless [CoroutineExceptionHandler] is explicitly specified), which means that when `launch` is used with |
| 50 | * the context of another coroutine, then any uncaught exception leads to the cancellation of parent coroutine. |
| 51 | * |
| 52 | * See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine. |
| 53 | * |
| 54 | * @param context context of the coroutine. The default value is [DefaultDispatcher]. |
| 55 | * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT]. |
| 56 | * @param parent explicitly specifies the parent job, overrides job from the [context] (if any). |
Roman Elizarov | fe8ba6b | 2018-03-13 17:34:29 +0300 | [diff] [blame] | 57 | * @param onCompletion optional completion handler for the coroutine (see [Job.invokeOnCompletion]). |
Roman Elizarov | e1c0b65 | 2017-12-01 14:02:57 +0300 | [diff] [blame] | 58 | * @param block the coroutine code. |
| 59 | */ |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 60 | public fun launch( |
Roman Elizarov | fe8ba6b | 2018-03-13 17:34:29 +0300 | [diff] [blame] | 61 | context: CoroutineContext = DefaultDispatcher, |
| 62 | start: CoroutineStart = CoroutineStart.DEFAULT, |
| 63 | parent: Job? = null, |
| 64 | onCompletion: CompletionHandler? = null, |
| 65 | block: suspend CoroutineScope.() -> Unit |
Roman Elizarov | e1c0b65 | 2017-12-01 14:02:57 +0300 | [diff] [blame] | 66 | ): Job { |
| 67 | val newContext = newCoroutineContext(context, parent) |
| 68 | val coroutine = if (start.isLazy) |
| 69 | LazyStandaloneCoroutine(newContext, block) else |
| 70 | StandaloneCoroutine(newContext, active = true) |
Roman Elizarov | fe8ba6b | 2018-03-13 17:34:29 +0300 | [diff] [blame] | 71 | if (onCompletion != null) coroutine.invokeOnCompletion(handler = onCompletion) |
Roman Elizarov | c12123e | 2018-01-24 22:07:12 +0300 | [diff] [blame] | 72 | coroutine.start(start, coroutine, block) |
Roman Elizarov | e1c0b65 | 2017-12-01 14:02:57 +0300 | [diff] [blame] | 73 | return coroutine |
| 74 | } |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 75 | /** @suppress **Deprecated**: Binary compatibility */ |
| 76 | @Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) |
| 77 | public fun launch( |
| 78 | context: CoroutineContext = DefaultDispatcher, |
| 79 | start: CoroutineStart = CoroutineStart.DEFAULT, |
| 80 | parent: Job? = null, |
| 81 | block: suspend CoroutineScope.() -> Unit |
| 82 | ): Job = launch(context, start, parent, block = block) |
| 83 | |
| 84 | /** @suppress **Deprecated**: Binary compatibility */ |
| 85 | @Deprecated(message = "Binary compatibility", level = DeprecationLevel.HIDDEN) |
| 86 | public fun launch( |
| 87 | context: CoroutineContext = DefaultDispatcher, |
| 88 | start: CoroutineStart = CoroutineStart.DEFAULT, |
| 89 | block: suspend CoroutineScope.() -> Unit |
| 90 | ): Job = |
| 91 | launch(context, start, block = block) |
| 92 | |
| 93 | /** |
| 94 | * @suppress **Deprecated**: Use `start = CoroutineStart.XXX` parameter |
| 95 | */ |
| 96 | @Deprecated(message = "Use `start = CoroutineStart.XXX` parameter", |
| 97 | replaceWith = ReplaceWith("launch(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block)")) |
| 98 | public fun launch(context: CoroutineContext, start: Boolean, block: suspend CoroutineScope.() -> Unit): Job = |
| 99 | launch(context, if (start) CoroutineStart.DEFAULT else CoroutineStart.LAZY, block = block) |
Roman Elizarov | e1c0b65 | 2017-12-01 14:02:57 +0300 | [diff] [blame] | 100 | |
Roman Elizarov | a12ee15 | 2017-12-20 10:50:17 +0300 | [diff] [blame] | 101 | /** |
Roman Elizarov | 9d5abcd | 2017-12-21 16:54:30 +0300 | [diff] [blame] | 102 | * Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns |
| 103 | * the result. |
| 104 | * |
| 105 | * This function immediately applies dispatcher from the new context, shifting execution of the block into the |
| 106 | * different thread inside the block, and back when it completes. |
| 107 | * The specified [context] is added onto the current coroutine context for the execution of the block. |
| 108 | * |
| 109 | * An optional `start` parameter is used only if the specified `context` uses a different [CoroutineDispatcher] than |
| 110 | * a current one, otherwise it is ignored. |
| 111 | * By default, the coroutine is immediately scheduled for execution and can be cancelled |
| 112 | * while it is waiting to be executed and it can be cancelled while the result is scheduled |
| 113 | * to be processed by the invoker context. |
| 114 | * Other options can be specified via `start` parameter. See [CoroutineStart] for details. |
| 115 | * A value of [CoroutineStart.LAZY] is not supported and produces [IllegalArgumentException]. |
| 116 | */ |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 117 | public suspend fun <T> withContext( |
Roman Elizarov | 9d5abcd | 2017-12-21 16:54:30 +0300 | [diff] [blame] | 118 | context: CoroutineContext, |
| 119 | start: CoroutineStart = CoroutineStart.DEFAULT, |
| 120 | block: suspend () -> T |
| 121 | ): T = suspendCoroutineOrReturn sc@ { cont -> |
| 122 | val oldContext = cont.context |
| 123 | // fast path #1 if there is no change in the actual context: |
| 124 | if (context === oldContext || context is CoroutineContext.Element && oldContext[context.key] === context) |
| 125 | return@sc block.startCoroutineUninterceptedOrReturn(cont) |
| 126 | // compute new context |
| 127 | val newContext = oldContext + context |
| 128 | // fast path #2 if the result is actually the same |
| 129 | if (newContext === oldContext) |
| 130 | return@sc block.startCoroutineUninterceptedOrReturn(cont) |
| 131 | // fast path #3 if the new dispatcher is the same as the old one. |
| 132 | // `equals` is used by design (see equals implementation is wrapper context like ExecutorCoroutineDispatcher) |
| 133 | if (newContext[ContinuationInterceptor] == oldContext[ContinuationInterceptor]) { |
| 134 | val newContinuation = RunContinuationDirect(newContext, cont) |
| 135 | return@sc block.startCoroutineUninterceptedOrReturn(newContinuation) |
| 136 | } |
| 137 | // slowest path otherwise -- use new interceptor, sync to its result via a full-blown instance of RunCompletion |
| 138 | require(!start.isLazy) { "$start start is not supported" } |
| 139 | val completion = RunCompletion( |
| 140 | context = newContext, |
| 141 | delegate = cont, |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 142 | resumeMode = if (start == CoroutineStart.ATOMIC) MODE_ATOMIC_DEFAULT else MODE_CANCELLABLE |
| 143 | ) |
Roman Elizarov | c12123e | 2018-01-24 22:07:12 +0300 | [diff] [blame] | 144 | completion.initParentJobInternal(newContext[Job]) // attach to job |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 145 | @Suppress("DEPRECATION") |
Roman Elizarov | 9d5abcd | 2017-12-21 16:54:30 +0300 | [diff] [blame] | 146 | start(block, completion) |
| 147 | completion.getResult() |
| 148 | } |
| 149 | |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 150 | /** @suppress **Deprecated**: Renamed to [withContext]. */ |
| 151 | @Deprecated(message = "Renamed to `withContext`", level=DeprecationLevel.WARNING, |
| 152 | replaceWith = ReplaceWith("withContext(context, start, block)")) |
| 153 | public suspend fun <T> run( |
| 154 | context: CoroutineContext, |
| 155 | start: CoroutineStart = CoroutineStart.DEFAULT, |
| 156 | block: suspend () -> T |
| 157 | ): T = |
| 158 | withContext(context, start, block) |
| 159 | |
| 160 | /** @suppress **Deprecated** */ |
| 161 | @Deprecated(message = "It is here for binary compatibility only", level=DeprecationLevel.HIDDEN) |
| 162 | public suspend fun <T> run(context: CoroutineContext, block: suspend () -> T): T = |
| 163 | withContext(context, start = CoroutineStart.ATOMIC, block = block) |
| 164 | |
Roman Elizarov | e1c0b65 | 2017-12-01 14:02:57 +0300 | [diff] [blame] | 165 | // --------------- implementation --------------- |
| 166 | |
| 167 | private open class StandaloneCoroutine( |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 168 | private val parentContext: CoroutineContext, |
| 169 | active: Boolean |
Roman Elizarov | e1c0b65 | 2017-12-01 14:02:57 +0300 | [diff] [blame] | 170 | ) : AbstractCoroutine<Unit>(parentContext, active) { |
Roman Elizarov | 0d7323a | 2018-01-29 20:59:15 +0300 | [diff] [blame] | 171 | override fun hasOnFinishingHandler(update: Any?) = update is CompletedExceptionally |
| 172 | override fun onFinishingInternal(update: Any?) { |
Roman Elizarov | e1c0b65 | 2017-12-01 14:02:57 +0300 | [diff] [blame] | 173 | // note the use of the parent's job context below! |
Vsevolod Tolstopyatov | c1092d5 | 2018-04-12 20:22:25 +0300 | [diff] [blame] | 174 | if (update is CompletedExceptionally) handleCoroutineException(parentContext, update.cause) |
Roman Elizarov | e1c0b65 | 2017-12-01 14:02:57 +0300 | [diff] [blame] | 175 | } |
| 176 | } |
| 177 | |
| 178 | private class LazyStandaloneCoroutine( |
Roman Elizarov | aa461cf | 2018-04-11 13:20:29 +0300 | [diff] [blame] | 179 | parentContext: CoroutineContext, |
| 180 | private val block: suspend CoroutineScope.() -> Unit |
Roman Elizarov | e1c0b65 | 2017-12-01 14:02:57 +0300 | [diff] [blame] | 181 | ) : StandaloneCoroutine(parentContext, active = false) { |
| 182 | override fun onStart() { |
| 183 | block.startCoroutineCancellable(this, this) |
| 184 | } |
| 185 | } |
| 186 | |
Roman Elizarov | 9d5abcd | 2017-12-21 16:54:30 +0300 | [diff] [blame] | 187 | private class RunContinuationDirect<in T>( |
| 188 | override val context: CoroutineContext, |
| 189 | continuation: Continuation<T> |
| 190 | ) : Continuation<T> by continuation |
| 191 | |
Roman Elizarov | 9d5abcd | 2017-12-21 16:54:30 +0300 | [diff] [blame] | 192 | @Suppress("UNCHECKED_CAST") |
| 193 | private class RunCompletion<in T>( |
| 194 | override val context: CoroutineContext, |
| 195 | delegate: Continuation<T>, |
| 196 | resumeMode: Int |
Vsevolod Tolstopyatov | 4aa18aa | 2018-04-17 15:43:12 +0300 | [diff] [blame] | 197 | ) : AbstractContinuation<T>(delegate, resumeMode) { |
| 198 | |
| 199 | override val useCancellingState: Boolean get() = true |
| 200 | } |