Files
kotlin-fork/libraries/stdlib/js/irRuntime/coroutines.kt
T
Roman Artemev c62e4b4fcf [JS IR BE] Support coroutines
* Move FinallyBlockLowering to common part
* Fix catching of dynamic exception
* Fix bridges for suspend functions
* Disable explicit cast to Unit
* Run lowering per module
* Update some test data
2018-08-08 18:33:39 +03:00

323 lines
12 KiB
Kotlin

/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package kotlin.coroutines.experimental
import kotlin.coroutines.experimental.intrinsics.*
/**
* 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 or exception.
*/
@SinceKotlin("1.1")
@Suppress("UNCHECKED_CAST")
public fun <R, T> (suspend R.() -> T).startCoroutine(
receiver: R,
completion: Continuation<T>
) {
createCoroutineUnchecked(receiver, completion).resume(Unit)
}
/**
* 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 or exception.
*/
@SinceKotlin("1.1")
@Suppress("UNCHECKED_CAST")
public fun <T> (suspend () -> T).startCoroutine(
completion: Continuation<T>
) {
createCoroutineUnchecked(completion).resume(Unit)
}
/**
* Creates a coroutine with receiver type [R] and result type [T].
* 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 or exception.
* Repeated invocation of any resume function on the resulting continuation produces [IllegalStateException].
*/
@SinceKotlin("1.1")
@Suppress("UNCHECKED_CAST")
public fun <R, T> (suspend R.() -> T).createCoroutine(
receiver: R,
completion: Continuation<T>
): Continuation<Unit> = SafeContinuation(createCoroutineUnchecked(receiver, completion), COROUTINE_SUSPENDED)
/**
* Creates a coroutine without receiver and with result type [T].
* 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 or exception.
* Repeated invocation of any resume function on the resulting continuation produces [IllegalStateException].
*/
@SinceKotlin("1.1")
@Suppress("UNCHECKED_CAST")
public fun <T> (suspend () -> T).createCoroutine(
completion: Continuation<T>
): Continuation<Unit> = SafeContinuation(createCoroutineUnchecked(completion), COROUTINE_SUSPENDED)
// TODO: remove this once implemented in stdlib
inline fun <T, R> T.let(f: (T) -> R) = f(this)
inline fun <R> run(f: () -> R) = f()
public interface CoroutineContext {
/**
* Returns the element with the given [key] from this context or `null`.
* Keys are compared _by reference_, that is to get an element from the context the reference to its actual key
* object must be presented to this function.
*/
public operator fun <E : Element> get(key: Key<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, Element) -> 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 =
if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
context.fold(this) { acc, element ->
val removed = acc.minusKey(element.key)
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)
}
}
}
/**
* Returns a context containing elements from this context, but without an element with
* the specified [key]. Keys are compared _by reference_, that is to remove an element from the context
* the reference to its actual key object must be presented to this function.
*/
public fun minusKey(key: Key<*>): CoroutineContext
/**
* An element of the [CoroutineContext]. An element of the coroutine context is a singleton context by itself.
*/
public interface Element : CoroutineContext {
/**
* A key of this coroutine context element.
*/
public val key: Key<*>
@Suppress("UNCHECKED_CAST")
public override operator fun <E : Element> get(key: Key<E>): E? =
if (this.key === key) this as E else null
public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
operation(initial, this)
public override fun minusKey(key: Key<*>): CoroutineContext =
if (this.key === key) EmptyCoroutineContext else this
}
/**
* Key for the elements of [CoroutineContext]. [E] is a type of element with this key.
* Keys in the context are compared _by reference_.
*/
public interface Key<E : Element>
}
public abstract class AbstractCoroutineContextElement(public override val key: CoroutineContext.Key<*>) : CoroutineContext.Element
public interface ContinuationInterceptor : CoroutineContext.Element {
/**
* The key that defines *the* context interceptor.
*/
companion object Key : CoroutineContext.Key<ContinuationInterceptor>
/**
* Returns continuation that wraps the original [continuation], thus intercepting all resumptions.
* This function is invoked by coroutines framework when needed and the resulting continuations are
* cached internally per each instance of the original [continuation].
*
* By convention, implementations that install themselves as *the* interceptor in the context with
* the [Key] shall also scan the context for other element that implement [ContinuationInterceptor] interface
* and use their [interceptContinuation] functions, too.
*/
public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
}
internal class CombinedContext(val left: CoroutineContext, val element: CoroutineContext.Element) : CoroutineContext {
override fun <E : CoroutineContext.Element> get(key: CoroutineContext.Key<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, CoroutineContext.Element) -> R): R =
operation(left.fold(initial, operation), element)
public override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext {
element[key]?.let { return left }
val newLeft = left.minusKey(key)
return when {
newLeft === left -> this
newLeft === EmptyCoroutineContext -> element
else -> CombinedContext(newLeft, element)
}
}
private fun size(): Int =
if (left is CombinedContext) left.size() + 1 else 2
private fun contains(element: CoroutineContext.Element): Boolean =
get(element.key) == 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 CoroutineContext.Element)
}
}
}
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 = "CC"
}
public object EmptyCoroutineContext : CoroutineContext {
public override fun <E : CoroutineContext.Element> get(key: CoroutineContext.Key<E>): E? = null
public override fun <R> fold(initial: R, operation: (R, CoroutineContext.Element) -> R): R = initial
public override fun plus(context: CoroutineContext): CoroutineContext = context
public override fun minusKey(key: CoroutineContext.Key<*>): CoroutineContext = this
public override fun hashCode(): Int = 0
public override fun toString(): String = "EmptyCoroutineContext"
}
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.
*/
public fun resume(value: T)
/**
* Resumes the execution of the corresponding coroutine so that the [exception] is re-thrown right after the
* last suspension point.
*/
public fun resumeWithException(exception: Throwable)
}
public class SafeContinuation<in T>
public constructor(
private val delegate: Continuation<T>,
initialResult: Any?
) : Continuation<T> {
public constructor(delegate: Continuation<T>) : this(delegate, UNDECIDED)
public override val context: CoroutineContext
get() = delegate.context
private var result: Any? = initialResult
override fun resume(value: T) {
when {
result === UNDECIDED -> {
result = value
}
result === COROUTINE_SUSPENDED -> {
result = RESUMED
delegate.resume(value)
}
else -> {
throw IllegalStateException("Already resumed")
}
}
}
override fun resumeWithException(exception: Throwable) {
when {
result === UNDECIDED -> {
result = Fail(exception)
}
result === COROUTINE_SUSPENDED -> {
result = RESUMED
delegate.resumeWithException(exception)
}
else -> {
throw IllegalStateException("Already resumed")
}
}
}
public fun getResult(): Any? {
if (result === UNDECIDED) {
result = COROUTINE_SUSPENDED
}
val result = this.result
return when {
result === RESUMED -> {
COROUTINE_SUSPENDED // already called continuation, indicate SUSPENDED upstream
}
result is Fail -> {
throw result.exception
}
else -> {
result // either SUSPENDED or data
}
}
}
}
suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T =
suspendCoroutineOrReturn { c: Continuation<T> ->
val safe = SafeContinuation(c)
block(safe)
safe.getResult()
}
private val UNDECIDED: Any? = Any()
private val RESUMED: Any? = Any()
private class Fail(val exception: Throwable)
@kotlin.internal.InlineOnly
internal inline fun processBareContinuationResume(completion: Continuation<*>, block: () -> Any?) {
try {
val result = block()
if (result !== COROUTINE_SUSPENDED) {
@Suppress("UNCHECKED_CAST")
(completion as Continuation<Any?>).resume(result)
}
} catch (t: Throwable) {
completion.resumeWithException(t)
}
}