diff --git a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts index 9e6aa31cb12..71e28677912 100644 --- a/libraries/tools/kotlin-gradle-plugin/build.gradle.kts +++ b/libraries/tools/kotlin-gradle-plugin/build.gradle.kts @@ -60,6 +60,7 @@ dependencies { } commonCompileOnly(project(":kotlin-tooling-metadata")) + commonImplementation(project(":kotlin-gradle-plugin-idea")) commonImplementation(project(":kotlin-gradle-plugin-idea-proto")) commonImplementation(project(":kotlin-util-klib")) @@ -110,6 +111,8 @@ dependencies { testImplementation(project(":kotlin-tooling-metadata")) } +configurations.commonCompileClasspath.get().exclude("org.jetbrains.kotlinx", "kotlinx-coroutines-core") + if (kotlinBuildProperties.isInJpsBuildIdeaSync) { configurations.commonApi.get().exclude("com.android.tools.external.com-intellij", "intellij-core") } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginLifecycle.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginLifecycle.kt index d152174a3c4..1d0c98a5f99 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginLifecycle.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/KotlinPluginLifecycle.kt @@ -5,8 +5,6 @@ package org.jetbrains.kotlin.gradle.plugin -import kotlinx.coroutines.currentCoroutineContext -import kotlinx.coroutines.withContext import org.gradle.api.Project import org.gradle.api.provider.Property import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle.* @@ -127,7 +125,7 @@ internal fun Project.startKotlinPluginLifecycle() { * the currently running coroutine. Throws if this coroutine was not started using a [KotlinPluginLifecycle] */ internal suspend fun currentKotlinPluginLifecycle(): KotlinPluginLifecycle { - return currentCoroutineContext()[KotlinPluginLifecycleCoroutineContextElement]?.lifecycle + return coroutineContext[KotlinPluginLifecycleCoroutineContextElement]?.lifecycle ?: error("Missing $KotlinPluginLifecycleCoroutineContextElement in currentCoroutineContext") } @@ -234,8 +232,17 @@ internal suspend fun requireCurrentStage(block: suspend () -> T): T { * ``` */ internal suspend fun withRestrictedStages(allowed: Set, block: suspend () -> T): T { - return withContext(RestrictedLifecycleStages(currentKotlinPluginLifecycle(), allowed)) { - block() + val newCoroutineContext = coroutineContext + RestrictedLifecycleStages(currentKotlinPluginLifecycle(), allowed) + return suspendCoroutine { continuation -> + val newContinuation = object : Continuation { + override val context: CoroutineContext + get() = newCoroutineContext + + override fun resumeWith(result: Result) { + continuation.resumeWith(result) + } + } + block.startCoroutine(newContinuation) } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/Future.kt b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/Future.kt index cb7ea3803b4..c4baa2a93fa 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/Future.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/utils/Future.kt @@ -5,9 +5,6 @@ package org.jetbrains.kotlin.gradle.utils -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.completeWith import org.gradle.api.Project import org.jetbrains.kotlin.gradle.plugin.HasProject import org.jetbrains.kotlin.gradle.plugin.KotlinPluginLifecycle @@ -17,6 +14,9 @@ import org.jetbrains.kotlin.tooling.core.ExtrasLazyProperty import org.jetbrains.kotlin.tooling.core.HasMutableExtras import org.jetbrains.kotlin.tooling.core.extrasLazyProperty import java.io.Serializable +import kotlin.coroutines.Continuation +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine /** * See [KotlinPluginLifecycle]: @@ -95,9 +95,8 @@ internal fun CompletableFuture(): CompletableFuture { return FutureImpl() } -@OptIn(ExperimentalCoroutinesApi::class) private class FutureImpl( - private val deferred: CompletableDeferred = CompletableDeferred(), + private val deferred: Completable = Completable(), private val lifecycle: KotlinPluginLifecycle? = null ) : CompletableFuture, Serializable { fun completeWith(result: Result) = deferred.completeWith(result) @@ -123,7 +122,7 @@ private class FutureImpl( private class Surrogate(private val value: T) : Serializable { private fun readResolve(): Any { - return FutureImpl(CompletableDeferred(value)) + return FutureImpl(Completable(value)) } } } @@ -153,7 +152,7 @@ private class LenientFutureImpl( private class Surrogate(private val value: T) : Serializable { private fun readResolve(): Any { - return LenientFutureImpl(FutureImpl(CompletableDeferred(value))) + return LenientFutureImpl(FutureImpl(Completable(value))) } } } @@ -173,7 +172,63 @@ private class LazyFutureImpl(private val future: Lazy>) : Future private class Surrogate(private val value: T) : Serializable { private fun readResolve(): Any { - return FutureImpl(CompletableDeferred(value)) + return FutureImpl(Completable(value)) } } +} + +/** + * Simple, Single Threaded, replacement for kotlinx.coroutines.CompletableDeferred. + */ +private class Completable( + private var value: Result? = null +) { + constructor(value: T) : this(Result.success(value)) + + val isCompleted: Boolean get() = value != null + + private val waitingContinuations = mutableListOf>>() + + fun completeWith(result: Result) { + check(value == null) { "Already completed with $value" } + value = result + + /* Capture and clear current waiting continuations */ + val continuations = waitingContinuations.toList() + waitingContinuations.clear() + + continuations.forEach { continuation -> + continuation.resume(result) + } + + /* + Safety check: + We do not expect any coroutines waiting: + Any continuation that, during its above .resume, calls into '.await()' shall + directly resume and receive the value currently set. + + If the waiting continuations are not empty, then those would be leaking. + */ + assert(waitingContinuations.isEmpty()) + } + + fun complete(value: T) { + completeWith(Result.success(value)) + } + + fun getCompleted(): T { + val value = this.value ?: throw IllegalStateException("Not completed yet") + return value.getOrThrow() + } + + suspend fun await(): T { + val value = this.value + if (value != null) { + return value.getOrThrow() + } + + return suspendCoroutine> { continuation -> + waitingContinuations.add(continuation) + }.getOrThrow() + } } \ No newline at end of file