CoroutineContext and ContinuationInterceptor (instead of dispatcher)

This commit is contained in:
Roman Elizarov
2017-01-13 21:01:57 +03:00
committed by Denis Zharkov
parent f611e39a69
commit 8d6a913cee
22 changed files with 507 additions and 148 deletions
@@ -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) {
}
@@ -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) {
+2 -2
View File
@@ -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
View File
@@ -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()
}
@@ -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
}
@@ -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
}
@@ -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
+14 -55
View File
@@ -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)
}
}