CoroutineContext and ContinuationInterceptor (instead of dispatcher)
This commit is contained in:
committed by
Denis Zharkov
parent
f611e39a69
commit
8d6a913cee
@@ -34,6 +34,8 @@ class AsyncGeneratorIterator<T>: AsyncIterator<T>, AsyncGenerator<T>, Continuati
|
||||
var computesNext = false
|
||||
var computeContinuation: Continuation<*>? = null
|
||||
|
||||
override val context = EmptyContext
|
||||
|
||||
suspend fun computeHasNext(): Boolean = suspendCoroutineOrReturn { c ->
|
||||
computesNext = false
|
||||
computeContinuation = c
|
||||
|
||||
@@ -9,6 +9,8 @@ fun builder(c: suspend () -> Unit) {
|
||||
var exception: Throwable? = null
|
||||
|
||||
c.createCoroutine(object : Continuation<Unit> {
|
||||
override val context = EmptyContext
|
||||
|
||||
override fun resume(data: Unit) {
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ class Controller {
|
||||
fun builder(c: suspend Controller.() -> Unit): String {
|
||||
val controller = Controller()
|
||||
c.startCoroutine(controller, object : Continuation<Unit> {
|
||||
override val context = EmptyContext
|
||||
|
||||
override fun resume(data: Unit) {
|
||||
|
||||
}
|
||||
|
||||
+2
@@ -17,6 +17,8 @@ class Controller {
|
||||
fun builder(c: suspend Controller.() -> Unit): String {
|
||||
val controller = Controller()
|
||||
c.startCoroutine(controller, object : Continuation<Unit> {
|
||||
override val context = EmptyContext
|
||||
|
||||
override fun resume(data: Unit) {}
|
||||
|
||||
override fun resumeWithException(exception: Throwable) {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -39,6 +39,8 @@ class GeneratedSequence<out T>(private val block: suspend Generator<T>.() -> Uni
|
||||
|
||||
class GeneratedIterator<T>(block: suspend Generator<T>.() -> Unit) : AbstractIterator<T>(), Generator<T> {
|
||||
private var nextStep: Continuation<Unit> = block.createCoroutine(this, object : Continuation<Unit> {
|
||||
override val context = EmptyContext
|
||||
|
||||
override fun resume(data: Unit) {
|
||||
done()
|
||||
}
|
||||
|
||||
+1
@@ -15,6 +15,7 @@ var result = ""
|
||||
|
||||
fun builder(c: suspend () -> Unit) {
|
||||
c.startCoroutine(object : Continuation<Unit> {
|
||||
override val context = EmptyContext
|
||||
override fun resume(value: Unit) {
|
||||
}
|
||||
override fun resumeWithException(exception: Throwable) {
|
||||
|
||||
@@ -12,6 +12,8 @@ fun builder(c: suspend () -> Int): Int {
|
||||
var res = 0
|
||||
|
||||
c.createCoroutine(object : Continuation<Int> {
|
||||
override val context = EmptyContext
|
||||
|
||||
override fun resume(data: Int) {
|
||||
res = data
|
||||
}
|
||||
|
||||
+2
-2
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -11,6 +11,8 @@ suspend fun suspendHere(): String = suspendCoroutineOrReturn { x ->
|
||||
fun builder(c: suspend () -> Unit) {
|
||||
var wasResumeCalled = false
|
||||
c.startCoroutine(object : Continuation<Unit> {
|
||||
override val context = EmptyContext
|
||||
|
||||
override fun resume(value: Unit) {
|
||||
wasResumeCalled = true
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ suspend fun suspendHere(): String = suspendCoroutineOrReturn { x ->
|
||||
fun builder(c: suspend () -> Unit) {
|
||||
var wasResumeCalled = false
|
||||
c.startCoroutine(object : Continuation<Unit> {
|
||||
override val context = EmptyContext
|
||||
|
||||
override fun resume(value: Unit) {
|
||||
wasResumeCalled = true
|
||||
}
|
||||
|
||||
@@ -649,6 +649,7 @@ public class KotlinTestUtils {
|
||||
"CoroutineUtil.kt",
|
||||
"import kotlin.coroutines.*\n" +
|
||||
"fun <T> handleResultContinuation(x: (T) -> Unit): Continuation<T> = object: Continuation<T> {\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<Any?> = object: Continuation<Any?> {\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<Any?> {\n" +
|
||||
"object EmptyContext : CoroutineContext {\n" +
|
||||
" override fun <E : CoroutineContextElement> get(key: CoroutineContextKey<E>): E? = null\n" +
|
||||
" override fun <R> 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<Any?> {\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 <T> dispatchResume(value: T, continuation: Continuation<T>): Boolean\n" +
|
||||
" abstract fun dispatchResumeWithException(exception: Throwable, continuation: Continuation<*>): Boolean\n" +
|
||||
" override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> = DispatchedContinuation(this, continuation)\n" +
|
||||
" override operator fun <E : CoroutineContextElement> get(key: CoroutineContextKey<E>): E? = if (this.contextKey == key) this as E else null\n" +
|
||||
" override fun <R> 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<T>(\n" +
|
||||
" val dispatcher: ContinuationDispatcher,\n" +
|
||||
" val continuation: Continuation<T>\n" +
|
||||
"): Continuation<T> {\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
|
||||
|
||||
@@ -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<ContinuationInterceptor>
|
||||
|
||||
/**
|
||||
* 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 <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
|
||||
}
|
||||
@@ -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 <E : CoroutineContextElement> get(key: CoroutineContextKey<E>): 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 <R> 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<E : CoroutineContextElement>
|
||||
|
||||
@@ -21,6 +21,11 @@ package kotlin.coroutines
|
||||
*/
|
||||
@SinceKotlin("1.1")
|
||||
public interface Continuation<in T> {
|
||||
/**
|
||||
* 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<in T> {
|
||||
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 <T> dispatchResume(value: T, continuation: Continuation<T>): 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
|
||||
|
||||
@@ -36,7 +36,7 @@ import kotlin.coroutines.Continuation
|
||||
* continuation instance.
|
||||
*/
|
||||
@SinceKotlin("1.1")
|
||||
public inline suspend fun <T> suspendCoroutineOrReturn(block: (Continuation<T>) -> Any?): T = null!!
|
||||
public inline suspend fun <T> suspendCoroutineOrReturn(crossinline block: (Continuation<T>) -> Any?): T = null!!
|
||||
|
||||
/**
|
||||
* This value is used as a return value of [suspendCoroutineOrReturn] `block` argument to state that
|
||||
|
||||
@@ -23,25 +23,23 @@ abstract class CoroutineImpl(
|
||||
arity: Int,
|
||||
@JvmField
|
||||
protected var completion: Continuation<Any?>?
|
||||
) : DispatchedContinuation<Any?>, Lambda(arity), Continuation<Any?> {
|
||||
) : Lambda(arity), Continuation<Any?> {
|
||||
|
||||
// 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<Any?>? = null
|
||||
|
||||
private var facade_: Continuation<Any?>? = null
|
||||
val facade: Continuation<Any?> 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<in T> : Continuation<T> {
|
||||
val dispatcher: ContinuationDispatcher?
|
||||
}
|
||||
|
||||
@@ -17,34 +17,6 @@
|
||||
package kotlin.jvm.internal
|
||||
|
||||
import kotlin.coroutines.Continuation
|
||||
import kotlin.coroutines.ContinuationDispatcher
|
||||
|
||||
fun <T> normalizeContinuation(c: Continuation<T>): Continuation<T> {
|
||||
if (c is CoroutineImpl) {
|
||||
return c.facade
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
internal fun <T> wrapContinuationIfNeeded(c: Continuation<T>, dispatcher: ContinuationDispatcher?): Continuation<T> {
|
||||
if (dispatcher == null) return c
|
||||
return DispatchedContinuationImpl(c, dispatcher)
|
||||
}
|
||||
|
||||
private class DispatchedContinuationImpl<in T>(
|
||||
private val c: Continuation<T>,
|
||||
private val dispatcher: ContinuationDispatcher
|
||||
) : Continuation<T> {
|
||||
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 <T> normalizeContinuation(continuation: Continuation<T>): Continuation<T> =
|
||||
(continuation as? CoroutineImpl)?.facade ?: continuation
|
||||
|
||||
@@ -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 <R, T> (suspend R.() -> T).createCoroutine(
|
||||
receiver: R,
|
||||
completion: Continuation<T>,
|
||||
dispatcher: ContinuationDispatcher? = null
|
||||
): Continuation<Unit> = this.asDynamic().call(receiver, withDispatcher(completion, dispatcher), true).facade
|
||||
completion: Continuation<T>
|
||||
): Continuation<Unit> = 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 <R, T> (suspend R.() -> T).startCoroutine(
|
||||
receiver: R,
|
||||
completion: Continuation<T>,
|
||||
dispatcher: ContinuationDispatcher? = null
|
||||
completion: Continuation<T>
|
||||
) {
|
||||
this.asDynamic().call(receiver, withDispatcher(completion, dispatcher))
|
||||
this.asDynamic().call(receiver, completion)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,26 +48,22 @@ public fun <R, T> (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 <T> (suspend () -> T).createCoroutine(
|
||||
completion: Continuation<T>,
|
||||
dispatcher: ContinuationDispatcher? = null
|
||||
): Continuation<Unit> = this.asDynamic()(withDispatcher(completion, dispatcher), true).facade
|
||||
completion: Continuation<T>
|
||||
): Continuation<Unit> = 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 <T> (suspend () -> T).startCoroutine(
|
||||
completion: Continuation<T>,
|
||||
dispatcher: ContinuationDispatcher? = null
|
||||
completion: Continuation<T>
|
||||
) {
|
||||
this.asDynamic()(withDispatcher(completion, dispatcher))
|
||||
this.asDynamic()(completion)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,21 +83,6 @@ public suspend fun <T> suspendCoroutine(block: (Continuation<T>) -> Unit): T = s
|
||||
|
||||
// ------- internal stuff -------
|
||||
|
||||
internal interface DispatchedContinuation {
|
||||
val dispatcher: ContinuationDispatcher?
|
||||
}
|
||||
|
||||
private fun <T> withDispatcher(completion: Continuation<T>, dispatcher: ContinuationDispatcher?): Continuation<T> {
|
||||
return if (dispatcher == null) {
|
||||
completion
|
||||
}
|
||||
else {
|
||||
object : Continuation<T> by completion, DispatchedContinuation {
|
||||
override val dispatcher = dispatcher
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@JsName("CoroutineImpl")
|
||||
internal abstract class CoroutineImpl(private val resultContinuation: Continuation<Any?>) : Continuation<Any?> {
|
||||
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<Int>? = null
|
||||
private val continuationDispatcher = (resultContinuation as? DispatchedContinuation)?.dispatcher
|
||||
val facade: Continuation<Any?>
|
||||
|
||||
init {
|
||||
facade = if (continuationDispatcher != null) {
|
||||
ContinuationFacade(this, continuationDispatcher)
|
||||
}
|
||||
else {
|
||||
this
|
||||
}
|
||||
}
|
||||
public override val context: CoroutineContext = resultContinuation.context
|
||||
|
||||
val facade: Continuation<Any?> = 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<Any?>, val dispatcher: ContinuationDispatcher) : Continuation<Any?> {
|
||||
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<in T> internal constructor(private val delegate: Continuation<T>) : Continuation<T> {
|
||||
public override val context: CoroutineContext
|
||||
get() = delegate.context
|
||||
|
||||
private var result: Any? = UNDECIDED
|
||||
|
||||
override fun resume(value: T) {
|
||||
|
||||
@@ -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 <E : CoroutineContextElement> get(key: CoroutineContextKey<E>): E? =
|
||||
if (this.contextKey == key) this as E else null
|
||||
|
||||
public override fun <R> 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 <E : CoroutineContextElement> get(key: CoroutineContextKey<E>): E? = null
|
||||
public override fun <R> 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 <E : CoroutineContextElement> get(key: CoroutineContextKey<E>): 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 <R> 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 <R, T> (suspend R.() -> T).createCoroutine(
|
||||
receiver: R,
|
||||
completion: Continuation<T>,
|
||||
dispatcher: ContinuationDispatcher? = null
|
||||
): Continuation<Unit> = (this as kotlin.jvm.internal.SuspendFunction1<R, T>).create(receiver, withDispatcher(completion, dispatcher))
|
||||
completion: Continuation<T>
|
||||
): Continuation<Unit> = (this as kotlin.jvm.internal.SuspendFunction1<R, T>).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 <R, T> (suspend R.() -> T).startCoroutine(
|
||||
receiver: R,
|
||||
completion: Continuation<T>,
|
||||
dispatcher: ContinuationDispatcher? = null
|
||||
completion: Continuation<T>
|
||||
) {
|
||||
(this as Function2<R, Continuation<T>, Any?>).invoke(receiver, withDispatcher(completion, dispatcher))
|
||||
(this as Function2<R, Continuation<T>, Any?>).invoke(receiver, completion)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -42,28 +38,24 @@ public fun <R, T> (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 <T> (suspend () -> T).createCoroutine(
|
||||
completion: Continuation<T>,
|
||||
dispatcher: ContinuationDispatcher? = null
|
||||
): Continuation<Unit> = (this as kotlin.jvm.internal.SuspendFunction0<T>).create(withDispatcher(completion, dispatcher))
|
||||
completion: Continuation<T>
|
||||
): Continuation<Unit> = (this as kotlin.jvm.internal.SuspendFunction0<T>).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 <T> (suspend () -> T).startCoroutine(
|
||||
completion: Continuation<T>,
|
||||
dispatcher: ContinuationDispatcher? = null
|
||||
completion: Continuation<T>
|
||||
) {
|
||||
(this as Function1<Continuation<T>, Any?>).invoke(withDispatcher(completion, dispatcher))
|
||||
(this as Function1<Continuation<T>, Any?>).invoke(completion)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,20 +76,15 @@ public inline suspend fun <T> suspendCoroutine(crossinline block: (Continuation<
|
||||
|
||||
// INTERNAL DECLARATIONS
|
||||
|
||||
@Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "CANNOT_OVERRIDE_INVISIBLE_MEMBER")
|
||||
private fun <T> withDispatcher(completion: Continuation<T>, dispatcher: ContinuationDispatcher?): Continuation<T> {
|
||||
return if (dispatcher == null) completion else
|
||||
object : kotlin.jvm.internal.DispatchedContinuation<T>, Continuation<T> 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<in T> @PublishedApi internal constructor(private val delegate: Continuation<T>) : Continuation<T> {
|
||||
public override val context: CoroutineContext
|
||||
get() = delegate.context
|
||||
|
||||
@Volatile
|
||||
private var result: Any? = UNDECIDED
|
||||
|
||||
|
||||
@@ -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<CtxA>
|
||||
override val contextKey get() = CtxA
|
||||
}
|
||||
|
||||
data class CtxB(val i: Int) : AbstractCoroutineContextElement() {
|
||||
companion object : CoroutineContextKey<CtxB>
|
||||
override val contextKey get() = CtxB
|
||||
}
|
||||
|
||||
data class CtxC(val i: Int) : AbstractCoroutineContextElement() {
|
||||
companion object : CoroutineContextKey<CtxC>
|
||||
override val contextKey get() = CtxC
|
||||
}
|
||||
|
||||
object Disp1 : AbstractCoroutineContextElement(), ContinuationInterceptor {
|
||||
override fun <T> interceptContinuation(continuation: CoroutineContinuation<T>): CoroutineContinuation<T> = continuation
|
||||
override val contextKey: CoroutineContextKey<*> = ContinuationInterceptor
|
||||
override fun toString(): String = "Disp1"
|
||||
}
|
||||
|
||||
object Disp2 : AbstractCoroutineContextElement(), ContinuationInterceptor {
|
||||
override fun <T> interceptContinuation(continuation: CoroutineContinuation<T>): CoroutineContinuation<T> = 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<CoroutineContext>()) { a, b -> a + b }
|
||||
assertEquals(listOf(*elements), set.toList())
|
||||
for (elem in elements)
|
||||
assertTrue(ctx[elem.contextKey] == elem)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user