diff --git a/compiler/testData/codegen/box/coroutines/asyncIterator.kt b/compiler/testData/codegen/box/coroutines/asyncIterator.kt index e2612049622..d23f8d8990b 100644 --- a/compiler/testData/codegen/box/coroutines/asyncIterator.kt +++ b/compiler/testData/codegen/box/coroutines/asyncIterator.kt @@ -34,6 +34,8 @@ class AsyncGeneratorIterator: AsyncIterator, AsyncGenerator, Continuati var computesNext = false var computeContinuation: Continuation<*>? = null + override val context = EmptyContext + suspend fun computeHasNext(): Boolean = suspendCoroutineOrReturn { c -> computesNext = false computeContinuation = c diff --git a/compiler/testData/codegen/box/coroutines/beginWithException.kt b/compiler/testData/codegen/box/coroutines/beginWithException.kt index dceb1f97dce..155437d74dc 100644 --- a/compiler/testData/codegen/box/coroutines/beginWithException.kt +++ b/compiler/testData/codegen/box/coroutines/beginWithException.kt @@ -9,6 +9,8 @@ fun builder(c: suspend () -> Unit) { var exception: Throwable? = null c.createCoroutine(object : Continuation { + override val context = EmptyContext + override fun resume(data: Unit) { } diff --git a/compiler/testData/codegen/box/coroutines/controlFlow/throwFromCatch.kt b/compiler/testData/codegen/box/coroutines/controlFlow/throwFromCatch.kt index a3b1c63b951..af31c7546fa 100644 --- a/compiler/testData/codegen/box/coroutines/controlFlow/throwFromCatch.kt +++ b/compiler/testData/codegen/box/coroutines/controlFlow/throwFromCatch.kt @@ -24,6 +24,8 @@ class Controller { fun builder(c: suspend Controller.() -> Unit): String { val controller = Controller() c.startCoroutine(controller, object : Continuation { + override val context = EmptyContext + override fun resume(data: Unit) { } diff --git a/compiler/testData/codegen/box/coroutines/controlFlow/throwInTryWithHandleResult.kt b/compiler/testData/codegen/box/coroutines/controlFlow/throwInTryWithHandleResult.kt index f65566187ef..71ab855033a 100644 --- a/compiler/testData/codegen/box/coroutines/controlFlow/throwInTryWithHandleResult.kt +++ b/compiler/testData/codegen/box/coroutines/controlFlow/throwInTryWithHandleResult.kt @@ -17,6 +17,8 @@ class Controller { fun builder(c: suspend Controller.() -> Unit): String { val controller = Controller() c.startCoroutine(controller, object : Continuation { + override val context = EmptyContext + override fun resume(data: Unit) {} override fun resumeWithException(exception: Throwable) { diff --git a/compiler/testData/codegen/box/coroutines/dispatchResume.kt b/compiler/testData/codegen/box/coroutines/dispatchResume.kt index 091c3c7a080..5d94e96cc34 100644 --- a/compiler/testData/codegen/box/coroutines/dispatchResume.kt +++ b/compiler/testData/codegen/box/coroutines/dispatchResume.kt @@ -23,7 +23,7 @@ class Controller { fun test(c: suspend Controller.() -> Unit): String { val controller = Controller() - c.startCoroutine(controller, EmptyContinuation, object: ContinuationDispatcher { + c.startCoroutine(controller, EmptyContinuation(object: ContinuationDispatcher() { private fun dispatchResume(block: () -> Unit) { val id = controller.resumeIndex++ controller.log += "before $id;" @@ -44,7 +44,7 @@ fun test(c: suspend Controller.() -> Unit): String { } return true } - }) + })) return controller.log } diff --git a/compiler/testData/codegen/box/coroutines/generate.kt b/compiler/testData/codegen/box/coroutines/generate.kt index f78fc9e0c62..42686c76af3 100644 --- a/compiler/testData/codegen/box/coroutines/generate.kt +++ b/compiler/testData/codegen/box/coroutines/generate.kt @@ -39,6 +39,8 @@ class GeneratedSequence(private val block: suspend Generator.() -> Uni class GeneratedIterator(block: suspend Generator.() -> Unit) : AbstractIterator(), Generator { private var nextStep: Continuation = block.createCoroutine(this, object : Continuation { + override val context = EmptyContext + override fun resume(data: Unit) { done() } diff --git a/compiler/testData/codegen/box/coroutines/multiModule/inlineFunctionWithOptionalParam.kt b/compiler/testData/codegen/box/coroutines/multiModule/inlineFunctionWithOptionalParam.kt index 91e8b51b9ae..5aeb3ece300 100644 --- a/compiler/testData/codegen/box/coroutines/multiModule/inlineFunctionWithOptionalParam.kt +++ b/compiler/testData/codegen/box/coroutines/multiModule/inlineFunctionWithOptionalParam.kt @@ -15,6 +15,7 @@ var result = "" fun builder(c: suspend () -> Unit) { c.startCoroutine(object : Continuation { + override val context = EmptyContext override fun resume(value: Unit) { } override fun resumeWithException(exception: Throwable) { diff --git a/compiler/testData/codegen/box/coroutines/simpleWithHandleResult.kt b/compiler/testData/codegen/box/coroutines/simpleWithHandleResult.kt index 4d6c632299e..b2d0c8e49f7 100644 --- a/compiler/testData/codegen/box/coroutines/simpleWithHandleResult.kt +++ b/compiler/testData/codegen/box/coroutines/simpleWithHandleResult.kt @@ -12,6 +12,8 @@ fun builder(c: suspend () -> Int): Int { var res = 0 c.createCoroutine(object : Continuation { + override val context = EmptyContext + override fun resume(data: Int) { res = data } diff --git a/compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/dispatchResume.kt b/compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/dispatchResume.kt index 5013b7b599c..391b58840ef 100644 --- a/compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/dispatchResume.kt +++ b/compiler/testData/codegen/box/coroutines/suspendFunctionAsCoroutine/dispatchResume.kt @@ -23,7 +23,7 @@ class Controller { fun test(c: suspend Controller.() -> Unit): String { val controller = Controller() - c.startCoroutine(controller, EmptyContinuation, object: ContinuationDispatcher { + c.startCoroutine(controller, EmptyContinuation(object: ContinuationDispatcher() { private fun dispatchResume(block: () -> Unit) { val id = controller.resumeIndex++ controller.log += "before $id;" @@ -44,7 +44,7 @@ fun test(c: suspend Controller.() -> Unit): String { } return true } - }) + })) return controller.log } diff --git a/compiler/testData/codegen/box/coroutines/unitTypeReturn/coroutineNonLocalReturn.kt b/compiler/testData/codegen/box/coroutines/unitTypeReturn/coroutineNonLocalReturn.kt index 62a6d70037a..277c7f8ef34 100644 --- a/compiler/testData/codegen/box/coroutines/unitTypeReturn/coroutineNonLocalReturn.kt +++ b/compiler/testData/codegen/box/coroutines/unitTypeReturn/coroutineNonLocalReturn.kt @@ -11,6 +11,8 @@ suspend fun suspendHere(): String = suspendCoroutineOrReturn { x -> fun builder(c: suspend () -> Unit) { var wasResumeCalled = false c.startCoroutine(object : Continuation { + override val context = EmptyContext + override fun resume(value: Unit) { wasResumeCalled = true } diff --git a/compiler/testData/codegen/box/coroutines/unitTypeReturn/coroutineReturn.kt b/compiler/testData/codegen/box/coroutines/unitTypeReturn/coroutineReturn.kt index be09587accd..1c661d31b15 100644 --- a/compiler/testData/codegen/box/coroutines/unitTypeReturn/coroutineReturn.kt +++ b/compiler/testData/codegen/box/coroutines/unitTypeReturn/coroutineReturn.kt @@ -11,6 +11,8 @@ suspend fun suspendHere(): String = suspendCoroutineOrReturn { x -> fun builder(c: suspend () -> Unit) { var wasResumeCalled = false c.startCoroutine(object : Continuation { + override val context = EmptyContext + override fun resume(value: Unit) { wasResumeCalled = true } diff --git a/compiler/tests-common/org/jetbrains/kotlin/test/KotlinTestUtils.java b/compiler/tests-common/org/jetbrains/kotlin/test/KotlinTestUtils.java index cc5eb0fa14c..5db4c7ca28d 100644 --- a/compiler/tests-common/org/jetbrains/kotlin/test/KotlinTestUtils.java +++ b/compiler/tests-common/org/jetbrains/kotlin/test/KotlinTestUtils.java @@ -649,6 +649,7 @@ public class KotlinTestUtils { "CoroutineUtil.kt", "import kotlin.coroutines.*\n" + "fun handleResultContinuation(x: (T) -> Unit): Continuation = object: Continuation {\n" + + " override val context = EmptyContext\n" + " override fun resumeWithException(exception: Throwable) {\n" + " throw exception\n" + " }\n" + @@ -657,6 +658,7 @@ public class KotlinTestUtils { "}\n" + "\n" + "fun handleExceptionContinuation(x: (Throwable) -> Unit): Continuation = object: Continuation {\n" + + " override val context = EmptyContext\n" + " override fun resumeWithException(exception: Throwable) {\n" + " x(exception)\n" + " }\n" + @@ -664,11 +666,44 @@ public class KotlinTestUtils { " override fun resume(data: Any?) { }\n" + "}\n" + "\n" + - "object EmptyContinuation : Continuation {\n" + + "object EmptyContext : CoroutineContext {\n" + + " override fun get(key: CoroutineContextKey): E? = null\n" + + " override fun fold(initial: R, operation: (R, CoroutineContextElement) -> R): R = initial\n" + + " override fun plus(context: CoroutineContext): CoroutineContext = context\n" + + " override fun minusKey(key: CoroutineContextKey<*>): CoroutineContext = this\n" + + "}\n" + + "\n" + + "open class EmptyContinuation(override val context: CoroutineContext = EmptyContext) : Continuation {\n" + + " companion object : EmptyContinuation()\n" + " override fun resume(data: Any?) {}\n" + + " override fun resumeWithException(exception: Throwable) { throw exception }\n" + + "}\n" + + "\n" + + "abstract class ContinuationDispatcher : ContinuationInterceptor {\n" + + " override val contextKey: CoroutineContextKey<*> = ContinuationInterceptor\n" + + " abstract fun dispatchResume(value: T, continuation: Continuation): Boolean\n" + + " abstract fun dispatchResumeWithException(exception: Throwable, continuation: Continuation<*>): Boolean\n" + + " override fun interceptContinuation(continuation: Continuation): Continuation = DispatchedContinuation(this, continuation)\n" + + " override operator fun get(key: CoroutineContextKey): E? = if (this.contextKey == key) this as E else null\n" + + " override fun fold(initial: R, operation: (R, CoroutineContextElement) -> R): R = operation(initial, this)\n" + + " override operator fun plus(context: CoroutineContext): CoroutineContext = this\n" + + " override fun minusKey(key: CoroutineContextKey<*>): CoroutineContext = if (this.contextKey == key) EmptyContext else this\n" + + "}\n" + + "\n" + + "private class DispatchedContinuation(\n" + + " val dispatcher: ContinuationDispatcher,\n" + + " val continuation: Continuation\n" + + "): Continuation {\n" + + " override val context: CoroutineContext = continuation.context\n" + + "\n" + + " override fun resume(value: T) {\n" + + " if (!dispatcher.dispatchResume(value, continuation))\n" + + " continuation.resume(value)\n" + + " }\n" + "\n" + " override fun resumeWithException(exception: Throwable) {\n" + - " throw exception\n" + + " if (!dispatcher.dispatchResumeWithException(exception, continuation))\n" + + " continuation.resumeWithException(exception)\n" + " }\n" + "}", directives diff --git a/core/builtins/src/kotlin/coroutines/ContinuationInterceptor.kt b/core/builtins/src/kotlin/coroutines/ContinuationInterceptor.kt new file mode 100644 index 00000000000..5c2087e4f50 --- /dev/null +++ b/core/builtins/src/kotlin/coroutines/ContinuationInterceptor.kt @@ -0,0 +1,34 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kotlin.coroutines + +/** + * Marks coroutine context element that intercepts coroutine continuations. + */ +@SinceKotlin("1.1") +public interface ContinuationInterceptor : CoroutineContextElement { + /** + * The key that defines *the* context interceptor. + */ + companion object : CoroutineContextKey + + /** + * Intercepts the given [continuation] by wrapping it. Application code should not call this method directly as + * it is invoked by coroutines framework appropriately and the resulting continuations are efficiently cached. + */ + public fun interceptContinuation(continuation: Continuation): Continuation +} diff --git a/core/builtins/src/kotlin/coroutines/CoroutineContext.kt b/core/builtins/src/kotlin/coroutines/CoroutineContext.kt new file mode 100644 index 00000000000..ebe9dbce6cd --- /dev/null +++ b/core/builtins/src/kotlin/coroutines/CoroutineContext.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kotlin.coroutines + +/** + * Persistent context for the coroutine. It is an indexed set of [CoroutineContextElement] instances. + * An indexed set is a mix between a set and a map. + * Every element in this set has a unique [CoroutineContextKey]. + */ +@SinceKotlin("1.1") +public interface CoroutineContext { + /** + * Returns an element with the given [key] in this context or `null`. + */ + public operator fun get(key: CoroutineContextKey): E? + + /** + * Accumulates entries of this context starting with [initial] value and applying [operation] + * from left to right to current accumulator value and each element of this context. + */ + public fun fold(initial: R, operation: (R, CoroutineContextElement) -> R): R + + /** + * Returns a context containing elements from this context and elements from other [context]. + * The elements from this context with the same key as in the other one are dropped. + */ + public operator fun plus(context: CoroutineContext): CoroutineContext + + /** + * Returns a context containing elements from this context, but without an element with + * the specified [key]. + */ + public fun minusKey(key: CoroutineContextKey<*>): CoroutineContext +} + +/** + * An element of the [CoroutineContext]. An element of the coroutine context is a singleton context by itself. + */ +@SinceKotlin("1.1") +public interface CoroutineContextElement : CoroutineContext { + /** + * A key of this coroutine context element. + */ + public val contextKey: CoroutineContextKey<*> +} + +/** + * Key for the elements of [CoroutineContext]. [E] is a type of the element with this key. + */ +@SinceKotlin("1.1") +public interface CoroutineContextKey + diff --git a/core/builtins/src/kotlin/coroutines/Coroutines.kt b/core/builtins/src/kotlin/coroutines/Coroutines.kt index 945e4a028bb..01393570537 100644 --- a/core/builtins/src/kotlin/coroutines/Coroutines.kt +++ b/core/builtins/src/kotlin/coroutines/Coroutines.kt @@ -21,6 +21,11 @@ package kotlin.coroutines */ @SinceKotlin("1.1") public interface Continuation { + /** + * Context of the coroutine that corresponds to this continuation. + */ + public val context: CoroutineContext + /** * Resumes the execution of the corresponding coroutine passing [value] as the return value of the last suspension point. */ @@ -33,24 +38,6 @@ public interface Continuation { public fun resumeWithException(exception: Throwable) } -/** - * An interface to customise dispatch of continuations. - */ -@SinceKotlin("1.1") -public interface ContinuationDispatcher { - /** - * Dispatches [Continuation.resume] invocation. - * This function must either return `false` or return `true` and invoke `continuation.resume(value)` asynchronously. - */ - public fun dispatchResume(value: T, continuation: Continuation): Boolean = false - - /** - * Dispatches [Continuation.resumeWithException] invocation. - * This function must either return `false` or return `true` and invoke `continuation.resumeWithException(exception)` asynchronously. - */ - public fun dispatchResumeWithException(exception: Throwable, continuation: Continuation<*>): Boolean = false -} - /** * Classes and interfaces marked with this annotation are restricted when used as receivers for extension * `suspend` functions. These `suspend` extensions can only invoke other member or extension `suspend` functions on this particular diff --git a/core/builtins/src/kotlin/coroutines/intrinsics/Intrinsics.kt b/core/builtins/src/kotlin/coroutines/intrinsics/Intrinsics.kt index 79253399eaf..c0ab260f44f 100644 --- a/core/builtins/src/kotlin/coroutines/intrinsics/Intrinsics.kt +++ b/core/builtins/src/kotlin/coroutines/intrinsics/Intrinsics.kt @@ -36,7 +36,7 @@ import kotlin.coroutines.Continuation * continuation instance. */ @SinceKotlin("1.1") -public inline suspend fun suspendCoroutineOrReturn(block: (Continuation) -> Any?): T = null!! +public inline suspend fun suspendCoroutineOrReturn(crossinline block: (Continuation) -> Any?): T = null!! /** * This value is used as a return value of [suspendCoroutineOrReturn] `block` argument to state that diff --git a/core/runtime.jvm/src/kotlin/jvm/internal/CoroutineImpl.kt b/core/runtime.jvm/src/kotlin/jvm/internal/CoroutineImpl.kt index 5b5cf2fd3da..782854c3c2d 100644 --- a/core/runtime.jvm/src/kotlin/jvm/internal/CoroutineImpl.kt +++ b/core/runtime.jvm/src/kotlin/jvm/internal/CoroutineImpl.kt @@ -23,25 +23,23 @@ abstract class CoroutineImpl( arity: Int, @JvmField protected var completion: Continuation? -) : DispatchedContinuation, Lambda(arity), Continuation { +) : Lambda(arity), Continuation { // label == -1 when coroutine cannot be started (it is just a factory object) or has already finished execution // label == 0 in initial part of the coroutine @JvmField protected var label: Int = if (completion != null) 0 else -1 - private val _dispatcher: ContinuationDispatcher? = (completion as? DispatchedContinuation<*>)?.dispatcher + private val _context: CoroutineContext? = completion?.context - override val dispatcher: ContinuationDispatcher? - get() = _dispatcher + override val context: CoroutineContext + get() = _context!! + + private var _facade: Continuation? = null - private var facade_: Continuation? = null val facade: Continuation get() { - if (facade_ == null) { - facade_ = wrapContinuationIfNeeded(this, dispatcher) - } - - return facade_!! + if (_facade == null) _facade = _context!![ContinuationInterceptor]?.interceptContinuation(this) ?: this + return _facade!! } override fun resume(value: Any?) { @@ -66,7 +64,3 @@ abstract class CoroutineImpl( protected abstract fun doResume(data: Any?, exception: Throwable?): Any? } - -internal interface DispatchedContinuation : Continuation { - val dispatcher: ContinuationDispatcher? -} diff --git a/core/runtime.jvm/src/kotlin/jvm/internal/CoroutineIntrinsics.kt b/core/runtime.jvm/src/kotlin/jvm/internal/CoroutineIntrinsics.kt index 0c239d4675f..62406e8a494 100644 --- a/core/runtime.jvm/src/kotlin/jvm/internal/CoroutineIntrinsics.kt +++ b/core/runtime.jvm/src/kotlin/jvm/internal/CoroutineIntrinsics.kt @@ -17,34 +17,6 @@ package kotlin.jvm.internal import kotlin.coroutines.Continuation -import kotlin.coroutines.ContinuationDispatcher -fun normalizeContinuation(c: Continuation): Continuation { - if (c is CoroutineImpl) { - return c.facade - } - - return c -} - -internal fun wrapContinuationIfNeeded(c: Continuation, dispatcher: ContinuationDispatcher?): Continuation { - if (dispatcher == null) return c - return DispatchedContinuationImpl(c, dispatcher) -} - -private class DispatchedContinuationImpl( - private val c: Continuation, - private val dispatcher: ContinuationDispatcher -) : Continuation { - override fun resume(value: T) { - if (!dispatcher.dispatchResume(value, c)) { - c.resume(value) - } - } - - override fun resumeWithException(exception: Throwable) { - if (!dispatcher.dispatchResumeWithException(exception, c)) { - c.resumeWithException(exception) - } - } -} +fun normalizeContinuation(continuation: Continuation): Continuation = + (continuation as? CoroutineImpl)?.facade ?: continuation diff --git a/js/js.libraries/src/core/coroutines.kt b/js/js.libraries/src/core/coroutines.kt index 64b8d987655..4ec04fe2d76 100644 --- a/js/js.libraries/src/core/coroutines.kt +++ b/js/js.libraries/src/core/coroutines.kt @@ -23,28 +23,24 @@ import kotlin.coroutines.intrinsics.* * This function creates a new, fresh instance of suspendable computation every time it is invoked. * To start executing the created coroutine, invoke `resume(Unit)` on the returned [Continuation] instance. * The [completion] continuation is invoked when coroutine completes with result of exception. - * An optional [dispatcher] may be specified to customise dispatch of continuations between suspension points inside the coroutine. */ @SinceKotlin("1.1") public fun (suspend R.() -> T).createCoroutine( receiver: R, - completion: Continuation, - dispatcher: ContinuationDispatcher? = null -): Continuation = this.asDynamic().call(receiver, withDispatcher(completion, dispatcher), true).facade + completion: Continuation +): Continuation = this.asDynamic().call(receiver, completion, true).facade /** * Starts coroutine with receiver type [R] and result type [T]. * This function creates and start a new, fresh instance of suspendable computation every time it is invoked. * The [completion] continuation is invoked when coroutine completes with result of exception. - * An optional [dispatcher] may be specified to customise dispatch of continuations between suspension points inside the coroutine. */ @SinceKotlin("1.1") public fun (suspend R.() -> T).startCoroutine( receiver: R, - completion: Continuation, - dispatcher: ContinuationDispatcher? = null + completion: Continuation ) { - this.asDynamic().call(receiver, withDispatcher(completion, dispatcher)) + this.asDynamic().call(receiver, completion) } /** @@ -52,26 +48,22 @@ public fun (suspend R.() -> T).startCoroutine( * This function creates a new, fresh instance of suspendable computation every time it is invoked. * To start executing the created coroutine, invoke `resume(Unit)` on the returned [Continuation] instance. * The [completion] continuation is invoked when coroutine completes with result of exception. - * An optional [dispatcher] may be specified to customise dispatch of continuations between suspension points inside the coroutine. */ @SinceKotlin("1.1") public fun (suspend () -> T).createCoroutine( - completion: Continuation, - dispatcher: ContinuationDispatcher? = null -): Continuation = this.asDynamic()(withDispatcher(completion, dispatcher), true).facade + completion: Continuation +): Continuation = this.asDynamic()(completion, true).facade /** * Starts coroutine without receiver and with result type [T]. * This function creates and start a new, fresh instance of suspendable computation every time it is invoked. * The [completion] continuation is invoked when coroutine completes with result of exception. - * An optional [dispatcher] may be specified to customise dispatch of continuations between suspension points inside the coroutine. */ @SinceKotlin("1.1") public fun (suspend () -> T).startCoroutine( - completion: Continuation, - dispatcher: ContinuationDispatcher? = null + completion: Continuation ) { - this.asDynamic()(withDispatcher(completion, dispatcher)) + this.asDynamic()(completion) } /** @@ -91,21 +83,6 @@ public suspend fun suspendCoroutine(block: (Continuation) -> Unit): T = s // ------- internal stuff ------- -internal interface DispatchedContinuation { - val dispatcher: ContinuationDispatcher? -} - -private fun withDispatcher(completion: Continuation, dispatcher: ContinuationDispatcher?): Continuation { - return if (dispatcher == null) { - completion - } - else { - object : Continuation by completion, DispatchedContinuation { - override val dispatcher = dispatcher - } - } -} - @JsName("CoroutineImpl") internal abstract class CoroutineImpl(private val resultContinuation: Continuation) : Continuation { protected var state = 0 @@ -113,17 +90,10 @@ internal abstract class CoroutineImpl(private val resultContinuation: Continuati protected var result: Any? = null protected var exception: Throwable? = null protected var finallyPath: Array? = null - private val continuationDispatcher = (resultContinuation as? DispatchedContinuation)?.dispatcher - val facade: Continuation - init { - facade = if (continuationDispatcher != null) { - ContinuationFacade(this, continuationDispatcher) - } - else { - this - } - } + public override val context: CoroutineContext = resultContinuation.context + + val facade: Continuation = context[ContinuationInterceptor]?.interceptContinuation(this) ?: this override fun resume(data: Any?) { result = data @@ -159,25 +129,14 @@ internal abstract class CoroutineImpl(private val resultContinuation: Continuati protected abstract fun doResume(): Any? } -private class ContinuationFacade(val innerContinuation: Continuation, val dispatcher: ContinuationDispatcher) : Continuation { - override fun resume(value: Any?) { - if (!dispatcher.dispatchResume(value, innerContinuation)) { - innerContinuation.resume(value) - } - } - - override fun resumeWithException(exception: Throwable) { - if (!dispatcher.dispatchResumeWithException(exception, innerContinuation)) { - innerContinuation.resumeWithException(exception) - } - } -} - private val UNDECIDED: Any? = Any() private val RESUMED: Any? = Any() private class Fail(val exception: Throwable) internal class SafeContinuation internal constructor(private val delegate: Continuation) : Continuation { + public override val context: CoroutineContext + get() = delegate.context + private var result: Any? = UNDECIDED override fun resume(value: T) { diff --git a/libraries/stdlib/src/kotlin/coroutines/CoroutineContextImpl.kt b/libraries/stdlib/src/kotlin/coroutines/CoroutineContextImpl.kt new file mode 100644 index 00000000000..6f8eb7cb707 --- /dev/null +++ b/libraries/stdlib/src/kotlin/coroutines/CoroutineContextImpl.kt @@ -0,0 +1,129 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package kotlin.coroutines + +/** + * Base class for [CoroutineContextElement] implementations. + */ +@SinceKotlin("1.1") +public abstract class AbstractCoroutineContextElement : CoroutineContextElement { + @Suppress("UNCHECKED_CAST") + public override operator fun get(key: CoroutineContextKey): E? = + if (this.contextKey == key) this as E else null + + public override fun fold(initial: R, operation: (R, CoroutineContextElement) -> R): R = + operation(initial, this) + + public override operator fun plus(context: CoroutineContext): CoroutineContext = + plusImpl(context) + + public override fun minusKey(key: CoroutineContextKey<*>): CoroutineContext = + if (this.contextKey == key) EmptyCoroutineContext else this +} + +/** + * An empty coroutine context. + */ +@SinceKotlin("1.1") +public object EmptyCoroutineContext : CoroutineContext { + public override fun get(key: CoroutineContextKey): E? = null + public override fun fold(initial: R, operation: (R, CoroutineContextElement) -> R): R = initial + public override fun plus(context: CoroutineContext): CoroutineContext = context + public override fun minusKey(key: CoroutineContextKey<*>): CoroutineContext = this + public override fun hashCode(): Int = 0 + public override fun toString(): String = "EmptyCoroutineContext" +} + +//--------------------- private impl --------------------- + +// this class is not exposed, but is hidden inside implementations +// this is a left-biased list, so that `plus` works naturally +private class CombinedContext(val left: CoroutineContext, val element: CoroutineContextElement) : CoroutineContext { + override fun get(key: CoroutineContextKey): E? { + var cur = this + while (true) { + cur.element[key]?.let { return it } + val next = cur.left + if (next is CombinedContext) { + cur = next + } else { + return next[key] + } + } + } + + public override fun fold(initial: R, operation: (R, CoroutineContextElement) -> R): R = + operation(left.fold(initial, operation), element) + + public override operator fun plus(context: CoroutineContext): CoroutineContext = + plusImpl(context) + + public override fun minusKey(key: CoroutineContextKey<*>): CoroutineContext { + element[key]?.let { return left } + val newLeft = left.minusKey(key) + return when (newLeft) { + left -> this + EmptyCoroutineContext -> element + else -> CombinedContext(newLeft, element) + } + } + + private fun size(): Int = + if (left is CombinedContext) left.size() + 1 else 2 + + private fun contains(element: CoroutineContextElement): Boolean = + get(element.contextKey) == element + + private fun containsAll(context: CombinedContext): Boolean { + var cur = context + while (true) { + if (!contains(cur.element)) return false + val next = cur.left + if (next is CombinedContext) { + cur = next + } else { + return contains(next as CoroutineContextElement) + } + } + } + + override fun equals(other: Any?): Boolean = + this === other || other is CombinedContext && other.size() == size() && other.containsAll(this) + + override fun hashCode(): Int = left.hashCode() + element.hashCode() + + override fun toString(): String = + "[" + fold("") { acc, element -> + if (acc.isEmpty()) element.toString() else acc + ", " + element + } + "]" +} + +private fun CoroutineContext.plusImpl(context: CoroutineContext): CoroutineContext = + if (context == EmptyCoroutineContext) this else // fast path -- avoid lambda creation + context.fold(this) { acc, element -> + val removed = acc.minusKey(element.contextKey) + if (removed == EmptyCoroutineContext) element else { + // make sure interceptor is always last in the context (and thus is fast to get when present) + val interceptor = removed[ContinuationInterceptor] + if (interceptor == null) CombinedContext(removed, element) else { + val left = removed.minusKey(ContinuationInterceptor) + if (left == EmptyCoroutineContext) CombinedContext(element, interceptor) else + CombinedContext(CombinedContext(left, element), interceptor) + } + } + } + diff --git a/libraries/stdlib/src/kotlin/coroutines/CoroutinesLibrary.kt b/libraries/stdlib/src/kotlin/coroutines/CoroutinesLibrary.kt index 66a37355b70..24df601efd9 100644 --- a/libraries/stdlib/src/kotlin/coroutines/CoroutinesLibrary.kt +++ b/libraries/stdlib/src/kotlin/coroutines/CoroutinesLibrary.kt @@ -11,30 +11,26 @@ import kotlin.coroutines.intrinsics.* * This function creates a new, fresh instance of suspendable computation every time it is invoked. * To start executing the created coroutine, invoke `resume(Unit)` on the returned [Continuation] instance. * The [completion] continuation is invoked when coroutine completes with result of exception. - * An optional [dispatcher] may be specified to customise dispatch of continuations between suspension points inside the coroutine. */ @SinceKotlin("1.1") @Suppress("UNCHECKED_CAST") public fun (suspend R.() -> T).createCoroutine( receiver: R, - completion: Continuation, - dispatcher: ContinuationDispatcher? = null -): Continuation = (this as kotlin.jvm.internal.SuspendFunction1).create(receiver, withDispatcher(completion, dispatcher)) + completion: Continuation +): Continuation = (this as kotlin.jvm.internal.SuspendFunction1).create(receiver, completion) /** * Starts coroutine with receiver type [R] and result type [T]. * This function creates and start a new, fresh instance of suspendable computation every time it is invoked. * The [completion] continuation is invoked when coroutine completes with result of exception. - * An optional [dispatcher] may be specified to customise dispatch of continuations between suspension points inside the coroutine. */ @SinceKotlin("1.1") @Suppress("UNCHECKED_CAST") public fun (suspend R.() -> T).startCoroutine( receiver: R, - completion: Continuation, - dispatcher: ContinuationDispatcher? = null + completion: Continuation ) { - (this as Function2, Any?>).invoke(receiver, withDispatcher(completion, dispatcher)) + (this as Function2, Any?>).invoke(receiver, completion) } /** @@ -42,28 +38,24 @@ public fun (suspend R.() -> T).startCoroutine( * This function creates a new, fresh instance of suspendable computation every time it is invoked. * To start executing the created coroutine, invoke `resume(Unit)` on the returned [Continuation] instance. * The [completion] continuation is invoked when coroutine completes with result of exception. - * An optional [dispatcher] may be specified to customise dispatch of continuations between suspension points inside the coroutine. */ @SinceKotlin("1.1") @Suppress("UNCHECKED_CAST") public fun (suspend () -> T).createCoroutine( - completion: Continuation, - dispatcher: ContinuationDispatcher? = null -): Continuation = (this as kotlin.jvm.internal.SuspendFunction0).create(withDispatcher(completion, dispatcher)) + completion: Continuation +): Continuation = (this as kotlin.jvm.internal.SuspendFunction0).create(completion) /** * Starts coroutine without receiver and with result type [T]. * This function creates and start a new, fresh instance of suspendable computation every time it is invoked. * The [completion] continuation is invoked when coroutine completes with result of exception. - * An optional [dispatcher] may be specified to customise dispatch of continuations between suspension points inside the coroutine. */ @SinceKotlin("1.1") @Suppress("UNCHECKED_CAST") public fun (suspend () -> T).startCoroutine( - completion: Continuation, - dispatcher: ContinuationDispatcher? = null + completion: Continuation ) { - (this as Function1, Any?>).invoke(withDispatcher(completion, dispatcher)) + (this as Function1, Any?>).invoke(completion) } /** @@ -84,20 +76,15 @@ public inline suspend fun suspendCoroutine(crossinline block: (Continuation< // INTERNAL DECLARATIONS -@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "CANNOT_OVERRIDE_INVISIBLE_MEMBER") -private fun withDispatcher(completion: Continuation, dispatcher: ContinuationDispatcher?): Continuation { - return if (dispatcher == null) completion else - object : kotlin.jvm.internal.DispatchedContinuation, Continuation by completion { - override val dispatcher: ContinuationDispatcher? = dispatcher - } -} - private val UNDECIDED: Any? = Any() private val RESUMED: Any? = Any() private class Fail(val exception: Throwable) @PublishedApi internal class SafeContinuation @PublishedApi internal constructor(private val delegate: Continuation) : Continuation { + public override val context: CoroutineContext + get() = delegate.context + @Volatile private var result: Any? = UNDECIDED diff --git a/libraries/stdlib/test/coroutines/CoroutineContextTest.kt b/libraries/stdlib/test/coroutines/CoroutineContextTest.kt new file mode 100644 index 00000000000..119d188d149 --- /dev/null +++ b/libraries/stdlib/test/coroutines/CoroutineContextTest.kt @@ -0,0 +1,179 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package test.coroutines + +import kotlin.test.* +import org.junit.Test + +class CoroutineContextTest { + data class CtxA(val i: Int) : AbstractCoroutineContextElement() { + companion object : CoroutineContextKey + override val contextKey get() = CtxA + } + + data class CtxB(val i: Int) : AbstractCoroutineContextElement() { + companion object : CoroutineContextKey + override val contextKey get() = CtxB + } + + data class CtxC(val i: Int) : AbstractCoroutineContextElement() { + companion object : CoroutineContextKey + override val contextKey get() = CtxC + } + + object Disp1 : AbstractCoroutineContextElement(), ContinuationInterceptor { + override fun interceptContinuation(continuation: CoroutineContinuation): CoroutineContinuation = continuation + override val contextKey: CoroutineContextKey<*> = ContinuationInterceptor + override fun toString(): String = "Disp1" + } + + object Disp2 : AbstractCoroutineContextElement(), ContinuationInterceptor { + override fun interceptContinuation(continuation: CoroutineContinuation): CoroutineContinuation = continuation + override val contextKey: CoroutineContextKey<*> = ContinuationInterceptor + override fun toString(): String = "Disp2" + } + + @Test + fun testGetPlusFold() { + var ctx: CoroutineContext = EmptyCoroutineContext + assertContents(ctx) + assertEquals("EmptyCoroutineContext", ctx.toString()) + + ctx += CtxA(1) + assertContents(ctx, CtxA(1)) + assertEquals("CtxA(i=1)", ctx.toString()) + assertEquals(CtxA(1), ctx[CtxA]) + assertEquals(null, ctx[CtxB]) + assertEquals(null, ctx[CtxC]) + + ctx += CtxB(2) + assertContents(ctx, CtxA(1), CtxB(2)) + assertEquals("[CtxA(i=1), CtxB(i=2)]", ctx.toString()) + assertEquals(CtxA(1), ctx[CtxA]) + assertEquals(CtxB(2), ctx[CtxB]) + assertEquals(null, ctx[CtxC]) + + ctx += CtxC(3) + assertContents(ctx, CtxA(1), CtxB(2), CtxC(3)) + assertEquals("[CtxA(i=1), CtxB(i=2), CtxC(i=3)]", ctx.toString()) + assertEquals(CtxA(1), ctx[CtxA]) + assertEquals(CtxB(2), ctx[CtxB]) + assertEquals(CtxC(3), ctx[CtxC]) + + ctx += CtxB(4) + assertContents(ctx, CtxA(1), CtxC(3), CtxB(4)) + assertEquals("[CtxA(i=1), CtxC(i=3), CtxB(i=4)]", ctx.toString()) + assertEquals(CtxA(1), ctx[CtxA]) + assertEquals(CtxB(4), ctx[CtxB]) + assertEquals(CtxC(3), ctx[CtxC]) + + ctx += CtxA(5) + assertContents(ctx, CtxC(3), CtxB(4), CtxA(5)) + assertEquals("[CtxC(i=3), CtxB(i=4), CtxA(i=5)]", ctx.toString()) + assertEquals(CtxA(5), ctx[CtxA]) + assertEquals(CtxB(4), ctx[CtxB]) + assertEquals(CtxC(3), ctx[CtxC]) + } + + @Test + fun testMinusKey() { + var ctx: CoroutineContext = CtxA(1) + CtxB(2) + CtxC(3) + assertContents(ctx, CtxA(1), CtxB(2), CtxC(3)) + assertEquals("[CtxA(i=1), CtxB(i=2), CtxC(i=3)]", ctx.toString()) + + ctx = ctx.minusKey(CtxA) + assertContents(ctx, CtxB(2), CtxC(3)) + assertEquals("[CtxB(i=2), CtxC(i=3)]", ctx.toString()) + assertEquals(null, ctx[CtxA]) + assertEquals(CtxB(2), ctx[CtxB]) + assertEquals(CtxC(3), ctx[CtxC]) + + ctx = ctx.minusKey(CtxC) + assertContents(ctx, CtxB(2)) + assertEquals("CtxB(i=2)", ctx.toString()) + assertEquals(null, ctx[CtxA]) + assertEquals(CtxB(2), ctx[CtxB]) + assertEquals(null, ctx[CtxC]) + + ctx = ctx.minusKey(CtxC) + assertContents(ctx, CtxB(2)) + assertEquals("CtxB(i=2)", ctx.toString()) + assertEquals(null, ctx[CtxA]) + assertEquals(CtxB(2), ctx[CtxB]) + assertEquals(null, ctx[CtxC]) + + ctx = ctx.minusKey(CtxB) + assertContents(ctx) + assertEquals("EmptyCoroutineContext", ctx.toString()) + assertEquals(null, ctx[CtxA]) + assertEquals(null, ctx[CtxB]) + assertEquals(null, ctx[CtxC]) + + assertEquals(EmptyCoroutineContext, ctx) + } + + @Test + fun testPlusCombined() { + val ctx1 = CtxA(1) + CtxB(2) + val ctx2 = CtxB(3) + CtxC(4) + val ctx = ctx1 + ctx2 + assertContents(ctx, CtxA(1), CtxB(3), CtxC(4)) + assertEquals("[CtxA(i=1), CtxB(i=3), CtxC(i=4)]", ctx.toString()) + assertEquals(CtxA(1), ctx[CtxA]) + assertEquals(CtxB(3), ctx[CtxB]) + assertEquals(CtxC(4), ctx[CtxC]) + } + + @Test + fun testLastDispatcher() { + var ctx: CoroutineContext = EmptyCoroutineContext + assertContents(ctx) + ctx += CtxA(1) + assertContents(ctx, CtxA(1)) + ctx += Disp1 + assertContents(ctx, CtxA(1), Disp1) + ctx += CtxA(2) + assertContents(ctx, CtxA(2), Disp1) + ctx += CtxB(3) + assertContents(ctx, CtxA(2), CtxB(3), Disp1) + ctx += Disp2 + assertContents(ctx, CtxA(2), CtxB(3), Disp2) + ctx += (CtxB(4) + CtxC(5)) + assertContents(ctx, CtxA(2), CtxB(4), CtxC(5), Disp2) + } + + @Test + fun testEquals() { + val ctx1 = CtxA(1) + CtxB(2) + CtxC(3) + val ctx2 = CtxB(2) + CtxC(3) + CtxA(1) // same + val ctx3 = CtxC(3) + CtxA(1) + CtxB(2) // same + val ctx4 = CtxA(1) + CtxB(2) + CtxC(4) // different + assertEquals(ctx1, ctx2) + assertEquals(ctx1, ctx3) + assertEquals(ctx2, ctx3) + assertNotEquals(ctx1, ctx4) + assertNotEquals(ctx2, ctx4) + assertNotEquals(ctx3, ctx4) + } + + private fun assertContents(ctx: CoroutineContext, vararg elements: CoroutineContextElement) { + val set = ctx.fold(setOf()) { a, b -> a + b } + assertEquals(listOf(*elements), set.toList()) + for (elem in elements) + assertTrue(ctx[elem.contextKey] == elem) + } +} \ No newline at end of file