blob: 3af728fb5638505a2af1da7ea7cbefd04391be25 [file] [log] [blame]
Roman Elizarova7db8ec2017-12-21 22:45:12 +03001/*
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 Elizarove1c0b652017-12-01 14:02:57 +030017package kotlinx.coroutines.experimental
18
Roman Elizarov9d5abcd2017-12-21 16:54:30 +030019import kotlin.coroutines.experimental.*
20import kotlin.coroutines.experimental.intrinsics.startCoroutineUninterceptedOrReturn
21import kotlin.coroutines.experimental.intrinsics.suspendCoroutineOrReturn
Roman Elizarove1c0b652017-12-01 14:02:57 +030022
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 Elizarov9d5abcd2017-12-21 16:54:30 +030029 * The [context][CoroutineScope.coroutineContext] of the parent coroutine from its [scope][CoroutineScope] may be used,
Roman Elizarove1c0b652017-12-01 14:02:57 +030030 * 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 */
52public 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 Elizarova12ee152017-12-20 10:50:17 +030067/**
Roman Elizarov9d5abcd2017-12-21 16:54:30 +030068 * 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 */
83public 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 Elizarove1c0b652017-12-01 14:02:57 +0300114// --------------- implementation ---------------
115
116private 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
126private 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 Elizarov9d5abcd2017-12-21 16:54:30 +0300135private class RunContinuationDirect<in T>(
136 override val context: CoroutineContext,
137 continuation: Continuation<T>
138) : Continuation<T> by continuation
139
140
141@Suppress("UNCHECKED_CAST")
142private class RunCompletion<in T>(
143 override val context: CoroutineContext,
144 delegate: Continuation<T>,
145 resumeMode: Int
Roman Elizarov4d626de2018-01-11 22:57:28 +0300146) : AbstractContinuation<T>(delegate, resumeMode)
Roman Elizarov9d5abcd2017-12-21 16:54:30 +0300147
Roman Elizarova12ee152017-12-20 10:50:17 +0300148