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 | e1c0b65 | 2017-12-01 14:02:57 +0300 | [diff] [blame] | 17 | package kotlinx.coroutines.experimental |
| 18 | |
Roman Elizarov | 9d5abcd | 2017-12-21 16:54:30 +0300 | [diff] [blame] | 19 | import kotlin.coroutines.experimental.* |
| 20 | import kotlin.coroutines.experimental.intrinsics.startCoroutineUninterceptedOrReturn |
| 21 | import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn |
Roman Elizarov | e1c0b65 | 2017-12-01 14:02:57 +0300 | [diff] [blame] | 22 | |
| 23 | /** |
| 24 | * Launches new coroutine without blocking current thread and returns a reference to the coroutine as a [Job]. |
| 25 | * The coroutine is cancelled when the resulting job is [cancelled][Job.cancel]. |
| 26 | * |
| 27 | * The [context] for the new coroutine can be explicitly specified. |
| 28 | * See [CoroutineDispatcher] for the standard context implementations that are provided by `kotlinx.coroutines`. |
Roman Elizarov | 9d5abcd | 2017-12-21 16:54:30 +0300 | [diff] [blame] | 29 | * The [context][CoroutineScope.coroutineContext] of the parent coroutine from its [scope][CoroutineScope] may be used, |
Roman Elizarov | e1c0b65 | 2017-12-01 14:02:57 +0300 | [diff] [blame] | 30 | * in which case the [Job] of the resulting coroutine is a child of the job of the parent coroutine. |
| 31 | * The parent job may be also explicitly specified using [parent] parameter. |
| 32 | * |
| 33 | * If the context does not have any dispatcher nor any other [ContinuationInterceptor], then [DefaultDispatcher] is used. |
| 34 | * |
| 35 | * By default, the coroutine is immediately scheduled for execution. |
| 36 | * Other options can be specified via `start` parameter. See [CoroutineStart] for details. |
| 37 | * An optional [start] parameter can be set to [CoroutineStart.LAZY] to start coroutine _lazily_. In this case, |
| 38 | * the coroutine [Job] is created in _new_ state. It can be explicitly started with [start][Job.start] function |
| 39 | * and will be started implicitly on the first invocation of [join][Job.join]. |
| 40 | * |
| 41 | * Uncaught exceptions in this coroutine cancel parent job in the context by default |
| 42 | * (unless [CoroutineExceptionHandler] is explicitly specified), which means that when `launch` is used with |
| 43 | * the context of another coroutine, then any uncaught exception leads to the cancellation of parent coroutine. |
| 44 | * |
| 45 | * See [newCoroutineContext] for a description of debugging facilities that are available for newly created coroutine. |
| 46 | * |
| 47 | * @param context context of the coroutine. The default value is [DefaultDispatcher]. |
| 48 | * @param start coroutine start option. The default value is [CoroutineStart.DEFAULT]. |
| 49 | * @param parent explicitly specifies the parent job, overrides job from the [context] (if any). |
| 50 | * @param block the coroutine code. |
| 51 | */ |
| 52 | public actual fun launch( |
| 53 | context: CoroutineContext = DefaultDispatcher, |
| 54 | start: CoroutineStart = CoroutineStart.DEFAULT, |
| 55 | parent: Job? = null, |
| 56 | block: suspend CoroutineScope.() -> Unit |
| 57 | ): Job { |
| 58 | val newContext = newCoroutineContext(context, parent) |
| 59 | val coroutine = if (start.isLazy) |
| 60 | LazyStandaloneCoroutine(newContext, block) else |
| 61 | StandaloneCoroutine(newContext, active = true) |
| 62 | coroutine.initParentJob(newContext[Job]) |
| 63 | start(block, coroutine, coroutine) |
| 64 | return coroutine |
| 65 | } |
| 66 | |
Roman Elizarov | a12ee15 | 2017-12-20 10:50:17 +0300 | [diff] [blame] | 67 | /** |
Roman Elizarov | 9d5abcd | 2017-12-21 16:54:30 +0300 | [diff] [blame] | 68 | * Calls the specified suspending block with a given coroutine context, suspends until it completes, and returns |
| 69 | * the result. |
| 70 | * |
| 71 | * This function immediately applies dispatcher from the new context, shifting execution of the block into the |
| 72 | * different thread inside the block, and back when it completes. |
| 73 | * The specified [context] is added onto the current coroutine context for the execution of the block. |
| 74 | * |
| 75 | * An optional `start` parameter is used only if the specified `context` uses a different [CoroutineDispatcher] than |
| 76 | * a current one, otherwise it is ignored. |
| 77 | * By default, the coroutine is immediately scheduled for execution and can be cancelled |
| 78 | * while it is waiting to be executed and it can be cancelled while the result is scheduled |
| 79 | * to be processed by the invoker context. |
| 80 | * Other options can be specified via `start` parameter. See [CoroutineStart] for details. |
| 81 | * A value of [CoroutineStart.LAZY] is not supported and produces [IllegalArgumentException]. |
| 82 | */ |
| 83 | public actual suspend fun <T> withContext( |
| 84 | context: CoroutineContext, |
| 85 | start: CoroutineStart = CoroutineStart.DEFAULT, |
| 86 | block: suspend () -> T |
| 87 | ): T = suspendCoroutineOrReturn sc@ { cont -> |
| 88 | val oldContext = cont.context |
| 89 | // fast path #1 if there is no change in the actual context: |
| 90 | if (context === oldContext || context is CoroutineContext.Element && oldContext[context.key] === context) |
| 91 | return@sc block.startCoroutineUninterceptedOrReturn(cont) |
| 92 | // compute new context |
| 93 | val newContext = oldContext + context |
| 94 | // fast path #2 if the result is actually the same |
| 95 | if (newContext === oldContext) |
| 96 | return@sc block.startCoroutineUninterceptedOrReturn(cont) |
| 97 | // fast path #3 if the new dispatcher is the same as the old one. |
| 98 | // `equals` is used by design (see equals implementation is wrapper context like ExecutorCoroutineDispatcher) |
| 99 | if (newContext[ContinuationInterceptor] == oldContext[ContinuationInterceptor]) { |
| 100 | val newContinuation = RunContinuationDirect(newContext, cont) |
| 101 | return@sc block.startCoroutineUninterceptedOrReturn(newContinuation) |
| 102 | } |
| 103 | // slowest path otherwise -- use new interceptor, sync to its result via a full-blown instance of RunCompletion |
| 104 | require(!start.isLazy) { "$start start is not supported" } |
| 105 | val completion = RunCompletion( |
| 106 | context = newContext, |
| 107 | delegate = cont, |
| 108 | resumeMode = if (start == CoroutineStart.ATOMIC) MODE_ATOMIC_DEFAULT else MODE_CANCELLABLE) |
| 109 | completion.initParentJob(newContext[Job]) // attach to job |
| 110 | start(block, completion) |
| 111 | completion.getResult() |
| 112 | } |
| 113 | |
Roman Elizarov | e1c0b65 | 2017-12-01 14:02:57 +0300 | [diff] [blame] | 114 | // --------------- implementation --------------- |
| 115 | |
| 116 | private open class StandaloneCoroutine( |
| 117 | private val parentContext: CoroutineContext, |
| 118 | active: Boolean |
| 119 | ) : AbstractCoroutine<Unit>(parentContext, active) { |
| 120 | override fun onCancellation(exceptionally: CompletedExceptionally?) { |
| 121 | // note the use of the parent's job context below! |
| 122 | if (exceptionally != null) handleCoroutineException(parentContext, exceptionally.exception) |
| 123 | } |
| 124 | } |
| 125 | |
| 126 | private class LazyStandaloneCoroutine( |
| 127 | parentContext: CoroutineContext, |
| 128 | private val block: suspend CoroutineScope.() -> Unit |
| 129 | ) : StandaloneCoroutine(parentContext, active = false) { |
| 130 | override fun onStart() { |
| 131 | block.startCoroutineCancellable(this, this) |
| 132 | } |
| 133 | } |
| 134 | |
Roman Elizarov | 9d5abcd | 2017-12-21 16:54:30 +0300 | [diff] [blame] | 135 | private class RunContinuationDirect<in T>( |
| 136 | override val context: CoroutineContext, |
| 137 | continuation: Continuation<T> |
| 138 | ) : Continuation<T> by continuation |
| 139 | |
| 140 | |
| 141 | @Suppress("UNCHECKED_CAST") |
| 142 | private class RunCompletion<in T>( |
| 143 | override val context: CoroutineContext, |
| 144 | delegate: Continuation<T>, |
| 145 | resumeMode: Int |
Roman Elizarov | 4d626de | 2018-01-11 22:57:28 +0300 | [diff] [blame] | 146 | ) : AbstractContinuation<T>(delegate, resumeMode) |
Roman Elizarov | 9d5abcd | 2017-12-21 16:54:30 +0300 | [diff] [blame] | 147 | |
Roman Elizarov | a12ee15 | 2017-12-20 10:50:17 +0300 | [diff] [blame] | 148 | |