JS: implement release coroutines in stdlib

This commit is contained in:
Anton Bannykh
2018-07-08 22:43:03 +03:00
committed by Anton Bannykh
parent 2604da6f0e
commit 99ac43eb84
5 changed files with 313 additions and 6 deletions
@@ -0,0 +1,52 @@
/*
* 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
import kotlin.coroutines.intrinsics.CoroutineSingletons.*
@PublishedApi
@SinceKotlin("1.3")
internal actual class SafeContinuation<in T>
internal actual constructor(
private val delegate: Continuation<T>,
initialResult: Any?
) : Continuation<T> {
@PublishedApi
internal actual constructor(delegate: Continuation<T>) : this(delegate, UNDECIDED)
public actual override val context: CoroutineContext
get() = delegate.context
private var result: Any? = initialResult
public actual override fun resumeWith(result: SuccessOrFailure<T>) {
val cur = this.result
when {
cur === UNDECIDED -> {
this.result = result
}
cur === COROUTINE_SUSPENDED -> {
this.result = RESUMED
delegate.resumeWith(result)
}
else -> throw IllegalStateException("Already resumed")
}
}
@PublishedApi
internal actual fun getOrThrow(): Any? {
if (result === UNDECIDED) {
result = COROUTINE_SUSPENDED
return COROUTINE_SUSPENDED
}
val result = this.result
return when {
result === RESUMED -> COROUTINE_SUSPENDED // already called continuation, indicate COROUTINE_SUSPENDED upstream
result is SuccessOrFailure.Failure -> throw result.exception
else -> result // either COROUTINE_SUSPENDED or data
}
}
}
@@ -0,0 +1,130 @@
/*
* 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.
*/
@file:Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "UNCHECKED_CAST")
package kotlin.coroutines.intrinsics
import kotlin.coroutines.*
import kotlin.internal.InlineOnly
/**
* Starts unintercepted coroutine without receiver and with result type [T] and executes it until its first suspension.
* Returns the result of the coroutine or throws its exception if it does not suspend or [COROUTINE_SUSPENDED] if it suspends.
* In the later case, the [completion] continuation is invoked when coroutine completes with result or exception.
*
* The coroutine is started directly in the invoker's thread without going through the [ContinuationInterceptor] that might
* be present in the completion's [CoroutineContext]. It is invoker's responsibility to ensure that the proper invocation
* context is established.
*
* This function is designed to be used from inside of [suspendCoroutineUninterceptedOrReturn] to resume the execution of suspended
* coroutine using a reference to the suspending function.
*/
@SinceKotlin("1.3")
@InlineOnly
public actual inline fun <T> (suspend () -> T).startCoroutineUninterceptedOrReturn(
completion: Continuation<T>
): Any? = this.asDynamic()(completion, false)
/**
* Starts unintercepted coroutine with receiver type [R] and result type [T] and executes it until its first suspension.
* Returns the result of the coroutine or throws its exception if it does not suspend or [COROUTINE_SUSPENDED] if it suspends.
* In the later case, the [completion] continuation is invoked when coroutine completes with result or exception.
*
* The coroutine is started directly in the invoker's thread without going through the [ContinuationInterceptor] that might
* be present in the completion's [CoroutineContext]. It is invoker's responsibility to ensure that the proper invocation
* context is established.
*
* This function is designed to be used from inside of [suspendCoroutineUninterceptedOrReturn] to resume the execution of suspended
* coroutine using a reference to the suspending function.
*/
@SinceKotlin("1.3")
@InlineOnly
public actual inline fun <R, T> (suspend R.() -> T).startCoroutineUninterceptedOrReturn(
receiver: R,
completion: Continuation<T>
): Any? = this.asDynamic()(receiver, completion, false)
/**
* Creates unintercepted 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.
*
* This function returns unintercepted continuation.
* Invocation of `resume(Unit)` starts coroutine directly in the invoker's thread without going through the
* [ContinuationInterceptor] that might be present in the completion's [CoroutineContext].
* It is invoker's responsibility to ensure that the proper invocation context is established.
* [Continuation.intercepted] can be used to acquire the intercepted continuation.
*
* Repeated invocation of any resume function on the resulting continuation corrupts the
* state machine of the coroutine and may result in arbitrary behaviour or exception.
*/
@SinceKotlin("1.3")
public actual fun <T> (suspend () -> T).createCoroutineUnintercepted(
completion: Continuation<T>
): Continuation<Unit> =
// Kotlin/JS suspend lambdas have an extra parameter `suspended`
if (this.asDynamic().length == 2) {
// When `suspended` is true the continuation is created, but not executed
this.asDynamic()(completion, true)
} else {
createCoroutineFromSuspendFunction(completion) {
this.asDynamic()(completion)
}
}
/**
* Creates unintercepted 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.
*
* This function returns unintercepted continuation.
* Invocation of `resume(Unit)` starts coroutine directly in the invoker's thread without going through the
* [ContinuationInterceptor] that might be present in the completion's [CoroutineContext].
* It is invoker's responsibility to ensure that the proper invocation context is established.
* [Continuation.intercepted] can be used to acquire the intercepted continuation.
*
* Repeated invocation of any resume function on the resulting continuation corrupts the
* state machine of the coroutine and may result in arbitrary behaviour or exception.
*/
@SinceKotlin("1.3")
public actual fun <R, T> (suspend R.() -> T).createCoroutineUnintercepted(
receiver: R,
completion: Continuation<T>
): Continuation<Unit> =
// Kotlin/JS suspend lambdas have an extra parameter `suspended`
if (this.asDynamic().length == 3) {
// When `suspended` is true the continuation is created, but not executed
this.asDynamic()(receiver, completion, true)
} else {
createCoroutineFromSuspendFunction(completion) {
this.asDynamic()(receiver, completion)
}
}
/**
* Intercepts this continuation with [ContinuationInterceptor].
*/
@SinceKotlin("1.3")
public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
(this as? CoroutineImpl)?.intercepted() ?: this
private inline fun <T> createCoroutineFromSuspendFunction(
completion: Continuation<T>,
crossinline block: () -> Any?
): Continuation<Unit> {
return object : CoroutineImpl(completion as Continuation<Any?>) {
override fun doResume(): Any? {
exception?.let { throw it }
return block()
}
}
}
@@ -0,0 +1,92 @@
/*
* 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
import kotlin.coroutines.intrinsics.CoroutineSingletons
@SinceKotlin("1.3")
@JsName("CoroutineImpl")
internal abstract class CoroutineImpl(private val resultContinuation: Continuation<Any?>) : Continuation<Any?> {
protected var state = 0
protected var exceptionState = 0
protected var result: Any? = null
protected var exception: Throwable? = null
protected var finallyPath: Array<Int>? = null
public override val context: CoroutineContext = resultContinuation.context
private var intercepted_: Continuation<Any?>? = null
public fun intercepted(): Continuation<Any?> =
intercepted_
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also { intercepted_ = it }
override fun resumeWith(result: SuccessOrFailure<Any?>) {
var current = this
var currentResult: Any? = result.getOrNull()
var currentException: Throwable? = result.exceptionOrNull()
// This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
while (true) {
with(current) {
val completion = resultContinuation
// Set result and exception fields in the current continuation
if (currentException == null) {
this.result = currentResult
} else {
state = exceptionState
exception = currentException
}
try {
val outcome = doResume()
if (outcome === CoroutineSingletons.COROUTINE_SUSPENDED) return
currentResult = outcome
currentException = null
} catch (exception: dynamic) { // Catch all exceptions
currentResult = null
currentException = exception.unsafeCast<Throwable>()
}
releaseIntercepted() // this state machine instance is terminating
if (completion is CoroutineImpl) {
// unrolling recursion via loop
current = completion
} else {
// top-level completion reached -- invoke and return
currentException?.let {
completion.resumeWithException(it)
} ?: completion.resume(currentResult)
return
}
}
}
}
private fun releaseIntercepted() {
val intercepted = intercepted_
if (intercepted != null && intercepted !== this) {
context[ContinuationInterceptor]!!.releaseInterceptedContinuation(intercepted)
}
this.intercepted_ = CompletedContinuation // just in case
}
protected abstract fun doResume(): Any?
}
internal object CompletedContinuation : Continuation<Any?> {
override val context: CoroutineContext
get() = error("This continuation is already complete")
override fun resumeWith(result: SuccessOrFailure<Any?>) {
error("This continuation is already complete")
}
override fun toString(): String = "This continuation is already complete"
}
@@ -10,7 +10,8 @@
"NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS",
"UNSUPPORTED_FEATURE",
"INVISIBLE_REFERENCE",
"INVISIBLE_MEMBER"
"INVISIBLE_MEMBER",
"CANNOT_OVERRIDE_INVISIBLE_MEMBER"
)
package kotlin
+37 -5
View File
@@ -15,10 +15,12 @@ def builtinsSrcDir = "${buildDir}/builtin-sources"
def builtinsSrcDir2 = "${buildDir}/builtin-sources-for-builtins"
def commonSrcDir = "${projectDir}/../src/kotlin"
def commonSrcDir2 = "${projectDir}/../common/src"
def coroutinesJsSrcDir = "${projectDir}/../coroutines/js/src"
def builtinsDir = "${rootDir}/core/builtins"
def experimentalSrcDir = "${rootDir}/libraries/stdlib/experimental"
def experimentalJsModuleName = 'kotlin-experimental'
def coroutinesJsModuleName = 'kotlin-stdlib-coroutines'
def jsSrcDir = "src"
def jsTestSrcDir = "test"
def jsSrcJsDir = "${jsSrcDir}/js"
@@ -59,6 +61,12 @@ sourceSets {
srcDir jsTestSrcDir
}
}
coroutines {
kotlin {
srcDir coroutinesJsSrcDir
}
}
}
configurations {
@@ -70,6 +78,7 @@ dependencies {
commonSources project(path: ":kotlin-stdlib-common", configuration: "sources")
testCompile project(':kotlin-test:kotlin-test-js')
merger project(":tools:kotlin-stdlib-js-merger")
coroutinesCompile project.files { sourceSets.main.output.files }.builtBy(compileKotlin2Js)
}
task prepareComparableSource(type: Copy) {
@@ -156,6 +165,16 @@ compileExperimentalKotlin2Js {
}
}
compileCoroutinesKotlin2Js {
kotlinOptions {
languageVersion = "1.3"
apiVersion = "1.3"
outputFile = "${buildDir}/classes/coroutines/kotlin.js"
sourceMap = true
sourceMapPrefix = "./"
}
}
compileTestKotlin2Js {
kotlinOptions {
moduleKind = "umd"
@@ -163,10 +182,11 @@ compileTestKotlin2Js {
}
task compileJs(type: JavaExec) {
dependsOn compileBuiltinsKotlin2Js, compileKotlin2Js, compileExperimentalKotlin2Js
dependsOn compileBuiltinsKotlin2Js, compileKotlin2Js, compileExperimentalKotlin2Js, compileCoroutinesKotlin2Js
inputs.files(compileBuiltinsKotlin2Js.outputs.files)
inputs.files(compileKotlin2Js.outputs.files)
inputs.files(compileExperimentalKotlin2Js.outputs.files)
inputs.files(compileCoroutinesKotlin2Js.outputs.files)
inputs.dir(jsSrcDir)
outputs.file(jsOutputFile)
outputs.file("${jsOutputFile}.map")
@@ -179,7 +199,8 @@ task compileJs(type: JavaExec) {
doFirst {
args = [jsOutputFile, rootDir, "$jsSrcDir/wrapper.js"] + inputFiles.collect { it.path }.sort() +
(compileBuiltinsKotlin2Js.outputs.files.collect { it.path }.sort() +
compileKotlin2Js.outputs.files.collect { it.path }.sort() /* +
compileKotlin2Js.outputs.files.collect { it.path }.sort() +
compileCoroutinesKotlin2Js.outputs.files.collect { it.path }.sort() /* +
compileExperimentalKotlin2Js.outputs.files.collect { it.path }.sort() */).findAll {
it.endsWith(".js") && !it.endsWith(".meta.js")
}
@@ -226,7 +247,8 @@ task compileJs(type: JavaExec) {
sourceMapFile.text = groovy.json.JsonOutput.toJson(sourceMap)
file(jsOutputMetaFile).text = file(compileKotlin2Js.outputFile.path.replaceAll('\\.js$', '.meta.js')).text /* +
file(jsOutputMetaFile).text = file(compileKotlin2Js.outputFile.path.replaceAll('\\.js$', '.meta.js')).text +
file(compileCoroutinesKotlin2Js.outputFile.path.replaceAll('\\.js$', '.meta.js')).text /* +
file(compileExperimentalKotlin2Js.outputFile.path.replaceAll('\\.js$', '.meta.js')).text
.replaceFirst(experimentalJsModuleName, 'kotlin') */
}
@@ -239,7 +261,7 @@ jar {
enabled false
}
task mergedJar(type: Jar, dependsOn: classes) {
task mergedJar(type: Jar, dependsOn: compileJs) {
classifier = null
manifestAttributes(manifest, project, 'Main')
@@ -257,10 +279,13 @@ task mergedJar(type: Jar, dependsOn: classes) {
from "${jsOutputFile}.map"
from sourceSets.main.output
from sourceSets.experimental.output
from("${buildDir}/classes/coroutines/kotlin") {
into coroutinesJsModuleName
}
exclude "${experimentalJsModuleName}.*"
}
task sourcesJar(type: Jar, dependsOn: classes) {
task sourcesJar(type: Jar, dependsOn: compileJs) {
classifier = 'sources'
includeEmptyDirs false
from(sourceSets.builtins.allSource) {
@@ -278,6 +303,9 @@ task sourcesJar(type: Jar, dependsOn: classes) {
from(sourceSets.experimental.allSource) {
into 'kotlin'
}
from(sourceSets.coroutines.allSource) {
into 'kotlin'
}
}
task distSourcesJar(type: Jar) {
@@ -293,6 +321,10 @@ task distSourcesJar(type: Jar) {
exclude 'META-INF/*'
into 'common'
}
from(project(":kotlin-stdlib-common").sourceSets.coroutines.allSource) {
into 'kotlin'
}
}