From b9c6106ff62d44ad837805d6092fc81e5dea7ebd Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Wed, 26 Feb 2025 14:32:03 +0100 Subject: [PATCH 1/5] Add the contract to runBlocking for shared JVM/Native code Additionally, on Native, make thread keepalive checks a bit more efficient. --- .../common/src/EventLoop.common.kt | 7 -- .../common/src/internal/Concurrent.common.kt | 5 +- .../concurrent/src/Builders.concurrent.kt | 50 ++++++++++- .../concurrent/test/RunBlockingTest.kt | 10 +++ kotlinx-coroutines-core/jvm/src/Builders.kt | 64 +------------ .../channels/testSendToChannel.txt | 4 +- .../jvm/test/RunBlockingJvmTest.kt | 8 -- .../native/src/Builders.kt | 90 ++++--------------- 8 files changed, 86 insertions(+), 152 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/EventLoop.common.kt b/kotlinx-coroutines-core/common/src/EventLoop.common.kt index 84291a1b69..3c37159556 100644 --- a/kotlinx-coroutines-core/common/src/EventLoop.common.kt +++ b/kotlinx-coroutines-core/common/src/EventLoop.common.kt @@ -65,13 +65,6 @@ internal abstract class EventLoop : CoroutineDispatcher() { task.run() return true } - /** - * Returns `true` if the invoking `runBlocking(context) { ... }` that was passed this event loop in its context - * parameter should call [processNextEvent] for this event loop (otherwise, it will process thread-local one). - * By default, event loop implementation is thread-local and should not processed in the context - * (current thread's event loop should be processed instead). - */ - open fun shouldBeProcessedFromContext(): Boolean = false /** * Dispatches task whose dispatcher returned `false` from [CoroutineDispatcher.isDispatchNeeded] diff --git a/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt b/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt index 0be8a104db..72dd3ef834 100644 --- a/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt +++ b/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt @@ -1,9 +1,6 @@ package kotlinx.coroutines.internal -internal expect class ReentrantLock() { - fun tryLock(): Boolean - fun unlock() -} +internal expect class ReentrantLock() internal expect inline fun ReentrantLock.withLock(action: () -> T): T diff --git a/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt b/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt index 7c0581b9d9..5ca1acf830 100644 --- a/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt +++ b/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt @@ -1,6 +1,15 @@ +@file:JvmMultifileClass +@file:JvmName("BuildersKt") +@file:Suppress("LEAKED_IN_PLACE_LAMBDA", "WRONG_INVOCATION_KIND") + package kotlinx.coroutines +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract import kotlin.coroutines.* +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName /** * Runs a new coroutine and **blocks** the current thread until its completion. @@ -20,5 +29,44 @@ import kotlin.coroutines.* * * Here, instead of releasing the thread on which `loadConfiguration` runs if `fetchConfigurationData` suspends, it will * block, potentially leading to thread starvation issues. + * + * The default [CoroutineDispatcher] for this builder is an internal implementation of event loop that processes continuations + * in this blocked thread until the completion of this coroutine. + * See [CoroutineDispatcher] for the other implementations that are provided by `kotlinx.coroutines`. + * + * When [CoroutineDispatcher] is explicitly specified in the [context], then the new coroutine runs in the context of + * the specified dispatcher while the current thread is blocked. If the specified dispatcher is an event loop of another `runBlocking`, + * then this invocation uses the outer event loop. + * + * If this blocked thread is interrupted (see `Thread.interrupt`), then the coroutine job is cancelled and + * this `runBlocking` invocation throws `InterruptedException`. + * + * See [newCoroutineContext][CoroutineScope.newCoroutineContext] for a description of debugging facilities that are available + * for a newly created coroutine. + * + * @param context the context of the coroutine. The default value is an event loop on the current thread. + * @param block the coroutine code. */ -public expect fun runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T +@OptIn(ExperimentalContracts::class) +public fun runBlocking( + context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T +): T { + contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } + val contextInterceptor = context[ContinuationInterceptor] + val eventLoop: EventLoop? + val newContext: CoroutineContext + if (contextInterceptor == null) { + // create or use private event loop if no dispatcher is specified + eventLoop = ThreadLocalEventLoop.eventLoop + newContext = GlobalScope.newCoroutineContext(context + eventLoop) + } else { + eventLoop = ThreadLocalEventLoop.currentOrNull() + newContext = GlobalScope.newCoroutineContext(context) + } + return runBlockingImpl(newContext, eventLoop, block) +} + +/** We can't inline it, because an `expect fun` can't have contracts. */ +internal expect fun runBlockingImpl( + newContext: CoroutineContext, eventLoop: EventLoop?, block: suspend CoroutineScope.() -> T +): T diff --git a/kotlinx-coroutines-core/concurrent/test/RunBlockingTest.kt b/kotlinx-coroutines-core/concurrent/test/RunBlockingTest.kt index 43f7976ffa..f4512e52ed 100644 --- a/kotlinx-coroutines-core/concurrent/test/RunBlockingTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/RunBlockingTest.kt @@ -194,4 +194,14 @@ class RunBlockingTest : TestBase() { } } } + + /** Will not compile if [runBlocking] doesn't have the "runs exactly once" contract. */ + @Test + fun testContract() { + val rb: Int + runBlocking { + rb = 42 + } + rb.hashCode() // unused + } } diff --git a/kotlinx-coroutines-core/jvm/src/Builders.kt b/kotlinx-coroutines-core/jvm/src/Builders.kt index 8f72e28606..9e8d132d9a 100644 --- a/kotlinx-coroutines-core/jvm/src/Builders.kt +++ b/kotlinx-coroutines-core/jvm/src/Builders.kt @@ -1,71 +1,15 @@ @file:JvmMultifileClass @file:JvmName("BuildersKt") -@file:OptIn(ExperimentalContracts::class) -@file:Suppress("LEAKED_IN_PLACE_LAMBDA", "WRONG_INVOCATION_KIND") package kotlinx.coroutines -import java.util.concurrent.locks.* -import kotlin.contracts.* import kotlin.coroutines.* -/** - * Runs a new coroutine and **blocks** the current thread _interruptibly_ until its completion. - * - * It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in - * `main` functions and in tests. - * - * Calling [runBlocking] from a suspend function is redundant. - * For example, the following code is incorrect: - * ``` - * suspend fun loadConfiguration() { - * // DO NOT DO THIS: - * val data = runBlocking { // <- redundant and blocks the thread, do not do that - * fetchConfigurationData() // suspending function - * } - * ``` - * - * Here, instead of releasing the thread on which `loadConfiguration` runs if `fetchConfigurationData` suspends, it will - * block, potentially leading to thread starvation issues. - * - * The default [CoroutineDispatcher] for this builder is an internal implementation of event loop that processes continuations - * in this blocked thread until the completion of this coroutine. - * See [CoroutineDispatcher] for the other implementations that are provided by `kotlinx.coroutines`. - * - * When [CoroutineDispatcher] is explicitly specified in the [context], then the new coroutine runs in the context of - * the specified dispatcher while the current thread is blocked. If the specified dispatcher is an event loop of another `runBlocking`, - * then this invocation uses the outer event loop. - * - * If this blocked thread is interrupted (see [Thread.interrupt]), then the coroutine job is cancelled and - * this `runBlocking` invocation throws [InterruptedException]. - * - * See [newCoroutineContext][CoroutineScope.newCoroutineContext] for a description of debugging facilities that are available - * for a newly created coroutine. - * - * @param context the context of the coroutine. The default value is an event loop on the current thread. - * @param block the coroutine code. - */ @Throws(InterruptedException::class) -public actual fun runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T { - contract { - callsInPlace(block, InvocationKind.EXACTLY_ONCE) - } - val currentThread = Thread.currentThread() - val contextInterceptor = context[ContinuationInterceptor] - val eventLoop: EventLoop? - val newContext: CoroutineContext - if (contextInterceptor == null) { - // create or use private event loop if no dispatcher is specified - eventLoop = ThreadLocalEventLoop.eventLoop - newContext = GlobalScope.newCoroutineContext(context + eventLoop) - } else { - // See if context's interceptor is an event loop that we shall use (to support TestContext) - // or take an existing thread-local event loop if present to avoid blocking it (but don't create one) - eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() } - ?: ThreadLocalEventLoop.currentOrNull() - newContext = GlobalScope.newCoroutineContext(context) - } - val coroutine = BlockingCoroutine(newContext, currentThread, eventLoop) +internal actual fun runBlockingImpl( + newContext: CoroutineContext, eventLoop: EventLoop?, block: suspend CoroutineScope.() -> T +): T { + val coroutine = BlockingCoroutine(newContext, Thread.currentThread(), eventLoop) coroutine.start(CoroutineStart.DEFAULT, coroutine, block) return coroutine.joinBlocking() } diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt index af6e564210..2d89c5fae2 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt @@ -15,6 +15,8 @@ Caused by: java.util.concurrent.CancellationException: Channel was cancelled at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt) at kotlinx.coroutines.EventLoopImplBase.processNextEvent(EventLoop.common.kt) at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt) - at kotlinx.coroutines.BuildersKt__BuildersKt.runBlocking(Builders.kt) + at kotlinx.coroutines.BuildersKt__BuildersKt.runBlockingImpl(Builders.kt) + at kotlinx.coroutines.BuildersKt.runBlockingImpl(Unknown Source) + at kotlinx.coroutines.BuildersKt__Builders_concurrentKt.runBlocking(Builders.concurrent.kt) at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source) at kotlinx.coroutines.testing.TestBase.runTest(TestBase.kt) diff --git a/kotlinx-coroutines-core/jvm/test/RunBlockingJvmTest.kt b/kotlinx-coroutines-core/jvm/test/RunBlockingJvmTest.kt index dcb908cc3a..37a53fc9c3 100644 --- a/kotlinx-coroutines-core/jvm/test/RunBlockingJvmTest.kt +++ b/kotlinx-coroutines-core/jvm/test/RunBlockingJvmTest.kt @@ -8,14 +8,6 @@ import kotlin.test.* import kotlin.time.Duration class RunBlockingJvmTest : TestBase() { - @Test - fun testContract() { - val rb: Int - runBlocking { - rb = 42 - } - rb.hashCode() // unused - } /** Tests that the [runBlocking] coroutine runs to completion even it was interrupted. */ @Test diff --git a/kotlinx-coroutines-core/native/src/Builders.kt b/kotlinx-coroutines-core/native/src/Builders.kt index 4f94f19b53..58c33ada3e 100644 --- a/kotlinx-coroutines-core/native/src/Builders.kt +++ b/kotlinx-coroutines-core/native/src/Builders.kt @@ -1,101 +1,49 @@ -@file:OptIn(ExperimentalContracts::class, ObsoleteWorkersApi::class) -@file:Suppress("LEAKED_IN_PLACE_LAMBDA", "WRONG_INVOCATION_KIND") +@file:OptIn(ObsoleteWorkersApi::class) package kotlinx.coroutines -import kotlinx.cinterop.* -import kotlin.contracts.* import kotlin.coroutines.* import kotlin.native.concurrent.* -/** - * Runs a new coroutine and **blocks** the current thread _interruptibly_ until its completion. - * - * It is designed to bridge regular blocking code to libraries that are written in suspending style, to be used in - * `main` functions and in tests. - * - * Calling [runBlocking] from a suspend function is redundant. - * For example, the following code is incorrect: - * ``` - * suspend fun loadConfiguration() { - * // DO NOT DO THIS: - * val data = runBlocking { // <- redundant and blocks the thread, do not do that - * fetchConfigurationData() // suspending function - * } - * ``` - * - * Here, instead of releasing the thread on which `loadConfiguration` runs if `fetchConfigurationData` suspends, it will - * block, potentially leading to thread starvation issues. - * - * The default [CoroutineDispatcher] for this builder is an internal implementation of event loop that processes continuations - * in this blocked thread until the completion of this coroutine. - * See [CoroutineDispatcher] for the other implementations that are provided by `kotlinx.coroutines`. - * - * When [CoroutineDispatcher] is explicitly specified in the [context], then the new coroutine runs in the context of - * the specified dispatcher while the current thread is blocked. If the specified dispatcher is an event loop of another `runBlocking`, - * then this invocation uses the outer event loop. - * - * If this blocked thread is interrupted (see [Thread.interrupt]), then the coroutine job is cancelled and - * this `runBlocking` invocation throws [InterruptedException]. - * - * See [newCoroutineContext][CoroutineScope.newCoroutineContext] for a description of debugging facilities that are available - * for a newly created coroutine. - * - * @param context the context of the coroutine. The default value is an event loop on the current thread. - * @param block the coroutine code. - */ -public actual fun runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T { - contract { - callsInPlace(block, InvocationKind.EXACTLY_ONCE) - } - val contextInterceptor = context[ContinuationInterceptor] - val eventLoop: EventLoop? - val newContext: CoroutineContext - if (contextInterceptor == null) { - // create or use private event loop if no dispatcher is specified - eventLoop = ThreadLocalEventLoop.eventLoop - newContext = GlobalScope.newCoroutineContext(context + eventLoop) - } else { - // See if context's interceptor is an event loop that we shall use (to support TestContext) - // or take an existing thread-local event loop if present to avoid blocking it (but don't create one) - eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() } - ?: ThreadLocalEventLoop.currentOrNull() - newContext = GlobalScope.newCoroutineContext(context) - } - val coroutine = BlockingCoroutine(newContext, eventLoop) - var completed = false - ThreadLocalKeepAlive.addCheck { !completed } +internal actual fun runBlockingImpl( + newContext: CoroutineContext, eventLoop: EventLoop?, block: suspend CoroutineScope.() -> T +): T { + val coroutine = BlockingCoroutine(newContext, Worker.current, eventLoop) + ThreadLocalKeepAlive.registerUsage() try { coroutine.start(CoroutineStart.DEFAULT, coroutine, block) return coroutine.joinBlocking() } finally { - completed = true + ThreadLocalKeepAlive.unregisterUsage() } } @ThreadLocal private object ThreadLocalKeepAlive { - /** If any of these checks passes, this means this [Worker] is still used. */ - private var checks = mutableListOf<() -> Boolean>() + /** If larger than 0, this means this [Worker] is still used. */ + private var usages = 0 /** Whether the worker currently tries to keep itself alive. */ private var keepAliveLoopActive = false - /** Adds another stopgap that must be passed before the [Worker] can be terminated. */ - fun addCheck(terminationForbidden: () -> Boolean) { - checks.add(terminationForbidden) + /** Ensure that the worker is kept alive until the matching [unregisterUsage] is called. */ + fun registerUsage() { + usages++ if (!keepAliveLoopActive) keepAlive() } + /** Undo [registerUsage]. */ + fun unregisterUsage() { + usages-- + } + /** * Send a ping to the worker to prevent it from terminating while this coroutine is running, * ensuring that continuations don't get dropped and forgotten. */ private fun keepAlive() { - // only keep the checks that still forbid the termination - checks = checks.filter { it() }.toMutableList() // if there are no checks left, we no longer keep the worker alive, it can be terminated - keepAliveLoopActive = checks.isNotEmpty() + keepAliveLoopActive = usages > 0 if (keepAliveLoopActive) { Worker.current.executeAfter(afterMicroseconds = 100_000) { keepAlive() @@ -106,9 +54,9 @@ private object ThreadLocalKeepAlive { private class BlockingCoroutine( parentContext: CoroutineContext, + private val joinWorker: Worker, private val eventLoop: EventLoop? ) : AbstractCoroutine(parentContext, true, true) { - private val joinWorker = Worker.current override val isScopedCoroutine: Boolean get() = true From 7e907aeda79bdf593c931d714b8fc12270435739 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 28 Feb 2025 08:45:48 +0100 Subject: [PATCH 2/5] Preserve source-level compatibility with Java --- .../api/kotlinx-coroutines-core.api | 5 +++++ .../concurrent/src/Builders.concurrent.kt | 4 ---- kotlinx-coroutines-core/jvm/src/Builders.kt | 16 ++++++++++++++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index 0e35d5fb38..3f5973405c 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -30,6 +30,11 @@ public final class kotlinx/coroutines/BuildersKt { public static final fun withContext (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class kotlinx/coroutines/Builders_concurrentKt { + public static final fun runBlocking (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; + public static synthetic fun runBlocking$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/lang/Object; +} + public abstract interface class kotlinx/coroutines/CancellableContinuation : kotlin/coroutines/Continuation { public abstract fun cancel (Ljava/lang/Throwable;)Z public abstract fun completeResume (Ljava/lang/Object;)V diff --git a/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt b/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt index 5ca1acf830..7365e3a10e 100644 --- a/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt +++ b/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt @@ -1,5 +1,3 @@ -@file:JvmMultifileClass -@file:JvmName("BuildersKt") @file:Suppress("LEAKED_IN_PLACE_LAMBDA", "WRONG_INVOCATION_KIND") package kotlinx.coroutines @@ -8,8 +6,6 @@ import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.coroutines.* -import kotlin.jvm.JvmMultifileClass -import kotlin.jvm.JvmName /** * Runs a new coroutine and **blocks** the current thread until its completion. diff --git a/kotlinx-coroutines-core/jvm/src/Builders.kt b/kotlinx-coroutines-core/jvm/src/Builders.kt index 9e8d132d9a..91a6972d23 100644 --- a/kotlinx-coroutines-core/jvm/src/Builders.kt +++ b/kotlinx-coroutines-core/jvm/src/Builders.kt @@ -5,6 +5,22 @@ package kotlinx.coroutines import kotlin.coroutines.* +/** + * The same as [runBlocking], but for consumption from Java. + * From Kotlin's point of view, this function has the exact same signature as the regular [runBlocking]. + * This is done so that it can not be called from Kotlin, despite the fact that it is public. + * + * We do not expose this [runBlocking] in the documentation, because it is not supposed to be used from Kotlin. + * + * @suppress + */ +@Throws(InterruptedException::class) +@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") +@kotlin.internal.LowPriorityInOverloadResolution +public fun runBlocking( + context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T +): T = runBlocking(context, block) + @Throws(InterruptedException::class) internal actual fun runBlockingImpl( newContext: CoroutineContext, eventLoop: EventLoop?, block: suspend CoroutineScope.() -> T From 9bae2db5246061b8b4482919e35ef7583d1064b6 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Fri, 28 Feb 2025 11:44:55 +0100 Subject: [PATCH 3/5] Add an integration test for calling `runBlocking` from Java --- integration-testing/build.gradle.kts | 18 ++++++++++++++++- .../java/RunBlockingJavaTest.java | 20 +++++++++++++++++++ .../channels/testSendToChannel.txt | 3 +-- 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 integration-testing/src/javaConsumersTest/java/RunBlockingJavaTest.java diff --git a/integration-testing/build.gradle.kts b/integration-testing/build.gradle.kts index dc68f14d36..78a3e32b65 100644 --- a/integration-testing/build.gradle.kts +++ b/integration-testing/build.gradle.kts @@ -134,6 +134,15 @@ sourceSets { implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") } } + + create("javaConsumersTest") { + compileClasspath += sourceSets.test.get().runtimeClasspath + runtimeClasspath += sourceSets.test.get().runtimeClasspath + + dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") + } + } } kotlin { @@ -199,6 +208,12 @@ tasks { classpath = sourceSet.runtimeClasspath } + create("javaConsumersTest") { + val sourceSet = sourceSets[name] + testClassesDirs = sourceSet.output.classesDirs + classpath = sourceSet.runtimeClasspath + } + check { dependsOn( "jvmCoreTest", @@ -206,9 +221,10 @@ tasks { "mavenTest", "debugAgentTest", "coreAgentTest", + "javaConsumersTest", ":jpmsTest:check", "smokeTest:build", - "java8Test:check" + "java8Test:check", ) } diff --git a/integration-testing/src/javaConsumersTest/java/RunBlockingJavaTest.java b/integration-testing/src/javaConsumersTest/java/RunBlockingJavaTest.java new file mode 100644 index 0000000000..d657d361cc --- /dev/null +++ b/integration-testing/src/javaConsumersTest/java/RunBlockingJavaTest.java @@ -0,0 +1,20 @@ +import kotlinx.coroutines.BuildersKt; +import kotlinx.coroutines.Dispatchers; +import org.junit.Test; + +public class RunBlockingJavaTest { + Boolean entered = false; + + /** This code will not compile if `runBlocking` doesn't declare `@Throws(InterruptedException::class)` */ + @Test + public void testRunBlocking() { + try { + BuildersKt.runBlocking(Dispatchers.getIO(), (scope, continuation) -> { + entered = true; + return null; + }); + } catch (InterruptedException e) { + } + assert entered; + } +} diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt index 2d89c5fae2..e543dc4ec4 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt @@ -17,6 +17,5 @@ Caused by: java.util.concurrent.CancellationException: Channel was cancelled at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt) at kotlinx.coroutines.BuildersKt__BuildersKt.runBlockingImpl(Builders.kt) at kotlinx.coroutines.BuildersKt.runBlockingImpl(Unknown Source) - at kotlinx.coroutines.BuildersKt__Builders_concurrentKt.runBlocking(Builders.concurrent.kt) - at kotlinx.coroutines.BuildersKt.runBlocking(Unknown Source) + at kotlinx.coroutines.Builders_concurrentKt.runBlocking(Builders.concurrent.kt) at kotlinx.coroutines.testing.TestBase.runTest(TestBase.kt) From 6f991bdcdd8e205472f6e5a06ec1aaf0c9779517 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Tue, 11 Mar 2025 09:13:20 +0100 Subject: [PATCH 4/5] Address the review --- .../src/javaConsumersTest/java/RunBlockingJavaTest.java | 3 ++- kotlinx-coroutines-core/api/kotlinx-coroutines-core.api | 7 ++----- .../concurrent/src/Builders.concurrent.kt | 5 +++++ .../stacktraces/channels/testSendToChannel.txt | 3 ++- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/integration-testing/src/javaConsumersTest/java/RunBlockingJavaTest.java b/integration-testing/src/javaConsumersTest/java/RunBlockingJavaTest.java index d657d361cc..49294b4d53 100644 --- a/integration-testing/src/javaConsumersTest/java/RunBlockingJavaTest.java +++ b/integration-testing/src/javaConsumersTest/java/RunBlockingJavaTest.java @@ -1,6 +1,7 @@ import kotlinx.coroutines.BuildersKt; import kotlinx.coroutines.Dispatchers; import org.junit.Test; +import org.junit.Assert; public class RunBlockingJavaTest { Boolean entered = false; @@ -15,6 +16,6 @@ public void testRunBlocking() { }); } catch (InterruptedException e) { } - assert entered; + Assert.assertTrue(entered); } } diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index 3f5973405c..af21cf7861 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -27,14 +27,11 @@ public final class kotlinx/coroutines/BuildersKt { public static synthetic fun launch$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job; public static final fun runBlocking (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; public static synthetic fun runBlocking$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun runBlockingForKotlinOnly (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; + public static synthetic fun runBlockingForKotlinOnly$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/lang/Object; public static final fun withContext (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } -public final class kotlinx/coroutines/Builders_concurrentKt { - public static final fun runBlocking (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; - public static synthetic fun runBlocking$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/lang/Object; -} - public abstract interface class kotlinx/coroutines/CancellableContinuation : kotlin/coroutines/Continuation { public abstract fun cancel (Ljava/lang/Throwable;)Z public abstract fun completeResume (Ljava/lang/Object;)V diff --git a/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt b/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt index 7365e3a10e..a1c3b79980 100644 --- a/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt +++ b/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt @@ -1,4 +1,6 @@ @file:Suppress("LEAKED_IN_PLACE_LAMBDA", "WRONG_INVOCATION_KIND") +@file:JvmMultifileClass +@file:JvmName("BuildersKt") package kotlinx.coroutines @@ -6,6 +8,8 @@ import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract import kotlin.coroutines.* +import kotlin.jvm.JvmMultifileClass +import kotlin.jvm.JvmName /** * Runs a new coroutine and **blocks** the current thread until its completion. @@ -44,6 +48,7 @@ import kotlin.coroutines.* * @param block the coroutine code. */ @OptIn(ExperimentalContracts::class) +@JvmName("runBlockingForKotlinOnly") public fun runBlocking( context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T ): T { diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt index e543dc4ec4..1f705f919b 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt @@ -17,5 +17,6 @@ Caused by: java.util.concurrent.CancellationException: Channel was cancelled at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt) at kotlinx.coroutines.BuildersKt__BuildersKt.runBlockingImpl(Builders.kt) at kotlinx.coroutines.BuildersKt.runBlockingImpl(Unknown Source) - at kotlinx.coroutines.Builders_concurrentKt.runBlocking(Builders.concurrent.kt) + at kotlinx.coroutines.BuildersKt__Builders_concurrentKt.runBlockingForKotlinOnly(Builders.concurrent.kt) + at kotlinx.coroutines.BuildersKt.runBlockingForKotlinOnly(Unknown Source) at kotlinx.coroutines.testing.TestBase.runTest(TestBase.kt) From 2b1dca966652c084edab9d7537484b5abff3394a Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Tue, 11 Mar 2025 09:54:31 +0100 Subject: [PATCH 5/5] Rename the JVM declaration for runBlocking --- kotlinx-coroutines-core/api/kotlinx-coroutines-core.api | 4 ++-- kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt | 2 +- .../test-resources/stacktraces/channels/testSendToChannel.txt | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index af21cf7861..c8f1e550eb 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -27,8 +27,8 @@ public final class kotlinx/coroutines/BuildersKt { public static synthetic fun launch$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/Job; public static final fun runBlocking (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; public static synthetic fun runBlocking$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/lang/Object; - public static final fun runBlockingForKotlinOnly (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; - public static synthetic fun runBlockingForKotlinOnly$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/lang/Object; + public static final fun runBlockingK (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object; + public static synthetic fun runBlockingK$default (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/lang/Object; public static final fun withContext (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } diff --git a/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt b/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt index a1c3b79980..6fd11ab107 100644 --- a/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt +++ b/kotlinx-coroutines-core/concurrent/src/Builders.concurrent.kt @@ -48,7 +48,7 @@ import kotlin.jvm.JvmName * @param block the coroutine code. */ @OptIn(ExperimentalContracts::class) -@JvmName("runBlockingForKotlinOnly") +@JvmName("runBlockingK") public fun runBlocking( context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T ): T { diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt index 1f705f919b..dd51f04c39 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt @@ -17,6 +17,6 @@ Caused by: java.util.concurrent.CancellationException: Channel was cancelled at kotlinx.coroutines.BlockingCoroutine.joinBlocking(Builders.kt) at kotlinx.coroutines.BuildersKt__BuildersKt.runBlockingImpl(Builders.kt) at kotlinx.coroutines.BuildersKt.runBlockingImpl(Unknown Source) - at kotlinx.coroutines.BuildersKt__Builders_concurrentKt.runBlockingForKotlinOnly(Builders.concurrent.kt) - at kotlinx.coroutines.BuildersKt.runBlockingForKotlinOnly(Unknown Source) + at kotlinx.coroutines.BuildersKt__Builders_concurrentKt.runBlockingK(Builders.concurrent.kt) + at kotlinx.coroutines.BuildersKt.runBlockingK(Unknown Source) at kotlinx.coroutines.testing.TestBase.runTest(TestBase.kt)