From 845e322a72eed43f2c6186dc27c8dd5f31f8ecd2 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 14 Jul 2022 15:26:39 +0200 Subject: [PATCH 001/106] Update Kotlin to 1.7.10 (#3324) Co-authored-by: mvicsokolova --- README.md | 10 +++++----- build.gradle | 1 + gradle.properties | 4 ++-- integration-testing/build.gradle | 4 +--- integration-testing/gradle.properties | 2 +- integration-testing/settings.gradle | 8 -------- kotlinx-coroutines-core/build.gradle | 2 +- .../jvm/resources/DebugProbesKt.bin | Bin 1738 -> 1733 bytes .../LockFreeLinkedListLongStressTest.kt | 2 +- 9 files changed, 12 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index d9019dc335..b33ee80bff 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ [![JetBrains official project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) [![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0) [![Download](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.6.4)](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.6.4/pom) -[![Kotlin](https://img.shields.io/badge/kotlin-1.6.21-blue.svg?logo=kotlin)](http://kotlinlang.org) +[![Kotlin](https://img.shields.io/badge/kotlin-1.7.10-blue.svg?logo=kotlin)](http://kotlinlang.org) [![Slack channel](https://img.shields.io/badge/chat-slack-green.svg?logo=slack)](https://kotlinlang.slack.com/messages/coroutines/) Library support for Kotlin coroutines with [multiplatform](#multiplatform) support. -This is a companion version for the Kotlin `1.6.21` release. +This is a companion version for the Kotlin `1.7.10` release. ```kotlin suspend fun main() = coroutineScope { @@ -92,7 +92,7 @@ And make sure that you use the latest Kotlin version: ```xml - 1.6.21 + 1.7.10 ``` @@ -111,10 +111,10 @@ And make sure that you use the latest Kotlin version: ```kotlin plugins { // For build.gradle.kts (Kotlin DSL) - kotlin("jvm") version "1.6.21" + kotlin("jvm") version "1.7.10" // For build.gradle (Groovy DSL) - id "org.jetbrains.kotlin.jvm" version "1.6.21" + id "org.jetbrains.kotlin.jvm" version "1.7.10" } ``` diff --git a/build.gradle b/build.gradle index 4d6af16570..47f18bd067 100644 --- a/build.gradle +++ b/build.gradle @@ -58,6 +58,7 @@ buildscript { classpath "org.jetbrains.kotlinx:binary-compatibility-validator:$binary_compatibility_validator_version" classpath "ru.vyarus:gradle-animalsniffer-plugin:1.5.4" // Android API check classpath "org.jetbrains.kotlinx:kover:$kover_version" + classpath "org.jetbrains.kotlin:atomicfu:$kotlin_version" // JMH plugins classpath "gradle.plugin.com.github.johnrengelman:shadow:7.1.2" diff --git a/gradle.properties b/gradle.properties index e452a07eef..308588ff72 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,12 +5,12 @@ # Kotlin version=1.6.4-SNAPSHOT group=org.jetbrains.kotlinx -kotlin_version=1.6.21 +kotlin_version=1.7.10 # Dependencies junit_version=4.12 junit5_version=5.7.0 -atomicfu_version=0.17.3 +atomicfu_version=0.18.2 knit_version=0.4.0 html_version=0.7.2 lincheck_version=2.14 diff --git a/integration-testing/build.gradle b/integration-testing/build.gradle index 985a40ed96..6c8d2f326f 100644 --- a/integration-testing/build.gradle +++ b/integration-testing/build.gradle @@ -2,10 +2,8 @@ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType - plugins { - id "org.jetbrains.kotlin.jvm" + id "org.jetbrains.kotlin.jvm" version "$kotlin_version" } repositories { diff --git a/integration-testing/gradle.properties b/integration-testing/gradle.properties index 1038d81718..5e68a9d9e2 100644 --- a/integration-testing/gradle.properties +++ b/integration-testing/gradle.properties @@ -1,4 +1,4 @@ -kotlin_version=1.6.21 +kotlin_version=1.7.10 coroutines_version=1.6.4-SNAPSHOT kotlin.code.style=official diff --git a/integration-testing/settings.gradle b/integration-testing/settings.gradle index 67336c9880..54b47a02b0 100644 --- a/integration-testing/settings.gradle +++ b/integration-testing/settings.gradle @@ -1,12 +1,4 @@ pluginManagement { - resolutionStrategy { - eachPlugin { - if (requested.id.id == "org.jetbrains.kotlin.multiplatform" || requested.id.id == "org.jetbrains.kotlin.jvm") { - useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version") - } - } - } - repositories { mavenCentral() maven { url "https://plugins.gradle.org/m2/" } diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index 9791b445bf..ede3255831 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -290,7 +290,7 @@ tasks.koverVerify { task testsJar(type: Jar, dependsOn: jvmTestClasses) { classifier = 'tests' - from compileTestKotlinJvm.destinationDir + from(compileTestKotlinJvm.destinationDirectory) } artifacts { diff --git a/kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin b/kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin index a6ea0a40e53b7f45e1792c1f1edd5ba3d246f546..4c3babb1db15a06d31f0457dababc98c1fe836fc 100644 GIT binary patch delta 320 zcmZ{dO=`k$5XFE0gqS9%h>I3uYZiiFtlHMkey-~oTzQ5$KzoO5y+pK#xNxOVJXGnZ z1*_oV@!l{qzqiS(Gydjhu>{(@CH|Lf-08|58`F(PPWax_MGB+x>1$4FhAbX|`lHIi zeb)o*Oa?RgRpvW~g-T2!HKMt_;5v=qNc5@0S?*U5fYuBzl%7=4u6E33}l|(ld z2XyuW}HW*sC5448f29aysHGYq%r1Nac6z664Zz(gb*3h_Y}iz9-k zdQbYQ?nC?-_l~FCFVN*DNiZ=U@3U66QH5Mp+HyU$$sCEt6) pp%kDEWkbdAPSsGOZfMX9P?i?$fT+c>q%9qL=^0B`;9H88-Wj@_9@YQ= diff --git a/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListLongStressTest.kt b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListLongStressTest.kt index a70a32b5d3..7e1b0c6d07 100644 --- a/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListLongStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListLongStressTest.kt @@ -70,4 +70,4 @@ class LockFreeLinkedListLongStressTest : TestBase() { } require(!expected.hasNext()) } -} \ No newline at end of file +} From 19666acabd2e818d455b24aec5e85c3c2185d506 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 14 Jul 2022 16:48:05 +0200 Subject: [PATCH 002/106] Use Platform.getAvailableProcessors for K/N Dispatchers.Default (#3366) --- kotlinx-coroutines-core/nativeOther/src/Dispatchers.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/kotlinx-coroutines-core/nativeOther/src/Dispatchers.kt b/kotlinx-coroutines-core/nativeOther/src/Dispatchers.kt index 517190d0a3..17278b0be7 100644 --- a/kotlinx-coroutines-core/nativeOther/src/Dispatchers.kt +++ b/kotlinx-coroutines-core/nativeOther/src/Dispatchers.kt @@ -5,6 +5,7 @@ package kotlinx.coroutines import kotlin.coroutines.* +import kotlin.native.* internal actual fun createMainDispatcher(default: CoroutineDispatcher): MainCoroutineDispatcher = MissingMainDispatcher @@ -12,10 +13,9 @@ internal actual fun createMainDispatcher(default: CoroutineDispatcher): MainCoro internal actual fun createDefaultDispatcher(): CoroutineDispatcher = DefaultDispatcher private object DefaultDispatcher : CoroutineDispatcher() { - - // Delegated, so users won't be able to downcast and call 'close' - // The precise number of threads cannot be obtained until KT-48179 is implemented, 4 is just "good enough" number. - private val ctx = newFixedThreadPoolContext(4, "Dispatchers.Default") + // Be consistent with JVM -- at least 2 threads to provide some liveness guarantees in case of improper uses + @OptIn(ExperimentalStdlibApi::class) + private val ctx = newFixedThreadPoolContext(Platform.getAvailableProcessors().coerceAtLeast(2), "Dispatchers.Default") override fun dispatch(context: CoroutineContext, block: Runnable) { ctx.dispatch(context, block) From 3e7873f28e2fa9113a3f3816017d44c163da1378 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 25 Jul 2022 18:08:43 +0200 Subject: [PATCH 003/106] Reduce unnecessary @PublishedApi surface of UnbiasedSelectBuilderImpl (#3371) --- .../api/kotlinx-coroutines-core.api | 2 -- .../common/src/selects/SelectUnbiased.kt | 10 ++++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index dd7f889edf..01f236fef6 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -1259,8 +1259,6 @@ public final class kotlinx/coroutines/selects/SelectUnbiasedKt { public final class kotlinx/coroutines/selects/UnbiasedSelectBuilderImpl : kotlinx/coroutines/selects/SelectBuilder { public fun (Lkotlin/coroutines/Continuation;)V - public final fun getClauses ()Ljava/util/ArrayList; - public final fun getInstance ()Lkotlinx/coroutines/selects/SelectBuilderImpl; public final fun handleBuilderException (Ljava/lang/Throwable;)V public final fun initSelectResult ()Ljava/lang/Object; public fun invoke (Lkotlinx/coroutines/selects/SelectClause0;Lkotlin/jvm/functions/Function1;)V diff --git a/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt b/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt index c33c5b1f2c..25172a2d05 100644 --- a/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt +++ b/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt @@ -30,10 +30,12 @@ public suspend inline fun selectUnbiased(crossinline builder: SelectBuilder< @PublishedApi -internal class UnbiasedSelectBuilderImpl(uCont: Continuation) : - SelectBuilder { - val instance = SelectBuilderImpl(uCont) - val clauses = arrayListOf<() -> Unit>() +internal class UnbiasedSelectBuilderImpl( + uCont: Continuation +) : SelectBuilder { + + private val instance = SelectBuilderImpl(uCont) + private val clauses = arrayListOf<() -> Unit>() @PublishedApi internal fun handleBuilderException(e: Throwable): Unit = instance.handleBuilderException(e) From 007b5a58668383b69d6125a21c8a6edfd5606115 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 26 Jul 2022 14:47:29 +0200 Subject: [PATCH 004/106] Mark context in DiagnosticCoroutineContextException as transient (#3370) * Mark context in DiagnosticCoroutineContextException as transient Fixes #3328 --- .../ListAllCoroutineThrowableSubclassesTest.kt | 12 +++++++++++- .../jvm/src/CoroutineExceptionHandlerImpl.kt | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/integration-testing/src/withGuavaTest/kotlin/ListAllCoroutineThrowableSubclassesTest.kt b/integration-testing/src/withGuavaTest/kotlin/ListAllCoroutineThrowableSubclassesTest.kt index fefcc00528..21fe496bd3 100644 --- a/integration-testing/src/withGuavaTest/kotlin/ListAllCoroutineThrowableSubclassesTest.kt +++ b/integration-testing/src/withGuavaTest/kotlin/ListAllCoroutineThrowableSubclassesTest.kt @@ -7,6 +7,8 @@ package kotlinx.coroutines import com.google.common.reflect.* import kotlinx.coroutines.* import org.junit.Test +import java.io.Serializable +import java.lang.reflect.Modifier import kotlin.test.* class ListAllCoroutineThrowableSubclassesTest { @@ -31,13 +33,21 @@ class ListAllCoroutineThrowableSubclassesTest { "kotlinx.coroutines.channels.ClosedReceiveChannelException", "kotlinx.coroutines.flow.internal.ChildCancelledException", "kotlinx.coroutines.flow.internal.AbortFlowException", - ) + ) @Test fun testThrowableSubclassesAreSerializable() { val classes = ClassPath.from(this.javaClass.classLoader) .getTopLevelClassesRecursive("kotlinx.coroutines"); val throwables = classes.filter { Throwable::class.java.isAssignableFrom(it.load()) }.map { it.toString() } + for (throwable in throwables) { + for (field in throwable.javaClass.declaredFields) { + if (Modifier.isStatic(field.modifiers)) continue + val type = field.type + assertTrue(type.isPrimitive || Serializable::class.java.isAssignableFrom(type), + "Throwable $throwable has non-serializable field $field") + } + } assertEquals(knownThrowables.sorted(), throwables.sorted()) } } diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt index 4259092e78..0d68b047f4 100644 --- a/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt +++ b/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt @@ -29,7 +29,7 @@ private val handlers: List = ServiceLoader.load( * The purpose of this exception is to add an otherwise inaccessible diagnostic information and to * be able to poke the failing coroutine context in the debugger. */ -private class DiagnosticCoroutineContextException(private val context: CoroutineContext) : RuntimeException() { +private class DiagnosticCoroutineContextException(@Transient private val context: CoroutineContext) : RuntimeException() { override fun getLocalizedMessage(): String { return context.toString() } From 5c582eeba5c0d1f0e900e2e14f2794ac32500eb3 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 27 Jul 2022 17:38:46 +0200 Subject: [PATCH 005/106] Get rid of the old Kotlin/Native memory model (#3376) * Get rid of the old Kotlin/Native memory model Fixes #3375 --- kotlinx-coroutines-core/build.gradle | 40 +++------- .../common/src/CancellableContinuationImpl.kt | 2 - .../common/src/EventLoop.common.kt | 6 +- .../common/src/JobSupport.kt | 8 -- .../common/src/channels/AbstractChannel.kt | 7 -- .../common/src/flow/SharedFlow.kt | 2 - .../common/src/flow/StateFlow.kt | 3 - .../src/flow/internal/AbstractSharedFlow.kt | 2 - .../common/src/flow/internal/NullSurrogate.kt | 4 - .../common/src/flow/operators/Distinct.kt | 4 - .../common/src/internal/Atomic.kt | 3 - .../src/internal/ConcurrentLinkedList.kt | 2 - .../src/internal/DispatchedContinuation.kt | 3 - .../src/internal/LockFreeLinkedList.common.kt | 2 - .../common/src/internal/ThreadLocal.common.kt | 11 ++- .../common/src/selects/Select.kt | 7 -- .../common/src/sync/Mutex.kt | 7 -- .../common/src/sync/Semaphore.kt | 8 -- .../common/test/flow/NamedDispatchers.kt | 2 - .../src/internal/LockFreeLinkedList.kt | 3 - .../test/AbstractDispatcherConcurrencyTest.kt | 6 +- .../concurrent/test/AtomicCancellationTest.kt | 1 + .../concurrent/test/CommonThreadLocalTest.kt | 75 +++++++++++++++++++ .../test/ConcurrentExceptionsStressTest.kt | 2 +- .../test/ConcurrentTestUtilities.common.kt | 1 + .../test/JobStructuredJoinStressTest.kt | 6 +- .../test/LimitedParallelismConcurrentTest.kt | 4 +- .../concurrent/test/RunBlockingTest.kt | 6 +- .../test/TestBaseExtension.common.kt | 10 --- .../channels/BroadcastChannelSubStressTest.kt | 2 +- ...annelCancelUndeliveredElementStressTest.kt | 2 +- ...nflatedBroadcastChannelNotifyStressTest.kt | 2 +- .../concurrent/test/flow/CombineStressTest.kt | 4 +- .../test/flow/FlowCancellationTest.kt | 6 +- .../test/flow/StateFlowCommonStressTest.kt | 2 +- .../test/flow/StateFlowUpdateCommonTest.kt | 2 +- .../test/internal/LockFreeLinkedListTest.kt | 1 + .../test/selects/SelectChannelStressTest.kt | 8 +- .../concurrent/test/sync/MutexStressTest.kt | 14 ++-- .../test/sync/SemaphoreStressTest.kt | 12 +-- kotlinx-coroutines-core/js/src/Dispatchers.kt | 5 -- .../js/src/internal/ThreadLocal.kt | 4 +- .../jvm/src/internal/ThreadLocal.kt | 2 + .../jvm/test/ConcurrentTestUtilities.kt | 2 +- .../jvm/test/TestBaseExtension.kt | 10 --- .../native/src/CoroutineContext.kt | 13 +--- .../native/src/Dispatchers.kt | 39 +--------- .../native/src/EventLoop.kt | 18 +---- .../native/src/MultithreadedDispatchers.kt | 3 - .../native/src/internal/Concurrent.kt | 5 -- .../native/src/internal/ThreadLocal.kt | 12 ++- .../native/test/ConcurrentTestUtilities.kt | 3 +- .../native/test/TestBaseExtension.kt | 19 ----- .../nativeDarwin/src/Dispatchers.kt | 4 +- .../nativeDarwin/test/Launcher.kt | 4 +- .../nativeDarwin/test/MainDispatcherTest.kt | 3 +- .../nativeOther/test/Launcher.kt | 4 +- kotlinx-coroutines-test/build.gradle.kts | 9 +++ .../common/test/RunTestTest.kt | 5 +- .../common/test/StandardTestDispatcherTest.kt | 3 +- .../common/test/TestDispatchersTest.kt | 1 - .../common/test/TestScopeTest.kt | 1 - .../test/UnconfinedTestDispatcherTest.kt | 3 +- .../native/test/FailingTests.kt | 25 ------- 64 files changed, 176 insertions(+), 313 deletions(-) create mode 100644 kotlinx-coroutines-core/concurrent/test/CommonThreadLocalTest.kt delete mode 100644 kotlinx-coroutines-core/concurrent/test/TestBaseExtension.common.kt delete mode 100644 kotlinx-coroutines-core/jvm/test/TestBaseExtension.kt delete mode 100644 kotlinx-coroutines-core/native/test/TestBaseExtension.kt delete mode 100644 kotlinx-coroutines-test/native/test/FailingTests.kt diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index ede3255831..8924830daa 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -56,6 +56,7 @@ void defineSourceSet(newName, dependsOn, includedInPred) { } static boolean isNativeDarwin(String name) { return ["ios", "macos", "tvos", "watchos"].any { name.startsWith(it) } } + static boolean isNativeOther(String name) { return ["linux", "mingw"].any { name.startsWith(it) } } defineSourceSet("concurrent", ["common"]) { it in ["jvm", "native"] } @@ -77,39 +78,18 @@ kotlin { } /* - * Configure four test runs: - * 1) Old memory model, Main thread - * 2) New memory model, Main thread - * 3) Old memory model, BG thread - * 4) New memory model, BG thread (required for Dispatchers.Main tests on Darwin) + * Configure two test runs: + * 1) New memory model, Main thread + * 2) New memory model, BG thread (required for Dispatchers.Main tests on Darwin) * * All new MM targets are build with optimize = true to have stress tests properly run. */ targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithTests.class).configureEach { - binaries { + binaries.getTest("DEBUG").with { + optimized = true // Test for memory leaks using a special entry point that does not exit but returns from main - binaries.getTest("DEBUG").freeCompilerArgs += ["-e", "kotlinx.coroutines.mainNoExit"] - } - - binaries.test("newMM", [DEBUG]) { - def thisTest = it freeCompilerArgs += ["-e", "kotlinx.coroutines.mainNoExit"] - optimized = true binaryOptions["memoryModel"] = "experimental" - testRuns.create("newMM") { - setExecutionSourceFrom(thisTest) - // A hack to get different suffixes in the aggregated report. - executionTask.configure { targetName = "$targetName new MM" } - } - } - - binaries.test("worker", [DEBUG]) { - def thisTest = it - freeCompilerArgs += ["-e", "kotlinx.coroutines.mainBackground"] - testRuns.create("worker") { - setExecutionSourceFrom(thisTest) - executionTask.configure { targetName = "$targetName worker" } - } } binaries.test("workerWithNewMM", [DEBUG]) { @@ -150,11 +130,11 @@ def configureNativeSourceSetPreset(name, preset) { def implementationConfiguration = configurations[hostMainCompilation.defaultSourceSet.implementationMetadataConfigurationName] // Now find the libraries: Finds platform libs & stdlib, but platform declarations are still not resolved due to IDE bugs def hostNativePlatformLibs = files( - provider { - implementationConfiguration.findAll { - it.path.endsWith(".klib") || it.absolutePath.contains("klib${File.separator}platform") || it.absolutePath.contains("stdlib") + provider { + implementationConfiguration.findAll { + it.path.endsWith(".klib") || it.absolutePath.contains("klib${File.separator}platform") || it.absolutePath.contains("stdlib") + } } - } ) // Add all those dependencies for (suffix in sourceSetSuffixes) { diff --git a/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt index 1a0169b65d..e32c10c408 100644 --- a/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt +++ b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt @@ -9,14 +9,12 @@ import kotlinx.coroutines.internal.* import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* import kotlin.jvm.* -import kotlin.native.concurrent.* private const val UNDECIDED = 0 private const val SUSPENDED = 1 private const val RESUMED = 2 @JvmField -@SharedImmutable internal val RESUME_TOKEN = Symbol("RESUME_TOKEN") /** diff --git a/kotlinx-coroutines-core/common/src/EventLoop.common.kt b/kotlinx-coroutines-core/common/src/EventLoop.common.kt index 12940c54e2..f2e70176b6 100644 --- a/kotlinx-coroutines-core/common/src/EventLoop.common.kt +++ b/kotlinx-coroutines-core/common/src/EventLoop.common.kt @@ -8,7 +8,6 @@ import kotlinx.atomicfu.* import kotlinx.coroutines.internal.* import kotlin.coroutines.* import kotlin.jvm.* -import kotlin.native.concurrent.* /** * Extended by [CoroutineDispatcher] implementations that have event loop inside and can @@ -123,9 +122,8 @@ internal abstract class EventLoop : CoroutineDispatcher() { open fun shutdown() {} } -@ThreadLocal internal object ThreadLocalEventLoop { - private val ref = CommonThreadLocal() + private val ref = commonThreadLocal(Symbol("ThreadLocalEventLoop")) internal val eventLoop: EventLoop get() = ref.get() ?: createEventLoop().also { ref.set(it) } @@ -142,7 +140,6 @@ internal object ThreadLocalEventLoop { } } -@SharedImmutable private val DISPOSED_TASK = Symbol("REMOVED_TASK") // results for scheduleImpl @@ -168,7 +165,6 @@ internal fun delayToNanos(timeMillis: Long): Long = when { internal fun delayNanosToMillis(timeNanos: Long): Long = timeNanos / MS_TO_NS -@SharedImmutable private val CLOSED_EMPTY = Symbol("CLOSED_EMPTY") private typealias Queue = LockFreeTaskQueueCore diff --git a/kotlinx-coroutines-core/common/src/JobSupport.kt b/kotlinx-coroutines-core/common/src/JobSupport.kt index 1b5975c8bc..f7adba3c9e 100644 --- a/kotlinx-coroutines-core/common/src/JobSupport.kt +++ b/kotlinx-coroutines-core/common/src/JobSupport.kt @@ -13,7 +13,6 @@ import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* import kotlin.js.* import kotlin.jvm.* -import kotlin.native.concurrent.* /** * A concrete implementation of [Job]. It is optionally a child to a parent job. @@ -1286,25 +1285,18 @@ internal fun Any?.unboxState(): Any? = (this as? IncompleteStateBox)?.state ?: t // --------------- helper classes & constants for job implementation -@SharedImmutable private val COMPLETING_ALREADY = Symbol("COMPLETING_ALREADY") @JvmField -@SharedImmutable internal val COMPLETING_WAITING_CHILDREN = Symbol("COMPLETING_WAITING_CHILDREN") -@SharedImmutable private val COMPLETING_RETRY = Symbol("COMPLETING_RETRY") -@SharedImmutable private val TOO_LATE_TO_CANCEL = Symbol("TOO_LATE_TO_CANCEL") private const val RETRY = -1 private const val FALSE = 0 private const val TRUE = 1 -@SharedImmutable private val SEALED = Symbol("SEALED") -@SharedImmutable private val EMPTY_NEW = Empty(false) -@SharedImmutable private val EMPTY_ACTIVE = Empty(true) private class Empty(override val isActive: Boolean) : Incomplete { diff --git a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt index b92ced6ab7..149ef88601 100644 --- a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt @@ -11,7 +11,6 @@ import kotlinx.coroutines.intrinsics.* import kotlinx.coroutines.selects.* import kotlin.coroutines.* import kotlin.jvm.* -import kotlin.native.concurrent.* /** * Abstract send channel. It is a base class for all send channel implementations. @@ -1008,27 +1007,21 @@ internal const val RECEIVE_THROWS_ON_CLOSE = 0 internal const val RECEIVE_RESULT = 1 @JvmField -@SharedImmutable internal val EMPTY = Symbol("EMPTY") // marker for Conflated & Buffered channels @JvmField -@SharedImmutable internal val OFFER_SUCCESS = Symbol("OFFER_SUCCESS") @JvmField -@SharedImmutable internal val OFFER_FAILED = Symbol("OFFER_FAILED") @JvmField -@SharedImmutable internal val POLL_FAILED = Symbol("POLL_FAILED") @JvmField -@SharedImmutable internal val ENQUEUE_FAILED = Symbol("ENQUEUE_FAILED") @JvmField -@SharedImmutable internal val HANDLER_INVOKED = Symbol("ON_CLOSE_HANDLER_INVOKED") internal typealias Handler = (Throwable?) -> Unit diff --git a/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt b/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt index 0a291f258f..b4833fead6 100644 --- a/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt +++ b/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.flow.internal.* import kotlinx.coroutines.internal.* import kotlin.coroutines.* import kotlin.jvm.* -import kotlin.native.concurrent.* /** * A _hot_ [Flow] that shares emitted values among all its collectors in a broadcast fashion, so that all collectors @@ -710,7 +709,6 @@ internal open class SharedFlowImpl( } } -@SharedImmutable @JvmField internal val NO_VALUE = Symbol("NO_VALUE") diff --git a/kotlinx-coroutines-core/common/src/flow/StateFlow.kt b/kotlinx-coroutines-core/common/src/flow/StateFlow.kt index be6cbd6bbd..85377de6d2 100644 --- a/kotlinx-coroutines-core/common/src/flow/StateFlow.kt +++ b/kotlinx-coroutines-core/common/src/flow/StateFlow.kt @@ -10,7 +10,6 @@ import kotlinx.coroutines.channels.* import kotlinx.coroutines.flow.internal.* import kotlinx.coroutines.internal.* import kotlin.coroutines.* -import kotlin.native.concurrent.* /** * A [SharedFlow] that represents a read-only state with a single updatable data [value] that emits updates @@ -238,10 +237,8 @@ public inline fun MutableStateFlow.update(function: (T) -> T) { // ------------------------------------ Implementation ------------------------------------ -@SharedImmutable private val NONE = Symbol("NONE") -@SharedImmutable private val PENDING = Symbol("PENDING") // StateFlow slots are allocated for its collectors diff --git a/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt b/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt index 39ca98391f..e717717cc4 100644 --- a/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt +++ b/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt @@ -9,10 +9,8 @@ import kotlinx.coroutines.flow.* import kotlinx.coroutines.internal.* import kotlin.coroutines.* import kotlin.jvm.* -import kotlin.native.concurrent.* @JvmField -@SharedImmutable internal val EMPTY_RESUMES = arrayOfNulls?>(0) internal abstract class AbstractSharedFlowSlot { diff --git a/kotlinx-coroutines-core/common/src/flow/internal/NullSurrogate.kt b/kotlinx-coroutines-core/common/src/flow/internal/NullSurrogate.kt index c7327bd35e..7b59e2715c 100644 --- a/kotlinx-coroutines-core/common/src/flow/internal/NullSurrogate.kt +++ b/kotlinx-coroutines-core/common/src/flow/internal/NullSurrogate.kt @@ -6,7 +6,6 @@ package kotlinx.coroutines.flow.internal import kotlinx.coroutines.internal.* import kotlin.jvm.* -import kotlin.native.concurrent.* /** * This value is used a a surrogate `null` value when needed. @@ -14,7 +13,6 @@ import kotlin.native.concurrent.* * Its usage typically are paired with [Symbol.unbox] usages. */ @JvmField -@SharedImmutable internal val NULL = Symbol("NULL") /** @@ -22,7 +20,6 @@ internal val NULL = Symbol("NULL") * It should never leak to the outside world. */ @JvmField -@SharedImmutable internal val UNINITIALIZED = Symbol("UNINITIALIZED") /* @@ -30,5 +27,4 @@ internal val UNINITIALIZED = Symbol("UNINITIALIZED") * It should never leak to the outside world. */ @JvmField -@SharedImmutable internal val DONE = Symbol("DONE") diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt b/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt index f211a1b2bf..006d37e14e 100644 --- a/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt +++ b/kotlinx-coroutines-core/common/src/flow/operators/Distinct.kt @@ -7,10 +7,8 @@ package kotlinx.coroutines.flow -import kotlinx.coroutines.* import kotlinx.coroutines.flow.internal.* import kotlin.jvm.* -import kotlin.native.concurrent.* /** * Returns flow where all subsequent repetitions of the same value are filtered out. @@ -45,10 +43,8 @@ public fun Flow.distinctUntilChanged(areEquivalent: (old: T, new: T) -> B public fun Flow.distinctUntilChangedBy(keySelector: (T) -> K): Flow = distinctUntilChangedBy(keySelector = keySelector, areEquivalent = defaultAreEquivalent) -@SharedImmutable private val defaultKeySelector: (Any?) -> Any? = { it } -@SharedImmutable private val defaultAreEquivalent: (Any?, Any?) -> Boolean = { old, new -> old == new } /** diff --git a/kotlinx-coroutines-core/common/src/internal/Atomic.kt b/kotlinx-coroutines-core/common/src/internal/Atomic.kt index cf43764c72..127b70f7e8 100644 --- a/kotlinx-coroutines-core/common/src/internal/Atomic.kt +++ b/kotlinx-coroutines-core/common/src/internal/Atomic.kt @@ -8,7 +8,6 @@ package kotlinx.coroutines.internal import kotlinx.atomicfu.atomic import kotlinx.coroutines.* import kotlin.jvm.* -import kotlin.native.concurrent.* /** * The most abstract operation that can be in process. Other threads observing an instance of this @@ -38,7 +37,6 @@ public abstract class OpDescriptor { } } -@SharedImmutable @JvmField internal val NO_DECISION: Any = Symbol("NO_DECISION") @@ -117,5 +115,4 @@ public abstract class AtomicDesc { * using [AtomicOp.opSequence] numbers. */ @JvmField -@SharedImmutable internal val RETRY_ATOMIC: Any = Symbol("RETRY_ATOMIC") diff --git a/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt b/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt index 638ec43200..5eee2bd59c 100644 --- a/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt +++ b/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt @@ -7,7 +7,6 @@ package kotlinx.coroutines.internal import kotlinx.atomicfu.* import kotlinx.coroutines.* import kotlin.jvm.* -import kotlin.native.concurrent.SharedImmutable /** * Returns the first segment `s` with `s.id >= id` or `CLOSED` @@ -237,5 +236,4 @@ internal value class SegmentOrClosed>(private val value: Any?) { private const val POINTERS_SHIFT = 16 -@SharedImmutable private val CLOSED = Symbol("CLOSED") diff --git a/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt b/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt index c689a38186..63e0677fad 100644 --- a/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt +++ b/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt @@ -8,11 +8,8 @@ import kotlinx.atomicfu.* import kotlinx.coroutines.* import kotlin.coroutines.* import kotlin.jvm.* -import kotlin.native.concurrent.* -@SharedImmutable private val UNDEFINED = Symbol("UNDEFINED") -@SharedImmutable @JvmField internal val REUSABLE_CLAIMED = Symbol("REUSABLE_CLAIMED") diff --git a/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt b/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt index 8b20ade1f0..5b9ab37f4d 100644 --- a/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt +++ b/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt @@ -6,7 +6,6 @@ package kotlinx.coroutines.internal import kotlin.jvm.* -import kotlin.native.concurrent.* /** @suppress **This is unstable API and it is subject to change.** */ public expect open class LockFreeLinkedListNode() { @@ -86,5 +85,4 @@ public expect class PrepareOp: OpDescriptor { } @JvmField -@SharedImmutable internal val REMOVE_PREPARED: Any = Symbol("REMOVE_PREPARED") diff --git a/kotlinx-coroutines-core/common/src/internal/ThreadLocal.common.kt b/kotlinx-coroutines-core/common/src/internal/ThreadLocal.common.kt index 890ae4e3de..73ec93f110 100644 --- a/kotlinx-coroutines-core/common/src/internal/ThreadLocal.common.kt +++ b/kotlinx-coroutines-core/common/src/internal/ThreadLocal.common.kt @@ -4,7 +4,16 @@ package kotlinx.coroutines.internal -internal expect class CommonThreadLocal() { +internal expect class CommonThreadLocal { fun get(): T fun set(value: T) } + +/** + * Create a thread-local storage for an object of type [T]. + * + * If two different thread-local objects share the same [name], they will not necessarily share the same value, + * but they may. + * Therefore, use a unique [name] for each thread-local object. + */ +internal expect fun commonThreadLocal(name: Symbol): CommonThreadLocal diff --git a/kotlinx-coroutines-core/common/src/selects/Select.kt b/kotlinx-coroutines-core/common/src/selects/Select.kt index 921322244a..a71c849262 100644 --- a/kotlinx-coroutines-core/common/src/selects/Select.kt +++ b/kotlinx-coroutines-core/common/src/selects/Select.kt @@ -10,12 +10,10 @@ import kotlinx.coroutines.* import kotlinx.coroutines.channels.* import kotlinx.coroutines.internal.* import kotlinx.coroutines.intrinsics.* -import kotlinx.coroutines.sync.* import kotlin.contracts.* import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* import kotlin.jvm.* -import kotlin.native.concurrent.* import kotlin.time.* /** @@ -211,13 +209,9 @@ public suspend inline fun select(crossinline builder: SelectBuilder.() -> } -@SharedImmutable internal val NOT_SELECTED: Any = Symbol("NOT_SELECTED") -@SharedImmutable internal val ALREADY_SELECTED: Any = Symbol("ALREADY_SELECTED") -@SharedImmutable private val UNDECIDED: Any = Symbol("UNDECIDED") -@SharedImmutable private val RESUMED: Any = Symbol("RESUMED") // Global counter of all atomic select operations for their deadlock resolution @@ -228,7 +222,6 @@ internal class SeqNumber { fun next() = number.incrementAndGet() } -@SharedImmutable private val selectOpSequenceNumber = SeqNumber() @PublishedApi diff --git a/kotlinx-coroutines-core/common/src/sync/Mutex.kt b/kotlinx-coroutines-core/common/src/sync/Mutex.kt index 681d5db6b0..1fe9a15dfc 100644 --- a/kotlinx-coroutines-core/common/src/sync/Mutex.kt +++ b/kotlinx-coroutines-core/common/src/sync/Mutex.kt @@ -11,7 +11,6 @@ import kotlinx.coroutines.intrinsics.* import kotlinx.coroutines.selects.* import kotlin.contracts.* import kotlin.jvm.* -import kotlin.native.concurrent.* /** * Mutual exclusion for coroutines. @@ -117,18 +116,12 @@ public suspend inline fun Mutex.withLock(owner: Any? = null, action: () -> T } } -@SharedImmutable private val LOCK_FAIL = Symbol("LOCK_FAIL") -@SharedImmutable private val UNLOCK_FAIL = Symbol("UNLOCK_FAIL") -@SharedImmutable private val LOCKED = Symbol("LOCKED") -@SharedImmutable private val UNLOCKED = Symbol("UNLOCKED") -@SharedImmutable private val EMPTY_LOCKED = Empty(LOCKED) -@SharedImmutable private val EMPTY_UNLOCKED = Empty(UNLOCKED) private class Empty( diff --git a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt index e8b28bc15c..c46a5c9f40 100644 --- a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt +++ b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt @@ -8,9 +8,7 @@ import kotlinx.atomicfu.* import kotlinx.coroutines.* import kotlinx.coroutines.internal.* import kotlin.contracts.* -import kotlin.coroutines.* import kotlin.math.* -import kotlin.native.concurrent.SharedImmutable /** * A counting semaphore for coroutines that logically maintains a number of available permits. @@ -289,15 +287,9 @@ private class SemaphoreSegment(id: Long, prev: SemaphoreSegment?, pointers: Int) override fun toString() = "SemaphoreSegment[id=$id, hashCode=${hashCode()}]" } -@SharedImmutable private val MAX_SPIN_CYCLES = systemProp("kotlinx.coroutines.semaphore.maxSpinCycles", 100) -@SharedImmutable private val PERMIT = Symbol("PERMIT") -@SharedImmutable private val TAKEN = Symbol("TAKEN") -@SharedImmutable private val BROKEN = Symbol("BROKEN") -@SharedImmutable private val CANCELLED = Symbol("CANCELLED") -@SharedImmutable private val SEGMENT_SIZE = systemProp("kotlinx.coroutines.semaphore.segmentSize", 16) diff --git a/kotlinx-coroutines-core/common/test/flow/NamedDispatchers.kt b/kotlinx-coroutines-core/common/test/flow/NamedDispatchers.kt index 67bcbdc282..2459255c8d 100644 --- a/kotlinx-coroutines-core/common/test/flow/NamedDispatchers.kt +++ b/kotlinx-coroutines-core/common/test/flow/NamedDispatchers.kt @@ -5,12 +5,10 @@ package kotlinx.coroutines import kotlin.coroutines.* -import kotlin.native.concurrent.* /** * Test dispatchers that emulate multiplatform context tracking. */ -@ThreadLocal public object NamedDispatchers { private val stack = ArrayStack() diff --git a/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt b/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt index b4b36dad34..c9a1b61261 100644 --- a/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt +++ b/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt @@ -8,7 +8,6 @@ package kotlinx.coroutines.internal import kotlinx.atomicfu.* import kotlinx.coroutines.* import kotlin.jvm.* -import kotlin.native.concurrent.* private typealias Node = LockFreeLinkedListNode @@ -22,11 +21,9 @@ internal const val SUCCESS: Int = 1 internal const val FAILURE: Int = 2 @PublishedApi -@SharedImmutable internal val CONDITION_FALSE: Any = Symbol("CONDITION_FALSE") @PublishedApi -@SharedImmutable internal val LIST_EMPTY: Any = Symbol("LIST_EMPTY") /** @suppress **This is unstable API and it is subject to change.** */ diff --git a/kotlinx-coroutines-core/concurrent/test/AbstractDispatcherConcurrencyTest.kt b/kotlinx-coroutines-core/concurrent/test/AbstractDispatcherConcurrencyTest.kt index 8fc4f4efe0..7dc500f556 100644 --- a/kotlinx-coroutines-core/concurrent/test/AbstractDispatcherConcurrencyTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/AbstractDispatcherConcurrencyTest.kt @@ -12,7 +12,7 @@ abstract class AbstractDispatcherConcurrencyTest : TestBase() { public abstract val dispatcher: CoroutineDispatcher @Test - fun testLaunchAndJoin() = runMtTest { + fun testLaunchAndJoin() = runTest { expect(1) var capturedMutableState = 0 val job = GlobalScope.launch(dispatcher) { @@ -25,7 +25,7 @@ abstract class AbstractDispatcherConcurrencyTest : TestBase() { } @Test - fun testDispatcherHasOwnThreads() = runMtTest { + fun testDispatcherHasOwnThreads() = runTest { val channel = Channel() GlobalScope.launch(dispatcher) { channel.send(42) @@ -41,7 +41,7 @@ abstract class AbstractDispatcherConcurrencyTest : TestBase() { } @Test - fun testDelayInDispatcher() = runMtTest { + fun testDelayInDispatcher() = runTest { expect(1) val job = GlobalScope.launch(dispatcher) { expect(2) diff --git a/kotlinx-coroutines-core/concurrent/test/AtomicCancellationTest.kt b/kotlinx-coroutines-core/concurrent/test/AtomicCancellationTest.kt index 74751fcc3f..7bbd7ebec7 100644 --- a/kotlinx-coroutines-core/concurrent/test/AtomicCancellationTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/AtomicCancellationTest.kt @@ -25,6 +25,7 @@ class AtomicCancellationTest : TestBase() { finish(4) } + @Suppress("UNUSED_VARIABLE") @Test fun testSelectSendCancellable() = runBlocking { expect(1) diff --git a/kotlinx-coroutines-core/concurrent/test/CommonThreadLocalTest.kt b/kotlinx-coroutines-core/concurrent/test/CommonThreadLocalTest.kt new file mode 100644 index 0000000000..598a96cc19 --- /dev/null +++ b/kotlinx-coroutines-core/concurrent/test/CommonThreadLocalTest.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import kotlinx.coroutines.exceptions.* +import kotlinx.coroutines.internal.* +import kotlin.test.* + +class CommonThreadLocalTest: TestBase() { + + /** + * Tests the basic functionality of [commonThreadLocal]: storing a separate value for each thread. + */ + @Test + fun testThreadLocalBeingThreadLocal() = runTest { + val threadLocal = commonThreadLocal(Symbol("Test1")) + newSingleThreadContext("").use { + threadLocal.set(10) + assertEquals(10, threadLocal.get()) + val job1 = launch(it) { + threadLocal.set(20) + assertEquals(20, threadLocal.get()) + } + assertEquals(10, threadLocal.get()) + job1.join() + val job2 = launch(it) { + assertEquals(20, threadLocal.get()) + } + job2.join() + } + } + + /** + * Tests using [commonThreadLocal] with a nullable type. + */ + @Test + fun testThreadLocalWithNullableType() = runTest { + val threadLocal = commonThreadLocal(Symbol("Test2")) + newSingleThreadContext("").use { + assertNull(threadLocal.get()) + threadLocal.set(10) + assertEquals(10, threadLocal.get()) + val job1 = launch(it) { + assertNull(threadLocal.get()) + threadLocal.set(20) + assertEquals(20, threadLocal.get()) + } + assertEquals(10, threadLocal.get()) + job1.join() + threadLocal.set(null) + assertNull(threadLocal.get()) + val job2 = launch(it) { + assertEquals(20, threadLocal.get()) + threadLocal.set(null) + assertNull(threadLocal.get()) + } + job2.join() + } + } + + /** + * Tests that several instances of [commonThreadLocal] with different names don't affect each other. + */ + @Test + fun testThreadLocalsWithDifferentNamesNotInterfering() { + val value1 = commonThreadLocal(Symbol("Test3a")) + val value2 = commonThreadLocal(Symbol("Test3b")) + value1.set(5) + value2.set(6) + assertEquals(5, value1.get()) + assertEquals(6, value2.get()) + } +} diff --git a/kotlinx-coroutines-core/concurrent/test/ConcurrentExceptionsStressTest.kt b/kotlinx-coroutines-core/concurrent/test/ConcurrentExceptionsStressTest.kt index d4252da300..cbfa3323a1 100644 --- a/kotlinx-coroutines-core/concurrent/test/ConcurrentExceptionsStressTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/ConcurrentExceptionsStressTest.kt @@ -22,7 +22,7 @@ class ConcurrentExceptionsStressTest : TestBase() { } @Test - fun testStress() = runMtTest { + fun testStress() = runTest { workers = Array(nWorkers) { index -> newSingleThreadContext("JobExceptionsStressTest-$index") } diff --git a/kotlinx-coroutines-core/concurrent/test/ConcurrentTestUtilities.common.kt b/kotlinx-coroutines-core/concurrent/test/ConcurrentTestUtilities.common.kt index a4d40fb2ef..d40f118ded 100644 --- a/kotlinx-coroutines-core/concurrent/test/ConcurrentTestUtilities.common.kt +++ b/kotlinx-coroutines-core/concurrent/test/ConcurrentTestUtilities.common.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.* internal expect open class SuppressSupportingThrowable() : Throwable expect val Throwable.suppressed: Array +// Unused on purpose, used manually during debugging sessions expect fun Throwable.printStackTrace() expect fun randomWait() diff --git a/kotlinx-coroutines-core/concurrent/test/JobStructuredJoinStressTest.kt b/kotlinx-coroutines-core/concurrent/test/JobStructuredJoinStressTest.kt index 431bb697fd..4b5c952d74 100644 --- a/kotlinx-coroutines-core/concurrent/test/JobStructuredJoinStressTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/JobStructuredJoinStressTest.kt @@ -16,12 +16,12 @@ class JobStructuredJoinStressTest : TestBase() { private val nRepeats = 10_000 * stressTestMultiplier @Test - fun testStressRegularJoin() = runMtTest { + fun testStressRegularJoin() = runTest { stress(Job::join) } @Test - fun testStressSuspendCancellable() = runMtTest { + fun testStressSuspendCancellable() = runTest { stress { job -> suspendCancellableCoroutine { cont -> job.invokeOnCompletion { cont.resume(Unit) } @@ -30,7 +30,7 @@ class JobStructuredJoinStressTest : TestBase() { } @Test - fun testStressSuspendCancellableReusable() = runMtTest { + fun testStressSuspendCancellableReusable() = runTest { stress { job -> suspendCancellableCoroutineReusable { cont -> job.invokeOnCompletion { cont.resume(Unit) } diff --git a/kotlinx-coroutines-core/concurrent/test/LimitedParallelismConcurrentTest.kt b/kotlinx-coroutines-core/concurrent/test/LimitedParallelismConcurrentTest.kt index 8d38f05b4b..7d82a67baf 100644 --- a/kotlinx-coroutines-core/concurrent/test/LimitedParallelismConcurrentTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/LimitedParallelismConcurrentTest.kt @@ -23,7 +23,7 @@ class LimitedParallelismConcurrentTest : TestBase() { } @Test - fun testLimitedExecutor() = runMtTest { + fun testLimitedExecutor() = runTest { val executor = newFixedThreadPoolContext(targetParallelism, "test") val view = executor.limitedParallelism(targetParallelism) doStress { @@ -45,7 +45,7 @@ class LimitedParallelismConcurrentTest : TestBase() { } @Test - fun testTaskFairness() = runMtTest { + fun testTaskFairness() = runTest { val executor = newSingleThreadContext("test") val view = executor.limitedParallelism(1) val view2 = executor.limitedParallelism(1) diff --git a/kotlinx-coroutines-core/concurrent/test/RunBlockingTest.kt b/kotlinx-coroutines-core/concurrent/test/RunBlockingTest.kt index 70f6b8ba60..f484476152 100644 --- a/kotlinx-coroutines-core/concurrent/test/RunBlockingTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/RunBlockingTest.kt @@ -10,7 +10,7 @@ import kotlin.test.* class RunBlockingTest : TestBase() { @Test - fun testWithTimeoutBusyWait() = runMtTest { + fun testWithTimeoutBusyWait() = runTest { val value = withTimeoutOrNull(10) { while (isActive) { // Busy wait @@ -52,7 +52,7 @@ class RunBlockingTest : TestBase() { } @Test - fun testOtherDispatcher() = runMtTest { + fun testOtherDispatcher() = runTest { expect(1) val name = "RunBlockingTest.testOtherDispatcher" val thread = newSingleThreadContext(name) @@ -68,7 +68,7 @@ class RunBlockingTest : TestBase() { } @Test - fun testCancellation() = runMtTest { + fun testCancellation() = runTest { newFixedThreadPoolContext(2, "testCancellation").use { val job = GlobalScope.launch(it) { runBlocking(coroutineContext) { diff --git a/kotlinx-coroutines-core/concurrent/test/TestBaseExtension.common.kt b/kotlinx-coroutines-core/concurrent/test/TestBaseExtension.common.kt deleted file mode 100644 index b19bf50ec8..0000000000 --- a/kotlinx-coroutines-core/concurrent/test/TestBaseExtension.common.kt +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ -package kotlinx.coroutines - -expect fun TestBase.runMtTest( - expected: ((Throwable) -> Boolean)? = null, - unhandled: List<(Throwable) -> Boolean> = emptyList(), - block: suspend CoroutineScope.() -> Unit -): TestResult diff --git a/kotlinx-coroutines-core/concurrent/test/channels/BroadcastChannelSubStressTest.kt b/kotlinx-coroutines-core/concurrent/test/channels/BroadcastChannelSubStressTest.kt index 30b1075c0a..245a80c222 100644 --- a/kotlinx-coroutines-core/concurrent/test/channels/BroadcastChannelSubStressTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/channels/BroadcastChannelSubStressTest.kt @@ -20,7 +20,7 @@ class BroadcastChannelSubStressTest: TestBase() { private val receivedTotal = atomic(0L) @Test - fun testStress() = runMtTest { + fun testStress() = runTest { TestBroadcastChannelKind.values().forEach { kind -> println("--- BroadcastChannelSubStressTest $kind") val broadcast = kind.create() diff --git a/kotlinx-coroutines-core/concurrent/test/channels/ChannelCancelUndeliveredElementStressTest.kt b/kotlinx-coroutines-core/concurrent/test/channels/ChannelCancelUndeliveredElementStressTest.kt index 3e38eec362..76918850e5 100644 --- a/kotlinx-coroutines-core/concurrent/test/channels/ChannelCancelUndeliveredElementStressTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/channels/ChannelCancelUndeliveredElementStressTest.kt @@ -28,7 +28,7 @@ class ChannelCancelUndeliveredElementStressTest : TestBase() { private val dUndeliveredCnt = atomic(0) @Test - fun testStress() = runMtTest { + fun testStress() = runTest { repeat(repeatTimes) { val channel = Channel(1) { dUndeliveredCnt.incrementAndGet() } val j1 = launch(Dispatchers.Default) { diff --git a/kotlinx-coroutines-core/concurrent/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt b/kotlinx-coroutines-core/concurrent/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt index 5da00d2af2..d9ec7ad582 100644 --- a/kotlinx-coroutines-core/concurrent/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/channels/ConflatedBroadcastChannelNotifyStressTest.kt @@ -22,7 +22,7 @@ class ConflatedBroadcastChannelNotifyStressTest : TestBase() { private val receivedTotal = atomic(0) @Test - fun testStressNotify()= runMtTest { + fun testStressNotify()= runTest { println("--- ConflatedBroadcastChannelNotifyStressTest") val senders = List(nSenders) { senderId -> launch(Dispatchers.Default + CoroutineName("Sender$senderId")) { diff --git a/kotlinx-coroutines-core/concurrent/test/flow/CombineStressTest.kt b/kotlinx-coroutines-core/concurrent/test/flow/CombineStressTest.kt index f262e78f81..ea6f96bb2c 100644 --- a/kotlinx-coroutines-core/concurrent/test/flow/CombineStressTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/flow/CombineStressTest.kt @@ -10,7 +10,7 @@ import kotlin.test.* class CombineStressTest : TestBase() { @Test - fun testCancellation() = runMtTest { + fun testCancellation() = runTest { withContext(Dispatchers.Default + CoroutineExceptionHandler { _, _ -> expectUnreached() }) { flow { expect(1) @@ -26,7 +26,7 @@ class CombineStressTest : TestBase() { } @Test - fun testFailure() = runMtTest { + fun testFailure() = runTest { val innerIterations = 100 * stressTestMultiplierSqrt val outerIterations = 10 * stressTestMultiplierSqrt withContext(Dispatchers.Default + CoroutineExceptionHandler { _, _ -> expectUnreached() }) { diff --git a/kotlinx-coroutines-core/concurrent/test/flow/FlowCancellationTest.kt b/kotlinx-coroutines-core/concurrent/test/flow/FlowCancellationTest.kt index 286ba751dd..8680ff7d53 100644 --- a/kotlinx-coroutines-core/concurrent/test/flow/FlowCancellationTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/flow/FlowCancellationTest.kt @@ -12,7 +12,7 @@ import kotlin.test.* class FlowCancellationTest : TestBase() { @Test - fun testEmitIsCooperative() = runMtTest { + fun testEmitIsCooperative() = runTest { val latch = Channel(1) val job = flow { expect(1) @@ -29,7 +29,7 @@ class FlowCancellationTest : TestBase() { } @Test - fun testIsActiveOnCurrentContext() = runMtTest { + fun testIsActiveOnCurrentContext() = runTest { val latch = Channel(1) val job = flow { expect(1) @@ -46,7 +46,7 @@ class FlowCancellationTest : TestBase() { } @Test - fun testFlowWithEmptyContext() = runMtTest { + fun testFlowWithEmptyContext() = runTest { expect(1) withEmptyContext { val flow = flow { diff --git a/kotlinx-coroutines-core/concurrent/test/flow/StateFlowCommonStressTest.kt b/kotlinx-coroutines-core/concurrent/test/flow/StateFlowCommonStressTest.kt index f2fb41a589..abd191e5aa 100644 --- a/kotlinx-coroutines-core/concurrent/test/flow/StateFlowCommonStressTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/flow/StateFlowCommonStressTest.kt @@ -13,7 +13,7 @@ class StateFlowCommonStressTest : TestBase() { private val state = MutableStateFlow(0) @Test - fun testSingleEmitterAndCollector() = runMtTest { + fun testSingleEmitterAndCollector() = runTest { var collected = 0L val collector = launch(Dispatchers.Default) { // collect, but abort and collect again after every 1000 values to stress allocation/deallocation diff --git a/kotlinx-coroutines-core/concurrent/test/flow/StateFlowUpdateCommonTest.kt b/kotlinx-coroutines-core/concurrent/test/flow/StateFlowUpdateCommonTest.kt index 1e79709481..8c75b46175 100644 --- a/kotlinx-coroutines-core/concurrent/test/flow/StateFlowUpdateCommonTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/flow/StateFlowUpdateCommonTest.kt @@ -21,7 +21,7 @@ class StateFlowUpdateCommonTest : TestBase() { @Test fun testGetAndUpdate() = doTest { getAndUpdate { it + 1 } } - private fun doTest(increment: MutableStateFlow.() -> Unit) = runMtTest { + private fun doTest(increment: MutableStateFlow.() -> Unit) = runTest { val flow = MutableStateFlow(0) val j1 = launch(Dispatchers.Default) { repeat(iterations / 2) { diff --git a/kotlinx-coroutines-core/concurrent/test/internal/LockFreeLinkedListTest.kt b/kotlinx-coroutines-core/concurrent/test/internal/LockFreeLinkedListTest.kt index 7e85d495fc..1e08c35a8e 100644 --- a/kotlinx-coroutines-core/concurrent/test/internal/LockFreeLinkedListTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/internal/LockFreeLinkedListTest.kt @@ -46,6 +46,7 @@ class LockFreeLinkedListTest { assertContents(list, 1, 3) } + @Suppress("UNUSED_VARIABLE") @Test fun testAtomicOpsSingle() { val list = LockFreeLinkedListHead() diff --git a/kotlinx-coroutines-core/concurrent/test/selects/SelectChannelStressTest.kt b/kotlinx-coroutines-core/concurrent/test/selects/SelectChannelStressTest.kt index 29c6c34889..f464912e81 100644 --- a/kotlinx-coroutines-core/concurrent/test/selects/SelectChannelStressTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/selects/SelectChannelStressTest.kt @@ -15,7 +15,7 @@ class SelectChannelStressTest: TestBase() { private val iterations = (if (isNative) 1_000 else 1_000_000) * stressTestMultiplier @Test - fun testSelectSendResourceCleanupArrayChannel() = runMtTest { + fun testSelectSendResourceCleanupArrayChannel() = runTest { val channel = Channel(1) expect(1) channel.send(-1) // fill the buffer, so all subsequent sends cannot proceed @@ -29,7 +29,7 @@ class SelectChannelStressTest: TestBase() { } @Test - fun testSelectReceiveResourceCleanupArrayChannel() = runMtTest { + fun testSelectReceiveResourceCleanupArrayChannel() = runTest { val channel = Channel(1) expect(1) repeat(iterations) { i -> @@ -42,7 +42,7 @@ class SelectChannelStressTest: TestBase() { } @Test - fun testSelectSendResourceCleanupRendezvousChannel() = runMtTest { + fun testSelectSendResourceCleanupRendezvousChannel() = runTest { val channel = Channel(Channel.RENDEZVOUS) expect(1) repeat(iterations) { i -> @@ -55,7 +55,7 @@ class SelectChannelStressTest: TestBase() { } @Test - fun testSelectReceiveResourceRendezvousChannel() = runMtTest { + fun testSelectReceiveResourceRendezvousChannel() = runTest { val channel = Channel(Channel.RENDEZVOUS) expect(1) repeat(iterations) { i -> diff --git a/kotlinx-coroutines-core/concurrent/test/sync/MutexStressTest.kt b/kotlinx-coroutines-core/concurrent/test/sync/MutexStressTest.kt index 73b62aee2e..77fa7bf1b9 100644 --- a/kotlinx-coroutines-core/concurrent/test/sync/MutexStressTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/sync/MutexStressTest.kt @@ -14,24 +14,24 @@ class MutexStressTest : TestBase() { private val n = (if (isNative) 1_000 else 10_000) * stressTestMultiplier @Test - fun testDefaultDispatcher() = runMtTest { testBody(Dispatchers.Default) } + fun testDefaultDispatcher() = runTest { testBody(Dispatchers.Default) } @Test - fun testSingleThreadContext() = runMtTest { + fun testSingleThreadContext() = runTest { newSingleThreadContext("testSingleThreadContext").use { testBody(it) } } @Test - fun testMultiThreadedContextWithSingleWorker() = runMtTest { + fun testMultiThreadedContextWithSingleWorker() = runTest { newFixedThreadPoolContext(1, "testMultiThreadedContextWithSingleWorker").use { testBody(it) } } @Test - fun testMultiThreadedContext() = runMtTest { + fun testMultiThreadedContext() = runTest { newFixedThreadPoolContext(8, "testMultiThreadedContext").use { testBody(it) } @@ -56,7 +56,7 @@ class MutexStressTest : TestBase() { } @Test - fun stressUnlockCancelRace() = runMtTest { + fun stressUnlockCancelRace() = runTest { val n = 10_000 * stressTestMultiplier val mutex = Mutex(true) // create a locked mutex newSingleThreadContext("SemaphoreStressTest").use { pool -> @@ -86,7 +86,7 @@ class MutexStressTest : TestBase() { } @Test - fun stressUnlockCancelRaceWithSelect() = runMtTest { + fun stressUnlockCancelRaceWithSelect() = runTest { val n = 10_000 * stressTestMultiplier val mutex = Mutex(true) // create a locked mutex newSingleThreadContext("SemaphoreStressTest").use { pool -> @@ -119,7 +119,7 @@ class MutexStressTest : TestBase() { } @Test - fun testShouldBeUnlockedOnCancellation() = runMtTest { + fun testShouldBeUnlockedOnCancellation() = runTest { val mutex = Mutex() val n = 1000 * stressTestMultiplier repeat(n) { diff --git a/kotlinx-coroutines-core/concurrent/test/sync/SemaphoreStressTest.kt b/kotlinx-coroutines-core/concurrent/test/sync/SemaphoreStressTest.kt index c5f2038937..9a3d25bcdd 100644 --- a/kotlinx-coroutines-core/concurrent/test/sync/SemaphoreStressTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/sync/SemaphoreStressTest.kt @@ -13,7 +13,7 @@ class SemaphoreStressTest : TestBase() { private val iterations = (if (isNative) 1_000 else 10_000) * stressTestMultiplier @Test - fun testStressTestAsMutex() = runMtTest { + fun testStressTestAsMutex() = runTest { val n = iterations val k = 100 var shared = 0 @@ -32,7 +32,7 @@ class SemaphoreStressTest : TestBase() { } @Test - fun testStress() = runMtTest { + fun testStress() = runTest { val n = iterations val k = 100 val semaphore = Semaphore(10) @@ -48,7 +48,7 @@ class SemaphoreStressTest : TestBase() { } @Test - fun testStressAsMutex() = runMtTest { + fun testStressAsMutex() = runTest { runBlocking(Dispatchers.Default) { val n = iterations val k = 100 @@ -69,7 +69,7 @@ class SemaphoreStressTest : TestBase() { } @Test - fun testStressCancellation() = runMtTest { + fun testStressCancellation() = runTest { val n = iterations val semaphore = Semaphore(1) semaphore.acquire() @@ -90,7 +90,7 @@ class SemaphoreStressTest : TestBase() { * the semaphore into an incorrect state where permits are leaked. */ @Test - fun testStressReleaseCancelRace() = runMtTest { + fun testStressReleaseCancelRace() = runTest { val n = iterations val semaphore = Semaphore(1, 1) newSingleThreadContext("SemaphoreStressTest").use { pool -> @@ -120,7 +120,7 @@ class SemaphoreStressTest : TestBase() { } @Test - fun testShouldBeUnlockedOnCancellation() = runMtTest { + fun testShouldBeUnlockedOnCancellation() = runTest { val semaphore = Semaphore(1) val n = 1000 * stressTestMultiplier repeat(n) { diff --git a/kotlinx-coroutines-core/js/src/Dispatchers.kt b/kotlinx-coroutines-core/js/src/Dispatchers.kt index 3eec5408cc..1304c5a9e5 100644 --- a/kotlinx-coroutines-core/js/src/Dispatchers.kt +++ b/kotlinx-coroutines-core/js/src/Dispatchers.kt @@ -19,11 +19,6 @@ public actual object Dispatchers { internal fun injectMain(dispatcher: MainCoroutineDispatcher) { injectedMainDispatcher = dispatcher } - - @PublishedApi - internal fun resetInjectedMain() { - injectedMainDispatcher = null - } } private class JsMainDispatcher( diff --git a/kotlinx-coroutines-core/js/src/internal/ThreadLocal.kt b/kotlinx-coroutines-core/js/src/internal/ThreadLocal.kt index e1825d67b8..c8dd09683f 100644 --- a/kotlinx-coroutines-core/js/src/internal/ThreadLocal.kt +++ b/kotlinx-coroutines-core/js/src/internal/ThreadLocal.kt @@ -4,9 +4,11 @@ package kotlinx.coroutines.internal -internal actual class CommonThreadLocal actual constructor() { +internal actual class CommonThreadLocal { private var value: T? = null @Suppress("UNCHECKED_CAST") actual fun get(): T = value as T actual fun set(value: T) { this.value = value } } + +internal actual fun commonThreadLocal(name: Symbol): CommonThreadLocal = CommonThreadLocal() diff --git a/kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt b/kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt index 0207334af0..0924a5b396 100644 --- a/kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt +++ b/kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt @@ -8,3 +8,5 @@ import java.lang.ThreadLocal @Suppress("ACTUAL_WITHOUT_EXPECT") // internal visibility internal actual typealias CommonThreadLocal = ThreadLocal + +internal actual fun commonThreadLocal(name: Symbol): CommonThreadLocal = ThreadLocal() diff --git a/kotlinx-coroutines-core/jvm/test/ConcurrentTestUtilities.kt b/kotlinx-coroutines-core/jvm/test/ConcurrentTestUtilities.kt index b46adda90d..4ccb74b427 100644 --- a/kotlinx-coroutines-core/jvm/test/ConcurrentTestUtilities.kt +++ b/kotlinx-coroutines-core/jvm/test/ConcurrentTestUtilities.kt @@ -23,7 +23,7 @@ private object BlackHole { @Suppress("ACTUAL_WITHOUT_EXPECT") internal actual typealias SuppressSupportingThrowable = Throwable -@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "unused") actual fun Throwable.printStackTrace() = printStackTrace() actual fun currentThreadName(): String = Thread.currentThread().name diff --git a/kotlinx-coroutines-core/jvm/test/TestBaseExtension.kt b/kotlinx-coroutines-core/jvm/test/TestBaseExtension.kt deleted file mode 100644 index 799e559a43..0000000000 --- a/kotlinx-coroutines-core/jvm/test/TestBaseExtension.kt +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ -package kotlinx.coroutines - -public actual fun TestBase.runMtTest( - expected: ((Throwable) -> Boolean)?, - unhandled: List<(Throwable) -> Boolean>, - block: suspend CoroutineScope.() -> Unit -): TestResult = runTest(expected, unhandled, block) diff --git a/kotlinx-coroutines-core/native/src/CoroutineContext.kt b/kotlinx-coroutines-core/native/src/CoroutineContext.kt index 6e2dac1a29..51ffd7318a 100644 --- a/kotlinx-coroutines-core/native/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/native/src/CoroutineContext.kt @@ -6,42 +6,31 @@ package kotlinx.coroutines import kotlinx.coroutines.internal.* import kotlin.coroutines.* -import kotlin.native.concurrent.* internal actual object DefaultExecutor : CoroutineDispatcher(), Delay { private val delegate = WorkerDispatcher(name = "DefaultExecutor") override fun dispatch(context: CoroutineContext, block: Runnable) { - checkState() delegate.dispatch(context, block) } override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) { - checkState() delegate.scheduleResumeAfterDelay(timeMillis, continuation) } override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle { - checkState() return delegate.invokeOnTimeout(timeMillis, block, context) } actual fun enqueue(task: Runnable): Unit { - checkState() delegate.dispatch(EmptyCoroutineContext, task) } - - private fun checkState() { - if (multithreadingSupported) return - error("DefaultExecutor should never be invoked in K/N with disabled new memory model. The top-most 'runBlocking' event loop has been shutdown") - } } internal expect fun createDefaultDispatcher(): CoroutineDispatcher -@SharedImmutable -internal actual val DefaultDelay: Delay = if (multithreadingSupported) DefaultExecutor else OldDefaultExecutor +internal actual val DefaultDelay: Delay = DefaultExecutor public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext { val combined = coroutineContext + context diff --git a/kotlinx-coroutines-core/native/src/Dispatchers.kt b/kotlinx-coroutines-core/native/src/Dispatchers.kt index 6c51a03463..9f990534cc 100644 --- a/kotlinx-coroutines-core/native/src/Dispatchers.kt +++ b/kotlinx-coroutines-core/native/src/Dispatchers.kt @@ -4,11 +4,9 @@ package kotlinx.coroutines -import kotlinx.coroutines.internal.multithreadingSupported -import kotlin.coroutines.* public actual object Dispatchers { - public actual val Default: CoroutineDispatcher = createDefaultDispatcherBasedOnMm() + public actual val Default: CoroutineDispatcher = createDefaultDispatcher() public actual val Main: MainCoroutineDispatcher get() = injectedMainDispatcher ?: mainDispatcher public actual val Unconfined: CoroutineDispatcher get() = kotlinx.coroutines.Unconfined // Avoid freezing @@ -19,43 +17,8 @@ public actual object Dispatchers { @PublishedApi internal fun injectMain(dispatcher: MainCoroutineDispatcher) { - if (!multithreadingSupported) { - throw IllegalStateException("Dispatchers.setMain is not supported in Kotlin/Native when new memory model is disabled") - } injectedMainDispatcher = dispatcher } - - @PublishedApi - internal fun resetInjectedMain() { - injectedMainDispatcher = null - } } internal expect fun createMainDispatcher(default: CoroutineDispatcher): MainCoroutineDispatcher - -private fun createDefaultDispatcherBasedOnMm(): CoroutineDispatcher { - return if (multithreadingSupported) createDefaultDispatcher() - else OldDefaultExecutor -} - -private fun takeEventLoop(): EventLoopImpl = - ThreadLocalEventLoop.currentOrNull() as? EventLoopImpl ?: - error("There is no event loop. Use runBlocking { ... } to start one.") - -internal object OldDefaultExecutor : CoroutineDispatcher(), Delay { - override fun dispatch(context: CoroutineContext, block: Runnable) = - takeEventLoop().dispatch(context, block) - override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) = - takeEventLoop().scheduleResumeAfterDelay(timeMillis, continuation) - override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle = - takeEventLoop().invokeOnTimeout(timeMillis, block, context) -} - -internal class OldMainDispatcher(private val delegate: CoroutineDispatcher) : MainCoroutineDispatcher() { - override val immediate: MainCoroutineDispatcher - get() = throw UnsupportedOperationException("Immediate dispatching is not supported on Native") - override fun dispatch(context: CoroutineContext, block: Runnable) = delegate.dispatch(context, block) - override fun isDispatchNeeded(context: CoroutineContext): Boolean = delegate.isDispatchNeeded(context) - override fun dispatchYield(context: CoroutineContext, block: Runnable) = delegate.dispatchYield(context, block) - override fun toString(): String = toStringInternalImpl() ?: delegate.toString() -} diff --git a/kotlinx-coroutines-core/native/src/EventLoop.kt b/kotlinx-coroutines-core/native/src/EventLoop.kt index f4e5b8c9c4..25c3c12b78 100644 --- a/kotlinx-coroutines-core/native/src/EventLoop.kt +++ b/kotlinx-coroutines-core/native/src/EventLoop.kt @@ -4,10 +4,6 @@ package kotlinx.coroutines -import kotlinx.cinterop.* -import kotlinx.coroutines.internal.* -import kotlinx.coroutines.internal.multithreadingSupported -import platform.posix.* import kotlin.coroutines.* import kotlin.native.concurrent.* import kotlin.system.* @@ -21,21 +17,13 @@ internal actual abstract class EventLoopImplPlatform : EventLoop() { } protected actual fun reschedule(now: Long, delayedTask: EventLoopImplBase.DelayedTask) { - if (multithreadingSupported) { - DefaultExecutor.invokeOnTimeout(now, delayedTask, EmptyCoroutineContext) - } else { - error("Cannot execute task because event loop was shut down") - } + DefaultExecutor.invokeOnTimeout(now, delayedTask, EmptyCoroutineContext) } } internal class EventLoopImpl: EventLoopImplBase() { - override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle { - if (!multithreadingSupported) { - return scheduleInvokeOnTimeout(timeMillis, block) - } - return DefaultDelay.invokeOnTimeout(timeMillis, block, context) - } + override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle = + DefaultDelay.invokeOnTimeout(timeMillis, block, context) } internal actual fun createEventLoop(): EventLoop = EventLoopImpl() diff --git a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt index 1c1306c291..a940dcbdb0 100644 --- a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt +++ b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt @@ -5,18 +5,15 @@ package kotlinx.coroutines import kotlinx.coroutines.channels.* -import kotlinx.coroutines.internal.* import kotlin.coroutines.* import kotlin.native.concurrent.* @ExperimentalCoroutinesApi public actual fun newSingleThreadContext(name: String): CloseableCoroutineDispatcher { - if (!multithreadingSupported) throw IllegalStateException("This API is only supported for experimental K/N memory model") return WorkerDispatcher(name) } public actual fun newFixedThreadPoolContext(nThreads: Int, name: String): CloseableCoroutineDispatcher { - if (!multithreadingSupported) throw IllegalStateException("This API is only supported for experimental K/N memory model") require(nThreads >= 1) { "Expected at least one thread, but got: $nThreads"} return MultiWorkerDispatcher(name, nThreads) } diff --git a/kotlinx-coroutines-core/native/src/internal/Concurrent.kt b/kotlinx-coroutines-core/native/src/internal/Concurrent.kt index f6e18dd5fc..55c3efe360 100644 --- a/kotlinx-coroutines-core/native/src/internal/Concurrent.kt +++ b/kotlinx-coroutines-core/native/src/internal/Concurrent.kt @@ -5,7 +5,6 @@ package kotlinx.coroutines.internal import kotlinx.atomicfu.* -import kotlin.native.concurrent.* import kotlinx.atomicfu.locks.withLock as withLock2 @Suppress("ACTUAL_WITHOUT_EXPECT") @@ -32,7 +31,3 @@ internal open class SuppressSupportingThrowableImpl : Throwable() { } } -// getter instead of a property due to the bug in the initialization dependencies tracking with '-Xir-property-lazy-initialization=disabled' that Ktor uses -@OptIn(ExperimentalStdlibApi::class) -internal val multithreadingSupported: Boolean - get() = kotlin.native.isExperimentalMM() diff --git a/kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt b/kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt index e1825d67b8..405cbfb6a5 100644 --- a/kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt +++ b/kotlinx-coroutines-core/native/src/internal/ThreadLocal.kt @@ -4,9 +4,13 @@ package kotlinx.coroutines.internal -internal actual class CommonThreadLocal actual constructor() { - private var value: T? = null +internal actual class CommonThreadLocal(private val name: Symbol) { @Suppress("UNCHECKED_CAST") - actual fun get(): T = value as T - actual fun set(value: T) { this.value = value } + actual fun get(): T = Storage[name] as T + actual fun set(value: T) { Storage[name] = value } } + +internal actual fun commonThreadLocal(name: Symbol): CommonThreadLocal = CommonThreadLocal(name) + +@ThreadLocal +private object Storage: MutableMap by mutableMapOf() diff --git a/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt b/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt index 639b5fb174..6690972dfa 100644 --- a/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt +++ b/kotlinx-coroutines-core/native/test/ConcurrentTestUtilities.kt @@ -4,7 +4,6 @@ package kotlinx.coroutines.exceptions -import kotlinx.atomicfu.* import kotlinx.coroutines.internal.* import platform.posix.* import kotlin.native.concurrent.* @@ -31,7 +30,7 @@ internal actual typealias SuppressSupportingThrowable = SuppressSupportingThrowa actual val Throwable.suppressed: Array get() = (this as? SuppressSupportingThrowableImpl)?.suppressed ?: emptyArray() -@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "unused") actual fun Throwable.printStackTrace() = printStackTrace() actual fun currentThreadName(): String = Worker.current.name diff --git a/kotlinx-coroutines-core/native/test/TestBaseExtension.kt b/kotlinx-coroutines-core/native/test/TestBaseExtension.kt deleted file mode 100644 index fde2cde5cf..0000000000 --- a/kotlinx-coroutines-core/native/test/TestBaseExtension.kt +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ -package kotlinx.coroutines - -import kotlinx.coroutines.internal.* - -actual fun TestBase.runMtTest( - expected: ((Throwable) -> Boolean)?, - unhandled: List<(Throwable) -> Boolean>, - block: suspend CoroutineScope.() -> Unit -) { - if (!multithreadingSupported) return - return runTest(expected, unhandled, block) -} diff --git a/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt b/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt index ace20422f6..a5588d853d 100644 --- a/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt +++ b/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt @@ -5,7 +5,6 @@ package kotlinx.coroutines import kotlinx.cinterop.* -import kotlinx.coroutines.internal.* import platform.CoreFoundation.* import platform.darwin.* import kotlin.coroutines.* @@ -14,8 +13,7 @@ import kotlin.native.internal.NativePtr internal fun isMainThread(): Boolean = CFRunLoopGetCurrent() == CFRunLoopGetMain() -internal actual fun createMainDispatcher(default: CoroutineDispatcher): MainCoroutineDispatcher = - if (multithreadingSupported) DarwinMainDispatcher(false) else OldMainDispatcher(Dispatchers.Default) +internal actual fun createMainDispatcher(default: CoroutineDispatcher): MainCoroutineDispatcher = DarwinMainDispatcher(false) internal actual fun createDefaultDispatcher(): CoroutineDispatcher = DarwinGlobalQueueDispatcher diff --git a/kotlinx-coroutines-core/nativeDarwin/test/Launcher.kt b/kotlinx-coroutines-core/nativeDarwin/test/Launcher.kt index 78ed765967..3a2820e9bd 100644 --- a/kotlinx-coroutines-core/nativeDarwin/test/Launcher.kt +++ b/kotlinx-coroutines-core/nativeDarwin/test/Launcher.kt @@ -12,7 +12,7 @@ import kotlin.system.* // This is a separate entry point for tests in background fun mainBackground(args: Array) { val worker = Worker.start(name = "main-background") - worker.execute(TransferMode.SAFE, { args.freeze() }) { + worker.execute(TransferMode.SAFE, { args }) { val result = testLauncherEntryPoint(it) exitProcess(result) } @@ -25,4 +25,4 @@ fun mainNoExit(args: Array) { workerMain { // autoreleasepool to make sure interop objects are properly freed testLauncherEntryPoint(args) } -} \ No newline at end of file +} diff --git a/kotlinx-coroutines-core/nativeDarwin/test/MainDispatcherTest.kt b/kotlinx-coroutines-core/nativeDarwin/test/MainDispatcherTest.kt index d460bd6e6e..9904f06c5f 100644 --- a/kotlinx-coroutines-core/nativeDarwin/test/MainDispatcherTest.kt +++ b/kotlinx-coroutines-core/nativeDarwin/test/MainDispatcherTest.kt @@ -4,7 +4,6 @@ package kotlinx.coroutines -import kotlinx.coroutines.internal.* import platform.CoreFoundation.* import platform.darwin.* import kotlin.coroutines.* @@ -13,7 +12,7 @@ import kotlin.test.* class MainDispatcherTest : TestBase() { private fun isMainThread(): Boolean = CFRunLoopGetCurrent() == CFRunLoopGetMain() - private fun canTestMainDispatcher() = !isMainThread() && multithreadingSupported + private fun canTestMainDispatcher() = !isMainThread() private fun runTestNotOnMainDispatcher(block: suspend CoroutineScope.() -> Unit) { // skip if already on the main thread, run blocking doesn't really work well with that diff --git a/kotlinx-coroutines-core/nativeOther/test/Launcher.kt b/kotlinx-coroutines-core/nativeOther/test/Launcher.kt index feddd4c097..58dbefcd04 100644 --- a/kotlinx-coroutines-core/nativeOther/test/Launcher.kt +++ b/kotlinx-coroutines-core/nativeOther/test/Launcher.kt @@ -11,7 +11,7 @@ import kotlin.system.* // This is a separate entry point for tests in background fun mainBackground(args: Array) { val worker = Worker.start(name = "main-background") - worker.execute(TransferMode.SAFE, { args.freeze() }) { + worker.execute(TransferMode.SAFE, { args }) { val result = testLauncherEntryPoint(it) exitProcess(result) }.result // block main thread @@ -20,4 +20,4 @@ fun mainBackground(args: Array) { // This is a separate entry point for tests with leak checker fun mainNoExit(args: Array) { testLauncherEntryPoint(args) -} \ No newline at end of file +} diff --git a/kotlinx-coroutines-test/build.gradle.kts b/kotlinx-coroutines-test/build.gradle.kts index 7b244bb091..98d3e9fa74 100644 --- a/kotlinx-coroutines-test/build.gradle.kts +++ b/kotlinx-coroutines-test/build.gradle.kts @@ -1,3 +1,5 @@ +import org.jetbrains.kotlin.gradle.plugin.mpp.* + /* * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ @@ -10,4 +12,11 @@ val experimentalAnnotations = listOf( kotlin { sourceSets.all { configureMultiplatform() } + + targets.withType(KotlinNativeTargetWithTests::class.java).configureEach { + binaries.getTest("DEBUG").apply { + optimized = true + binaryOptions["memoryModel"] = "experimental" + } + } } diff --git a/kotlinx-coroutines-test/common/test/RunTestTest.kt b/kotlinx-coroutines-test/common/test/RunTestTest.kt index 1430d830cc..dda89e3a31 100644 --- a/kotlinx-coroutines-test/common/test/RunTestTest.kt +++ b/kotlinx-coroutines-test/common/test/RunTestTest.kt @@ -71,7 +71,6 @@ class RunTestTest { /** Tests that a dispatch timeout of `0` will fail the test if there are some dispatches outside the scheduler. */ @Test - @NoNative // TODO: timeout leads to `Cannot execute task because event loop was shut down` on Native fun testRunTestWithZeroTimeoutWithUncontrolledDispatches() = testResultMap({ fn -> assertFailsWith { fn() } }) { @@ -86,7 +85,6 @@ class RunTestTest { /** Tests that too low of a dispatch timeout causes crashes. */ @Test - @NoNative // TODO: timeout leads to `Cannot execute task because event loop was shut down` on Native fun testRunTestWithSmallTimeout() = testResultMap({ fn -> assertFailsWith { fn() } }) { @@ -112,7 +110,7 @@ class RunTestTest { it() fail("unreached") } catch (e: UncompletedCoroutinesError) { - assertTrue((e.message ?: "").contains(name1)) + assertContains(e.message ?: "", name1) assertFalse((e.message ?: "").contains(name2)) } }) { @@ -162,7 +160,6 @@ class RunTestTest { /** Tests uncaught exceptions being suppressed by the dispatch timeout error. */ @Test - @NoNative // TODO: timeout leads to `Cannot execute task because event loop was shut down` on Native fun testRunTestTimingOutAndThrowing() = testResultMap({ fn -> try { fn() diff --git a/kotlinx-coroutines-test/common/test/StandardTestDispatcherTest.kt b/kotlinx-coroutines-test/common/test/StandardTestDispatcherTest.kt index d66be9bdb6..9e9c93e10e 100644 --- a/kotlinx-coroutines-test/common/test/StandardTestDispatcherTest.kt +++ b/kotlinx-coroutines-test/common/test/StandardTestDispatcherTest.kt @@ -64,7 +64,6 @@ class StandardTestDispatcherTest: OrderedExecutionTestBase() { /** Tests that the [TestCoroutineScheduler] used for [Dispatchers.Main] gets used by default. */ @Test - @NoNative fun testSchedulerReuse() { val dispatcher1 = StandardTestDispatcher() Dispatchers.setMain(dispatcher1) @@ -76,4 +75,4 @@ class StandardTestDispatcherTest: OrderedExecutionTestBase() { } } -} \ No newline at end of file +} diff --git a/kotlinx-coroutines-test/common/test/TestDispatchersTest.kt b/kotlinx-coroutines-test/common/test/TestDispatchersTest.kt index bcf016b3d3..da48e7f47a 100644 --- a/kotlinx-coroutines-test/common/test/TestDispatchersTest.kt +++ b/kotlinx-coroutines-test/common/test/TestDispatchersTest.kt @@ -8,7 +8,6 @@ import kotlinx.coroutines.test.internal.* import kotlin.coroutines.* import kotlin.test.* -@NoNative class TestDispatchersTest: OrderedExecutionTestBase() { @BeforeTest diff --git a/kotlinx-coroutines-test/common/test/TestScopeTest.kt b/kotlinx-coroutines-test/common/test/TestScopeTest.kt index 4138ca058f..45f7f3ef80 100644 --- a/kotlinx-coroutines-test/common/test/TestScopeTest.kt +++ b/kotlinx-coroutines-test/common/test/TestScopeTest.kt @@ -54,7 +54,6 @@ class TestScopeTest { /** Part of [testCreateProvidesScheduler], disabled for Native */ @Test - @NoNative fun testCreateReusesScheduler() { // Reuses the scheduler of `Dispatchers.Main` run { diff --git a/kotlinx-coroutines-test/common/test/UnconfinedTestDispatcherTest.kt b/kotlinx-coroutines-test/common/test/UnconfinedTestDispatcherTest.kt index ee63e6d118..23ff0ac16b 100644 --- a/kotlinx-coroutines-test/common/test/UnconfinedTestDispatcherTest.kt +++ b/kotlinx-coroutines-test/common/test/UnconfinedTestDispatcherTest.kt @@ -153,7 +153,6 @@ class UnconfinedTestDispatcherTest { /** Tests that the [TestCoroutineScheduler] used for [Dispatchers.Main] gets used by default. */ @Test - @NoNative fun testSchedulerReuse() { val dispatcher1 = StandardTestDispatcher() Dispatchers.setMain(dispatcher1) @@ -165,4 +164,4 @@ class UnconfinedTestDispatcherTest { } } -} \ No newline at end of file +} diff --git a/kotlinx-coroutines-test/native/test/FailingTests.kt b/kotlinx-coroutines-test/native/test/FailingTests.kt deleted file mode 100644 index 9fb77ce7c8..0000000000 --- a/kotlinx-coroutines-test/native/test/FailingTests.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines.test - -import kotlinx.coroutines.* -import kotlin.test.* - -/** These are tests that we want to fail. They are here so that, when the issue is fixed, their failure indicates that - * everything is better now. */ -class FailingTests { - @Test - fun testRunTestLoopShutdownOnTimeout() = testResultMap({ fn -> - assertFailsWith { fn() } - }) { - runTest(dispatchTimeoutMs = 1) { - withContext(Dispatchers.Default) { - delay(10000) - } - fail("shouldn't be reached") - } - } - -} \ No newline at end of file From 3120530d5a1fad6d39b0436a9fe98ed79bf531b5 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 29 Jul 2022 21:48:04 +0200 Subject: [PATCH 006/106] Introduce Job.parent API (#3384) * Introduce Job.parent API * The API is crucial for debugger extension and deadlock detection * It enables allows more fluent coroutines hierarchy inspection, e.g. capability to build a list of roots and procrss them top-bottom separately Fixes #3201 --- .../api/kotlinx-coroutines-core.api | 3 +++ kotlinx-coroutines-core/common/src/Job.kt | 18 +++++++++++++++++- .../common/src/JobSupport.kt | 3 +++ .../common/src/NonCancellable.kt | 8 ++++++++ .../common/src/internal/Scopes.kt | 1 - .../common/test/JobStatesTest.kt | 7 ++++++- kotlinx-coroutines-core/common/test/JobTest.kt | 5 ++++- 7 files changed, 41 insertions(+), 4 deletions(-) diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index 01f236fef6..457ede648f 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -360,6 +360,7 @@ public abstract interface class kotlinx/coroutines/Job : kotlin/coroutines/Corou public abstract fun getCancellationException ()Ljava/util/concurrent/CancellationException; public abstract fun getChildren ()Lkotlin/sequences/Sequence; public abstract fun getOnJoin ()Lkotlinx/coroutines/selects/SelectClause0; + public abstract fun getParent ()Lkotlinx/coroutines/Job; public abstract fun invokeOnCompletion (Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle; public abstract fun invokeOnCompletion (ZZLkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle; public abstract fun isActive ()Z @@ -443,6 +444,7 @@ public class kotlinx/coroutines/JobSupport : kotlinx/coroutines/ChildJob, kotlin public final fun getCompletionExceptionOrNull ()Ljava/lang/Throwable; public final fun getKey ()Lkotlin/coroutines/CoroutineContext$Key; public final fun getOnJoin ()Lkotlinx/coroutines/selects/SelectClause0; + public fun getParent ()Lkotlinx/coroutines/Job; protected fun handleJobException (Ljava/lang/Throwable;)Z protected final fun initParentJob (Lkotlinx/coroutines/Job;)V public final fun invokeOnCompletion (Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle; @@ -485,6 +487,7 @@ public final class kotlinx/coroutines/NonCancellable : kotlin/coroutines/Abstrac public fun getCancellationException ()Ljava/util/concurrent/CancellationException; public fun getChildren ()Lkotlin/sequences/Sequence; public fun getOnJoin ()Lkotlinx/coroutines/selects/SelectClause0; + public fun getParent ()Lkotlinx/coroutines/Job; public fun invokeOnCompletion (Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle; public fun invokeOnCompletion (ZZLkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/DisposableHandle; public fun isActive ()Z diff --git a/kotlinx-coroutines-core/common/src/Job.kt b/kotlinx-coroutines-core/common/src/Job.kt index 31d90eeef0..81fe978b19 100644 --- a/kotlinx-coroutines-core/common/src/Job.kt +++ b/kotlinx-coroutines-core/common/src/Job.kt @@ -31,7 +31,7 @@ import kotlin.jvm.* * It is completed by calling [CompletableJob.complete]. * * Conceptually, an execution of a job does not produce a result value. Jobs are launched solely for their - * side-effects. See [Deferred] interface for a job that produces a result. + * side effects. See [Deferred] interface for a job that produces a result. * * ### Job states * @@ -117,6 +117,22 @@ public interface Job : CoroutineContext.Element { // ------------ state query ------------ + /** + * Returns the parent of the current job if the parent-child relationship + * is established or `null` if the job has no parent or was successfully completed. + * + * Accesses to this property are not idempotent, the property becomes `null` as soon + * as the job is transitioned to its final state, whether it is cancelled or completed, + * and all job children are completed. + * + * For a coroutine, its corresponding job completes as soon as the coroutine itself + * and all its children are complete. + * + * @see [Job] state transitions for additional details. + */ + @ExperimentalCoroutinesApi + public val parent: Job? + /** * Returns `true` when this job is active -- it was already started and has not completed nor was cancelled yet. * The job that is waiting for its [children] to complete is still considered to be active if it diff --git a/kotlinx-coroutines-core/common/src/JobSupport.kt b/kotlinx-coroutines-core/common/src/JobSupport.kt index f7adba3c9e..428e861839 100644 --- a/kotlinx-coroutines-core/common/src/JobSupport.kt +++ b/kotlinx-coroutines-core/common/src/JobSupport.kt @@ -132,6 +132,9 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren get() = _parentHandle.value set(value) { _parentHandle.value = value } + override val parent: Job? + get() = parentHandle?.parent + // ------------ initialization ------------ /** diff --git a/kotlinx-coroutines-core/common/src/NonCancellable.kt b/kotlinx-coroutines-core/common/src/NonCancellable.kt index c278109224..9fb72dddbc 100644 --- a/kotlinx-coroutines-core/common/src/NonCancellable.kt +++ b/kotlinx-coroutines-core/common/src/NonCancellable.kt @@ -29,6 +29,14 @@ public object NonCancellable : AbstractCoroutineContextElement(Job), Job { private const val message = "NonCancellable can be used only as an argument for 'withContext', direct usages of its API are prohibited" + /** + * Always returns `null`. + * @suppress **This an internal API and should not be used from general code.** + */ + @Deprecated(level = DeprecationLevel.WARNING, message = message) + override val parent: Job? + get() = null + /** * Always returns `true`. * @suppress **This an internal API and should not be used from general code.** diff --git a/kotlinx-coroutines-core/common/src/internal/Scopes.kt b/kotlinx-coroutines-core/common/src/internal/Scopes.kt index ad8d86ed5a..e0a183028d 100644 --- a/kotlinx-coroutines-core/common/src/internal/Scopes.kt +++ b/kotlinx-coroutines-core/common/src/internal/Scopes.kt @@ -21,7 +21,6 @@ internal open class ScopeCoroutine( final override fun getStackTraceElement(): StackTraceElement? = null final override val isScopedCoroutine: Boolean get() = true - internal val parent: Job? get() = parentHandle?.parent override fun afterCompletion(state: Any?) { // Resume in a cancellable way by default when resuming from another context diff --git a/kotlinx-coroutines-core/common/test/JobStatesTest.kt b/kotlinx-coroutines-core/common/test/JobStatesTest.kt index dfcb462cba..739401f6a0 100644 --- a/kotlinx-coroutines-core/common/test/JobStatesTest.kt +++ b/kotlinx-coroutines-core/common/test/JobStatesTest.kt @@ -16,6 +16,7 @@ class JobStatesTest : TestBase() { @Test public fun testNormalCompletion() = runTest { expect(1) + val parent = coroutineContext.job val job = launch(start = CoroutineStart.LAZY) { expect(2) // launches child @@ -28,23 +29,27 @@ class JobStatesTest : TestBase() { assertFalse(job.isActive) assertFalse(job.isCompleted) assertFalse(job.isCancelled) + assertSame(parent, job.parent) // New -> Active job.start() assertTrue(job.isActive) assertFalse(job.isCompleted) assertFalse(job.isCancelled) + assertSame(parent, job.parent) // Active -> Completing yield() // scheduled & starts child expect(3) assertTrue(job.isActive) assertFalse(job.isCompleted) assertFalse(job.isCancelled) + assertSame(parent, job.parent) // Completing -> Completed yield() finish(5) assertFalse(job.isActive) assertTrue(job.isCompleted) assertFalse(job.isCancelled) + assertNull(job.parent) } @Test @@ -159,4 +164,4 @@ class JobStatesTest : TestBase() { assertTrue(job.isCompleted) assertTrue(job.isCancelled) } -} \ No newline at end of file +} diff --git a/kotlinx-coroutines-core/common/test/JobTest.kt b/kotlinx-coroutines-core/common/test/JobTest.kt index 04d3c9e012..f009c7e55d 100644 --- a/kotlinx-coroutines-core/common/test/JobTest.kt +++ b/kotlinx-coroutines-core/common/test/JobTest.kt @@ -12,6 +12,7 @@ class JobTest : TestBase() { @Test fun testState() { val job = Job() + assertNull(job.parent) assertTrue(job.isActive) job.cancel() assertTrue(!job.isActive) @@ -210,11 +211,13 @@ class JobTest : TestBase() { @Test fun testIncompleteJobState() = runTest { + val parent = coroutineContext.job val job = launch { coroutineContext[Job]!!.invokeOnCompletion { } } - + assertSame(parent, job.parent) job.join() + assertNull(job.parent) assertTrue(job.isCompleted) assertFalse(job.isActive) assertFalse(job.isCancelled) From 371e4f3fe7d39dac30a87b20f837f9d7a8fa1fbc Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 2 Aug 2022 12:10:04 +0200 Subject: [PATCH 007/106] Introduce a rendezvous on CancellableContinuationImpl.parentHandle to avoid race with original thread setting parent handle, while another thread nulls it out during cancellation via detachChild (#3380) Fixes one of the races found in #3378 --- .../common/src/CancellableContinuationImpl.kt | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt index e32c10c408..ef49da23df 100644 --- a/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt +++ b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt @@ -70,7 +70,28 @@ internal open class CancellableContinuationImpl( */ private val _state = atomic(Active) - private var parentHandle: DisposableHandle? = null + /* + * This field has a concurrent rendezvous in the following scenario: + * + * - installParentHandle publishes this instance on T1 + * + * T1 writes: + * * handle = installed; right after the installation + * * Shortly after: if (isComplete) handle = NonDisposableHandle + * + * Any other T writes if the parent job is cancelled in detachChild: + * * handle = NonDisposableHandle + * + * We want to preserve a strict invariant on parentHandle transition, allowing only three of them: + * null -> anyHandle + * anyHandle -> NonDisposableHandle + * null -> NonDisposableHandle + * + * With a guarantee that after disposal the only state handle may end up in is NonDisposableHandle + */ + private val _parentHandle = atomic(null) + private val parentHandle: DisposableHandle? + get() = _parentHandle.value internal val state: Any? get() = _state.value @@ -101,7 +122,7 @@ internal open class CancellableContinuationImpl( if (isCompleted) { // Can be invoked concurrently in 'parentCancelled', no problems here handle.dispose() - parentHandle = NonDisposableHandle + _parentHandle.value = NonDisposableHandle } } @@ -307,7 +328,7 @@ internal open class CancellableContinuationImpl( onCancelling = true, handler = ChildContinuation(this).asHandler ) - parentHandle = handle + _parentHandle.compareAndSet(null, handle) return handle } @@ -492,7 +513,7 @@ internal open class CancellableContinuationImpl( internal fun detachChild() { val handle = parentHandle ?: return handle.dispose() - parentHandle = NonDisposableHandle + _parentHandle.value = NonDisposableHandle } // Note: Always returns RESUME_TOKEN | null From 521ee780dc08540e43b7df486c5e772fc8ec8632 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 4 Aug 2022 12:42:44 +0200 Subject: [PATCH 008/106] Update Kover to 0.6.0-Beta (#3386) --- .../main/kotlin/kover-conventions.gradle.kts | 61 ++++++++++--------- gradle.properties | 2 +- .../build.gradle.kts | 12 ++-- .../build.gradle.kts | 12 ++-- .../build.gradle.kts | 2 +- 5 files changed, 45 insertions(+), 44 deletions(-) diff --git a/buildSrc/src/main/kotlin/kover-conventions.gradle.kts b/buildSrc/src/main/kotlin/kover-conventions.gradle.kts index 052e2bb684..e9490f1ee0 100644 --- a/buildSrc/src/main/kotlin/kover-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/kover-conventions.gradle.kts @@ -4,7 +4,6 @@ import kotlinx.kover.tasks.* /* * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -apply(plugin = "kover") val notCovered = sourceless + internal + unpublished @@ -14,41 +13,43 @@ val expectedCoverage = mutableMapOf( "kotlinx-coroutines-javafx" to 39, // JavaFx is not tested on TC because its graphic subsystem cannot be initialized in headless mode // Reactor has lower coverage in general due to various fatal error handling features - "kotlinx-coroutines-reactor" to 75) - -extensions.configure { - disabledProjects = notCovered - /* - * Is explicitly enabled on TC in a separate build step. - * Examples: - * ./gradlew :p:check -- doesn't verify coverage - * ./gradlew :p:check -Pkover.enabled=true -- verifies coverage - * ./gradlew :p:koverReport -Pkover.enabled=true -- generates report - */ - isDisabled = !(properties["kover.enabled"]?.toString()?.toBoolean() ?: false) - // TODO remove when updating Kover to version 0.5.x - intellijEngineVersion.set("1.0.657") -} + "kotlinx-coroutines-reactor" to 75 +) + + subprojects { val projectName = name if (projectName in notCovered) return@subprojects - tasks.withType { - rule { - bound { - /* - * 85 is our baseline that we aim to raise to 90+. - * Missing coverage is typically due to bugs in the agent - * (e.g. signatures deprecated with an error are counted), - * sometimes it's various diagnostic `toString` or `catch` for OOMs/VerificationErrors, - * but some places are definitely worth visiting. - */ - minValue = expectedCoverage[projectName] ?: 85 // COVERED_LINES_PERCENTAGE + apply(plugin = "kover") + + extensions.configure { + /* + * Is explicitly enabled on TC in a separate build step. + * Examples: + * ./gradlew :p:check -- doesn't verify coverage + * ./gradlew :p:check -Pkover.enabled=true -- verifies coverage + * ./gradlew :p:koverReport -Pkover.enabled=true -- generates report + */ + isDisabled.set(!(properties["kover.enabled"]?.toString()?.toBoolean() ?: false)) + + verify { + rule { + bound { + /* + * 85 is our baseline that we aim to raise to 90+. + * Missing coverage is typically due to bugs in the agent + * (e.g. signatures deprecated with an error are counted), + * sometimes it's various diagnostic `toString` or `catch` for OOMs/VerificationErrors, + * but some places are definitely worth visiting. + */ + minValue = expectedCoverage[projectName] ?: 85 // COVERED_LINES_PERCENTAGE + } } } - } - tasks.withType { - htmlReportDir.set(file(rootProject.buildDir.toString() + "/kover/" + project.name + "/html")) + htmlReport { + reportDir.set(file(rootProject.buildDir.toString() + "/kover/" + project.name + "/html")) + } } } diff --git a/gradle.properties b/gradle.properties index 308588ff72..755517a7ef 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,7 +23,7 @@ rxjava3_version=3.0.2 javafx_version=11.0.2 javafx_plugin_version=0.0.8 binary_compatibility_validator_version=0.11.0 -kover_version=0.5.0 +kover_version=0.6.0-Beta blockhound_version=1.0.2.RELEASE jna_version=5.9.0 diff --git a/reactive/kotlinx-coroutines-reactive/build.gradle.kts b/reactive/kotlinx-coroutines-reactive/build.gradle.kts index c2e4b5c9f0..3f2ba6b829 100644 --- a/reactive/kotlinx-coroutines-reactive/build.gradle.kts +++ b/reactive/kotlinx-coroutines-reactive/build.gradle.kts @@ -41,10 +41,10 @@ val commonKoverExcludes = listOf( "kotlinx.coroutines.reactive.ConvertKt" // Deprecated ) -tasks.koverHtmlReport { - excludes = commonKoverExcludes -} - -tasks.koverVerify { - excludes = commonKoverExcludes +kover { + filters { + classes { + excludes += commonKoverExcludes + } + } } diff --git a/reactive/kotlinx-coroutines-reactor/build.gradle.kts b/reactive/kotlinx-coroutines-reactor/build.gradle.kts index d4bb135f73..a90b3cbd3c 100644 --- a/reactive/kotlinx-coroutines-reactor/build.gradle.kts +++ b/reactive/kotlinx-coroutines-reactor/build.gradle.kts @@ -33,10 +33,10 @@ val commonKoverExcludes = listOf( "kotlinx.coroutines.reactor.ConvertKt\$asFlux$1" // Deprecated ) -tasks.koverHtmlReport { - excludes = commonKoverExcludes -} - -tasks.koverVerify { - excludes = commonKoverExcludes +kover { + filters { + classes { + excludes += commonKoverExcludes + } + } } diff --git a/ui/kotlinx-coroutines-android/build.gradle.kts b/ui/kotlinx-coroutines-android/build.gradle.kts index 7618c529f7..b5c9c0cf5d 100644 --- a/ui/kotlinx-coroutines-android/build.gradle.kts +++ b/ui/kotlinx-coroutines-android/build.gradle.kts @@ -115,6 +115,6 @@ open class RunR8 : JavaExec() { tasks.withType { extensions.configure { - excludes = excludes + listOf("com.android.*", "android.*") // Exclude robolectric-generated classes + excludes.addAll(listOf("com.android.*", "android.*")) // Exclude robolectric-generated classes } } From 9e886f1452d24d39f7699b1be24b1b8e2f4df960 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 4 Aug 2022 12:55:50 +0200 Subject: [PATCH 009/106] Update select-guide timings to be precise (#3387) Fixes #3385 --- docs/topics/select-expression.md | 6 +++--- kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt | 4 ++-- .../jvm/test/guide/test/SelectGuideTest.kt | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/topics/select-expression.md b/docs/topics/select-expression.md index f3055737c8..8c4866ba89 100644 --- a/docs/topics/select-expression.md +++ b/docs/topics/select-expression.md @@ -63,14 +63,14 @@ import kotlinx.coroutines.selects.* fun CoroutineScope.fizz() = produce { while (true) { // sends "Fizz" every 300 ms - delay(300) + delay(500) send("Fizz") } } fun CoroutineScope.buzz() = produce { while (true) { // sends "Buzz!" every 500 ms - delay(500) + delay(1000) send("Buzz!") } } @@ -112,7 +112,7 @@ fizz -> 'Fizz' fizz -> 'Fizz' buzz -> 'Buzz!' fizz -> 'Fizz' -buzz -> 'Buzz!' +fizz -> 'Fizz' ``` diff --git a/kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt b/kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt index c1a962e60d..e4f9d31250 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt @@ -11,14 +11,14 @@ import kotlinx.coroutines.selects.* fun CoroutineScope.fizz() = produce { while (true) { // sends "Fizz" every 300 ms - delay(300) + delay(500) send("Fizz") } } fun CoroutineScope.buzz() = produce { while (true) { // sends "Buzz!" every 500 ms - delay(500) + delay(1000) send("Buzz!") } } diff --git a/kotlinx-coroutines-core/jvm/test/guide/test/SelectGuideTest.kt b/kotlinx-coroutines-core/jvm/test/guide/test/SelectGuideTest.kt index 55650d4c6a..8bc81913d7 100644 --- a/kotlinx-coroutines-core/jvm/test/guide/test/SelectGuideTest.kt +++ b/kotlinx-coroutines-core/jvm/test/guide/test/SelectGuideTest.kt @@ -18,7 +18,7 @@ class SelectGuideTest { "fizz -> 'Fizz'", "buzz -> 'Buzz!'", "fizz -> 'Fizz'", - "buzz -> 'Buzz!'" + "fizz -> 'Fizz'" ) } From feea6a56296153bed8047c593d8cdcbf64ca24c0 Mon Sep 17 00:00:00 2001 From: Nikita Koval Date: Thu, 4 Aug 2022 14:50:43 +0200 Subject: [PATCH 010/106] New `select` and `Mutex` implementations (#3020) * New selects are twice as fast as the previous version * The implementation doesn't require an NCAS protocol by giving up lock-freedom and linearizability in corner cases * The overall algorithm is much more straightforward and easier to maintain Co-authored-by: Nikita Koval --- .../benchmarks/tailcall/SelectBenchmark.kt | 42 + gradle.properties | 2 +- .../api/kotlinx-coroutines-core.api | 89 +- .../common/src/Builders.common.kt | 6 +- .../common/src/CompletableDeferred.kt | 6 +- .../common/src/JobSupport.kt | 125 +- .../common/src/channels/AbstractChannel.kt | 507 ++++--- .../src/channels/ArrayBroadcastChannel.kt | 57 +- .../common/src/channels/ArrayChannel.kt | 115 +- .../common/src/channels/Channel.kt | 13 +- .../src/channels/ConflatedBroadcastChannel.kt | 30 +- .../common/src/channels/ConflatedChannel.kt | 49 +- .../common/src/channels/LinkedListChannel.kt | 18 - .../src/internal/LockFreeLinkedList.common.kt | 1 + .../common/src/selects/OnTimeout.kt | 65 + .../common/src/selects/Select.kt | 1190 ++++++++++------- .../common/src/selects/SelectOld.kt | 147 ++ .../common/src/selects/SelectUnbiased.kt | 73 +- .../common/src/sync/Mutex.kt | 394 ++---- .../common/src/sync/Semaphore.kt | 181 ++- .../common/test/channels/TestChannelKind.kt | 2 + .../test/selects/SelectArrayChannelTest.kt | 6 +- .../common/test/selects/SelectOldTest.kt | 124 ++ .../selects/SelectRendezvousChannelTest.kt | 7 +- .../common/test/sync/MutexTest.kt | 31 + .../test/selects/SelectChannelStressTest.kt | 6 +- .../test/selects/SelectMutexStressTest.kt | 3 +- .../js/src/internal/LinkedList.kt | 8 +- .../jvm/src/channels/Actor.kt | 17 +- .../stacktraces/select/testSelectJoin.txt | 3 +- .../select/testSelectOnReceive.txt | 32 + .../jvm/test/MutexCancellationStressTest.kt | 76 ++ ...elUndeliveredElementSelectOldStressTest.kt | 254 ++++ .../ChannelUndeliveredElementStressTest.kt | 24 +- .../StackTraceRecoverySelectTest.kt | 20 +- .../jvm/test/lincheck/ChannelsLincheckTest.kt | 6 +- .../jvm/test/lincheck/MutexLincheckTest.kt | 26 +- .../test/lincheck/SemaphoreLincheckTest.kt | 4 +- .../test/SanitizedProbesTest.kt | 1 - .../api/kotlinx-coroutines-reactive.api | 3 +- .../src/Publish.kt | 66 +- .../src/RxObservable.kt | 70 +- .../src/RxObservable.kt | 70 +- 43 files changed, 2334 insertions(+), 1635 deletions(-) create mode 100644 benchmarks/src/jmh/kotlin/benchmarks/tailcall/SelectBenchmark.kt create mode 100644 kotlinx-coroutines-core/common/src/selects/OnTimeout.kt create mode 100644 kotlinx-coroutines-core/common/src/selects/SelectOld.kt create mode 100644 kotlinx-coroutines-core/common/test/selects/SelectOldTest.kt create mode 100644 kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectOnReceive.txt create mode 100644 kotlinx-coroutines-core/jvm/test/MutexCancellationStressTest.kt create mode 100644 kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementSelectOldStressTest.kt diff --git a/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SelectBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SelectBenchmark.kt new file mode 100644 index 0000000000..cb4d39eed6 --- /dev/null +++ b/benchmarks/src/jmh/kotlin/benchmarks/tailcall/SelectBenchmark.kt @@ -0,0 +1,42 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package benchmarks.tailcall + +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.* +import kotlinx.coroutines.selects.* +import org.openjdk.jmh.annotations.* +import java.util.concurrent.* + +@Warmup(iterations = 8, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 8, time = 1, timeUnit = TimeUnit.SECONDS) +@Fork(value = 1) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@State(Scope.Benchmark) +open class SelectBenchmark { + // 450 + private val iterations = 1000 + + @Benchmark + fun stressSelect() = runBlocking { + val pingPong = Channel() + launch { + repeat(iterations) { + select { + pingPong.onSend(Unit) {} + } + } + } + + launch { + repeat(iterations) { + select { + pingPong.onReceive() {} + } + } + } + } +} diff --git a/gradle.properties b/gradle.properties index 755517a7ef..c5234eaa08 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,7 @@ junit5_version=5.7.0 atomicfu_version=0.18.2 knit_version=0.4.0 html_version=0.7.2 -lincheck_version=2.14 +lincheck_version=2.14.1 dokka_version=1.6.21 byte_buddy_version=1.10.9 reactor_version=3.4.1 diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index 457ede648f..3dbd211de1 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -423,10 +423,11 @@ public final class kotlinx/coroutines/JobKt { public static final fun isActive (Lkotlin/coroutines/CoroutineContext;)Z } -public class kotlinx/coroutines/JobSupport : kotlinx/coroutines/ChildJob, kotlinx/coroutines/Job, kotlinx/coroutines/ParentJob, kotlinx/coroutines/selects/SelectClause0 { +public class kotlinx/coroutines/JobSupport : kotlinx/coroutines/ChildJob, kotlinx/coroutines/Job, kotlinx/coroutines/ParentJob { public fun (Z)V protected fun afterCompletion (Ljava/lang/Object;)V public final fun attachChild (Lkotlinx/coroutines/ChildJob;)Lkotlinx/coroutines/ChildHandle; + protected final fun awaitInternal (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public synthetic fun cancel ()V public synthetic fun cancel (Ljava/lang/Throwable;)Z public fun cancel (Ljava/util/concurrent/CancellationException;)V @@ -443,6 +444,7 @@ public class kotlinx/coroutines/JobSupport : kotlinx/coroutines/ChildJob, kotlin protected final fun getCompletionCauseHandled ()Z public final fun getCompletionExceptionOrNull ()Ljava/lang/Throwable; public final fun getKey ()Lkotlin/coroutines/CoroutineContext$Key; + protected final fun getOnAwaitInternal ()Lkotlinx/coroutines/selects/SelectClause1; public final fun getOnJoin ()Lkotlinx/coroutines/selects/SelectClause0; public fun getParent ()Lkotlinx/coroutines/Job; protected fun handleJobException (Ljava/lang/Throwable;)Z @@ -462,7 +464,6 @@ public class kotlinx/coroutines/JobSupport : kotlinx/coroutines/ChildJob, kotlin public final fun parentCancelled (Lkotlinx/coroutines/ParentJob;)V public fun plus (Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext; public fun plus (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job; - public final fun registerSelectClause0 (Lkotlinx/coroutines/selects/SelectInstance;Lkotlin/jvm/functions/Function1;)V public final fun start ()Z protected final fun toCancellationException (Ljava/lang/Throwable;Ljava/lang/String;)Ljava/util/concurrent/CancellationException; public static synthetic fun toCancellationException$default (Lkotlinx/coroutines/JobSupport;Ljava/lang/Throwable;Ljava/lang/String;ILjava/lang/Object;)Ljava/util/concurrent/CancellationException; @@ -1194,6 +1195,11 @@ public class kotlinx/coroutines/scheduling/ExperimentalCoroutineDispatcher : kot public fun toString ()Ljava/lang/String; } +public final class kotlinx/coroutines/selects/OnTimeoutKt { + public static final fun onTimeout (Lkotlinx/coroutines/selects/SelectBuilder;JLkotlin/jvm/functions/Function1;)V + public static final fun onTimeout-8Mi8wO0 (Lkotlinx/coroutines/selects/SelectBuilder;JLkotlin/jvm/functions/Function1;)V +} + public abstract interface class kotlinx/coroutines/selects/SelectBuilder { public abstract fun invoke (Lkotlinx/coroutines/selects/SelectClause0;Lkotlin/jvm/functions/Function1;)V public abstract fun invoke (Lkotlinx/coroutines/selects/SelectClause1;Lkotlin/jvm/functions/Function2;)V @@ -1204,71 +1210,80 @@ public abstract interface class kotlinx/coroutines/selects/SelectBuilder { public final class kotlinx/coroutines/selects/SelectBuilder$DefaultImpls { public static fun invoke (Lkotlinx/coroutines/selects/SelectBuilder;Lkotlinx/coroutines/selects/SelectClause2;Lkotlin/jvm/functions/Function2;)V + public static fun onTimeout (Lkotlinx/coroutines/selects/SelectBuilder;JLkotlin/jvm/functions/Function1;)V } -public final class kotlinx/coroutines/selects/SelectBuilderImpl : kotlinx/coroutines/internal/LockFreeLinkedListHead, kotlin/coroutines/Continuation, kotlin/coroutines/jvm/internal/CoroutineStackFrame, kotlinx/coroutines/selects/SelectBuilder, kotlinx/coroutines/selects/SelectInstance { +public final class kotlinx/coroutines/selects/SelectBuilderImpl : kotlinx/coroutines/selects/SelectImplementation { public fun (Lkotlin/coroutines/Continuation;)V - public fun disposeOnSelect (Lkotlinx/coroutines/DisposableHandle;)V - public fun getCallerFrame ()Lkotlin/coroutines/jvm/internal/CoroutineStackFrame; - public fun getCompletion ()Lkotlin/coroutines/Continuation; - public fun getContext ()Lkotlin/coroutines/CoroutineContext; public final fun getResult ()Ljava/lang/Object; - public fun getStackTraceElement ()Ljava/lang/StackTraceElement; public final fun handleBuilderException (Ljava/lang/Throwable;)V - public fun invoke (Lkotlinx/coroutines/selects/SelectClause0;Lkotlin/jvm/functions/Function1;)V - public fun invoke (Lkotlinx/coroutines/selects/SelectClause1;Lkotlin/jvm/functions/Function2;)V - public fun invoke (Lkotlinx/coroutines/selects/SelectClause2;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V - public fun invoke (Lkotlinx/coroutines/selects/SelectClause2;Lkotlin/jvm/functions/Function2;)V - public fun isSelected ()Z - public fun onTimeout (JLkotlin/jvm/functions/Function1;)V - public fun performAtomicTrySelect (Lkotlinx/coroutines/internal/AtomicDesc;)Ljava/lang/Object; - public fun resumeSelectWithException (Ljava/lang/Throwable;)V - public fun resumeWith (Ljava/lang/Object;)V - public fun toString ()Ljava/lang/String; - public fun trySelect ()Z - public fun trySelectOther (Lkotlinx/coroutines/internal/LockFreeLinkedListNode$PrepareOp;)Ljava/lang/Object; } -public abstract interface class kotlinx/coroutines/selects/SelectClause0 { - public abstract fun registerSelectClause0 (Lkotlinx/coroutines/selects/SelectInstance;Lkotlin/jvm/functions/Function1;)V +public abstract interface class kotlinx/coroutines/selects/SelectClause { + public abstract fun getClauseObject ()Ljava/lang/Object; + public abstract fun getOnCancellationConstructor ()Lkotlin/jvm/functions/Function3; + public abstract fun getProcessResFunc ()Lkotlin/jvm/functions/Function3; + public abstract fun getRegFunc ()Lkotlin/jvm/functions/Function3; +} + +public abstract interface class kotlinx/coroutines/selects/SelectClause0 : kotlinx/coroutines/selects/SelectClause { } -public abstract interface class kotlinx/coroutines/selects/SelectClause1 { - public abstract fun registerSelectClause1 (Lkotlinx/coroutines/selects/SelectInstance;Lkotlin/jvm/functions/Function2;)V +public abstract interface class kotlinx/coroutines/selects/SelectClause1 : kotlinx/coroutines/selects/SelectClause { } -public abstract interface class kotlinx/coroutines/selects/SelectClause2 { - public abstract fun registerSelectClause2 (Lkotlinx/coroutines/selects/SelectInstance;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V +public abstract interface class kotlinx/coroutines/selects/SelectClause2 : kotlinx/coroutines/selects/SelectClause { +} + +public class kotlinx/coroutines/selects/SelectImplementation : kotlinx/coroutines/selects/SelectBuilder, kotlinx/coroutines/selects/SelectInstanceInternal { + public fun (Lkotlin/coroutines/CoroutineContext;)V + public fun disposeOnCompletion (Lkotlinx/coroutines/DisposableHandle;)V + public fun doSelect (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public fun getContext ()Lkotlin/coroutines/CoroutineContext; + public synthetic fun invoke (Ljava/lang/Object;)Ljava/lang/Object; + public fun invoke (Ljava/lang/Throwable;)V + public fun invoke (Lkotlinx/coroutines/selects/SelectClause0;Lkotlin/jvm/functions/Function1;)V + public fun invoke (Lkotlinx/coroutines/selects/SelectClause1;Lkotlin/jvm/functions/Function2;)V + public fun invoke (Lkotlinx/coroutines/selects/SelectClause2;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V + public fun invoke (Lkotlinx/coroutines/selects/SelectClause2;Lkotlin/jvm/functions/Function2;)V + public fun onTimeout (JLkotlin/jvm/functions/Function1;)V + public fun selectInRegistrationPhase (Ljava/lang/Object;)V + public fun trySelect (Ljava/lang/Object;Ljava/lang/Object;)Z + public final fun trySelectDetailed (Ljava/lang/Object;Ljava/lang/Object;)Lkotlinx/coroutines/selects/TrySelectDetailedResult; } public abstract interface class kotlinx/coroutines/selects/SelectInstance { - public abstract fun disposeOnSelect (Lkotlinx/coroutines/DisposableHandle;)V - public abstract fun getCompletion ()Lkotlin/coroutines/Continuation; - public abstract fun isSelected ()Z - public abstract fun performAtomicTrySelect (Lkotlinx/coroutines/internal/AtomicDesc;)Ljava/lang/Object; - public abstract fun resumeSelectWithException (Ljava/lang/Throwable;)V - public abstract fun trySelect ()Z - public abstract fun trySelectOther (Lkotlinx/coroutines/internal/LockFreeLinkedListNode$PrepareOp;)Ljava/lang/Object; + public abstract fun disposeOnCompletion (Lkotlinx/coroutines/DisposableHandle;)V + public abstract fun getContext ()Lkotlin/coroutines/CoroutineContext; + public abstract fun selectInRegistrationPhase (Ljava/lang/Object;)V + public abstract fun trySelect (Ljava/lang/Object;Ljava/lang/Object;)Z } public final class kotlinx/coroutines/selects/SelectKt { - public static final fun onTimeout-8Mi8wO0 (Lkotlinx/coroutines/selects/SelectBuilder;JLkotlin/jvm/functions/Function1;)V public static final fun select (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class kotlinx/coroutines/selects/SelectOldKt { + public static final fun selectOld (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun selectUnbiasedOld (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + public final class kotlinx/coroutines/selects/SelectUnbiasedKt { public static final fun selectUnbiased (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } -public final class kotlinx/coroutines/selects/UnbiasedSelectBuilderImpl : kotlinx/coroutines/selects/SelectBuilder { +public final class kotlinx/coroutines/selects/UnbiasedSelectBuilderImpl : kotlinx/coroutines/selects/UnbiasedSelectImplementation { public fun (Lkotlin/coroutines/Continuation;)V public final fun handleBuilderException (Ljava/lang/Throwable;)V public final fun initSelectResult ()Ljava/lang/Object; +} + +public class kotlinx/coroutines/selects/UnbiasedSelectImplementation : kotlinx/coroutines/selects/SelectImplementation { + public fun (Lkotlin/coroutines/CoroutineContext;)V + public fun doSelect (Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun invoke (Lkotlinx/coroutines/selects/SelectClause0;Lkotlin/jvm/functions/Function1;)V public fun invoke (Lkotlinx/coroutines/selects/SelectClause1;Lkotlin/jvm/functions/Function2;)V public fun invoke (Lkotlinx/coroutines/selects/SelectClause2;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V - public fun invoke (Lkotlinx/coroutines/selects/SelectClause2;Lkotlin/jvm/functions/Function2;)V - public fun onTimeout (JLkotlin/jvm/functions/Function1;)V } public final class kotlinx/coroutines/selects/WhileSelectKt { diff --git a/kotlinx-coroutines-core/common/src/Builders.common.kt b/kotlinx-coroutines-core/common/src/Builders.common.kt index 3dea68cfde..1219b9d859 100644 --- a/kotlinx-coroutines-core/common/src/Builders.common.kt +++ b/kotlinx-coroutines-core/common/src/Builders.common.kt @@ -96,12 +96,10 @@ public fun CoroutineScope.async( private open class DeferredCoroutine( parentContext: CoroutineContext, active: Boolean -) : AbstractCoroutine(parentContext, true, active = active), Deferred, SelectClause1 { +) : AbstractCoroutine(parentContext, true, active = active), Deferred { override fun getCompleted(): T = getCompletedInternal() as T override suspend fun await(): T = awaitInternal() as T - override val onAwait: SelectClause1 get() = this - override fun registerSelectClause1(select: SelectInstance, block: suspend (T) -> R) = - registerSelectClause1Internal(select, block) + override val onAwait: SelectClause1 get() = onAwaitInternal as SelectClause1 } private class LazyDeferredCoroutine( diff --git a/kotlinx-coroutines-core/common/src/CompletableDeferred.kt b/kotlinx-coroutines-core/common/src/CompletableDeferred.kt index 5e76593df2..293c516721 100644 --- a/kotlinx-coroutines-core/common/src/CompletableDeferred.kt +++ b/kotlinx-coroutines-core/common/src/CompletableDeferred.kt @@ -79,14 +79,12 @@ public fun CompletableDeferred(value: T): CompletableDeferred = Completab @Suppress("UNCHECKED_CAST") private class CompletableDeferredImpl( parent: Job? -) : JobSupport(true), CompletableDeferred, SelectClause1 { +) : JobSupport(true), CompletableDeferred { init { initParentJob(parent) } override val onCancelComplete get() = true override fun getCompleted(): T = getCompletedInternal() as T override suspend fun await(): T = awaitInternal() as T - override val onAwait: SelectClause1 get() = this - override fun registerSelectClause1(select: SelectInstance, block: suspend (T) -> R) = - registerSelectClause1Internal(select, block) + override val onAwait: SelectClause1 get() = onAwaitInternal as SelectClause1 override fun complete(value: T): Boolean = makeCompleting(value) diff --git a/kotlinx-coroutines-core/common/src/JobSupport.kt b/kotlinx-coroutines-core/common/src/JobSupport.kt index 428e861839..2950ed9814 100644 --- a/kotlinx-coroutines-core/common/src/JobSupport.kt +++ b/kotlinx-coroutines-core/common/src/JobSupport.kt @@ -7,7 +7,6 @@ package kotlinx.coroutines import kotlinx.atomicfu.* import kotlinx.coroutines.internal.* -import kotlinx.coroutines.intrinsics.* import kotlinx.coroutines.selects.* import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* @@ -24,7 +23,7 @@ import kotlin.jvm.* * @suppress **This is unstable API and it is subject to change.** */ @Deprecated(level = DeprecationLevel.ERROR, message = "This is internal API and may be removed in the future releases") -public open class JobSupport constructor(active: Boolean) : Job, ChildJob, ParentJob, SelectClause0 { +public open class JobSupport constructor(active: Boolean) : Job, ChildJob, ParentJob { final override val key: CoroutineContext.Key<*> get() = Job /* @@ -561,26 +560,28 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren cont.disposeOnCancellation(invokeOnCompletion(handler = ResumeOnCompletion(cont).asHandler)) } + @Suppress("UNCHECKED_CAST") public final override val onJoin: SelectClause0 - get() = this + get() = SelectClause0Impl( + clauseObject = this@JobSupport, + regFunc = JobSupport::registerSelectForOnJoin as RegistrationFunction + ) - // registerSelectJoin - public final override fun registerSelectClause0(select: SelectInstance, block: suspend () -> R) { - // fast-path -- check state and select/return if needed - loopOnState { state -> - if (select.isSelected) return - if (state !is Incomplete) { - // already complete -- select result - if (select.trySelect()) { - block.startCoroutineUnintercepted(select.completion) - } - return - } - if (startInternal(state) == 0) { - // slow-path -- register waiter for completion - select.disposeOnSelect(invokeOnCompletion(handler = SelectJoinOnCompletion(select, block).asHandler)) - return - } + @Suppress("UNUSED_PARAMETER") + private fun registerSelectForOnJoin(select: SelectInstance<*>, ignoredParam: Any?) { + if (!joinInternal()) { + select.selectInRegistrationPhase(Unit) + return + } + val disposableHandle = invokeOnCompletion(SelectOnJoinCompletionHandler(select).asHandler) + select.disposeOnCompletion(disposableHandle) + } + + private inner class SelectOnJoinCompletionHandler( + private val select: SelectInstance<*> + ) : JobNode() { + override fun invoke(cause: Throwable?) { + select.trySelect(this@JobSupport, Unit) } } @@ -1206,7 +1207,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren /** * @suppress **This is unstable API and it is subject to change.** */ - internal suspend fun awaitInternal(): Any? { + protected suspend fun awaitInternal(): Any? { // fast-path -- check state (avoid extra object creation) while (true) { // lock-free loop on state val state = this.state @@ -1236,46 +1237,42 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren cont.getResult() } - /** - * @suppress **This is unstable API and it is subject to change.** - */ - // registerSelectAwaitInternal @Suppress("UNCHECKED_CAST") - internal fun registerSelectClause1Internal(select: SelectInstance, block: suspend (T) -> R) { - // fast-path -- check state and select/return if needed - loopOnState { state -> - if (select.isSelected) return + protected val onAwaitInternal: SelectClause1<*> get() = SelectClause1Impl( + clauseObject = this@JobSupport, + regFunc = JobSupport::onAwaitInternalRegFunc as RegistrationFunction, + processResFunc = JobSupport::onAwaitInternalProcessResFunc as ProcessResultFunction + ) + + @Suppress("UNUSED_PARAMETER") + private fun onAwaitInternalRegFunc(select: SelectInstance<*>, ignoredParam: Any?) { + while (true) { + val state = this.state if (state !is Incomplete) { - // already complete -- select result - if (select.trySelect()) { - if (state is CompletedExceptionally) { - select.resumeSelectWithException(state.cause) - } - else { - block.startCoroutineUnintercepted(state.unboxState() as T, select.completion) - } - } - return - } - if (startInternal(state) == 0) { - // slow-path -- register waiter for completion - select.disposeOnSelect(invokeOnCompletion(handler = SelectAwaitOnCompletion(select, block).asHandler)) + val result = if (state is CompletedExceptionally) state else state.unboxState() + select.selectInRegistrationPhase(result) return } + if (startInternal(state) >= 0) break // break unless needs to retry } + val disposableHandle = invokeOnCompletion(SelectOnAwaitCompletionHandler(select).asHandler) + select.disposeOnCompletion(disposableHandle) } - /** - * @suppress **This is unstable API and it is subject to change.** - */ - @Suppress("UNCHECKED_CAST") - internal fun selectAwaitCompletion(select: SelectInstance, block: suspend (T) -> R) { - val state = this.state - // Note: await is non-atomic (can be cancelled while dispatched) - if (state is CompletedExceptionally) - select.resumeSelectWithException(state.cause) - else - block.startCoroutineCancellable(state.unboxState() as T, select.completion) + @Suppress("UNUSED_PARAMETER") + private fun onAwaitInternalProcessResFunc(ignoredParam: Any?, result: Any?): Any? { + if (result is CompletedExceptionally) throw result.cause + return result + } + + private inner class SelectOnAwaitCompletionHandler( + private val select: SelectInstance<*> + ) : JobNode() { + override fun invoke(cause: Throwable?) { + val state = this@JobSupport.state + val result = if (state is CompletedExceptionally) state else state.unboxState() + select.trySelect(this@JobSupport, result) + } } } @@ -1416,26 +1413,6 @@ internal class DisposeOnCompletion( override fun invoke(cause: Throwable?) = handle.dispose() } -private class SelectJoinOnCompletion( - private val select: SelectInstance, - private val block: suspend () -> R -) : JobNode() { - override fun invoke(cause: Throwable?) { - if (select.trySelect()) - block.startCoroutineCancellable(select.completion) - } -} - -private class SelectAwaitOnCompletion( - private val select: SelectInstance, - private val block: suspend (T) -> R -) : JobNode() { - override fun invoke(cause: Throwable?) { - if (select.trySelect()) - job.selectAwaitCompletion(select, block) - } -} - // -------- invokeOnCancellation nodes /** diff --git a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt index 149ef88601..0fe07488ed 100644 --- a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.* import kotlinx.coroutines.internal.* import kotlinx.coroutines.intrinsics.* import kotlinx.coroutines.selects.* +import kotlinx.coroutines.selects.TrySelectDetailedResult.* import kotlin.coroutines.* import kotlin.jvm.* @@ -49,7 +50,7 @@ internal abstract class AbstractSendChannel( protected open fun offerInternal(element: E): Any { while (true) { val receive = takeFirstReceiveOrPeekClosed() ?: return OFFER_FAILED - val token = receive.tryResumeReceive(element, null) + val token = receive.tryResumeReceive(element) if (token != null) { assert { token === RESUME_TOKEN } receive.completeResumeReceive(element) @@ -58,20 +59,6 @@ internal abstract class AbstractSendChannel( } } - /** - * Tries to add element to buffer or to queued receiver if select statement clause was not selected yet. - * Return type is `ALREADY_SELECTED | OFFER_SUCCESS | OFFER_FAILED | RETRY_ATOMIC | Closed`. - * @suppress **This is unstable API and it is subject to change.** - */ - protected open fun offerSelectInternal(element: E, select: SelectInstance<*>): Any { - // offer atomically with select - val offerOp = describeTryOffer(element) - val failure = select.performAtomicTrySelect(offerOp) - if (failure != null) return failure - val receive = offerOp.result - receive.completeResumeReceive(element) - return receive.offerResult - } // ------ state functions & helpers for concrete implementations ------ @@ -187,20 +174,39 @@ internal abstract class AbstractSendChannel( } private suspend fun sendSuspend(element: E): Unit = suspendCancellableCoroutineReusable sc@ { cont -> + sendSlowPath(element, cont) + } + + // waiter is either CancellableContinuation or SelectInstance + private fun sendSlowPath(element: E, waiter: Any) { loop@ while (true) { if (isFullImpl) { - val send = if (onUndeliveredElement == null) - SendElement(element, cont) else - SendElementWithUndeliveredHandler(element, cont, onUndeliveredElement) + val send: Send = if (waiter is SelectInstance<*>) { + if (onUndeliveredElement == null) SendElementSelect(element, waiter, this) + else SendElementSelectWithUndeliveredHandler(element, waiter, this, onUndeliveredElement) + } else { + if (onUndeliveredElement == null) SendElement(element, waiter as CancellableContinuation) + else SendElementWithUndeliveredHandler(element, waiter as CancellableContinuation, onUndeliveredElement) + } val enqueueResult = enqueueSend(send) when { enqueueResult == null -> { // enqueued successfully - cont.removeOnCancellation(send) - return@sc + if (waiter is SelectInstance<*>) { + waiter.disposeOnCompletion { send.remove() } + } else { + waiter as CancellableContinuation + waiter.removeOnCancellation(send) + } + return } enqueueResult is Closed<*> -> { - cont.helpCloseAndResumeWithSendException(element, enqueueResult) - return@sc + if (waiter is SelectInstance<*>) { + waiter.helpCloseAndSelectInRegistrationPhaseClosed(element, enqueueResult) + } else { + waiter as CancellableContinuation + waiter.helpCloseAndResumeWithSendException(element, enqueueResult) + } + return } enqueueResult === ENQUEUE_FAILED -> {} // try to offer instead enqueueResult is Receive<*> -> {} // try to offer instead @@ -211,13 +217,23 @@ internal abstract class AbstractSendChannel( val offerResult = offerInternal(element) when { offerResult === OFFER_SUCCESS -> { - cont.resume(Unit) - return@sc + if (waiter is SelectInstance<*>) { + waiter.selectInRegistrationPhase(Unit) + } else { + waiter as CancellableContinuation + waiter.resume(Unit) + } + return } offerResult === OFFER_FAILED -> continue@loop offerResult is Closed<*> -> { - cont.helpCloseAndResumeWithSendException(element, offerResult) - return@sc + if (waiter is SelectInstance<*>) { + waiter.helpCloseAndSelectInRegistrationPhaseClosed(element, offerResult) + } else { + waiter as CancellableContinuation + waiter.helpCloseAndResumeWithSendException(element, offerResult) + } + return } else -> error("offerInternal returned $offerResult") } @@ -235,6 +251,16 @@ internal abstract class AbstractSendChannel( resumeWithException(sendException) } + private fun SelectInstance<*>.helpCloseAndSelectInRegistrationPhaseClosed(element: E, closed: Closed<*>) { + helpClose(closed) + onUndeliveredElement?.callUndeliveredElementCatchingException(element)?.let { + it.addSuppressed(closed.sendException) + selectInRegistrationPhase(Closed(it)) + return + } + selectInRegistrationPhase(closed) + } + /** * Result is: * * null -- successfully enqueued @@ -356,75 +382,25 @@ internal abstract class AbstractSendChannel( protected open fun takeFirstReceiveOrPeekClosed(): ReceiveOrClosed? = queue.removeFirstIfIsInstanceOfOrPeekIf>({ it is Closed<*> }) - // ------ registerSelectSend ------ - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - protected fun describeTryOffer(element: E): TryOfferDesc = TryOfferDesc(element, queue) - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - protected class TryOfferDesc( - @JvmField val element: E, - queue: LockFreeLinkedListHead - ) : RemoveFirstDesc>(queue) { - override fun failure(affected: LockFreeLinkedListNode): Any? = when (affected) { - is Closed<*> -> affected - !is ReceiveOrClosed<*> -> OFFER_FAILED - else -> null - } - - @Suppress("UNCHECKED_CAST") - override fun onPrepare(prepareOp: PrepareOp): Any? { - val affected = prepareOp.affected as ReceiveOrClosed // see "failure" impl - val token = affected.tryResumeReceive(element, prepareOp) ?: return REMOVE_PREPARED - if (token === RETRY_ATOMIC) return RETRY_ATOMIC - assert { token === RESUME_TOKEN } - return null + final override val onSend + get() = SelectClause2Impl>( + clauseObject = this, + regFunc = AbstractSendChannel<*>::registerSelectForSend as RegistrationFunction, + processResFunc = AbstractSendChannel<*>::processResultSelectSend as ProcessResultFunction + ) + + private fun registerSelectForSend(select: SelectInstance<*>, element: Any?) { + element as E + if (offerInternal(element) === OFFER_SUCCESS) { + select.selectInRegistrationPhase(Unit) + return } + sendSlowPath(element, select) } - final override val onSend: SelectClause2> - get() = object : SelectClause2> { - override fun registerSelectClause2(select: SelectInstance, param: E, block: suspend (SendChannel) -> R) { - registerSelectSend(select, param, block) - } - } - - private fun registerSelectSend(select: SelectInstance, element: E, block: suspend (SendChannel) -> R) { - while (true) { - if (select.isSelected) return - if (isFullImpl) { - val node = SendSelect(element, this, select, block) - val enqueueResult = enqueueSend(node) - when { - enqueueResult == null -> { // enqueued successfully - select.disposeOnSelect(node) - return - } - enqueueResult is Closed<*> -> throw recoverStackTrace(helpCloseAndGetSendException(element, enqueueResult)) - enqueueResult === ENQUEUE_FAILED -> {} // try to offer - enqueueResult is Receive<*> -> {} // try to offer - else -> error("enqueueSend returned $enqueueResult ") - } - } - // hm... receiver is waiting or buffer is not full. try to offer - val offerResult = offerSelectInternal(element, select) - when { - offerResult === ALREADY_SELECTED -> return - offerResult === OFFER_FAILED -> {} // retry - offerResult === RETRY_ATOMIC -> {} // retry - offerResult === OFFER_SUCCESS -> { - block.startCoroutineUnintercepted(receiver = this, completion = select.completion) - return - } - offerResult is Closed<*> -> throw recoverStackTrace(helpCloseAndGetSendException(element, offerResult)) - else -> error("offerSelectInternal returned $offerResult") - } - } - } + private fun processResultSelectSend(ignoredParam: Any?, selectResult: Any?): Any? = + if (selectResult is Closed<*>) throw selectResult.sendException + else this // ------ debug ------ @@ -459,37 +435,6 @@ internal abstract class AbstractSendChannel( // ------ private ------ - private class SendSelect( - override val pollResult: E, // E | Closed - the result pollInternal returns when it rendezvous with this node - @JvmField val channel: AbstractSendChannel, - @JvmField val select: SelectInstance, - @JvmField val block: suspend (SendChannel) -> R - ) : Send(), DisposableHandle { - override fun tryResumeSend(otherOp: PrepareOp?): Symbol? = - select.trySelectOther(otherOp) as Symbol? // must return symbol - - override fun completeResumeSend() { - block.startCoroutineCancellable(receiver = channel, completion = select.completion) - } - - override fun dispose() { // invoked on select completion - if (!remove()) return - // if the node was successfully removed (meaning it was added but was not received) then element not delivered - undeliveredElement() - } - - override fun resumeSendClosed(closed: Closed<*>) { - if (select.trySelect()) - select.resumeSelectWithException(closed.sendException) - } - - override fun undeliveredElement() { - channel.onUndeliveredElement?.callUndeliveredElement(pollResult, select.completion.context) - } - - override fun toString(): String = "SendSelect@$hexAddress($pollResult)[$channel, $select]" - } - internal class SendBuffered( @JvmField val element: E ) : Send() { @@ -545,26 +490,14 @@ internal abstract class AbstractChannel( send.completeResumeSend() return send.pollResult } - // too late, already cancelled, but we removed it from the queue and need to notify on undelivered element - send.undeliveredElement() + // Too late, already cancelled, but we removed it from the queue and need to notify on undelivered element. + // The only exception is when this "send" operation is an `onSend` clause that has to be re-registered + // in the corresponding `select` invocation. + if (!(send is SendElementSelectWithUndeliveredHandler<*> && send.trySelectResult == REREGISTER)) + send.undeliveredElement() } } - /** - * Tries to remove element from buffer or from queued sender if select statement clause was not selected yet. - * Return type is `ALREADY_SELECTED | E | POLL_FAILED | RETRY_ATOMIC | Closed` - * @suppress **This is unstable API and it is subject to change.** - */ - protected open fun pollSelectInternal(select: SelectInstance<*>): Any? { - // poll atomically with select - val pollOp = describeTryPoll() - val failure = select.performAtomicTrySelect(pollOp) - if (failure != null) return failure - val send = pollOp.result - send.completeResumeSend() - return pollOp.result.pollResult - } - // ------ state functions & helpers for concrete implementations ------ /** @@ -594,23 +527,48 @@ internal abstract class AbstractChannel( @Suppress("UNCHECKED_CAST") private suspend fun receiveSuspend(receiveMode: Int): R = suspendCancellableCoroutineReusable sc@ { cont -> - val receive = if (onUndeliveredElement == null) - ReceiveElement(cont as CancellableContinuation, receiveMode) else - ReceiveElementWithUndeliveredHandler(cont as CancellableContinuation, receiveMode, onUndeliveredElement) + receiveSlowPath(receiveMode, cont) + } + + @Suppress("UNCHECKED_CAST") + private fun receiveSlowPath(receiveMode: Int, waiter: Any) { + val receive = if (waiter is SelectInstance<*>) { + if (onUndeliveredElement == null) ReceiveElementSelect(waiter) + else ReceiveElementSelectWithUndeliveredHandler(waiter, onUndeliveredElement) + } else { + waiter as CancellableContinuation + if (onUndeliveredElement == null) ReceiveElement(waiter, receiveMode) + else ReceiveElementWithUndeliveredHandler(waiter, receiveMode, onUndeliveredElement) + } while (true) { if (enqueueReceive(receive)) { - removeReceiveOnCancel(cont, receive) - return@sc + if (waiter is SelectInstance<*>) { + waiter.disposeOnCompletion(RemoveReceiveOnCancel(receive)) + } else { + waiter as CancellableContinuation + removeReceiveOnCancel(waiter, receive) + } + return } // hm... something is not right. try to poll val result = pollInternal() if (result is Closed<*>) { - receive.resumeReceiveClosed(result) - return@sc + if (waiter is SelectInstance<*>) { + waiter.selectInRegistrationPhase(result) + } else { // CancellableContinuation + receive.resumeReceiveClosed(result) + } + return } if (result !== POLL_FAILED) { - cont.resume(receive.resumeValue(result as E), receive.resumeOnCancellationFun(result as E)) - return@sc + if (waiter is SelectInstance<*>) { + waiter.selectInRegistrationPhase(result as E) + } else { + waiter as CancellableContinuation + receive as ReceiveElement + waiter.resume(receive.resumeValue(result as E), receive.resumeOnCancellationFun(result as E)) + } + return } } } @@ -729,71 +687,57 @@ internal abstract class AbstractChannel( } } - final override val onReceive: SelectClause1 - get() = object : SelectClause1 { - @Suppress("UNCHECKED_CAST") - override fun registerSelectClause1(select: SelectInstance, block: suspend (E) -> R) { - registerSelectReceiveMode(select, RECEIVE_THROWS_ON_CLOSE, block as suspend (Any?) -> R) - } + // ------ the `select` expression support ------ + + final override val onReceive + get() = SelectClause1Impl( + clauseObject = this, + regFunc = AbstractChannel<*>::registerSelectForReceive as RegistrationFunction, + processResFunc = AbstractChannel<*>::processResultSelectReceive as ProcessResultFunction, + onCancellationConstructor = onUndeliveredElementReceiveCancellationConstructor + ) + + final override val onReceiveCatching + get() = SelectClause1Impl>( + clauseObject = this, + regFunc = AbstractChannel<*>::registerSelectForReceive as RegistrationFunction, + processResFunc = AbstractChannel<*>::processResultSelectReceiveCatching as ProcessResultFunction, + onCancellationConstructor = onUndeliveredElementReceiveCancellationConstructor + ) + + override val onReceiveOrNull: SelectClause1 + get() = SelectClause1Impl( + clauseObject = this, + regFunc = AbstractChannel<*>::registerSelectForReceive as RegistrationFunction, + processResFunc = AbstractChannel<*>::processResultSelectReceiveOrNull as ProcessResultFunction, + onCancellationConstructor = onUndeliveredElementReceiveCancellationConstructor + ) + + private fun registerSelectForReceive(select: SelectInstance<*>, ignoredParam: Any?) { + val result = pollInternal() + if (result !== POLL_FAILED && result !is Closed<*>) { + select.selectInRegistrationPhase(result as E) + return } + receiveSlowPath(RECEIVE_RESULT, select) + } - final override val onReceiveCatching: SelectClause1> - get() = object : SelectClause1> { - @Suppress("UNCHECKED_CAST") - override fun registerSelectClause1(select: SelectInstance, block: suspend (ChannelResult) -> R) { - registerSelectReceiveMode(select, RECEIVE_RESULT, block as suspend (Any?) -> R) - } - } + private fun processResultSelectReceive(ignoredParam: Any?, selectResult: Any?): Any? = + if (selectResult is Closed<*>) throw selectResult.receiveException + else selectResult as E - private fun registerSelectReceiveMode(select: SelectInstance, receiveMode: Int, block: suspend (Any?) -> R) { - while (true) { - if (select.isSelected) return - if (isEmptyImpl) { - if (enqueueReceiveSelect(select, block, receiveMode)) return - } else { - val pollResult = pollSelectInternal(select) - when { - pollResult === ALREADY_SELECTED -> return - pollResult === POLL_FAILED -> {} // retry - pollResult === RETRY_ATOMIC -> {} // retry - else -> block.tryStartBlockUnintercepted(select, receiveMode, pollResult) - } - } - } - } + private fun processResultSelectReceiveCatching(ignoredParam: Any?, selectResult: Any?): Any? = + if (selectResult is Closed<*>) ChannelResult.closed(selectResult.closeCause) + else ChannelResult.success(selectResult as E) - private fun (suspend (Any?) -> R).tryStartBlockUnintercepted(select: SelectInstance, receiveMode: Int, value: Any?) { - when (value) { - is Closed<*> -> { - when (receiveMode) { - RECEIVE_THROWS_ON_CLOSE -> { - throw recoverStackTrace(value.receiveException) - } - RECEIVE_RESULT -> { - if (!select.trySelect()) return - startCoroutineUnintercepted(ChannelResult.closed(value.closeCause), select.completion) - } - } - } - else -> { - if (receiveMode == RECEIVE_RESULT) { - startCoroutineUnintercepted(value.toResult(), select.completion) - } else { - startCoroutineUnintercepted(value, select.completion) - } - } - } - } + private fun processResultSelectReceiveOrNull(ignoredParam: Any?, selectResult: Any?): Any? = + if (selectResult is Closed<*>) null + else selectResult as E - private fun enqueueReceiveSelect( - select: SelectInstance, - block: suspend (Any?) -> R, - receiveMode: Int - ): Boolean { - val node = ReceiveSelect(this, select, block, receiveMode) - val result = enqueueReceive(node) - if (result) select.disposeOnSelect(node) - return result + private val onUndeliveredElementReceiveCancellationConstructor: OnCancellationConstructor? = onUndeliveredElement?.let { + { select: SelectInstance<*>, ignoredParam: Any?, element: Any? -> + { cause: Throwable -> if (element !is Closed<*>) onUndeliveredElement.callUndeliveredElement(element as E, select.context) } + } } // ------ protected ------ @@ -820,11 +764,14 @@ internal abstract class AbstractChannel( private fun removeReceiveOnCancel(cont: CancellableContinuation<*>, receive: Receive<*>) = cont.invokeOnCancellation(handler = RemoveReceiveOnCancel(receive).asHandler) - private inner class RemoveReceiveOnCancel(private val receive: Receive<*>) : BeforeResumeCancelHandler() { - override fun invoke(cause: Throwable?) { + private inner class RemoveReceiveOnCancel(private val receive: Receive<*>) : BeforeResumeCancelHandler(), DisposableHandle { + override fun invoke(cause: Throwable?) = dispose() + + override fun dispose() { if (receive.remove()) onReceiveDequeued() } + override fun toString(): String = "RemoveReceiveOnCancel[$receive]" } @@ -896,19 +843,17 @@ internal abstract class AbstractChannel( else -> value } - override fun tryResumeReceive(value: E, otherOp: PrepareOp?): Symbol? { - val token = cont.tryResume(resumeValue(value), otherOp?.desc, resumeOnCancellationFun(value)) ?: return null + override fun tryResumeReceive(value: E): Symbol? { + val token = cont.tryResume(resumeValue(value), null, resumeOnCancellationFun(value)) ?: return null assert { token === RESUME_TOKEN } // the only other possible result - // We can call finishPrepare only after successful tryResume, so that only good affected node is saved - otherOp?.finishPrepare() return RESUME_TOKEN } override fun completeResumeReceive(value: E) = cont.completeResume(RESUME_TOKEN) override fun resumeReceiveClosed(closed: Closed<*>) { - when { - receiveMode == RECEIVE_RESULT -> cont.resume(closed.toResult()) + when (receiveMode) { + RECEIVE_RESULT -> cont.resume(closed.toResult()) else -> cont.resumeWithException(closed.receiveException) } } @@ -924,24 +869,44 @@ internal abstract class AbstractChannel( onUndeliveredElement.bindCancellationFun(value, cont.context) } + internal open inner class ReceiveElementSelect( + @JvmField val select: SelectInstance<*>, + ) : Receive() { + private val lock = ReentrantLock() + private var success: Boolean? = null + + override fun tryResumeReceive(value: E): Symbol? = lock.withLock { + if (success == null) success = select.trySelect(this@AbstractChannel, value) + if (success!!) RESUME_TOKEN else null + } + override fun completeResumeReceive(value: E) {} + + override fun resumeReceiveClosed(closed: Closed<*>) { + select.trySelect(this@AbstractChannel, closed) + } + override fun toString(): String = "ReceiveElementSelect@$hexAddress" + } + + private open inner class ReceiveElementSelectWithUndeliveredHandler( + select: SelectInstance<*>, + @JvmField val onUndeliveredElement: OnUndeliveredElement + ) : ReceiveElementSelect(select) { + override fun resumeOnCancellationFun(value: E): ((Throwable) -> Unit)? = + onUndeliveredElement.bindCancellationFun(value, select.context) + } + private open class ReceiveHasNext( @JvmField val iterator: Itr, @JvmField val cont: CancellableContinuation ) : Receive() { - override fun tryResumeReceive(value: E, otherOp: PrepareOp?): Symbol? { - val token = cont.tryResume(true, otherOp?.desc, resumeOnCancellationFun(value)) + override fun tryResumeReceive(value: E): Symbol? { + val token = cont.tryResume(true, null, resumeOnCancellationFun(value)) ?: return null assert { token === RESUME_TOKEN } // the only other possible result - // We can call finishPrepare only after successful tryResume, so that only good affected node is saved - otherOp?.finishPrepare() return RESUME_TOKEN } override fun completeResumeReceive(value: E) { - /* - When otherOp != null invocation of tryResumeReceive can happen multiple times and much later, - but completeResumeReceive is called once so we set iterator result here. - */ iterator.result = value cont.completeResume(RESUME_TOKEN) } @@ -963,43 +928,6 @@ internal abstract class AbstractChannel( override fun toString(): String = "ReceiveHasNext@$hexAddress" } - - private class ReceiveSelect( - @JvmField val channel: AbstractChannel, - @JvmField val select: SelectInstance, - @JvmField val block: suspend (Any?) -> R, - @JvmField val receiveMode: Int - ) : Receive(), DisposableHandle { - override fun tryResumeReceive(value: E, otherOp: PrepareOp?): Symbol? = - select.trySelectOther(otherOp) as Symbol? - - @Suppress("UNCHECKED_CAST") - override fun completeResumeReceive(value: E) { - block.startCoroutineCancellable( - if (receiveMode == RECEIVE_RESULT) ChannelResult.success(value) else value, - select.completion, - resumeOnCancellationFun(value) - ) - } - - override fun resumeReceiveClosed(closed: Closed<*>) { - if (!select.trySelect()) return - when (receiveMode) { - RECEIVE_THROWS_ON_CLOSE -> select.resumeSelectWithException(closed.receiveException) - RECEIVE_RESULT -> block.startCoroutineCancellable(ChannelResult.closed(closed.closeCause), select.completion) - } - } - - override fun dispose() { // invoked on select completion - if (remove()) - channel.onReceiveDequeued() // notify cancellation of receive - } - - override fun resumeOnCancellationFun(value: E): ((Throwable) -> Unit)? = - channel.onUndeliveredElement?.bindCancellationFun(value, select.completion.context) - - override fun toString(): String = "ReceiveSelect@$hexAddress[$select,receiveMode=$receiveMode]" - } } // receiveMode values @@ -1029,7 +957,8 @@ internal typealias Handler = (Throwable?) -> Unit /** * Represents sending waiter in the queue. */ -internal abstract class Send : LockFreeLinkedListNode() { +internal abstract class +Send : LockFreeLinkedListNode() { abstract val pollResult: Any? // E | Closed - the result pollInternal returns when it rendezvous with this node // Returns: null - failure, // RETRY_ATOMIC for retry (only when otherOp != null), @@ -1047,10 +976,8 @@ internal abstract class Send : LockFreeLinkedListNode() { internal interface ReceiveOrClosed { val offerResult: Any // OFFER_SUCCESS | Closed // Returns: null - failure, - // RETRY_ATOMIC for retry (only when otherOp != null), // RESUME_TOKEN on success (call completeResumeReceive) - // Must call otherOp?.finishPrepare() after deciding on result other than RETRY_ATOMIC - fun tryResumeReceive(value: E, otherOp: PrepareOp?): Symbol? + fun tryResumeReceive(value: E): Symbol? fun completeResumeReceive(value: E) } @@ -1074,6 +1001,35 @@ internal open class SendElement( override fun toString(): String = "$classSimpleName@$hexAddress($pollResult)" } +internal open class SendElementSelect( + override val pollResult: E, + @JvmField val select: SelectInstance<*>, + private val channel: SendChannel +) : Send() { + private val lock = ReentrantLock() + private var _trySelectResult: TrySelectDetailedResult? = null + + override fun tryResumeSend(otherOp: PrepareOp?): Symbol? = lock.withLock { + select as SelectImplementation<*> + if (_trySelectResult == null) { + _trySelectResult = select.trySelectDetailed(channel, Unit) + } + return@withLock if (_trySelectResult == SUCCESSFUL) { + otherOp?.finishPrepare() // finish preparations + RESUME_TOKEN + } else null + } + + val trySelectResult: TrySelectDetailedResult + get() = checkNotNull(_trySelectResult) { "trySelect(..) has not been invoked yet" } + + override fun completeResumeSend() {} + override fun resumeSendClosed(closed: Closed<*>) { + select.trySelect(channel, closed) + } + override fun toString(): String = "$classSimpleName@$hexAddress($pollResult)" +} + internal class SendElementWithUndeliveredHandler( pollResult: E, cont: CancellableContinuation, @@ -1091,6 +1047,23 @@ internal class SendElementWithUndeliveredHandler( } } +internal class SendElementSelectWithUndeliveredHandler( + pollResult: E, + select: SelectInstance<*>, + channel: SendChannel, + onUndeliveredElement: OnUndeliveredElement +) : SendElementSelect(pollResult, select, channel) { + private val onUndeliveredElement = atomic?>(onUndeliveredElement) + + override fun remove(): Boolean { + undeliveredElement() + return super.remove() + } + + override fun undeliveredElement() { + onUndeliveredElement.getAndSet(null)?.callUndeliveredElement(pollResult, select.context) + } +} /** * Represents closed channel. */ @@ -1104,7 +1077,7 @@ internal class Closed( override val pollResult get() = this override fun tryResumeSend(otherOp: PrepareOp?): Symbol = RESUME_TOKEN.also { otherOp?.finishPrepare() } override fun completeResumeSend() {} - override fun tryResumeReceive(value: E, otherOp: PrepareOp?): Symbol = RESUME_TOKEN.also { otherOp?.finishPrepare() } + override fun tryResumeReceive(value: E): Symbol = RESUME_TOKEN override fun completeResumeReceive(value: E) {} override fun resumeSendClosed(closed: Closed<*>) = assert { false } // "Should be never invoked" override fun toString(): String = "Closed@$hexAddress[$closeCause]" diff --git a/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt index 0a96f75380..a9aaf08a53 100644 --- a/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt @@ -112,27 +112,6 @@ internal class ArrayBroadcastChannel( return OFFER_SUCCESS } - // result is `ALREADY_SELECTED | OFFER_SUCCESS | OFFER_FAILED | Closed` - override fun offerSelectInternal(element: E, select: SelectInstance<*>): Any { - bufferLock.withLock { - // check if closed for send (under lock, so size cannot change) - closedForSend?.let { return it } - val size = this.size - if (size >= capacity) return OFFER_FAILED - // let's try to select sending this element to buffer - if (!select.trySelect()) { // :todo: move trySelect completion outside of lock - return ALREADY_SELECTED - } - val tail = this.tail - buffer[(tail % capacity).toInt()] = element - this.size = size + 1 - this.tail = tail + 1 - } - // if offered successfully, then check subscribers outside of lock - checkSubOffers() - return OFFER_SUCCESS - } - private fun checkSubOffers() { var updated = false var hasSubs = false @@ -262,7 +241,7 @@ internal class ArrayBroadcastChannel( // find a receiver for an element receive = takeFirstReceiveOrPeekClosed() ?: break // break when no one's receiving if (receive is Closed<*>) break // noting more to do if this sub already closed - val token = receive.tryResumeReceive(result as E, null) ?: continue + val token = receive.tryResumeReceive(result as E) ?: continue assert { token === RESUME_TOKEN } val subHead = this.subHead this.subHead = subHead + 1 // retrieved element for this subscriber @@ -306,40 +285,6 @@ internal class ArrayBroadcastChannel( return result } - // result is `ALREADY_SELECTED | E | POLL_FAILED | Closed` - override fun pollSelectInternal(select: SelectInstance<*>): Any? { - var updated = false - val result = subLock.withLock { - var result = peekUnderLock() - when { - result is Closed<*> -> { /* just bail out of lock */ } - result === POLL_FAILED -> { /* just bail out of lock */ } - else -> { - // let's try to select receiving this element from buffer - if (!select.trySelect()) { // :todo: move trySelect completion outside of lock - result = ALREADY_SELECTED - } else { - // update subHead after retrieiving element from buffer - val subHead = this.subHead - this.subHead = subHead + 1 - updated = true - } - } - } - result - } - // do close outside of lock - (result as? Closed<*>)?.also { close(cause = it.closeCause) } - // there could have been checkOffer attempt while we were holding lock - // now outside the lock recheck if anything else to offer - if (checkOffer()) - updated = true - // and finally update broadcast's channel head if needed - if (updated) - broadcastChannel.updateHead() - return result - } - // Must invoke this check this after lock, because offer's invocation of `checkOffer` might have failed // to `tryLock` just before the lock was about to unlocked, thus loosing notification to this // subscription about an element that was just offered diff --git a/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt b/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt index 7e6c0e68c5..93f99cf1f6 100644 --- a/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt @@ -7,7 +7,7 @@ package kotlinx.coroutines.channels import kotlinx.atomicfu.* import kotlinx.coroutines.* import kotlinx.coroutines.internal.* -import kotlinx.coroutines.selects.* +import kotlinx.coroutines.selects.TrySelectDetailedResult.* import kotlin.math.* /** @@ -68,7 +68,7 @@ internal open class ArrayChannel( this.size.value = size // restore size return receive!! } - val token = receive!!.tryResumeReceive(element, null) + val token = receive!!.tryResumeReceive(element) if (token != null) { assert { token === RESUME_TOKEN } this.size.value = size // restore size @@ -84,48 +84,6 @@ internal open class ArrayChannel( return receive!!.offerResult } - // result is `ALREADY_SELECTED | OFFER_SUCCESS | OFFER_FAILED | Closed` - protected override fun offerSelectInternal(element: E, select: SelectInstance<*>): Any { - var receive: ReceiveOrClosed? = null - lock.withLock { - val size = this.size.value - closedForSend?.let { return it } - // update size before checking queue (!!!) - updateBufferSize(size)?.let { return it } - // check for receivers that were waiting on empty queue - if (size == 0) { - loop@ while (true) { - val offerOp = describeTryOffer(element) - val failure = select.performAtomicTrySelect(offerOp) - when { - failure == null -> { // offered successfully - this.size.value = size // restore size - receive = offerOp.result - return@withLock - } - failure === OFFER_FAILED -> break@loop // cannot offer -> Ok to queue to buffer - failure === RETRY_ATOMIC -> {} // retry - failure === ALREADY_SELECTED || failure is Closed<*> -> { - this.size.value = size // restore size - return failure - } - else -> error("performAtomicTrySelect(describeTryOffer) returned $failure") - } - } - } - // let's try to select sending this element to buffer - if (!select.trySelect()) { // :todo: move trySelect completion outside of lock - this.size.value = size // restore size - return ALREADY_SELECTED - } - enqueueElement(size, element) - return OFFER_SUCCESS - } - // breaks here if offer meets receiver - receive!!.completeResumeReceive(element) - return receive!!.offerResult - } - override fun enqueueSend(send: Send): Any? = lock.withLock { super.enqueueSend(send) } @@ -197,8 +155,12 @@ internal open class ArrayChannel( replacement = send!!.pollResult break@loop } - // too late, already cancelled, but we removed it from the queue and need to notify on undelivered element - send!!.undeliveredElement() + // Too late, already cancelled, but we removed it from the queue and need to notify on undelivered element. + // The only exception is when this "send" operation is an `onSend` clause that has to be re-registered + // in the corresponding `select` invocation. + val send = send!! + if (!(send is SendElementSelectWithUndeliveredHandler<*> && send.trySelectResult == REREGISTER)) + send.undeliveredElement() } } if (replacement !== POLL_FAILED && replacement !is Closed<*>) { @@ -213,67 +175,6 @@ internal open class ArrayChannel( return result } - // result is `ALREADY_SELECTED | E | POLL_FAILED | Closed` - protected override fun pollSelectInternal(select: SelectInstance<*>): Any? { - var send: Send? = null - var success = false - var result: Any? = null - lock.withLock { - val size = this.size.value - if (size == 0) return closedForSend ?: POLL_FAILED - // size > 0: not empty -- retrieve element - result = buffer[head] - buffer[head] = null - this.size.value = size - 1 // update size before checking queue (!!!) - // check for senders that were waiting on full queue - var replacement: Any? = POLL_FAILED - if (size == capacity) { - loop@ while (true) { - val pollOp = describeTryPoll() - val failure = select.performAtomicTrySelect(pollOp) - when { - failure == null -> { // polled successfully - send = pollOp.result - success = true - replacement = send!!.pollResult - break@loop - } - failure === POLL_FAILED -> break@loop // cannot poll -> Ok to take from buffer - failure === RETRY_ATOMIC -> {} // retry - failure === ALREADY_SELECTED -> { - this.size.value = size // restore size - buffer[head] = result // restore head - return failure - } - failure is Closed<*> -> { - send = failure - success = true - replacement = failure - break@loop - } - else -> error("performAtomicTrySelect(describeTryOffer) returned $failure") - } - } - } - if (replacement !== POLL_FAILED && replacement !is Closed<*>) { - this.size.value = size // restore size - buffer[(head + size) % buffer.size] = replacement - } else { - // failed to poll or is already closed --> let's try to select receiving this element from buffer - if (!select.trySelect()) { // :todo: move trySelect completion outside of lock - this.size.value = size // restore size - buffer[head] = result // restore head - return ALREADY_SELECTED - } - } - head = (head + 1) % buffer.size - } - // complete send the we're taken replacement from - if (success) - send!!.completeResumeSend() - return result - } - override fun enqueueReceiveInternal(receive: Receive): Boolean = lock.withLock { super.enqueueReceiveInternal(receive) } diff --git a/kotlinx-coroutines-core/common/src/channels/Channel.kt b/kotlinx-coroutines-core/common/src/channels/Channel.kt index 5ad79fdcff..b8ee0be366 100644 --- a/kotlinx-coroutines-core/common/src/channels/Channel.kt +++ b/kotlinx-coroutines-core/common/src/channels/Channel.kt @@ -365,18 +365,7 @@ public interface ReceiveChannel { level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("onReceiveCatching") ) // Warning since 1.3.0, error in 1.5.0, will be hidden or removed in 1.7.0 - public val onReceiveOrNull: SelectClause1 - get() { - return object : SelectClause1 { - @InternalCoroutinesApi - override fun registerSelectClause1(select: SelectInstance, block: suspend (E?) -> R) { - onReceiveCatching.registerSelectClause1(select) { - it.exceptionOrNull()?.let { throw it } - block(it.getOrNull()) - } - } - } - } + public val onReceiveOrNull: SelectClause1 get() = (this as AbstractChannel).onReceiveOrNull } /** diff --git a/kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt index b768d7c38c..ff9f769ec9 100644 --- a/kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt @@ -14,7 +14,7 @@ import kotlin.jvm.* /** * Broadcasts the most recently sent element (aka [value]) to all [openSubscription] subscribers. * - * Back-to-send sent elements are _conflated_ -- only the the most recently sent value is received, + * Back-to-send sent elements are _conflated_ -- only the most recently sent value is received, * while previously sent elements **are lost**. * Every subscriber immediately receives the most recently sent element. * Sender to this broadcast channel never suspends and [trySend] always succeeds. @@ -89,7 +89,7 @@ public class ConflatedBroadcastChannel() : BroadcastChannel { */ public val valueOrNull: E? get() = when (val state = _state.value) { is Closed -> null - is State<*> -> UNDEFINED.unbox(state.value) + is State<*> -> UNDEFINED.unbox(state.value) else -> error("Invalid state $state") } @@ -263,22 +263,24 @@ public class ConflatedBroadcastChannel() : BroadcastChannel { } } + @Suppress("UNCHECKED_CAST") public override val onSend: SelectClause2> - get() = object : SelectClause2> { - override fun registerSelectClause2(select: SelectInstance, param: E, block: suspend (SendChannel) -> R) { - registerSelectSend(select, param, block) - } - } + get() = SelectClause2Impl( + clauseObject = this, + regFunc = ConflatedBroadcastChannel<*>::registerSelectForSend as RegistrationFunction, + processResFunc = ConflatedBroadcastChannel<*>::processResultSelectSend as ProcessResultFunction + ) - private fun registerSelectSend(select: SelectInstance, element: E, block: suspend (SendChannel) -> R) { - if (!select.trySelect()) return - offerInternal(element)?.let { - select.resumeSelectWithException(it.sendException) - return - } - block.startCoroutineUnintercepted(receiver = this, completion = select.completion) + @Suppress("UNCHECKED_CAST") + private fun registerSelectForSend(select: SelectInstance<*>, element: Any?) { + select.selectInRegistrationPhase(offerInternal(element as E)) } + @Suppress("RedundantNullableReturnType", "UNUSED_PARAMETER") + private fun processResultSelectSend(ignoredParam: Any?, selectResult: Any?): Any? = + if (selectResult is Closed) throw selectResult.sendException + else this + private class Subscriber( private val broadcastChannel: ConflatedBroadcastChannel ) : ConflatedChannel(null), ReceiveChannel { diff --git a/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt b/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt index 177e80cb49..54033a8764 100644 --- a/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt @@ -6,7 +6,6 @@ package kotlinx.coroutines.channels import kotlinx.coroutines.* import kotlinx.coroutines.internal.* -import kotlinx.coroutines.selects.* /** * Channel that buffers at most one element and conflates all subsequent `send` and `trySend` invocations, @@ -42,7 +41,7 @@ internal open class ConflatedChannel(onUndeliveredElement: OnUndeliveredEleme if (receive is Closed) { return receive!! } - val token = receive!!.tryResumeReceive(element, null) + val token = receive!!.tryResumeReceive(element) if (token != null) { assert { token === RESUME_TOKEN } return@withLock @@ -57,39 +56,6 @@ internal open class ConflatedChannel(onUndeliveredElement: OnUndeliveredEleme return receive!!.offerResult } - // result is `ALREADY_SELECTED | OFFER_SUCCESS | Closed` - protected override fun offerSelectInternal(element: E, select: SelectInstance<*>): Any { - var receive: ReceiveOrClosed? = null - lock.withLock { - closedForSend?.let { return it } - if (value === EMPTY) { - loop@ while(true) { - val offerOp = describeTryOffer(element) - val failure = select.performAtomicTrySelect(offerOp) - when { - failure == null -> { // offered successfully - receive = offerOp.result - return@withLock - } - failure === OFFER_FAILED -> break@loop // cannot offer -> Ok to queue to buffer - failure === RETRY_ATOMIC -> {} // retry - failure === ALREADY_SELECTED || failure is Closed<*> -> return failure - else -> error("performAtomicTrySelect(describeTryOffer) returned $failure") - } - } - } - // try to select sending this element to buffer - if (!select.trySelect()) { - return ALREADY_SELECTED - } - updateValueLocked(element)?.let { throw it } - return OFFER_SUCCESS - } - // breaks here if offer meets receiver - receive!!.completeResumeReceive(element) - return receive!!.offerResult - } - // result is `E | POLL_FAILED | Closed` protected override fun pollInternal(): Any? { var result: Any? = null @@ -101,19 +67,6 @@ internal open class ConflatedChannel(onUndeliveredElement: OnUndeliveredEleme return result } - // result is `E | POLL_FAILED | Closed` - protected override fun pollSelectInternal(select: SelectInstance<*>): Any? { - var result: Any? = null - lock.withLock { - if (value === EMPTY) return closedForSend ?: POLL_FAILED - if (!select.trySelect()) - return ALREADY_SELECTED - result = value - value = EMPTY - } - return result - } - protected override fun onCancelIdempotent(wasClosed: Boolean) { var undeliveredElementException: UndeliveredElementException? = null // resource cancel exception lock.withLock { diff --git a/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt b/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt index b5f607b230..18bdcc6465 100644 --- a/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt @@ -5,7 +5,6 @@ package kotlinx.coroutines.channels import kotlinx.coroutines.internal.* -import kotlinx.coroutines.selects.* /** * Channel with linked-list buffer of a unlimited capacity (limited only by available memory). @@ -42,23 +41,6 @@ internal open class LinkedListChannel(onUndeliveredElement: OnUndeliveredElem } } - // result is always `ALREADY_SELECTED | OFFER_SUCCESS | Closed`. - protected override fun offerSelectInternal(element: E, select: SelectInstance<*>): Any { - while (true) { - val result = if (hasReceiveOrClosed) - super.offerSelectInternal(element, select) else - (select.performAtomicTrySelect(describeSendBuffered(element)) ?: OFFER_SUCCESS) - when { - result === ALREADY_SELECTED -> return ALREADY_SELECTED - result === OFFER_SUCCESS -> return OFFER_SUCCESS - result === OFFER_FAILED -> {} // retry - result === RETRY_ATOMIC -> {} // retry - result is Closed<*> -> return result - else -> error("Invalid result $result") - } - } - } - override fun onCancelIdempotentList(list: InlineList, closed: Closed<*>) { var undeliveredElementException: UndeliveredElementException? = null list.forEachReversed { diff --git a/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt b/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt index 5b9ab37f4d..26fef8c012 100644 --- a/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt +++ b/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt @@ -5,6 +5,7 @@ package kotlinx.coroutines.internal +import kotlinx.coroutines.* import kotlin.jvm.* /** @suppress **This is unstable API and it is subject to change.** */ diff --git a/kotlinx-coroutines-core/common/src/selects/OnTimeout.kt b/kotlinx-coroutines-core/common/src/selects/OnTimeout.kt new file mode 100644 index 0000000000..ba39181378 --- /dev/null +++ b/kotlinx-coroutines-core/common/src/selects/OnTimeout.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.selects + +import kotlinx.coroutines.* +import kotlin.time.* + +/** + * Clause that selects the given [block] after a specified timeout passes. + * If timeout is negative or zero, [block] is selected immediately. + * + * **Note: This is an experimental api.** It may be replaced with light-weight timer/timeout channels in the future. + * + * @param timeMillis timeout time in milliseconds. + */ +@ExperimentalCoroutinesApi +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +public fun SelectBuilder.onTimeout(timeMillis: Long, block: suspend () -> R): Unit = + OnTimeout(timeMillis).selectClause.invoke(block) + +/** + * Clause that selects the given [block] after the specified [timeout] passes. + * If timeout is negative or zero, [block] is selected immediately. + * + * **Note: This is an experimental api.** It may be replaced with light-weight timer/timeout channels in the future. + */ +@ExperimentalCoroutinesApi +public fun SelectBuilder.onTimeout(timeout: Duration, block: suspend () -> R): Unit = + onTimeout(timeout.toDelayMillis(), block) + +/** + * We implement [SelectBuilder.onTimeout] as a clause, so each invocation creates + * an instance of [OnTimeout] that specifies the registration part according to + * the [timeout][timeMillis] parameter. + */ +private class OnTimeout( + private val timeMillis: Long +) { + @Suppress("UNCHECKED_CAST") + val selectClause: SelectClause0 + get() = SelectClause0Impl( + clauseObject = this@OnTimeout, + regFunc = OnTimeout::register as RegistrationFunction + ) + + @Suppress("UNUSED_PARAMETER") + private fun register(select: SelectInstance<*>, ignoredParam: Any?) { + // Should this clause complete immediately? + if (timeMillis <= 0) { + select.selectInRegistrationPhase(Unit) + return + } + // Invoke `trySelect` after the timeout is reached. + val action = Runnable { + select.trySelect(this@OnTimeout, Unit) + } + select as SelectImplementation<*> + val context = select.context + val disposableHandle = context.delay.invokeOnTimeout(timeMillis, action, context) + // Do not forget to clean-up when this `select` is completed or cancelled. + select.disposeOnCompletion(disposableHandle) + } +} diff --git a/kotlinx-coroutines-core/common/src/selects/Select.kt b/kotlinx-coroutines-core/common/src/selects/Select.kt index a71c849262..5da5e95702 100644 --- a/kotlinx-coroutines-core/common/src/selects/Select.kt +++ b/kotlinx-coroutines-core/common/src/selects/Select.kt @@ -1,7 +1,6 @@ /* * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:OptIn(ExperimentalContracts::class) package kotlinx.coroutines.selects @@ -9,17 +8,73 @@ import kotlinx.atomicfu.* import kotlinx.coroutines.* import kotlinx.coroutines.channels.* import kotlinx.coroutines.internal.* -import kotlinx.coroutines.intrinsics.* +import kotlinx.coroutines.selects.TrySelectDetailedResult.* import kotlin.contracts.* import kotlin.coroutines.* -import kotlin.coroutines.intrinsics.* +import kotlin.internal.* import kotlin.jvm.* -import kotlin.time.* + +/** + * Waits for the result of multiple suspending functions simultaneously, which are specified using _clauses_ + * in the [builder] scope of this select invocation. The caller is suspended until one of the clauses + * is either _selected_ or _fails_. + * + * At most one clause is *atomically* selected and its block is executed. The result of the selected clause + * becomes the result of the select. If any clause _fails_, then the select invocation produces the + * corresponding exception. No clause is selected in this case. + * + * This select function is _biased_ to the first clause. When multiple clauses can be selected at the same time, + * the first one of them gets priority. Use [selectUnbiased] for an unbiased (randomized) selection among + * the clauses. + + * There is no `default` clause for select expression. Instead, each selectable suspending function has the + * corresponding non-suspending version that can be used with a regular `when` expression to select one + * of the alternatives or to perform the default (`else`) action if none of them can be immediately selected. + * + * ### List of supported select methods + * + * | **Receiver** | **Suspending function** | **Select clause** + * | ---------------- | --------------------------------------------- | ----------------------------------------------------- + * | [Job] | [join][Job.join] | [onJoin][Job.onJoin] + * | [Deferred] | [await][Deferred.await] | [onAwait][Deferred.onAwait] + * | [SendChannel] | [send][SendChannel.send] | [onSend][SendChannel.onSend] + * | [ReceiveChannel] | [receive][ReceiveChannel.receive] | [onReceive][ReceiveChannel.onReceive] + * | [ReceiveChannel] | [receiveCatching][ReceiveChannel.receiveCatching] | [onReceiveCatching][ReceiveChannel.onReceiveCatching] + * | none | [delay] | [onTimeout][SelectBuilder.onTimeout] + * + * This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this + * function is suspended, this function immediately resumes with [CancellationException]. + * There is a **prompt cancellation guarantee**. If the job was cancelled while this function was + * suspended, it will not resume successfully. See [suspendCancellableCoroutine] documentation for low-level details. + * + * Note that this function does not check for cancellation when it is not suspended. + * Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed. + */ +@OptIn(ExperimentalContracts::class) +public suspend inline fun select(crossinline builder: SelectBuilder.() -> Unit): R { + contract { + callsInPlace(builder, InvocationKind.EXACTLY_ONCE) + } + return SelectImplementation(coroutineContext).run { + builder(this) + // TAIL-CALL OPTIMIZATION: the only + // suspend call is at the last position. + doSelect() + } +} /** * Scope for [select] invocation. + * + * An instance of [SelectBuilder] can only be retrieved as a receiver of a [select] block call, + * and it is only valid during the registration phase of the select builder. + * Any uses outside it lead to unspecified behaviour and are prohibited. + * + * The general rule of thumb is that instances of this type should always be used + * implicitly and there shouldn't be any signatures mentioning this type, + * whether explicitly (e.g. function signature) or implicitly (e.g. inferred `val` type). */ -public interface SelectBuilder { +public sealed interface SelectBuilder { /** * Registers a clause in this [select] expression without additional parameters that does not select any value. */ @@ -50,601 +105,720 @@ public interface SelectBuilder { * @param timeMillis timeout time in milliseconds. */ @ExperimentalCoroutinesApi - public fun onTimeout(timeMillis: Long, block: suspend () -> R) + @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + @LowPriorityInOverloadResolution + @Deprecated( + message = "Replaced with the same extension function", + level = DeprecationLevel.ERROR, replaceWith = ReplaceWith(expression = "onTimeout", imports = ["kotlinx.coroutines.selects.onTimeout"]) + ) + public fun onTimeout(timeMillis: Long, block: suspend () -> R): Unit = onTimeout(timeMillis, block) } /** - * Clause that selects the given [block] after the specified [timeout] passes. - * If timeout is negative or zero, [block] is selected immediately. - * - * **Note: This is an experimental api.** It may be replaced with light-weight timer/timeout channels in the future. + * Each [select] clause is specified with: + * 1) the [object of this clause][clauseObject], + * such as the channel instance for [SendChannel.onSend]; + * 2) the function that specifies how this clause + * should be registered in the object above; + * 3) the function that modifies the internal result + * (passed via [SelectInstance.trySelect] or + * [SelectInstance.selectInRegistrationPhase]) + * to the argument of the user-specified block. + * 4) the function that specifies how the internal result provided via + * [SelectInstance.trySelect] or [SelectInstance.selectInRegistrationPhase] + * should be processed in case of this `select` cancellation while dispatching. + */ +@InternalCoroutinesApi +public sealed interface SelectClause { + public val clauseObject: Any + public val regFunc: RegistrationFunction + public val processResFunc: ProcessResultFunction + public val onCancellationConstructor: OnCancellationConstructor? +} + +/** + * The registration function specifies how the `select` instance should be registered into + * the specified clause object. In case of channels, the registration logic + * coincides with the plain `send/receive` operation with the only difference that + * the `select` instance is stored as a waiter instead of continuation. */ -@ExperimentalCoroutinesApi -public fun SelectBuilder.onTimeout(timeout: Duration, block: suspend () -> R): Unit = - onTimeout(timeout.toDelayMillis(), block) +@InternalCoroutinesApi +public typealias RegistrationFunction = (clauseObject: Any, select: SelectInstance<*>, param: Any?) -> Unit + +/** + * This function specifies how the _internal_ result, provided via [SelectInstance.selectInRegistrationPhase] + * or [SelectInstance.trySelect] should be processed. For example, both [ReceiveChannel.onReceive] and + * [ReceiveChannel.onReceiveCatching] clauses perform exactly the same synchronization logic, + * but differ when the channel has been discovered in the closed or cancelled state. + */ +@InternalCoroutinesApi +public typealias ProcessResultFunction = (clauseObject: Any, param: Any?, clauseResult: Any?) -> Any? + +/** + * This function specifies how the internal result, provided via [SelectInstance.trySelect] + * or [SelectInstance.selectInRegistrationPhase], should be processed in case of this `select` + * cancellation while dispatching. Unfortunately, we cannot pass this function only in [SelectInstance.trySelect], + * as [SelectInstance.selectInRegistrationPhase] can be called when the coroutine is already cancelled. + */ +@InternalCoroutinesApi +public typealias OnCancellationConstructor = (select: SelectInstance<*>, param: Any?, internalResult: Any?) -> (Throwable) -> Unit /** * Clause for [select] expression without additional parameters that does not select any value. */ -public interface SelectClause0 { - /** - * Registers this clause with the specified [select] instance and [block] of code. - * @suppress **This is unstable API and it is subject to change.** - */ - @InternalCoroutinesApi - public fun registerSelectClause0(select: SelectInstance, block: suspend () -> R) +public sealed interface SelectClause0 : SelectClause + +internal class SelectClause0Impl( + override val clauseObject: Any, + override val regFunc: RegistrationFunction, + override val onCancellationConstructor: OnCancellationConstructor? = null +) : SelectClause0 { + override val processResFunc: ProcessResultFunction = DUMMY_PROCESS_RESULT_FUNCTION } +private val DUMMY_PROCESS_RESULT_FUNCTION: ProcessResultFunction = { _, _, _ -> null } /** * Clause for [select] expression without additional parameters that selects value of type [Q]. */ -public interface SelectClause1 { - /** - * Registers this clause with the specified [select] instance and [block] of code. - * @suppress **This is unstable API and it is subject to change.** - */ - @InternalCoroutinesApi - public fun registerSelectClause1(select: SelectInstance, block: suspend (Q) -> R) -} +public sealed interface SelectClause1 : SelectClause + +internal class SelectClause1Impl( + override val clauseObject: Any, + override val regFunc: RegistrationFunction, + override val processResFunc: ProcessResultFunction, + override val onCancellationConstructor: OnCancellationConstructor? = null +) : SelectClause1 /** * Clause for [select] expression with additional parameter of type [P] that selects value of type [Q]. */ -public interface SelectClause2 { - /** - * Registers this clause with the specified [select] instance and [block] of code. - * @suppress **This is unstable API and it is subject to change.** - */ - @InternalCoroutinesApi - public fun registerSelectClause2(select: SelectInstance, param: P, block: suspend (Q) -> R) -} +public sealed interface SelectClause2 : SelectClause + +internal class SelectClause2Impl( + override val clauseObject: Any, + override val regFunc: RegistrationFunction, + override val processResFunc: ProcessResultFunction, + override val onCancellationConstructor: OnCancellationConstructor? = null +) : SelectClause2 /** - * Internal representation of select instance. This instance is called _selected_ when - * the clause to execute is already picked. + * Internal representation of `select` instance. * - * @suppress **This is unstable API and it is subject to change.** + * @suppress **This is unstable API, and it is subject to change.** */ -@InternalCoroutinesApi // todo: sealed interface https://youtrack.jetbrains.com/issue/KT-22286 -public interface SelectInstance { +@InternalCoroutinesApi +public sealed interface SelectInstance { /** - * Returns `true` when this [select] statement had already picked a clause to execute. + * The context of the coroutine that is performing this `select` operation. */ - public val isSelected: Boolean + public val context: CoroutineContext /** - * Tries to select this instance. Returns `true` on success. + * This function should be called by other operations, + * which are trying to perform a rendezvous with this `select`. + * Returns `true` if the rendezvous succeeds, `false` otherwise. + * + * Note that according to the current implementation, a rendezvous attempt can fail + * when either another clause is already selected or this `select` is still in + * REGISTRATION phase. To distinguish the reasons, [SelectImplementation.trySelectDetailed] + * function can be used instead. */ - public fun trySelect(): Boolean + public fun trySelect(clauseObject: Any, result: Any?): Boolean /** - * Tries to select this instance. Returns: - * * [RESUME_TOKEN] on success, - * * [RETRY_ATOMIC] on deadlock (needs retry, it is only possible when [otherOp] is not `null`) - * * `null` on failure to select (already selected). - * [otherOp] is not null when trying to rendezvous with this select from inside of another select. - * In this case, [PrepareOp.finishPrepare] must be called before deciding on any value other than [RETRY_ATOMIC]. - * - * Note, that this method's actual return type is `Symbol?` but we cannot declare it as such, because this - * member is public, but [Symbol] is internal. When [SelectInstance] becomes a `sealed interface` - * (see KT-222860) we can declare this method as internal. + * When this `select` instance is stored as a waiter, the specified [handle][disposableHandle] + * defines how the stored `select` should be removed in case of cancellation or another clause selection. */ - public fun trySelectOther(otherOp: PrepareOp?): Any? + public fun disposeOnCompletion(disposableHandle: DisposableHandle) /** - * Performs action atomically with [trySelect]. - * May return [RETRY_ATOMIC], caller shall retry with **fresh instance of desc**. + * When a clause becomes selected during registration, the corresponding internal result + * (which is further passed to the clause's [ProcessResultFunction]) should be provided + * via this function. After that, other clause registrations are ignored and [trySelect] fails. */ - public fun performAtomicTrySelect(desc: AtomicDesc): Any? + public fun selectInRegistrationPhase(internalResult: Any?) +} +internal interface SelectInstanceInternal: SelectInstance + +@PublishedApi +internal open class SelectImplementation constructor( + override val context: CoroutineContext +) : CancelHandler(), SelectBuilder, SelectInstanceInternal { /** - * Returns completion continuation of this select instance. - * This select instance must be _selected_ first. - * All resumption through this instance happen _directly_ without going through dispatcher. + * Essentially, the `select` operation is split into three phases: REGISTRATION, WAITING, and COMPLETION. + * + * == Phase 1: REGISTRATION == + * In the first REGISTRATION phase, the user-specified [SelectBuilder] is applied, and all the listed clauses + * are registered via the provided [registration functions][SelectClause.regFunc]. Intuitively, `select` clause + * registration is similar to the plain blocking operation, with the only difference that this [SelectInstance] + * is stored as a waiter instead of continuation, and [SelectInstance.trySelect] is used to make a rendezvous. + * Also, when registering, it is possible for the operation to complete immediately, without waiting. In this case, + * [SelectInstance.selectInRegistrationPhase] should be used. Otherwise, when no rendezvous happens and this `select` + * instance is stored as a waiter, a completion handler for the registering clause should be specified via + * [SelectInstance.disposeOnCompletion]; this handler specifies how to remove this `select` instance from the + * clause object when another clause becomes selected or the operation cancels. + * + * After a clause registration is completed, another coroutine can attempt to make a rendezvous with this `select`. + * However, to resolve a race between clauses registration and [SelectInstance.trySelect], the latter fails when + * this `select` is still in REGISTRATION phase. Thus, the corresponding clause has to be registered again. + * + * In this phase, the `state` field stores either a special [STATE_REG] marker or + * a list of clauses to be re-registered due to failed rendezvous attempts. + * + * == Phase 2: WAITING == + * If no rendezvous happens in REGISTRATION phase, the `select` operation moves to WAITING one and suspends until + * [SelectInstance.trySelect] is called. Also, when waiting, this `select` can be cancelled. In the latter case, + * further [SelectInstance.trySelect] attempts fail, and all the completion handlers, specified via + * [SelectInstance.disposeOnCompletion], are invoked to remove this `select` instance from the corresponding + * clause objects. + * + * In this phase, the `state` field stores either the continuation to be later resumed or a special `Cancelled` + * object (with the cancellation cause inside) when this `select` becomes cancelled. + * + * == Phase 3: COMPLETION == + * Once a rendezvous happens either in REGISTRATION phase (via [SelectInstance.selectInRegistrationPhase]) or + * in WAITING phase (via [SelectInstance.trySelect]), this `select` moves to the final `COMPLETION` phase. + * First, the provided internal result is processed via the [ProcessResultFunction] of the selected clause; + * it returns the argument for the user-specified block or throws an exception (see [SendChannel.onSend] as + * an example). After that, this `select` should be removed from all other clause objects by calling the + * corresponding [DisposableHandle]-s, provided via [SelectInstance.disposeOnCompletion] during registration. + * At the end, the user-specified block is called and this `select` finishes. + * + * In this phase, once a rendezvous is happened, the `state` field stores the corresponding clause. + * After that, it moves to [STATE_COMPLETED] to avoid memory leaks. + * + * + * + * The state machine is listed below: + * + * REGISTRATION PHASE WAITING PHASE COMPLETION PHASE + * ⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢ ⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢ ⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢⌢ + * + * +-----------+ +-----------+ + * | CANCELLED | | COMPLETED | + * +-----------+ +-----------+ + * ^ ^ + * INITIAL STATE | | this `select` + * ------------+ | cancelled | is completed + * \ | | + * +=============+ move to +------+ successful +------------+ + * +--| STATE_REG |---------------> | cont |-----------------| ClauseData | + * | +=============+ WAITING phase +------+ trySelect(..) +------------+ + * | ^ | ^ + * | | | some clause has been selected during registration | + * add a | | +-------------------------------------------------------+ + * clause to be | | | + * re-registered | | re-register some clause has been selected | + * | | clauses during registration while there | + * v | are clauses to be re-registered; | + * +------------------+ ignore the latter | + * +--| List |-----------------------------------------------------+ + * | +------------------+ + * | ^ + * | | add one more clause + * | | for re-registration + * +------------+ + * + * One of the most valuable benefits of this `select` design is that it allows processing clauses + * in a way similar to plain operations, such as `send` or `receive` on channels. The only difference + * is that instead of continuation, the operation should store the provided `select` instance object. + * Thus, this design makes it possible to support the `select` expression for any blocking data structure + * in Kotlin Coroutines. + * + * It is worth mentioning that the algorithm above provides "obstruction-freedom" non-blocking guarantee + * instead of the standard "lock-freedom" to avoid using heavy descriptors. In practice, this relaxation + * does not make significant difference. However, it is vital for Kotlin Coroutines to provide some + * non-blocking guarantee, as users may add blocking code in [SelectBuilder], and this blocking code + * should not cause blocking behaviour in other places, such as an attempt to make a rendezvous with + * the `select` that is hang in REGISTRATION phase. + * + * Also, this implementation is NOT linearizable under some circumstances. The reason is that a rendezvous + * attempt with `select` (via [SelectInstance.trySelect]) may fail when this `select` operation is still + * in REGISTRATION phase. Consider the following situation on two empty rendezvous channels `c1` and `c2` + * and the `select` operation that tries to send an element to one of these channels. First, this `select` + * instance is registered as a waiter in `c1`. After that, another thread can observe that `c1` is no longer + * empty and try to receive an element from `c1` -- this receive attempt fails due to the `select` operation + * being in REGISTRATION phase. + * It is also possible to observe that this `select` operation registered in `c2` first, and only after that in + * `c1` (it has to re-register in `c1` after the unsuccessful rendezvous attempt), which is also non-linearizable. + * We, however, find such a non-linearizable behaviour not so important in practice and leverage the correctness + * relaxation for the algorithm simplicity and the non-blocking progress guarantee. */ - public val completion: Continuation /** - * Resumes this instance in a dispatched way with exception. - * This method can be called from any context. + * The state of this `select` operation. See the description above for details. */ - public fun resumeSelectWithException(exception: Throwable) - + private val state = atomic(STATE_REG) /** - * Disposes the specified handle when this instance is selected. - * Note, that [DisposableHandle.dispose] could be called multiple times. + * Returns `true` if this `select` instance is in the REGISTRATION phase; + * otherwise, returns `false`. */ - public fun disposeOnSelect(handle: DisposableHandle) -} - -/** - * Waits for the result of multiple suspending functions simultaneously, which are specified using _clauses_ - * in the [builder] scope of this select invocation. The caller is suspended until one of the clauses - * is either _selected_ or _fails_. - * - * At most one clause is *atomically* selected and its block is executed. The result of the selected clause - * becomes the result of the select. If any clause _fails_, then the select invocation produces the - * corresponding exception. No clause is selected in this case. - * - * This select function is _biased_ to the first clause. When multiple clauses can be selected at the same time, - * the first one of them gets priority. Use [selectUnbiased] for an unbiased (randomized) selection among - * the clauses. - - * There is no `default` clause for select expression. Instead, each selectable suspending function has the - * corresponding non-suspending version that can be used with a regular `when` expression to select one - * of the alternatives or to perform the default (`else`) action if none of them can be immediately selected. - * - * ### List of supported select methods - * - * | **Receiver** | **Suspending function** | **Select clause** - * | ---------------- | --------------------------------------------- | ----------------------------------------------------- - * | [Job] | [join][Job.join] | [onJoin][Job.onJoin] - * | [Deferred] | [await][Deferred.await] | [onAwait][Deferred.onAwait] - * | [SendChannel] | [send][SendChannel.send] | [onSend][SendChannel.onSend] - * | [ReceiveChannel] | [receive][ReceiveChannel.receive] | [onReceive][ReceiveChannel.onReceive] - * | [ReceiveChannel] | [receiveCatching][ReceiveChannel.receiveCatching] | [onReceiveCatching][ReceiveChannel.onReceiveCatching] - * | none | [delay] | [onTimeout][SelectBuilder.onTimeout] - * - * This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this - * function is suspended, this function immediately resumes with [CancellationException]. - * There is a **prompt cancellation guarantee**. If the job was cancelled while this function was - * suspended, it will not resume successfully. See [suspendCancellableCoroutine] documentation for low-level details. - * - * Note that this function does not check for cancellation when it is not suspended. - * Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed. - */ -public suspend inline fun select(crossinline builder: SelectBuilder.() -> Unit): R { - contract { - callsInPlace(builder, InvocationKind.EXACTLY_ONCE) - } - return suspendCoroutineUninterceptedOrReturn { uCont -> - val scope = SelectBuilderImpl(uCont) - try { - builder(scope) - } catch (e: Throwable) { - scope.handleBuilderException(e) + private val inRegistrationPhase + get() = state.value.let { + it === STATE_REG || it is List<*> } - scope.getResult() - } -} - - -internal val NOT_SELECTED: Any = Symbol("NOT_SELECTED") -internal val ALREADY_SELECTED: Any = Symbol("ALREADY_SELECTED") -private val UNDECIDED: Any = Symbol("UNDECIDED") -private val RESUMED: Any = Symbol("RESUMED") + /** + * Returns `true` if this `select` is already selected; + * thus, other parties are bound to fail when making a rendezvous with it. + */ + private val isSelected + get() = state.value is ClauseData<*> + /** + * Returns `true` if this `select` is cancelled. + */ + private val isCancelled + get() = state.value === STATE_CANCELLED -// Global counter of all atomic select operations for their deadlock resolution -// The separate internal class is work-around for Atomicfu's current implementation that creates public classes -// for static atomics -internal class SeqNumber { - private val number = atomic(1L) - fun next() = number.incrementAndGet() -} + /** + * List of clauses waiting on this `select` instance. + */ + private var clauses: MutableList>? = ArrayList(2) -private val selectOpSequenceNumber = SeqNumber() + /** + * Stores the completion action provided through [disposeOnCompletion] during clause registration. + * After that, if the clause is successfully registered (so, it has not completed immediately), + * this [DisposableHandle] is stored into the corresponding [ClauseData] instance. + */ + private var disposableHandle: DisposableHandle? = null -@PublishedApi -internal class SelectBuilderImpl( - private val uCont: Continuation // unintercepted delegate continuation -) : LockFreeLinkedListHead(), SelectBuilder, - SelectInstance, Continuation, CoroutineStackFrame -{ - override val callerFrame: CoroutineStackFrame? - get() = uCont as? CoroutineStackFrame - - override fun getStackTraceElement(): StackTraceElement? = null - - // selection state is NOT_SELECTED initially and is replaced by idempotent marker (or null) when selected - private val _state = atomic(NOT_SELECTED) - - // this is basically our own SafeContinuation - private val _result = atomic(UNDECIDED) - - // cancellability support - private val _parentHandle = atomic(null) - private var parentHandle: DisposableHandle? - get() = _parentHandle.value - set(value) { _parentHandle.value = value } - - /* Result state machine - - +-----------+ getResult +---------------------+ resume +---------+ - | UNDECIDED | ------------> | COROUTINE_SUSPENDED | ---------> | RESUMED | - +-----------+ +---------------------+ +---------+ - | - | resume - V - +------------+ getResult - | value/Fail | -----------+ - +------------+ | - ^ | - | | - +-------------------+ + /** + * Stores the result passed via [selectInRegistrationPhase] during clause registration + * or [trySelect], which is called by another coroutine trying to make a rendezvous + * with this `select` instance. Further, this result is processed via the + * [ProcessResultFunction] of the selected clause. + * + * Unfortunately, we cannot store the result in the [state] field, as the latter stores + * the clause object upon selection (see [ClauseData.clauseObject] and [SelectClause.clauseObject]). + * Instead, it is possible to merge the [internalResult] and [disposableHandle] fields into + * one that stores either result when the clause is successfully registered ([inRegistrationPhase] is `true`), + * or [DisposableHandle] instance when the clause is completed during registration ([inRegistrationPhase] is `false`). + * Yet, this optimization is omitted for code simplicity. */ + private var internalResult: Any? = NO_RESULT - override val context: CoroutineContext get() = uCont.context + /** + * This function is called after the [SelectBuilder] is applied. In case one of the clauses is already selected, + * the algorithm applies the corresponding [ProcessResultFunction] and invokes the user-specified [block][ClauseData.block]. + * Otherwise, it moves this `select` to WAITING phase (re-registering clauses if needed), suspends until a rendezvous + * is happened, and then completes the operation by applying the corresponding [ProcessResultFunction] and + * invoking the user-specified [block][ClauseData.block]. + */ + @PublishedApi + internal open suspend fun doSelect(): R = + if (isSelected) complete() // Fast path + else doSelectSuspend() // Slow path + + // We separate the following logic as it has two suspension points + // and, therefore, breaks the tail-call optimization if it were + // inlined in [doSelect] + private suspend fun doSelectSuspend(): R { + // In case no clause has been selected during registration, + // the `select` operation suspends and waits for a rendezvous. + waitUntilSelected() // <-- suspend call => no tail-call optimization here + // There is a selected clause! Apply the corresponding + // [ProcessResultFunction] and invoke the user-specified block. + return complete() // <-- one more suspend call + } - override val completion: Continuation get() = this + // ======================== + // = CLAUSES REGISTRATION = + // ======================== - private inline fun doResume(value: () -> Any?, block: () -> Unit) { - assert { isSelected } // "Must be selected first" - _result.loop { result -> - when { - result === UNDECIDED -> { - val update = value() - if (_result.compareAndSet(UNDECIDED, update)) return - } - result === COROUTINE_SUSPENDED -> if (_result.compareAndSet(COROUTINE_SUSPENDED, RESUMED)) { - block() - return - } - else -> throw IllegalStateException("Already resumed") - } - } - } + override fun SelectClause0.invoke(block: suspend () -> R) = + ClauseData(clauseObject, regFunc, processResFunc, PARAM_CLAUSE_0, block, onCancellationConstructor).register() + override fun SelectClause1.invoke(block: suspend (Q) -> R) = + ClauseData(clauseObject, regFunc, processResFunc, null, block, onCancellationConstructor).register() + override fun SelectClause2.invoke(param: P, block: suspend (Q) -> R) = + ClauseData(clauseObject, regFunc, processResFunc, param, block, onCancellationConstructor).register() - // Resumes in direct mode, without going through dispatcher. Should be called in the same context. - override fun resumeWith(result: Result) { - doResume({ result.toState() }) { - if (result.isFailure) { - uCont.resumeWithStackTrace(result.exceptionOrNull()!!) - } else { - uCont.resumeWith(result) - } + /** + * Attempts to register this `select` clause. If another clause is already selected, + * this function does nothing and completes immediately. + * Otherwise, it registers this `select` instance in + * the [clause object][ClauseData.clauseObject] + * according to the provided [registration function][ClauseData.regFunc]. + * On success, this `select` instance is stored as a waiter + * in the clause object -- the algorithm also stores + * the provided via [disposeOnCompletion] completion action + * and adds the clause to the list of registered one. + * In case of registration failure, the internal result + * (not processed by [ProcessResultFunction] yet) must be + * provided via [selectInRegistrationPhase] -- the algorithm + * updates the state to this clause reference. + */ + @JvmName("register") + internal fun ClauseData.register(reregister: Boolean = false) { + assert { state.value !== STATE_CANCELLED } + // Is there already selected clause? + if (state.value.let { it is ClauseData<*> }) return + // For new clauses, check that there does not exist + // another clause with the same object. + if (!reregister) checkClauseObject(clauseObject) + // Try to register in the corresponding object. + if (tryRegisterAsWaiter(this@SelectImplementation)) { + // Successfully registered, and this `select` instance + // is stored as a waiter. Add this clause to the list + // of registered clauses and store the provided via + // [invokeOnCompletion] completion action into the clause. + // + // Importantly, the [waitUntilSelected] function is implemented + // carefully to ensure that the cancellation handler has not been + // installed when clauses re-register, so the logic below cannot + // be invoked concurrently with the clean-up procedure. + // This also guarantees that the list of clauses cannot be cleared + // in the registration phase, so it is safe to read it with "!!". + if (!reregister) clauses!! += this + disposableHandle = this@SelectImplementation.disposableHandle + this@SelectImplementation.disposableHandle = null + } else { + // This clause has been selected! + // Update the state correspondingly. + state.value = this } } - // Resumes in dispatched way so that it can be called from an arbitrary context - override fun resumeSelectWithException(exception: Throwable) { - doResume({ CompletedExceptionally(recoverStackTrace(exception, uCont)) }) { - uCont.intercepted().resumeWith(Result.failure(exception)) + /** + * Checks that there does not exist another clause with the same object. + */ + private fun checkClauseObject(clauseObject: Any) { + // Read the list of clauses, it is guaranteed that it is non-null. + // In fact, it can become `null` only in the clean-up phase, while + // this check can be called only in the registration one. + val clauses = clauses!! + // Check that there does not exist another clause with the same object. + check(clauses.none { it.clauseObject === clauseObject }) { + "Cannot use select clauses on the same object: $clauseObject" } } - @PublishedApi - internal fun getResult(): Any? { - if (!isSelected) initCancellability() - var result = _result.value // atomic read - if (result === UNDECIDED) { - if (_result.compareAndSet(UNDECIDED, COROUTINE_SUSPENDED)) return COROUTINE_SUSPENDED - result = _result.value // reread volatile var - } - when { - result === RESUMED -> throw IllegalStateException("Already resumed") - result is CompletedExceptionally -> throw result.cause - else -> return result // either COROUTINE_SUSPENDED or data - } + override fun disposeOnCompletion(disposableHandle: DisposableHandle) { + this.disposableHandle = disposableHandle } - private fun initCancellability() { - val parent = context[Job] ?: return - val newRegistration = parent.invokeOnCompletion( - onCancelling = true, handler = SelectOnCancelling().asHandler) - parentHandle = newRegistration - // now check our state _after_ registering - if (isSelected) newRegistration.dispose() + override fun selectInRegistrationPhase(internalResult: Any?) { + this.internalResult = internalResult } - private inner class SelectOnCancelling : JobCancellingNode() { - // Note: may be invoked multiple times, but only the first trySelect succeeds anyway - override fun invoke(cause: Throwable?) { - if (trySelect()) - resumeSelectWithException(job.getCancellationException()) - } - } + // ========================= + // = WAITING FOR SELECTION = + // ========================= - @PublishedApi - internal fun handleBuilderException(e: Throwable) { - if (trySelect()) { - resumeWithException(e) - } else if (e !is CancellationException) { - /* - * Cannot handle this exception -- builder was already resumed with a different exception, - * so treat it as "unhandled exception". But only if it is not the completion reason - * and it's not the cancellation. Otherwise, in the face of structured concurrency - * the same exception will be reported to the global exception handler. - */ - val result = getResult() - if (result !is CompletedExceptionally || unwrap(result.cause) !== unwrap(e)) { - handleCoroutineException(context, e) + /** + * Suspends and waits until some clause is selected. However, it is possible for a concurrent + * coroutine to invoke [trySelect] while this `select` is still in REGISTRATION phase. + * In this case, [trySelect] marks the corresponding select clause to be re-registered, and + * this function performs registration of such clauses. After that, it atomically stores + * the continuation into the [state] field if there is no more clause to be re-registered. + */ + private suspend fun waitUntilSelected() = suspendCancellableCoroutine sc@ { cont -> + // Update the state. + state.loop { curState -> + when { + // This `select` is in REGISTRATION phase, and there is no clause to be re-registered. + // Perform a transition to WAITING phase by storing the current continuation. + curState === STATE_REG -> if (state.compareAndSet(curState, cont)) { + // Perform a clean-up in case of cancellation. + // + // Importantly, we MUST install the cancellation handler + // only when the algorithm is bound to suspend. Otherwise, + // a race with [tryRegister] is possible, and the provided + // via [disposeOnCompletion] cancellation action can be ignored. + // Also, we MUST guarantee that this dispose handle is _visible_ + // according to the memory model, and we CAN guarantee this when + // the state is updated. + cont.invokeOnCancellation(this.asHandler) + return@sc + } + // This `select` is in REGISTRATION phase, but there are clauses that has to be registered again. + // Perform the required registrations and try again. + curState is List<*> -> if (state.compareAndSet(curState, STATE_REG)) { + @Suppress("UNCHECKED_CAST") + curState as List + curState.forEach { reregisterClause(it) } + } + // This `select` operation became completed during clauses re-registration. + curState is ClauseData<*> -> { + cont.resume(Unit, curState.createOnCancellationAction(this, internalResult)) + return@sc + } + // This `select` cannot be in any other state. + else -> error("unexpected state: $curState") } } } - override val isSelected: Boolean get() = _state.loop { state -> - when { - state === NOT_SELECTED -> return false - state is OpDescriptor -> state.perform(this) // help - else -> return true // already selected - } - } - - override fun disposeOnSelect(handle: DisposableHandle) { - val node = DisposeNode(handle) - // check-add-check pattern is Ok here since handle.dispose() is safe to be called multiple times - if (!isSelected) { - addLast(node) // add handle to list - // double-check node after adding - if (!isSelected) return // all ok - still not selected - } - // already selected - handle.dispose() + /** + * Re-registers the clause with the specified + * [clause object][clauseObject] after unsuccessful + * [trySelect] of this clause while the `select` + * was still in REGISTRATION phase. + */ + private fun reregisterClause(clauseObject: Any) { + val clause = findClause(clauseObject)!! // it is guaranteed that the corresponding clause is presented + clause.disposableHandle = null + clause.register(reregister = true) } - private fun doAfterSelect() { - parentHandle?.dispose() - forEach { - it.handle.dispose() - } - } + // ============== + // = RENDEZVOUS = + // ============== - override fun trySelect(): Boolean { - val result = trySelectOther(null) - return when { - result === RESUME_TOKEN -> true - result == null -> false - else -> error("Unexpected trySelectIdempotent result $result") - } - } + override fun trySelect(clauseObject: Any, result: Any?): Boolean = + trySelectInternal(clauseObject, result) == TRY_SELECT_SUCCESSFUL - /* - Diagram for rendezvous between two select operations: - - +---------+ +------------------------+ state(c) - | Channel | | SelectBuilderImpl(1) | -----------------------------------+ - +---------+ +------------------------+ | - | queue ^ | - V | select | - +---------+ next +------------------------+ next +--------------+ | - | LLHead | ------> | Send/ReceiveSelect(3) | -+----> | NextNode ... | | - +---------+ +------------------------+ | +--------------+ | - ^ ^ | next(b) ^ | - | affected | V | | - | +-----------------+ next | V - | | PrepareOp(6) | ----------+ +-----------------+ - | +-----------------+ <-------------------- | PairSelectOp(7) | - | | desc +-----------------+ - | V - | queue +----------------------+ - +------------------------- | TryPoll/OfferDesc(5) | - +----------------------+ - atomicOp | ^ - V | desc - +----------------------+ impl +---------------------+ - | SelectBuilderImpl(2) | <----- | AtomicSelectOp(4) | - +----------------------+ +---------------------+ - | state(a) ^ - | | - +----------------------------+ - - - 0. The first select operation SelectBuilderImpl(1) had already registered Send/ReceiveSelect(3) node - in the channel. - 1. The second select operation SelectBuilderImpl(2) is trying to rendezvous calling - performAtomicTrySelect(TryPoll/TryOfferDesc). - 2. A linked pair of AtomicSelectOp(4) and TryPoll/OfferDesc(5) is created to initiate this operation. - 3. AtomicSelectOp.prepareSelectOp installs a reference to AtomicSelectOp(4) in SelectBuilderImpl(2).state(a) - field. STARTING AT THIS MOMENT CONCURRENT HELPERS CAN DISCOVER AND TRY TO HELP PERFORM THIS OPERATION. - 4. Then TryPoll/OfferDesc.prepare discovers "affectedNode" for this operation as Send/ReceiveSelect(3) and - creates PrepareOp(6) that references it. It installs reference to PrepareOp(6) in Send/ReceiveSelect(3).next(b) - instead of its original next pointer that was stored in PrepareOp(6).next. - 5. PrepareOp(6).perform calls TryPoll/OfferDesc(5).onPrepare which validates that PrepareOp(6).affected node - is of the correct type and tries to secure ability to resume it by calling affected.tryResumeSend/Receive. - Note, that different PrepareOp instances can be repeatedly created for different candidate nodes. If node is - found to be be resumed/selected, then REMOVE_PREPARED result causes Send/ReceiveSelect(3).next change to - undone and new PrepareOp is created with a different candidate node. Different concurrent helpers may end up - creating different PrepareOp instances, so it is important that they ultimately come to consensus about - node on which perform operation upon. - 6. Send/ReceiveSelect(3).affected.tryResumeSend/Receive forwards this call to SelectBuilderImpl.trySelectOther, - passing it a reference to PrepareOp(6) as an indication of the other select instance rendezvous. - 7. SelectBuilderImpl.trySelectOther creates PairSelectOp(7) and installs it as SelectBuilderImpl(1).state(c) - to secure the state of the first builder and commit ability to make it selected for this operation. - 8. NOW THE RENDEZVOUS IS FULLY PREPARED via descriptors installed at - - SelectBuilderImpl(2).state(a) - - Send/ReceiveSelect(3).next(b) - - SelectBuilderImpl(1).state(c) - Any concurrent operation that is trying to access any of the select instances or the queue is going to help. - Any helper that helps AtomicSelectOp(4) calls TryPoll/OfferDesc(5).prepare which tries to determine - "affectedNode" but is bound to discover the same Send/ReceiveSelect(3) node that cannot become - non-first node until this operation completes (there are no insertions to the head of the queue!) - We have not yet decided to complete this operation, but we cannot ever decide to complete this operation - on any other node but Send/ReceiveSelect(3), so it is now safe to perform the next step. - 9. PairSelectOp(7).perform calls PrepareOp(6).finishPrepare which copies PrepareOp(6).affected and PrepareOp(6).next - to the corresponding TryPoll/OfferDesc(5) fields. - 10. PairSelectOp(7).perform calls AtomicSelect(4).decide to reach consensus on successful completion of this - operation. This consensus is important in light of dead-lock resolution algorithm, because a stale helper - could have stumbled upon a higher-numbered atomic operation and had decided to abort this atomic operation, - reaching decision on RETRY_ATOMIC status of it. We cannot proceed with completion in this case and must abort, - all objects including AtomicSelectOp(4) will be dropped, reverting all the three updated pointers to - their original values and atomic operation will retry from scratch. - 11. NOW WITH SUCCESSFUL UPDATE OF AtomicSelectOp(4).consensus to null THE RENDEZVOUS IS COMMITTED. The rest - of the code proceeds to update: - - SelectBuilderImpl(1).state to TryPoll/OfferDesc(5) so that late helpers would know that we have - already successfully completed rendezvous. - - Send/ReceiveSelect(3).next to Removed(next) so that this node becomes marked as removed. - - SelectBuilderImpl(2).state to null to mark this select instance as selected. - - Note, that very late helper may try to perform this AtomicSelectOp(4) when it is already completed. - It can proceed as far as finding affected node, creating PrepareOp, installing this new PrepareOp into the - node's next pointer, but PrepareOp.perform checks that AtomicSelectOp(4) is already decided and undoes all - the preparations. + /** + * Similar to [trySelect] but provides a failure reason + * if this rendezvous is unsuccessful. We need this function + * in the channel implementation. */ - - // it is just like plain trySelect, but support idempotent start - // Returns RESUME_TOKEN | RETRY_ATOMIC | null (when already selected) - override fun trySelectOther(otherOp: PrepareOp?): Any? { - _state.loop { state -> // lock-free loop on state - when { - // Found initial state (not selected yet) -- try to make it selected - state === NOT_SELECTED -> { - if (otherOp == null) { - // regular trySelect -- just mark as select - if (!_state.compareAndSet(NOT_SELECTED, null)) return@loop - } else { - // Rendezvous with another select instance -- install PairSelectOp - val pairSelectOp = PairSelectOp(otherOp) - if (!_state.compareAndSet(NOT_SELECTED, pairSelectOp)) return@loop - val decision = pairSelectOp.perform(this) - if (decision !== null) return decision - } - doAfterSelect() - return RESUME_TOKEN - } - state is OpDescriptor -> { // state is either AtomicSelectOp or PairSelectOp - // Found descriptor of ongoing operation while working in the context of other select operation - if (otherOp != null) { - val otherAtomicOp = otherOp.atomicOp - when { - // It is the same select instance - otherAtomicOp is AtomicSelectOp && otherAtomicOp.impl === this -> { - /* - * We cannot do state.perform(this) here and "help" it since it is the same - * select and we'll get StackOverflowError. - * See https://github.com/Kotlin/kotlinx.coroutines/issues/1411 - * We cannot support this because select { ... } is an expression and its clauses - * have a result that shall be returned from the select. - */ - error("Cannot use matching select clauses on the same object") - } - // The other select (that is trying to proceed) had started earlier - otherAtomicOp.isEarlierThan(state) -> { - /** - * Abort to prevent deadlock by returning a failure to it. - * See https://github.com/Kotlin/kotlinx.coroutines/issues/504 - * The other select operation will receive a failure and will restart itself with a - * larger sequence number. This guarantees obstruction-freedom of this algorithm. - */ - return RETRY_ATOMIC - } - } + fun trySelectDetailed(clauseObject: Any, result: Any?) = + TrySelectDetailedResult(trySelectInternal(clauseObject, result)) + + private fun trySelectInternal(clauseObject: Any, internalResult: Any?): Int { + while (true) { + when (val curState = state.value) { + // Perform a rendezvous with this select if it is in WAITING state. + is CancellableContinuation<*> -> { + val clause = findClause(clauseObject) ?: continue // retry if `clauses` is already `null` + val onCancellation = clause.createOnCancellationAction(this@SelectImplementation, internalResult) + if (state.compareAndSet(curState, clause)) { + @Suppress("UNCHECKED_CAST") + val cont = curState as CancellableContinuation + // Success! Store the resumption value and + // try to resume the continuation. + this.internalResult = internalResult + if (cont.tryResume(onCancellation)) return TRY_SELECT_SUCCESSFUL + // If the resumption failed, we need to clean + // the [result] field to avoid memory leaks. + this.internalResult = null + return TRY_SELECT_CANCELLED } - // Otherwise (not a special descriptor) - state.perform(this) // help it } - // otherwise -- already selected - otherOp == null -> return null // already selected - state === otherOp.desc -> return RESUME_TOKEN // was selected with this marker - else -> return null // selected with different marker + // Already selected. + STATE_COMPLETED, is ClauseData<*> -> return TRY_SELECT_ALREADY_SELECTED + // Already cancelled. + STATE_CANCELLED -> return TRY_SELECT_CANCELLED + // This select is still in REGISTRATION phase, re-register the clause + // in order not to wait until this select moves to WAITING phase. + // This is a rare race, so we do not need to worry about performance here. + STATE_REG -> if (state.compareAndSet(curState, listOf(clauseObject))) return TRY_SELECT_REREGISTER + // This select is still in REGISTRATION phase, and the state stores a list of clauses + // for re-registration, add the selecting clause to this list. + // This is a rare race, so we do not need to worry about performance here. + is List<*> -> if (state.compareAndSet(curState, curState + clauseObject)) return TRY_SELECT_REREGISTER + // Another state? Something went really wrong. + else -> error("Unexpected state: $curState") } } } - // The very last step of rendezvous between two select operations - private class PairSelectOp( - @JvmField val otherOp: PrepareOp - ) : OpDescriptor() { - override fun perform(affected: Any?): Any? { - val impl = affected as SelectBuilderImpl<*> - // here we are definitely not going to RETRY_ATOMIC, so - // we must finish preparation of another operation before attempting to reach decision to select - otherOp.finishPrepare() - val decision = otherOp.atomicOp.decide(null) // try decide for success of operation - val update: Any = if (decision == null) otherOp.desc else NOT_SELECTED - impl._state.compareAndSet(this, update) - return decision - } - - override val atomicOp: AtomicOp<*> - get() = otherOp.atomicOp + /** + * Finds the clause with the corresponding [clause object][SelectClause.clauseObject]. + * If the reference to the list of clauses is already cleared due to completion/cancellation, + * this function returns `null` + */ + private fun findClause(clauseObject: Any): ClauseData? { + // Read the list of clauses. If the `clauses` field is already `null`, + // the clean-up phase has already completed, and this function returns `null`. + val clauses = this.clauses ?: return null + // Find the clause with the specified clause object. + return clauses.find { it.clauseObject === clauseObject } + ?: error("Clause with object $clauseObject is not found") } - override fun performAtomicTrySelect(desc: AtomicDesc): Any? = - AtomicSelectOp(this, desc).perform(null) + // ============== + // = COMPLETION = + // ============== - override fun toString(): String = "SelectInstance(state=${_state.value}, result=${_result.value})" - - private class AtomicSelectOp( - @JvmField val impl: SelectBuilderImpl<*>, - @JvmField val desc: AtomicDesc - ) : AtomicOp() { - // all select operations are totally ordered by their creating time using selectOpSequenceNumber - override val opSequence = selectOpSequenceNumber.next() - - init { - desc.atomicOp = this + /** + * Completes this `select` operation after the internal result is provided + * via [SelectInstance.trySelect] or [SelectInstance.selectInRegistrationPhase]. + * (1) First, this function applies the [ProcessResultFunction] of the selected clause + * to the internal result. + * (2) After that, the [clean-up procedure][cleanup] + * is called to remove this `select` instance from other clause objects, and + * make it possible to collect it by GC after this `select` finishes. + * (3) Finally, the user-specified block is invoked + * with the processed result as an argument. + */ + private suspend fun complete(): R { + assert { isSelected } + // Get the selected clause. + @Suppress("UNCHECKED_CAST") + val selectedClause = state.value as ClauseData + // Perform the clean-up before the internal result processing and + // the user-specified block invocation to guarantee the absence + // of memory leaks. Collect the internal result before that. + val internalResult = this.internalResult + cleanup(selectedClause) + // Process the internal result and invoke the user's block. + return if (!RECOVER_STACK_TRACES) { + // TAIL-CALL OPTIMIZATION: the `suspend` block + // is invoked at the very end. + val blockArgument = selectedClause.processResult(internalResult) + selectedClause.invokeBlock(blockArgument) + } else { + // TAIL-CALL OPTIMIZATION: the `suspend` + // function is invoked at the very end. + // However, internally this `suspend` function + // constructs a state machine to recover a + // possible stack-trace. + processResultAndInvokeBlockRecoveringException(selectedClause, internalResult) } + } - override fun prepare(affected: Any?): Any? { - // only originator of operation makes preparation move of installing descriptor into this selector's state - // helpers should never do it, or risk ruining progress when they come late - if (affected == null) { - // we are originator (affected reference is not null if helping) - prepareSelectOp()?.let { return it } - } - try { - return desc.prepare(this) - } catch (e: Throwable) { - // undo prepareSelectedOp on crash (for example if IllegalStateException is thrown) - if (affected == null) undoPrepare() - throw e - } + private suspend fun processResultAndInvokeBlockRecoveringException(clause: ClauseData, internalResult: Any?): R = + try { + val blockArgument = clause.processResult(internalResult) + clause.invokeBlock(blockArgument) + } catch (e: Throwable) { + // In the debug mode, we need to properly recover + // the stack-trace of the exception; the tail-call + // optimization cannot be applied here. + recoverAndThrow(e) } - override fun complete(affected: Any?, failure: Any?) { - completeSelect(failure) - desc.complete(this, failure) + /** + * Invokes all [DisposableHandle]-s provided via + * [SelectInstance.disposeOnCompletion] during + * clause registrations. + */ + private fun cleanup(selectedClause: ClauseData) { + assert { state.value == selectedClause } + // Read the list of clauses. If the `clauses` field is already `null`, + // a concurrent clean-up procedure has already completed, and it is safe to finish. + val clauses = this.clauses ?: return + // Invoke all cancellation handlers except for the + // one related to the selected clause, if specified. + clauses.forEach { clause -> + if (clause !== selectedClause) clause.disposableHandle?.dispose() } + // We do need to clean all the data to avoid memory leaks. + this.state.value = STATE_COMPLETED + this.internalResult = NO_RESULT + this.clauses = null + } - private fun prepareSelectOp(): Any? { - impl._state.loop { state -> - when { - state === this -> return null // already in progress - state is OpDescriptor -> state.perform(impl) // help - state === NOT_SELECTED -> { - if (impl._state.compareAndSet(NOT_SELECTED, this)) - return null // success - } - else -> return ALREADY_SELECTED - } - } + // [CompletionHandler] implementation, must be invoked on cancellation. + override fun invoke(cause: Throwable?) { + // Update the state. + state.update { cur -> + // Finish immediately when this `select` is already completed. + if (cur is ClauseData<*> || cur == STATE_COMPLETED) return + STATE_CANCELLED } + // Read the list of clauses. If the `clauses` field is already `null`, + // a concurrent clean-up procedure has already completed, and it is safe to finish. + val clauses = this.clauses ?: return + // Remove this `select` instance from all the clause object (channels, mutexes, etc.). + clauses.forEach { it.disposableHandle?.dispose() } + // We do need to clean all the data to avoid memory leaks. + this.internalResult = NO_RESULT + this.clauses = null + } - // reverts the change done by prepareSelectedOp - private fun undoPrepare() { - impl._state.compareAndSet(this, NOT_SELECTED) + /** + * Each `select` clause is internally represented with a [ClauseData] instance. + */ + internal class ClauseData( + @JvmField val clauseObject: Any, // the object of this `select` clause: Channel, Mutex, Job, ... + private val regFunc: RegistrationFunction, + private val processResFunc: ProcessResultFunction, + private val param: Any?, // the user-specified param + private val block: Any, // the user-specified block, which should be called if this clause becomes selected + @JvmField val onCancellationConstructor: OnCancellationConstructor?, + @JvmField var disposableHandle: DisposableHandle? = null + ) { + /** + * Tries to register the specified [select] instance in [clauseObject] and check + * whether the registration succeeded or a rendezvous has happened during the registration. + * This function returns `true` if this [select] is successfully registered and + * is _waiting_ for a rendezvous, or `false` when this clause becomes + * selected during registration. + * + * For example, the [Channel.onReceive] clause registration + * on a non-empty channel retrieves the first element and completes + * the corresponding [select] via [SelectInstance.selectInRegistrationPhase]. + */ + fun tryRegisterAsWaiter(select: SelectImplementation): Boolean { + assert { select.inRegistrationPhase || select.isCancelled } + assert { select.internalResult === NO_RESULT } + regFunc(clauseObject, select, param) + return select.internalResult === NO_RESULT } - private fun completeSelect(failure: Any?) { - val selectSuccess = failure == null - val update = if (selectSuccess) null else NOT_SELECTED - if (impl._state.compareAndSet(this, update)) { - if (selectSuccess) - impl.doAfterSelect() + /** + * Processes the internal result provided via either + * [SelectInstance.selectInRegistrationPhase] or + * [SelectInstance.trySelect] and returns an argument + * for the user-specified [block]. + * + * Importantly, this function may throw an exception + * (e.g., when the channel is closed in [Channel.onSend], the + * corresponding [ProcessResultFunction] is bound to fail). + */ + fun processResult(result: Any?) = processResFunc(clauseObject, param, result) + + /** + * Invokes the user-specified block and returns + * the final result of this `select` clause. + */ + @Suppress("UNCHECKED_CAST") + suspend fun invokeBlock(argument: Any?): R { + val block = block + // We distinguish no-argument and 1-argument + // lambdas via special markers for the clause + // parameters. Specifically, PARAM_CLAUSE_0 + // is always used with [SelectClause0], which + // takes a no-argument lambda. + // + // TAIL-CALL OPTIMIZATION: we invoke + // the `suspend` block at the very end. + return if (this.param === PARAM_CLAUSE_0) { + block as suspend () -> R + block() + } else { + block as suspend (Any?) -> R + block(argument) } } - override fun toString(): String = "AtomicSelectOp(sequence=$opSequence)" - } - - override fun SelectClause0.invoke(block: suspend () -> R) { - registerSelectClause0(this@SelectBuilderImpl, block) - } - - override fun SelectClause1.invoke(block: suspend (Q) -> R) { - registerSelectClause1(this@SelectBuilderImpl, block) - } - - override fun SelectClause2.invoke(param: P, block: suspend (Q) -> R) { - registerSelectClause2(this@SelectBuilderImpl, param, block) + fun createOnCancellationAction(select: SelectInstance<*>, internalResult: Any?) = + onCancellationConstructor?.invoke(select, param, internalResult) } +} - override fun onTimeout(timeMillis: Long, block: suspend () -> R) { - if (timeMillis <= 0L) { - if (trySelect()) - block.startCoroutineUnintercepted(completion) - return - } - val action = Runnable { - // todo: we could have replaced startCoroutine with startCoroutineUndispatched - // But we need a way to know that Delay.invokeOnTimeout had used the right thread - if (trySelect()) - block.startCoroutineCancellable(completion) // shall be cancellable while waits for dispatch - } - disposeOnSelect(context.delay.invokeOnTimeout(timeMillis, action, context)) - } +private fun CancellableContinuation.tryResume(onCancellation: ((cause: Throwable) -> Unit)?): Boolean { + val token = tryResume(Unit, null, onCancellation) ?: return false + completeResume(token) + return true +} - private class DisposeNode( - @JvmField val handle: DisposableHandle - ) : LockFreeLinkedListNode() +// trySelectInternal(..) results. +private const val TRY_SELECT_SUCCESSFUL = 0 +private const val TRY_SELECT_REREGISTER = 1 +private const val TRY_SELECT_CANCELLED = 2 +private const val TRY_SELECT_ALREADY_SELECTED = 3 +// trySelectDetailed(..) results. +internal enum class TrySelectDetailedResult { + SUCCESSFUL, REREGISTER, CANCELLED, ALREADY_SELECTED +} +private fun TrySelectDetailedResult(trySelectInternalResult: Int): TrySelectDetailedResult = when(trySelectInternalResult) { + TRY_SELECT_SUCCESSFUL -> SUCCESSFUL + TRY_SELECT_REREGISTER -> REREGISTER + TRY_SELECT_CANCELLED -> CANCELLED + TRY_SELECT_ALREADY_SELECTED -> ALREADY_SELECTED + else -> error("Unexpected internal result: $trySelectInternalResult") } + +// Markers for REGISTRATION, COMPLETED, and CANCELLED states. +private val STATE_REG = Symbol("STATE_REG") +private val STATE_COMPLETED = Symbol("STATE_COMPLETED") +private val STATE_CANCELLED = Symbol("STATE_CANCELLED") +// As the selection result is nullable, we use this special +// marker for the absence of result. +private val NO_RESULT = Symbol("NO_RESULT") +// We use this marker parameter objects to distinguish +// SelectClause[0,1,2] and invoke the user-specified block correctly. +internal val PARAM_CLAUSE_0 = Symbol("PARAM_CLAUSE_0") diff --git a/kotlinx-coroutines-core/common/src/selects/SelectOld.kt b/kotlinx-coroutines-core/common/src/selects/SelectOld.kt new file mode 100644 index 0000000000..85476d2ca3 --- /dev/null +++ b/kotlinx-coroutines-core/common/src/selects/SelectOld.kt @@ -0,0 +1,147 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.selects + +import kotlinx.coroutines.* +import kotlin.coroutines.* +import kotlin.coroutines.intrinsics.* + +/* + * For binary compatibility, we need to maintain the previous `select` implementations. + * Thus, we keep [SelectBuilderImpl] and [UnbiasedSelectBuilderImpl] and implement the + * functions marked with `@PublishedApi`. + * + * We keep the old `select` functions as [selectOld] and [selectUnbiasedOld] for test purpose. + */ + +@PublishedApi +internal class SelectBuilderImpl( + uCont: Continuation // unintercepted delegate continuation +) : SelectImplementation(uCont.context) { + private val cont = CancellableContinuationImpl(uCont.intercepted(), MODE_CANCELLABLE) + + @PublishedApi + internal fun getResult(): Any? { + // In the current `select` design, the [select] and [selectUnbiased] functions + // do not wrap the operation in `suspendCoroutineUninterceptedOrReturn` and + // suspend explicitly via [doSelect] call, which returns the final result. + // However, [doSelect] is a suspend function, so it cannot be invoked directly. + // In addition, the `select` builder is eligible to throw an exception, which + // should be handled properly. + // + // As a solution, we: + // 1) check whether the `select` building is already completed with exception, finishing immediately in this case; + // 2) create a CancellableContinuationImpl with the provided unintercepted continuation as a delegate; + // 3) wrap the [doSelect] call in an additional coroutine, which we launch in UNDISPATCHED mode; + // 4) resume the created CancellableContinuationImpl after the [doSelect] invocation completes; + // 5) use CancellableContinuationImpl.getResult() as a result of this function. + if (cont.isCompleted) return cont.getResult() + CoroutineScope(context).launch(start = CoroutineStart.UNDISPATCHED) { + val result = try { + doSelect() + } catch (e: Throwable) { + cont.resumeUndispatchedWithException(e) + return@launch + } + cont.resumeUndispatched(result) + } + return cont.getResult() + } + + @PublishedApi + internal fun handleBuilderException(e: Throwable) { + cont.resumeWithException(e) // will be thrown later via `cont.getResult()` + } +} + +@PublishedApi +internal class UnbiasedSelectBuilderImpl( + uCont: Continuation // unintercepted delegate continuation +) : UnbiasedSelectImplementation(uCont.context) { + private val cont = CancellableContinuationImpl(uCont.intercepted(), MODE_CANCELLABLE) + + @PublishedApi + internal fun initSelectResult(): Any? { + // Here, we do the same trick as in [SelectBuilderImpl]. + if (cont.isCompleted) return cont.getResult() + CoroutineScope(context).launch(start = CoroutineStart.UNDISPATCHED) { + val result = try { + doSelect() + } catch (e: Throwable) { + cont.resumeUndispatchedWithException(e) + return@launch + } + cont.resumeUndispatched(result) + } + return cont.getResult() + } + + @PublishedApi + internal fun handleBuilderException(e: Throwable) { + cont.resumeWithException(e) + } +} + +/* + * This is the old version of `select`. It should work to guarantee binary compatibility. + * + * Internal note: + * We do test it manually by changing the implementation of **new** select with the following: + * ``` + * public suspend inline fun select(crossinline builder: SelectBuilder.() -> Unit): R { + * contract { + * callsInPlace(builder, InvocationKind.EXACTLY_ONCE) + * } + * return selectOld(builder) + * } + * ``` + * + * These signatures are not used by the already compiled code, but their body is. + */ +@PublishedApi +internal suspend inline fun selectOld(crossinline builder: SelectBuilder.() -> Unit): R { + return suspendCoroutineUninterceptedOrReturn { uCont -> + val scope = SelectBuilderImpl(uCont) + try { + builder(scope) + } catch (e: Throwable) { + scope.handleBuilderException(e) + } + scope.getResult() + } +} + +// This is the old version of `selectUnbiased`. It should work to guarantee binary compatibility. +@PublishedApi +internal suspend inline fun selectUnbiasedOld(crossinline builder: SelectBuilder.() -> Unit): R = + suspendCoroutineUninterceptedOrReturn { uCont -> + val scope = UnbiasedSelectBuilderImpl(uCont) + try { + builder(scope) + } catch (e: Throwable) { + scope.handleBuilderException(e) + } + scope.initSelectResult() + } + +@OptIn(ExperimentalStdlibApi::class) +private fun CancellableContinuation.resumeUndispatched(result: T) { + val dispatcher = context[CoroutineDispatcher] + if (dispatcher != null) { + dispatcher.resumeUndispatched(result) + } else { + resume(result) + } +} + +@OptIn(ExperimentalStdlibApi::class) +private fun CancellableContinuation<*>.resumeUndispatchedWithException(exception: Throwable) { + val dispatcher = context[CoroutineDispatcher] + if (dispatcher != null) { + dispatcher.resumeUndispatchedWithException(exception) + } else { + resumeWithException(exception) + } +} diff --git a/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt b/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt index 25172a2d05..0b68e411d5 100644 --- a/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt +++ b/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt @@ -1,11 +1,12 @@ /* * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +@file:OptIn(ExperimentalContracts::class) package kotlinx.coroutines.selects +import kotlin.contracts.* import kotlin.coroutines.* -import kotlin.coroutines.intrinsics.* /** * Waits for the result of multiple suspending functions simultaneously like [select], but in an _unbiased_ @@ -17,55 +18,51 @@ import kotlin.coroutines.intrinsics.* * * See [select] function description for all the other details. */ -public suspend inline fun selectUnbiased(crossinline builder: SelectBuilder.() -> Unit): R = - suspendCoroutineUninterceptedOrReturn { uCont -> - val scope = UnbiasedSelectBuilderImpl(uCont) - try { - builder(scope) - } catch (e: Throwable) { - scope.handleBuilderException(e) - } - scope.initSelectResult() +@OptIn(ExperimentalContracts::class) +public suspend inline fun selectUnbiased(crossinline builder: SelectBuilder.() -> Unit): R { + contract { + callsInPlace(builder, InvocationKind.EXACTLY_ONCE) } + return UnbiasedSelectImplementation(coroutineContext).run { + builder(this) + doSelect() + } +} - +/** + * The unbiased `select` inherits the [standard one][SelectImplementation], + * but does not register clauses immediately. Instead, it stores all of them + * in [clausesToRegister] lists, shuffles and registers them in the beginning of [doSelect] + * (see [shuffleAndRegisterClauses]), and then delegates the rest + * to the parent's [doSelect] implementation. + */ @PublishedApi -internal class UnbiasedSelectBuilderImpl( - uCont: Continuation -) : SelectBuilder { - - private val instance = SelectBuilderImpl(uCont) - private val clauses = arrayListOf<() -> Unit>() - - @PublishedApi - internal fun handleBuilderException(e: Throwable): Unit = instance.handleBuilderException(e) - - @PublishedApi - internal fun initSelectResult(): Any? { - if (!instance.isSelected) { - try { - clauses.shuffle() - clauses.forEach { it.invoke() } - } catch (e: Throwable) { - instance.handleBuilderException(e) - } - } - return instance.getResult() - } +internal open class UnbiasedSelectImplementation(context: CoroutineContext) : SelectImplementation(context) { + private val clausesToRegister: MutableList> = arrayListOf() override fun SelectClause0.invoke(block: suspend () -> R) { - clauses += { registerSelectClause0(instance, block) } + clausesToRegister += ClauseData(clauseObject, regFunc, processResFunc, PARAM_CLAUSE_0, block, onCancellationConstructor) } override fun SelectClause1.invoke(block: suspend (Q) -> R) { - clauses += { registerSelectClause1(instance, block) } + clausesToRegister += ClauseData(clauseObject, regFunc, processResFunc, null, block, onCancellationConstructor) } override fun SelectClause2.invoke(param: P, block: suspend (Q) -> R) { - clauses += { registerSelectClause2(instance, param, block) } + clausesToRegister += ClauseData(clauseObject, regFunc, processResFunc, param, block, onCancellationConstructor) + } + + @PublishedApi + override suspend fun doSelect(): R { + shuffleAndRegisterClauses() + return super.doSelect() } - override fun onTimeout(timeMillis: Long, block: suspend () -> R) { - clauses += { instance.onTimeout(timeMillis, block) } + @Suppress("UNCHECKED_CAST") + private fun shuffleAndRegisterClauses() = try { + clausesToRegister.shuffle() + clausesToRegister.forEach { it.register() } + } finally { + clausesToRegister.clear() } } diff --git a/kotlinx-coroutines-core/common/src/sync/Mutex.kt b/kotlinx-coroutines-core/common/src/sync/Mutex.kt index 1fe9a15dfc..945bac07b5 100644 --- a/kotlinx-coroutines-core/common/src/sync/Mutex.kt +++ b/kotlinx-coroutines-core/common/src/sync/Mutex.kt @@ -7,7 +7,6 @@ package kotlinx.coroutines.sync import kotlinx.atomicfu.* import kotlinx.coroutines.* import kotlinx.coroutines.internal.* -import kotlinx.coroutines.intrinsics.* import kotlinx.coroutines.selects.* import kotlin.contracts.* import kotlin.jvm.* @@ -21,18 +20,22 @@ import kotlin.jvm.* * * JVM API note: * Memory semantic of the [Mutex] is similar to `synchronized` block on JVM: - * An unlock on a [Mutex] happens-before every subsequent successful lock on that [Mutex]. + * An unlock operation on a [Mutex] happens-before every subsequent successful lock on that [Mutex]. * Unsuccessful call to [tryLock] do not have any memory effects. */ public interface Mutex { /** - * Returns `true` when this mutex is locked. + * Returns `true` if this mutex is locked. */ public val isLocked: Boolean /** * Tries to lock this mutex, returning `false` if this mutex is already locked. * + * It is recommended to use [withLock] for safety reasons, so that the acquired lock is always + * released at the end of your critical section, and [unlock] is never invoked before a successful + * lock acquisition. + * * @param owner Optional owner token for debugging. When `owner` is specified (non-null value) and this mutex * is already locked with the same token (same identity), this function throws [IllegalStateException]. */ @@ -51,26 +54,33 @@ public interface Mutex { * Note that this function does not check for cancellation when it is not suspended. * Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed. * - * Use [tryLock] to try acquiring a lock without waiting. + * Use [tryLock] to try acquiring the lock without waiting. * * This function is fair; suspended callers are resumed in first-in-first-out order. * + * It is recommended to use [withLock] for safety reasons, so that the acquired lock is always + * released at the end of the critical section, and [unlock] is never invoked before a successful + * lock acquisition. + * * @param owner Optional owner token for debugging. When `owner` is specified (non-null value) and this mutex * is already locked with the same token (same identity), this function throws [IllegalStateException]. */ public suspend fun lock(owner: Any? = null) /** - * Deprecated for removal without built-in replacement. + * Clause for [select] expression of [lock] suspending function that selects when the mutex is locked. + * Additional parameter for the clause in the `owner` (see [lock]) and when the clause is selected + * the reference to this mutex is passed into the corresponding block. */ @Deprecated(level = DeprecationLevel.WARNING, message = "Mutex.onLock deprecated without replacement. " + "For additional details please refer to #2794") // WARNING since 1.6.0 public val onLock: SelectClause2 /** - * Checks mutex locked by owner + * Checks whether this mutex is locked by the specified owner. * - * @return `true` on mutex lock by owner, `false` if not locker or it is locked by different owner + * @return `true` when this mutex is locked by the specified owner; + * `false` if the mutex is not locked or locked by another owner. */ public fun holdsLock(owner: Any): Boolean @@ -78,6 +88,10 @@ public interface Mutex { * Unlocks this mutex. Throws [IllegalStateException] if invoked on a mutex that is not locked or * was locked with a different owner token (by identity). * + * It is recommended to use [withLock] for safety reasons, so that the acquired lock is always + * released at the end of the critical section, and [unlock] is never invoked before a successful + * lock acquisition. + * * @param owner Optional owner token for debugging. When `owner` is specified (non-null value) and this mutex * was locked with the different token (by identity), this function throws [IllegalStateException]. */ @@ -104,7 +118,7 @@ public fun Mutex(locked: Boolean = false): Mutex = */ @OptIn(ExperimentalContracts::class) public suspend inline fun Mutex.withLock(owner: Any? = null, action: () -> T): T { - contract { + contract { callsInPlace(action, InvocationKind.EXACTLY_ONCE) } @@ -116,301 +130,137 @@ public suspend inline fun Mutex.withLock(owner: Any? = null, action: () -> T } } -private val LOCK_FAIL = Symbol("LOCK_FAIL") -private val UNLOCK_FAIL = Symbol("UNLOCK_FAIL") -private val LOCKED = Symbol("LOCKED") -private val UNLOCKED = Symbol("UNLOCKED") - -private val EMPTY_LOCKED = Empty(LOCKED) -private val EMPTY_UNLOCKED = Empty(UNLOCKED) - -private class Empty( - @JvmField val locked: Any -) { - override fun toString(): String = "Empty[$locked]" -} -internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2 { - // State is: Empty | LockedQueue | OpDescriptor - // shared objects while we have no waiters - private val _state = atomic(if (locked) EMPTY_LOCKED else EMPTY_UNLOCKED) +internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1 else 0), Mutex { + /** + * After the lock is acquired, the corresponding owner is stored in this field. + * The [unlock] operation checks the owner and either re-sets it to [NO_OWNER], + * if there is no waiting request, or to the owner of the suspended [lock] operation + * to be resumed, otherwise. + */ + private val owner = atomic(if (locked) null else NO_OWNER) - public override val isLocked: Boolean get() { - _state.loop { state -> - when (state) { - is Empty -> return state.locked !== UNLOCKED - is LockedQueue -> return true - is OpDescriptor -> state.perform(this) // help - else -> error("Illegal state $state") - } + private val onSelectCancellationUnlockConstructor: OnCancellationConstructor = + { _: SelectInstance<*>, owner: Any?, _: Any? -> + { unlock(owner) } } - } - - // for tests ONLY - internal val isLockedEmptyQueueState: Boolean get() { - val state = _state.value - return state is LockedQueue && state.isEmpty - } - public override fun tryLock(owner: Any?): Boolean { - _state.loop { state -> - when (state) { - is Empty -> { - if (state.locked !== UNLOCKED) return false - val update = if (owner == null) EMPTY_LOCKED else Empty( - owner - ) - if (_state.compareAndSet(state, update)) return true - } - is LockedQueue -> { - check(state.owner !== owner) { "Already locked by $owner" } - return false - } - is OpDescriptor -> state.perform(this) // help - else -> error("Illegal state $state") - } + override val isLocked: Boolean get() = + availablePermits == 0 + + override fun holdsLock(owner: Any): Boolean { + while (true) { + // Is this mutex locked? + if (!isLocked) return false + val curOwner = this.owner.value + // Wait in a spin-loop until the owner is set + if (curOwner === NO_OWNER) continue // <-- ATTENTION, BLOCKING PART HERE + // Check the owner + return curOwner === owner } } - public override suspend fun lock(owner: Any?) { - // fast-path -- try lock + override suspend fun lock(owner: Any?) { if (tryLock(owner)) return - // slow-path -- suspend - return lockSuspend(owner) + lockSuspend(owner) } - private suspend fun lockSuspend(owner: Any?) = suspendCancellableCoroutineReusable sc@ { cont -> - var waiter = LockCont(owner, cont) - _state.loop { state -> - when (state) { - is Empty -> { - if (state.locked !== UNLOCKED) { // try upgrade to queue & retry - _state.compareAndSet(state, LockedQueue(state.locked)) - } else { - // try lock - val update = if (owner == null) EMPTY_LOCKED else Empty(owner) - if (_state.compareAndSet(state, update)) { // locked - // TODO implement functional type in LockCont as soon as we get rid of legacy JS - cont.resume(Unit) { unlock(owner) } - return@sc - } - } - } - is LockedQueue -> { - val curOwner = state.owner - check(curOwner !== owner) { "Already locked by $owner" } - - state.addLast(waiter) - /* - * If the state has been changed while we were adding the waiter, - * it means that 'unlock' has taken it and _either_ resumed it successfully or just overwritten. - * To rendezvous that, we try to "invalidate" our node and go for retry. - * - * Node has to be re-instantiated as we do not support node re-adding, even to - * another list - */ - if (_state.value === state || !waiter.take()) { - // added to waiter list - cont.removeOnCancellation(waiter) - return@sc - } - - waiter = LockCont(owner, cont) - return@loop - } - is OpDescriptor -> state.perform(this) // help - else -> error("Illegal state $state") - } - } - } - - override val onLock: SelectClause2 - get() = this - - // registerSelectLock - @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") - override fun registerSelectClause2(select: SelectInstance, owner: Any?, block: suspend (Mutex) -> R) { - while (true) { // lock-free loop on state - if (select.isSelected) return - when (val state = _state.value) { - is Empty -> { - if (state.locked !== UNLOCKED) { // try upgrade to queue & retry - _state.compareAndSet(state, LockedQueue(state.locked)) - } else { - // try lock - val failure = select.performAtomicTrySelect(TryLockDesc(this, owner)) - when { - failure == null -> { // success - block.startCoroutineUnintercepted(receiver = this, completion = select.completion) - return - } - failure === ALREADY_SELECTED -> return // already selected -- bail out - failure === LOCK_FAIL -> {} // retry - failure === RETRY_ATOMIC -> {} // retry - else -> error("performAtomicTrySelect(TryLockDesc) returned $failure") - } - } - } - is LockedQueue -> { - check(state.owner !== owner) { "Already locked by $owner" } - val node = LockSelect(owner, select, block) - /* - * If the state has been changed while we were adding the waiter, - * it means that 'unlock' has taken it and _either_ resumed it successfully or just overwritten. - * To rendezvous that, we try to "invalidate" our node and go for retry. - * - * Node has to be re-instantiated as we do not support node re-adding, even to - * another list - */ - state.addLast(node) - if (_state.value === state || !node.take()) { - // added to waiter list - select.disposeOnSelect(node) - return - } - } - is OpDescriptor -> state.perform(this) // help - else -> error("Illegal state $state") - } - } + private suspend fun lockSuspend(owner: Any?) = suspendCancellableCoroutineReusable { cont -> + val contWithOwner = CancellableContinuationWithOwner(cont, owner) + acquire(contWithOwner) } - private class TryLockDesc( - @JvmField val mutex: MutexImpl, - @JvmField val owner: Any? - ) : AtomicDesc() { - // This is Harris's RDCSS (Restricted Double-Compare Single Swap) operation - private inner class PrepareOp(override val atomicOp: AtomicOp<*>) : OpDescriptor() { - override fun perform(affected: Any?): Any? { - val update: Any = if (atomicOp.isDecided) EMPTY_UNLOCKED else atomicOp // restore if was already decided - (affected as MutexImpl)._state.compareAndSet(this, update) - return null // ok - } - } - - override fun prepare(op: AtomicOp<*>): Any? { - val prepare = PrepareOp(op) - if (!mutex._state.compareAndSet(EMPTY_UNLOCKED, prepare)) return LOCK_FAIL - return prepare.perform(mutex) + override fun tryLock(owner: Any?): Boolean = + if (tryAcquire()) { + assert { this.owner.value === NO_OWNER } + this.owner.value = owner + true + } else { + false } - override fun complete(op: AtomicOp<*>, failure: Any?) { - val update = if (failure != null) EMPTY_UNLOCKED else { - if (owner == null) EMPTY_LOCKED else Empty(owner) - } - mutex._state.compareAndSet(op, update) - } - } - - public override fun holdsLock(owner: Any) = - _state.value.let { state -> - when (state) { - is Empty -> state.locked === owner - is LockedQueue -> state.owner === owner - else -> false - } - } - override fun unlock(owner: Any?) { - _state.loop { state -> - when (state) { - is Empty -> { - if (owner == null) - check(state.locked !== UNLOCKED) { "Mutex is not locked" } - else - check(state.locked === owner) { "Mutex is locked by ${state.locked} but expected $owner" } - if (_state.compareAndSet(state, EMPTY_UNLOCKED)) return - } - is OpDescriptor -> state.perform(this) - is LockedQueue -> { - if (owner != null) - check(state.owner === owner) { "Mutex is locked by ${state.owner} but expected $owner" } - val waiter = state.removeFirstOrNull() - if (waiter == null) { - val op = UnlockOp(state) - if (_state.compareAndSet(state, op) && op.perform(this) == null) return - } else { - if ((waiter as LockWaiter).tryResumeLockWaiter()) { - state.owner = waiter.owner ?: LOCKED - waiter.completeResumeLockWaiter() - return - } - } - } - else -> error("Illegal state $state") - } + while (true) { + // Is this mutex locked? + check(isLocked) { "This mutex is not locked" } + // Read the owner, waiting until it is set in a spin-loop if required. + val curOwner = this.owner.value + if (curOwner === NO_OWNER) continue // <-- ATTENTION, BLOCKING PART HERE + // Check the owner. + check(curOwner === owner || owner == null) { "This mutex is locked by $curOwner, but $owner is expected" } + // Try to clean the owner first. We need to use CAS here to synchronize with concurrent `unlock(..)`-s. + if (!this.owner.compareAndSet(curOwner, NO_OWNER)) continue + // Release the semaphore permit at the end. + release() + return } } - override fun toString(): String { - _state.loop { state -> - when (state) { - is Empty -> return "Mutex[${state.locked}]" - is OpDescriptor -> state.perform(this) - is LockedQueue -> return "Mutex[${state.owner}]" - else -> error("Illegal state $state") - } - } - } + @Suppress("UNCHECKED_CAST", "OverridingDeprecatedMember", "OVERRIDE_DEPRECATION") + override val onLock: SelectClause2 get() = SelectClause2Impl( + clauseObject = this, + regFunc = MutexImpl::onLockRegFunction as RegistrationFunction, + processResFunc = MutexImpl::onLockProcessResult as ProcessResultFunction, + onCancellationConstructor = onSelectCancellationUnlockConstructor + ) - private class LockedQueue( - @Volatile @JvmField var owner: Any - ) : LockFreeLinkedListHead() { - override fun toString(): String = "LockedQueue[$owner]" + protected open fun onLockRegFunction(select: SelectInstance<*>, owner: Any?) { + onAcquireRegFunction(SelectInstanceWithOwner(select, owner), owner) } - private abstract inner class LockWaiter( - @JvmField val owner: Any? - ) : LockFreeLinkedListNode(), DisposableHandle { - private val isTaken = atomic(false) - fun take(): Boolean = isTaken.compareAndSet(false, true) - final override fun dispose() { remove() } - abstract fun tryResumeLockWaiter(): Boolean - abstract fun completeResumeLockWaiter() + protected open fun onLockProcessResult(owner: Any?, result: Any?): Any? { + return this } - private inner class LockCont( - owner: Any?, - private val cont: CancellableContinuation - ) : LockWaiter(owner) { - - override fun tryResumeLockWaiter(): Boolean { - if (!take()) return false - return cont.tryResume(Unit, idempotent = null) { - // if this continuation gets cancelled during dispatch to the caller, then release the lock + private inner class CancellableContinuationWithOwner( + @JvmField + val cont: CancellableContinuation, + @JvmField + val owner: Any? + ) : CancellableContinuation by cont { + override fun tryResume(value: Unit, idempotent: Any?, onCancellation: ((cause: Throwable) -> Unit)?): Any? { + assert { this@MutexImpl.owner.value === NO_OWNER } + val token = cont.tryResume(value, idempotent) { + assert { this@MutexImpl.owner.value.let { it === NO_OWNER ||it === owner } } + this@MutexImpl.owner.value = owner unlock(owner) - } != null + } + if (token != null) { + assert { this@MutexImpl.owner.value === NO_OWNER } + this@MutexImpl.owner.value = owner + } + return token } - override fun completeResumeLockWaiter() = cont.completeResume(RESUME_TOKEN) - override fun toString(): String = "LockCont[$owner, ${cont}] for ${this@MutexImpl}" + override fun resume(value: Unit, onCancellation: ((cause: Throwable) -> Unit)?) { + assert { this@MutexImpl.owner.value === NO_OWNER } + this@MutexImpl.owner.value = owner + cont.resume(value) { unlock(owner) } + } } - private inner class LockSelect( - owner: Any?, - @JvmField val select: SelectInstance, - @JvmField val block: suspend (Mutex) -> R - ) : LockWaiter(owner) { - override fun tryResumeLockWaiter(): Boolean = take() && select.trySelect() - override fun completeResumeLockWaiter() { - block.startCoroutineCancellable(receiver = this@MutexImpl, completion = select.completion) { - // if this continuation gets cancelled during dispatch to the caller, then release the lock - unlock(owner) + private inner class SelectInstanceWithOwner( + @JvmField + val select: SelectInstance, + @JvmField + val owner: Any? + ) : SelectInstanceInternal by select as SelectInstanceInternal { + override fun trySelect(clauseObject: Any, result: Any?): Boolean { + assert { this@MutexImpl.owner.value === NO_OWNER } + this@MutexImpl.owner.value = owner + return select.trySelect(clauseObject, result).also { success -> + if (!success) this@MutexImpl.owner.value = NO_OWNER } } - override fun toString(): String = "LockSelect[$owner, $select] for ${this@MutexImpl}" - } - - // atomic unlock operation that checks that waiters queue is empty - private class UnlockOp( - @JvmField val queue: LockedQueue - ) : AtomicOp() { - override fun prepare(affected: MutexImpl): Any? = - if (queue.isEmpty) null else UNLOCK_FAIL - override fun complete(affected: MutexImpl, failure: Any?) { - val update: Any = if (failure == null) EMPTY_UNLOCKED else queue - affected._state.compareAndSet(this, update) + override fun selectInRegistrationPhase(internalResult: Any?) { + assert { this@MutexImpl.owner.value === NO_OWNER } + this@MutexImpl.owner.value = owner + select.selectInRegistrationPhase(internalResult) } } + + override fun toString() = "Mutex@${hexAddress}[isLocked=$isLocked,owner=${owner.value}]" } + +private val NO_OWNER = Symbol("NO_OWNER") diff --git a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt index c46a5c9f40..9ba6eaf36b 100644 --- a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt +++ b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt @@ -7,7 +7,9 @@ package kotlinx.coroutines.sync import kotlinx.atomicfu.* import kotlinx.coroutines.* import kotlinx.coroutines.internal.* +import kotlinx.coroutines.selects.* import kotlin.contracts.* +import kotlin.js.* import kotlin.math.* /** @@ -16,7 +18,7 @@ import kotlin.math.* * Each [release] adds a permit, potentially releasing a suspended acquirer. * Semaphore is fair and maintains a FIFO order of acquirers. * - * Semaphores are mostly used to limit the number of coroutines that have an access to particular resource. + * Semaphores are mostly used to limit the number of coroutines that have access to particular resource. * Semaphore with `permits = 1` is essentially a [Mutex]. **/ public interface Semaphore { @@ -40,7 +42,7 @@ public interface Semaphore { * Use [CoroutineScope.isActive] or [CoroutineScope.ensureActive] to periodically * check for cancellation in tight loops if needed. * - * Use [tryAcquire] to try acquire a permit of this semaphore without suspension. + * Use [tryAcquire] to try to acquire a permit of this semaphore without suspension. */ public suspend fun acquire() @@ -88,7 +90,8 @@ public suspend inline fun Semaphore.withPermit(action: () -> T): T { } } -private class SemaphoreImpl(private val permits: Int, acquiredPermits: Int) : Semaphore { +@Suppress("UNCHECKED_CAST") +internal open class SemaphoreImpl(private val permits: Int, acquiredPermits: Int) : Semaphore { /* The queue of waiting acquirers is essentially an infinite array based on the list of segments (see `SemaphoreSegment`); each segment contains a fixed number of slots. To determine a slot for each enqueue @@ -138,11 +141,11 @@ private class SemaphoreImpl(private val permits: Int, acquiredPermits: Int) : Se } /** - * This counter indicates a number of available permits if it is non-negative, - * or the size with minus sign otherwise. Note, that 32-bit counter is enough here - * since the maximal number of available permits is [permits] which is [Int], - * and the maximum number of waiting acquirers cannot be greater than 2^31 in any - * real application. + * This counter indicates the number of available permits if it is positive, + * or the negated number of waiters on this semaphore otherwise. + * Note, that 32-bit counter is enough here since the maximal number of available + * permits is [permits] which is [Int], and the maximum number of waiting acquirers + * cannot be greater than 2^31 in any real application. */ private val _availablePermits = atomic(permits - acquiredPermits) override val availablePermits: Int get() = max(_availablePermits.value, 0) @@ -150,62 +153,167 @@ private class SemaphoreImpl(private val permits: Int, acquiredPermits: Int) : Se private val onCancellationRelease = { _: Throwable -> release() } override fun tryAcquire(): Boolean { - _availablePermits.loop { p -> + while (true) { + // Get the current number of available permits. + val p = _availablePermits.value + // Is the number of available permits greater + // than the maximal one because of an incorrect + // `release()` call without a preceding `acquire()`? + // Change it to `permits` and start from the beginning. + if (p > permits) { + coerceAvailablePermitsAtMaximum() + continue + } + // Try to decrement the number of available + // permits if it is greater than zero. if (p <= 0) return false if (_availablePermits.compareAndSet(p, p - 1)) return true } } override suspend fun acquire() { - val p = _availablePermits.getAndDecrement() + // Decrement the number of available permits. + val p = decPermits() + // Is the permit acquired? if (p > 0) return // permit acquired + // Try to suspend otherwise. // While it looks better when the following function is inlined, // it is important to make `suspend` function invocations in a way - // so that the tail-call optimization can be applied. + // so that the tail-call optimization can be applied here. acquireSlowPath() } private suspend fun acquireSlowPath() = suspendCancellableCoroutineReusable sc@ { cont -> + // Try to suspend. + if (addAcquireToQueue(cont)) return@sc + // The suspension has been failed + // due to the synchronous resumption mode. + // Restart the whole `acquire`. + acquire(cont) + } + + @JsName("acquireCont") + protected fun acquire(waiter: CancellableContinuation) = acquire( + waiter = waiter, + suspend = { cont -> addAcquireToQueue(cont) }, + onAcquired = { cont -> cont.resume(Unit, onCancellationRelease) } + ) + + @JsName("acquireInternal") + private inline fun acquire(waiter: W, suspend: (waiter: W) -> Boolean, onAcquired: (waiter: W) -> Unit) { while (true) { - if (addAcquireToQueue(cont)) return@sc - val p = _availablePermits.getAndDecrement() - if (p > 0) { // permit acquired - cont.resume(Unit, onCancellationRelease) - return@sc + // Decrement the number of available permits at first. + val p = decPermits() + // Is the permit acquired? + if (p > 0) { + onAcquired(waiter) + return } + // Permit has not been acquired, try to suspend. + if (suspend(waiter)) return + } + } + + // We do not fully support `onAcquire` as it is needed only for `Mutex.onLock`. + @Suppress("UNUSED_PARAMETER") + protected fun onAcquireRegFunction(select: SelectInstance<*>, ignoredParam: Any?) = + acquire( + waiter = select, + suspend = { s -> addAcquireToQueue(s) }, + onAcquired = { s -> s.selectInRegistrationPhase(Unit) } + ) + + /** + * Decrements the number of available permits + * and ensures that it is not greater than [permits] + * at the point of decrement. The last may happen + * due to an incorrect `release()` call without + * a preceding `acquire()`. + */ + private fun decPermits(): Int { + while (true) { + // Decrement the number of available permits. + val p = _availablePermits.getAndDecrement() + // Is the number of available permits greater + // than the maximal one due to an incorrect + // `release()` call without a preceding `acquire()`? + if (p > permits) continue + // The number of permits is correct, return it. + return p } } override fun release() { while (true) { - val p = _availablePermits.getAndUpdate { cur -> - check(cur < permits) { "The number of released permits cannot be greater than $permits" } - cur + 1 + // Increment the number of available permits. + val p = _availablePermits.getAndIncrement() + // Is this `release` call correct and does not + // exceed the maximal number of permits? + if (p >= permits) { + // Revert the number of available permits + // back to the correct one and fail with error. + coerceAvailablePermitsAtMaximum() + error("The number of released permits cannot be greater than $permits") } + // Is there a waiter that should be resumed? if (p >= 0) return + // Try to resume the first waiter, and + // restart the operation if either this + // first waiter is cancelled or + // due to `SYNC` resumption mode. if (tryResumeNextFromQueue()) return } } + /** + * Changes the number of available permits to + * [permits] if it became greater due to an + * incorrect [release] call. + */ + private fun coerceAvailablePermitsAtMaximum() { + while (true) { + val cur = _availablePermits.value + if (cur <= permits) break + if (_availablePermits.compareAndSet(cur, permits)) break + } + } + /** * Returns `false` if the received permit cannot be used and the calling operation should restart. */ - private fun addAcquireToQueue(cont: CancellableContinuation): Boolean { + private fun addAcquireToQueue(waiter: Any): Boolean { val curTail = this.tail.value val enqIdx = enqIdx.getAndIncrement() val segment = this.tail.findSegmentAndMoveForward(id = enqIdx / SEGMENT_SIZE, startFrom = curTail, createNewSegment = ::createSegment).segment // cannot be closed val i = (enqIdx % SEGMENT_SIZE).toInt() // the regular (fast) path -- if the cell is empty, try to install continuation - if (segment.cas(i, null, cont)) { // installed continuation successfully - cont.invokeOnCancellation(CancelSemaphoreAcquisitionHandler(segment, i).asHandler) + if (segment.cas(i, null, waiter)) { // installed continuation successfully + when (waiter) { + is CancellableContinuation<*> -> { + waiter.invokeOnCancellation(CancelSemaphoreAcquisitionHandler(segment, i).asHandler) + } + is SelectInstance<*> -> { + waiter.disposeOnCompletion(CancelSemaphoreAcquisitionHandler(segment, i)) + } + else -> error("unexpected: $waiter") + } return true } // On CAS failure -- the cell must be either PERMIT or BROKEN // If the cell already has PERMIT from tryResumeNextFromQueue, try to grab it if (segment.cas(i, PERMIT, TAKEN)) { // took permit thus eliminating acquire/release pair /// This continuation is not yet published, but still can be cancelled via outer job - cont.resume(Unit, onCancellationRelease) + when (waiter) { + is CancellableContinuation<*> -> { + waiter as CancellableContinuation + waiter.resume(Unit, onCancellationRelease) + } + is SelectInstance<*> -> { + waiter.selectInRegistrationPhase(Unit) + } + else -> error("unexpected: $waiter") + } return true } assert { segment.get(i) === BROKEN } // it must be broken in this case, no other way around it @@ -233,23 +341,34 @@ private class SemaphoreImpl(private val permits: Int, acquiredPermits: Int) : Se // Try to break the slot in order not to wait return !segment.cas(i, PERMIT, BROKEN) } - cellState === CANCELLED -> return false // the acquire was already cancelled - else -> return (cellState as CancellableContinuation).tryResumeAcquire() + cellState === CANCELLED -> return false // the acquirer has already been cancelled + else -> return cellState.tryResumeAcquire() } } - private fun CancellableContinuation.tryResumeAcquire(): Boolean { - val token = tryResume(Unit, null, onCancellationRelease) ?: return false - completeResume(token) - return true + private fun Any.tryResumeAcquire(): Boolean = when(this) { + is CancellableContinuation<*> -> { + this as CancellableContinuation + val token = tryResume(Unit, null, onCancellationRelease) + if (token != null) { + completeResume(token) + true + } else false + } + is SelectInstance<*> -> { + trySelect(this@SemaphoreImpl, Unit) + } + else -> error("unexpected: $this") } } private class CancelSemaphoreAcquisitionHandler( private val segment: SemaphoreSegment, private val index: Int -) : CancelHandler() { - override fun invoke(cause: Throwable?) { +) : CancelHandler(), DisposableHandle { + override fun invoke(cause: Throwable?) = dispose() + + override fun dispose() { segment.cancel(index) } diff --git a/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt b/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt index f234e141fe..227e690ce8 100644 --- a/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt +++ b/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt @@ -56,4 +56,6 @@ private class ChannelViaBroadcast( get() = sub.onReceive override val onReceiveCatching: SelectClause1> get() = sub.onReceiveCatching + override val onReceiveOrNull: SelectClause1 + get() = error("unsupported") } diff --git a/kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt index 0158c84307..88c21160ea 100644 --- a/kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt +++ b/kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt @@ -383,11 +383,7 @@ class SelectArrayChannelTest : TestBase() { } // only for debugging - internal fun SelectBuilder.default(block: suspend () -> R) { - this as SelectBuilderImpl // type assertion - if (!trySelect()) return - block.startCoroutineUnintercepted(this) - } + internal fun SelectBuilder.default(block: suspend () -> R) = onTimeout(0, block) @Test fun testSelectReceiveOrClosedForClosedChannel() = runTest { diff --git a/kotlinx-coroutines-core/common/test/selects/SelectOldTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectOldTest.kt new file mode 100644 index 0000000000..1f4b95d29b --- /dev/null +++ b/kotlinx-coroutines-core/common/test/selects/SelectOldTest.kt @@ -0,0 +1,124 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.selects + +import kotlinx.coroutines.* +import kotlin.test.* + +class SelectOldTest : TestBase() { + @Test + fun testSelectCompleted() = runTest { + expect(1) + launch { // makes sure we don't yield to it earlier + finish(4) // after main exits + } + val job = Job() + job.cancel() + selectOld { + job.onJoin { + expect(2) + } + } + expect(3) + // will wait for the first coroutine + } + + @Test + fun testSelectUnbiasedCompleted() = runTest { + expect(1) + launch { // makes sure we don't yield to it earlier + finish(4) // after main exits + } + val job = Job() + job.cancel() + selectUnbiasedOld { + job.onJoin { + expect(2) + } + } + expect(3) + // will wait for the first coroutine + } + + @Test + fun testSelectIncomplete() = runTest { + expect(1) + val job = Job() + launch { // makes sure we don't yield to it earlier + expect(3) + val res = selectOld { + job.onJoin { + expect(6) + "OK" + } + } + expect(7) + assertEquals("OK", res) + } + expect(2) + yield() + expect(4) + job.cancel() + expect(5) + yield() + finish(8) + } + + @Test + fun testSelectUnbiasedIncomplete() = runTest { + expect(1) + val job = Job() + launch { // makes sure we don't yield to it earlier + expect(3) + val res = selectUnbiasedOld { + job.onJoin { + expect(6) + "OK" + } + } + expect(7) + assertEquals("OK", res) + } + expect(2) + yield() + expect(4) + job.cancel() + expect(5) + yield() + finish(8) + } + + @Test + fun testSelectLazy() = runTest { + expect(1) + val job = launch(start = CoroutineStart.LAZY) { + expect(2) + } + val res = selectOld { + job.onJoin { + expect(3) + "OK" + } + } + finish(4) + assertEquals("OK", res) + } + + @Test + fun testSelectUnbiasedLazy() = runTest { + expect(1) + val job = launch(start = CoroutineStart.LAZY) { + expect(2) + } + val res = selectUnbiasedOld { + job.onJoin { + expect(3) + "OK" + } + } + finish(4) + assertEquals("OK", res) + } +} diff --git a/kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt index 6a1576761a..f3c5b4f366 100644 --- a/kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt +++ b/kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt @@ -7,7 +7,6 @@ package kotlinx.coroutines.selects import kotlinx.coroutines.* import kotlinx.coroutines.channels.* -import kotlinx.coroutines.intrinsics.* import kotlin.test.* class SelectRendezvousChannelTest : TestBase() { @@ -442,11 +441,7 @@ class SelectRendezvousChannelTest : TestBase() { } // only for debugging - internal fun SelectBuilder.default(block: suspend () -> R) { - this as SelectBuilderImpl // type assertion - if (!trySelect()) return - block.startCoroutineUnintercepted(this) - } + internal fun SelectBuilder.default(block: suspend () -> R) = onTimeout(0, block) @Test fun testSelectSendAndReceive() = runTest { diff --git a/kotlinx-coroutines-core/common/test/sync/MutexTest.kt b/kotlinx-coroutines-core/common/test/sync/MutexTest.kt index 4f428bc4b0..6a60387672 100644 --- a/kotlinx-coroutines-core/common/test/sync/MutexTest.kt +++ b/kotlinx-coroutines-core/common/test/sync/MutexTest.kt @@ -107,4 +107,35 @@ class MutexTest : TestBase() { assertFalse(mutex.holdsLock(firstOwner)) assertFalse(mutex.holdsLock(secondOwner)) } + + @Test + fun testUnlockWithNullOwner() { + val owner = Any() + val mutex = Mutex() + assertTrue(mutex.tryLock(owner)) + assertFailsWith { mutex.unlock(Any()) } + mutex.unlock(null) + assertFalse(mutex.holdsLock(owner)) + assertFalse(mutex.isLocked) + } + + @Test + fun testUnlockWithoutOwnerWithLockedQueue() = runTest { + val owner = Any() + val owner2 = Any() + val mutex = Mutex() + assertTrue(mutex.tryLock(owner)) + expect(1) + launch { + expect(2) + mutex.lock(owner2) + } + yield() + expect(3) + assertFailsWith { mutex.unlock(owner2) } + mutex.unlock() + assertFalse(mutex.holdsLock(owner)) + assertTrue(mutex.holdsLock(owner2)) + finish(4) + } } diff --git a/kotlinx-coroutines-core/concurrent/test/selects/SelectChannelStressTest.kt b/kotlinx-coroutines-core/concurrent/test/selects/SelectChannelStressTest.kt index f464912e81..0b687a49c6 100644 --- a/kotlinx-coroutines-core/concurrent/test/selects/SelectChannelStressTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/selects/SelectChannelStressTest.kt @@ -67,9 +67,5 @@ class SelectChannelStressTest: TestBase() { finish(iterations + 2) } - internal fun SelectBuilder.default(block: suspend () -> R) { - this as SelectBuilderImpl // type assertion - if (!trySelect()) return - block.startCoroutineUnintercepted(this) - } + internal fun SelectBuilder.default(block: suspend () -> R) = onTimeout(0, block) } diff --git a/kotlinx-coroutines-core/concurrent/test/selects/SelectMutexStressTest.kt b/kotlinx-coroutines-core/concurrent/test/selects/SelectMutexStressTest.kt index 8f649c2fb8..9395a98ba7 100644 --- a/kotlinx-coroutines-core/concurrent/test/selects/SelectMutexStressTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/selects/SelectMutexStressTest.kt @@ -28,7 +28,6 @@ class SelectMutexStressTest : TestBase() { yield() // so it can cleanup after itself } assertTrue(mutex.isLocked) - assertTrue(mutex.isLockedEmptyQueueState) finish(n + 2) } -} \ No newline at end of file +} diff --git a/kotlinx-coroutines-core/js/src/internal/LinkedList.kt b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt index d8c07f4e19..7286496b4b 100644 --- a/kotlinx-coroutines-core/js/src/internal/LinkedList.kt +++ b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt @@ -6,6 +6,8 @@ package kotlinx.coroutines.internal +import kotlinx.coroutines.* + private typealias Node = LinkedListNode /** @suppress **This is unstable API and it is subject to change.** */ @Suppress("NO_ACTUAL_CLASS_MEMBER_FOR_EXPECTED_CLASS") // :TODO: Remove when fixed: https://youtrack.jetbrains.com/issue/KT-23703 @@ -15,7 +17,7 @@ public actual typealias LockFreeLinkedListNode = LinkedListNode public actual typealias LockFreeLinkedListHead = LinkedListHead /** @suppress **This is unstable API and it is subject to change.** */ -public open class LinkedListNode { +public open class LinkedListNode : DisposableHandle { @PublishedApi internal var _next = this @PublishedApi internal var _prev = this @PublishedApi internal var _removed: Boolean = false @@ -42,6 +44,10 @@ public open class LinkedListNode { return removeImpl() } + override fun dispose() { + remove() + } + @PublishedApi internal fun removeImpl(): Boolean { if (_removed) return false diff --git a/kotlinx-coroutines-core/jvm/src/channels/Actor.kt b/kotlinx-coroutines-core/jvm/src/channels/Actor.kt index 748f52833f..76d8aae3eb 100644 --- a/kotlinx-coroutines-core/jvm/src/channels/Actor.kt +++ b/kotlinx-coroutines-core/jvm/src/channels/Actor.kt @@ -149,8 +149,7 @@ private class LazyActorCoroutine( parentContext: CoroutineContext, channel: Channel, block: suspend ActorScope.() -> Unit -) : ActorCoroutine(parentContext, channel, active = false), - SelectClause2> { +) : ActorCoroutine(parentContext, channel, active = false) { private var continuation = block.createCoroutineUnintercepted(this, this) @@ -182,12 +181,14 @@ private class LazyActorCoroutine( return closed } - override val onSend: SelectClause2> - get() = this + override val onSend: SelectClause2> get() = SelectClause2Impl( + clauseObject = this, + regFunc = LazyActorCoroutine<*>::onSendRegFunction as RegistrationFunction, + processResFunc = super.onSend.processResFunc + ) - // registerSelectSend - override fun registerSelectClause2(select: SelectInstance, param: E, block: suspend (SendChannel) -> R) { - start() - super.onSend.registerSelectClause2(select, param, block) + private fun onSendRegFunction(select: SelectInstance<*>, element: Any?) { + onStart() + super.onSend.regFunc(this, select, element) } } diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectJoin.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectJoin.txt index 3bfd08e590..049002f58e 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectJoin.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectJoin.txt @@ -1,7 +1,8 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$doSelect$2$1.invokeSuspend(StackTraceRecoverySelectTest.kt) (Coroutine boundary) + at kotlinx.coroutines.selects.SelectImplementation.processResultAndInvokeBlockRecoveringException(Select.kt) at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$testSelectJoin$1.invokeSuspend(StackTraceRecoverySelectTest.kt) Caused by: kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$doSelect$2$1.invokeSuspend(StackTraceRecoverySelectTest.kt) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt) + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt) \ No newline at end of file diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectOnReceive.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectOnReceive.txt new file mode 100644 index 0000000000..d00ea5b0a2 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectOnReceive.txt @@ -0,0 +1,32 @@ +kotlinx.coroutines.channels.ClosedReceiveChannelException: Channel was closed + at kotlinx.coroutines.channels.Closed.getReceiveException(AbstractChannel.kt) + at kotlinx.coroutines.channels.AbstractChannel.processResultSelectReceive(AbstractChannel.kt) + at kotlinx.coroutines.channels.AbstractChannel.access$processResultSelectReceive(AbstractChannel.kt) + at kotlinx.coroutines.channels.AbstractChannel$onReceive$2.invoke(AbstractChannel.kt) + at kotlinx.coroutines.channels.AbstractChannel$onReceive$2.invoke(AbstractChannel.kt) + at kotlinx.coroutines.selects.SelectImplementation$ClauseData.processResult(Select.kt) + at kotlinx.coroutines.selects.SelectImplementation.processResultAndInvokeBlockRecoveringException(Select.kt) + at kotlinx.coroutines.selects.SelectImplementation.complete(Select.kt) + at kotlinx.coroutines.selects.SelectImplementation.doSelect$suspendImpl(Select.kt) + at kotlinx.coroutines.selects.SelectImplementation.doSelect(Select.kt) + at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest.doSelectOnReceive(StackTraceRecoverySelectTest.kt) + at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest.access$doSelectOnReceive(StackTraceRecoverySelectTest.kt) + at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$testSelectOnReceive$1.invokeSuspend(StackTraceRecoverySelectTest.kt) + (Coroutine boundary) + at kotlinx.coroutines.selects.SelectImplementation.processResultAndInvokeBlockRecoveringException(Select.kt) + at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$testSelectOnReceive$1.invokeSuspend(StackTraceRecoverySelectTest.kt) +Caused by: kotlinx.coroutines.channels.ClosedReceiveChannelException: Channel was closed + at kotlinx.coroutines.channels.Closed.getReceiveException(AbstractChannel.kt) + at kotlinx.coroutines.channels.AbstractChannel.processResultSelectReceive(AbstractChannel.kt) + at kotlinx.coroutines.channels.AbstractChannel.access$processResultSelectReceive(AbstractChannel.kt) + at kotlinx.coroutines.channels.AbstractChannel$onReceive$2.invoke(AbstractChannel.kt) + at kotlinx.coroutines.channels.AbstractChannel$onReceive$2.invoke(AbstractChannel.kt) + at kotlinx.coroutines.selects.SelectImplementation$ClauseData.processResult(Select.kt) + at kotlinx.coroutines.selects.SelectImplementation.processResultAndInvokeBlockRecoveringException(Select.kt) + at kotlinx.coroutines.selects.SelectImplementation.complete(Select.kt) + at kotlinx.coroutines.selects.SelectImplementation.doSelect$suspendImpl(Select.kt) + at kotlinx.coroutines.selects.SelectImplementation.doSelect(Select.kt) + at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest.doSelectOnReceive(StackTraceRecoverySelectTest.kt) + at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest.access$doSelectOnReceive(StackTraceRecoverySelectTest.kt) + at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$testSelectOnReceive$1.invokeSuspend(StackTraceRecoverySelectTest.kt) + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt) \ No newline at end of file diff --git a/kotlinx-coroutines-core/jvm/test/MutexCancellationStressTest.kt b/kotlinx-coroutines-core/jvm/test/MutexCancellationStressTest.kt new file mode 100644 index 0000000000..9e676e9a60 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/MutexCancellationStressTest.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import kotlinx.coroutines.internal.* +import kotlinx.coroutines.selects.* +import kotlinx.coroutines.sync.* +import org.junit.* +import java.util.concurrent.* + +class MutexCancellationStressTest : TestBase() { + @Test + fun testStressCancellationDoesNotBreakMutex() = runTest { + val mutex = Mutex() + val mutexJobNumber = 3 + val mutexOwners = Array(mutexJobNumber) { "$it" } + val dispatcher = Executors.newFixedThreadPool(mutexJobNumber + 2).asCoroutineDispatcher() + var counter = 0 + val counterLocal = Array(mutexJobNumber) { LocalAtomicInt(0) } + val completed = LocalAtomicInt(0) + val mutexJobLauncher: (jobNumber: Int) -> Job = { jobId -> + val coroutineName = "MutexJob-$jobId" + launch(dispatcher + CoroutineName(coroutineName)) { + while (completed.value == 0) { + mutex.holdsLock(mutexOwners[(jobId + 1) % mutexJobNumber]) + if (mutex.tryLock(mutexOwners[jobId])) { + counterLocal[jobId].incrementAndGet() + counter++ + mutex.unlock(mutexOwners[jobId]) + } + mutex.withLock(mutexOwners[jobId]) { + counterLocal[jobId].incrementAndGet() + counter++ + } + select { + mutex.onLock(mutexOwners[jobId]) { + counterLocal[jobId].incrementAndGet() + counter++ + mutex.unlock(mutexOwners[jobId]) + } + } + } + } + } + val mutexJobs = (0 until mutexJobNumber).map { mutexJobLauncher(it) }.toMutableList() + val checkProgressJob = launch(dispatcher + CoroutineName("checkProgressJob")) { + var lastCounterLocalSnapshot = (0 until mutexJobNumber).map { 0 } + while (completed.value == 0) { + delay(500) + val c = counterLocal.map { it.value } + for (i in 0 until mutexJobNumber) { + assert(c[i] > lastCounterLocalSnapshot[i]) { "No progress in MutexJob-$i" } + } + lastCounterLocalSnapshot = c + } + } + val cancellationJob = launch(dispatcher + CoroutineName("cancellationJob")) { + var cancellingJobId = 0 + while (completed.value == 0) { + val jobToCancel = mutexJobs.removeFirst() + jobToCancel.cancelAndJoin() + mutexJobs += mutexJobLauncher(cancellingJobId) + cancellingJobId = (cancellingJobId + 1) % mutexJobNumber + } + } + delay(2000L * stressTestMultiplier) + completed.value = 1 + cancellationJob.join() + mutexJobs.forEach { it.join() } + checkProgressJob.join() + check(counter == counterLocal.sumOf { it.value }) + dispatcher.close() + } +} diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementSelectOldStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementSelectOldStressTest.kt new file mode 100644 index 0000000000..1b4b83eb53 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementSelectOldStressTest.kt @@ -0,0 +1,254 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.channels + +import kotlinx.atomicfu.* +import kotlinx.coroutines.* +import kotlinx.coroutines.selects.* +import org.junit.After +import org.junit.Test +import org.junit.runner.* +import org.junit.runners.* +import kotlin.random.Random +import kotlin.test.* + +/** + * Tests resource transfer via channel send & receive operations, including their select versions, + * using `onUndeliveredElement` to detect lost resources and close them properly. + */ +@RunWith(Parameterized::class) +class ChannelUndeliveredElementSelectOldStressTest(private val kind: TestChannelKind) : TestBase() { + companion object { + @Parameterized.Parameters(name = "{0}") + @JvmStatic + fun params(): Collection> = + TestChannelKind.values() + .filter { !it.viaBroadcast } + .map { arrayOf(it) } + } + + private val iterationDurationMs = 100L + private val testIterations = 20 * stressTestMultiplier // 2 sec + + private val dispatcher = newFixedThreadPoolContext(2, "ChannelAtomicCancelStressTest") + private val scope = CoroutineScope(dispatcher) + + private val channel = kind.create { it.failedToDeliver() } + private val senderDone = Channel(1) + private val receiverDone = Channel(1) + + @Volatile + private var lastReceived = -1L + + private var stoppedSender = 0L + private var stoppedReceiver = 0L + + private var sentCnt = 0L // total number of send attempts + private var receivedCnt = 0L // actually received successfully + private var dupCnt = 0L // duplicates (should never happen) + private val failedToDeliverCnt = atomic(0L) // out of sent + + private val modulo = 1 shl 25 + private val mask = (modulo - 1).toLong() + private val sentStatus = ItemStatus() // 1 - send norm, 2 - send select, +2 - did not throw exception + private val receivedStatus = ItemStatus() // 1-6 received + private val failedStatus = ItemStatus() // 1 - failed + + lateinit var sender: Job + lateinit var receiver: Job + + @After + fun tearDown() { + dispatcher.close() + } + + private inline fun cancellable(done: Channel, block: () -> Unit) { + try { + block() + } finally { + if (!done.trySend(true).isSuccess) + error(IllegalStateException("failed to offer to done channel")) + } + } + + @Test + fun testAtomicCancelStress() = runBlocking { + println("=== ChannelAtomicCancelStressTest $kind") + var nextIterationTime = System.currentTimeMillis() + iterationDurationMs + var iteration = 0 + launchSender() + launchReceiver() + while (!hasError()) { + if (System.currentTimeMillis() >= nextIterationTime) { + nextIterationTime += iterationDurationMs + iteration++ + verify(iteration) + if (iteration % 10 == 0) printProgressSummary(iteration) + if (iteration >= testIterations) break + launchSender() + launchReceiver() + } + when (Random.nextInt(3)) { + 0 -> { // cancel & restart sender + stopSender() + launchSender() + } + 1 -> { // cancel & restart receiver + stopReceiver() + launchReceiver() + } + 2 -> yield() // just yield (burn a little time) + } + } + } + + private suspend fun verify(iteration: Int) { + stopSender() + drainReceiver() + stopReceiver() + try { + assertEquals(0, dupCnt) + assertEquals(sentCnt - failedToDeliverCnt.value, receivedCnt) + } catch (e: Throwable) { + printProgressSummary(iteration) + printErrorDetails() + throw e + } + sentStatus.clear() + receivedStatus.clear() + failedStatus.clear() + } + + private fun printProgressSummary(iteration: Int) { + println("--- ChannelAtomicCancelStressTest $kind -- $iteration of $testIterations") + println(" Sent $sentCnt times to channel") + println(" Received $receivedCnt times from channel") + println(" Failed to deliver ${failedToDeliverCnt.value} times") + println(" Stopped sender $stoppedSender times") + println(" Stopped receiver $stoppedReceiver times") + println(" Duplicated $dupCnt deliveries") + } + + private fun printErrorDetails() { + val min = minOf(sentStatus.min, receivedStatus.min, failedStatus.min) + val max = maxOf(sentStatus.max, receivedStatus.max, failedStatus.max) + for (x in min..max) { + val sentCnt = if (sentStatus[x] != 0) 1 else 0 + val receivedCnt = if (receivedStatus[x] != 0) 1 else 0 + val failedToDeliverCnt = failedStatus[x] + if (sentCnt - failedToDeliverCnt != receivedCnt) { + println("!!! Error for value $x: " + + "sentStatus=${sentStatus[x]}, " + + "receivedStatus=${receivedStatus[x]}, " + + "failedStatus=${failedStatus[x]}" + ) + } + } + } + + + private fun launchSender() { + sender = scope.launch(start = CoroutineStart.ATOMIC) { + cancellable(senderDone) { + var counter = 0 + while (true) { + val trySendData = Data(sentCnt++) + sentStatus[trySendData.x] = 1 + selectOld { channel.onSend(trySendData) {} } + sentStatus[trySendData.x] = 3 + when { + // must artificially slow down LINKED_LIST sender to avoid overwhelming receiver and going OOM + kind == TestChannelKind.LINKED_LIST -> while (sentCnt > lastReceived + 100) yield() + // yield periodically to check cancellation on conflated channels + kind.isConflated -> if (counter++ % 100 == 0) yield() + } + } + } + } + } + + private suspend fun stopSender() { + stoppedSender++ + sender.cancelAndJoin() + senderDone.receive() + } + + private fun launchReceiver() { + receiver = scope.launch(start = CoroutineStart.ATOMIC) { + cancellable(receiverDone) { + while (true) { + selectOld { + channel.onReceive { receivedData -> + receivedData.onReceived() + receivedCnt++ + val received = receivedData.x + if (received <= lastReceived) + dupCnt++ + lastReceived = received + receivedStatus[received] = 1 + } + } + } + } + } + } + + private suspend fun drainReceiver() { + while (!channel.isEmpty) yield() // burn time until receiver gets it all + } + + private suspend fun stopReceiver() { + stoppedReceiver++ + receiver.cancelAndJoin() + receiverDone.receive() + } + + private inner class Data(val x: Long) { + private val firstFailedToDeliverOrReceivedCallTrace = atomic(null) + + fun failedToDeliver() { + val trace = if (TRACING_ENABLED) Exception("First onUndeliveredElement() call") else DUMMY_TRACE_EXCEPTION + if (firstFailedToDeliverOrReceivedCallTrace.compareAndSet(null, trace)) { + failedToDeliverCnt.incrementAndGet() + failedStatus[x] = 1 + return + } + throw IllegalStateException("onUndeliveredElement()/onReceived() notified twice", firstFailedToDeliverOrReceivedCallTrace.value!!) + } + + fun onReceived() { + val trace = if (TRACING_ENABLED) Exception("First onReceived() call") else DUMMY_TRACE_EXCEPTION + if (firstFailedToDeliverOrReceivedCallTrace.compareAndSet(null, trace)) return + throw IllegalStateException("onUndeliveredElement()/onReceived() notified twice", firstFailedToDeliverOrReceivedCallTrace.value!!) + } + } + + inner class ItemStatus { + private val a = ByteArray(modulo) + private val _min = atomic(Long.MAX_VALUE) + private val _max = atomic(-1L) + + val min: Long get() = _min.value + val max: Long get() = _max.value + + operator fun set(x: Long, value: Int) { + a[(x and mask).toInt()] = value.toByte() + _min.update { y -> minOf(x, y) } + _max.update { y -> maxOf(x, y) } + } + + operator fun get(x: Long): Int = a[(x and mask).toInt()].toInt() + + fun clear() { + if (_max.value < 0) return + for (x in _min.value.._max.value) a[(x and mask).toInt()] = 0 + _min.value = Long.MAX_VALUE + _max.value = -1L + } + } +} + +private const val TRACING_ENABLED = false // Change to `true` to enable the tracing +private val DUMMY_TRACE_EXCEPTION = Exception("The tracing is disabled; please enable it by changing the `TRACING_ENABLED` constant to `true`.") diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt index 1233432615..335980daaf 100644 --- a/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt @@ -176,7 +176,7 @@ class ChannelUndeliveredElementStressTest(private val kind: TestChannelKind) : T private suspend fun stopSender() { stoppedSender++ - sender.cancel() + sender.cancelAndJoin() senderDone.receive() } @@ -198,6 +198,7 @@ class ChannelUndeliveredElementStressTest(private val kind: TestChannelKind) : T } else -> error("cannot happen") } + receivedData.onReceived() receivedCnt++ val received = receivedData.x if (received <= lastReceived) @@ -220,12 +221,22 @@ class ChannelUndeliveredElementStressTest(private val kind: TestChannelKind) : T } private inner class Data(val x: Long) { - private val failedToDeliver = atomic(false) + private val firstFailedToDeliverOrReceivedCallTrace = atomic(null) fun failedToDeliver() { - check(failedToDeliver.compareAndSet(false, true)) { "onUndeliveredElement notified twice" } - failedToDeliverCnt.incrementAndGet() - failedStatus[x] = 1 + val trace = if (TRACING_ENABLED) Exception("First onUndeliveredElement() call") else DUMMY_TRACE_EXCEPTION + if (firstFailedToDeliverOrReceivedCallTrace.compareAndSet(null, trace)) { + failedToDeliverCnt.incrementAndGet() + failedStatus[x] = 1 + return + } + throw IllegalStateException("onUndeliveredElement()/onReceived() notified twice", firstFailedToDeliverOrReceivedCallTrace.value!!) + } + + fun onReceived() { + val trace = if (TRACING_ENABLED) Exception("First onReceived() call") else DUMMY_TRACE_EXCEPTION + if (firstFailedToDeliverOrReceivedCallTrace.compareAndSet(null, trace)) return + throw IllegalStateException("onUndeliveredElement()/onReceived() notified twice", firstFailedToDeliverOrReceivedCallTrace.value!!) } } @@ -253,3 +264,6 @@ class ChannelUndeliveredElementStressTest(private val kind: TestChannelKind) : T } } } + +private const val TRACING_ENABLED = false // Change to `true` to enable the tracing +private val DUMMY_TRACE_EXCEPTION = Exception("The tracing is disabled; please enable it by changing the `TRACING_ENABLED` constant to `true`.") diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoverySelectTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoverySelectTest.kt index 0d7648c54d..0efa252e18 100644 --- a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoverySelectTest.kt +++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoverySelectTest.kt @@ -5,6 +5,7 @@ package kotlinx.coroutines.exceptions import kotlinx.coroutines.* +import kotlinx.coroutines.channels.* import kotlinx.coroutines.selects.* import org.junit.* import org.junit.rules.* @@ -27,7 +28,7 @@ class StackTraceRecoverySelectTest : TestBase() { val job = CompletableDeferred(Unit) return select { job.onJoin { - yield() // Hide the stackstrace + yield() // Hide the stacktrace expect(2) throw RecoverableTestException() } @@ -50,4 +51,21 @@ class StackTraceRecoverySelectTest : TestBase() { } } } + + @Test + fun testSelectOnReceive() = runTest { + val c = Channel() + c.close() + val result = kotlin.runCatching { doSelectOnReceive(c) } + verifyStackTrace("select/${name.methodName}", result.exceptionOrNull()!!) + } + + private suspend fun doSelectOnReceive(c: Channel) { + // The channel is closed, should throw an exception + select { + c.onReceive { + expectUnreached() + } + } + } } diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/ChannelsLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/ChannelsLincheckTest.kt index 74cc17836b..46611b792b 100644 --- a/kotlinx-coroutines-core/jvm/test/lincheck/ChannelsLincheckTest.kt +++ b/kotlinx-coroutines-core/jvm/test/lincheck/ChannelsLincheckTest.kt @@ -70,8 +70,7 @@ abstract class ChannelLincheckTestBase( else false } - // TODO: this operation should be (and can be!) linearizable, but is not - // @Operation + @Operation(promptCancellation = true) suspend fun sendViaSelect(@Param(name = "value") value: Int): Any = try { select { c.onSend(value) {} } } catch (e: NumberedCancellationException) { @@ -91,8 +90,7 @@ abstract class ChannelLincheckTestBase( .onSuccess { return it } .onFailure { return if (it is NumberedCancellationException) it.testResult else null } - // TODO: this operation should be (and can be!) linearizable, but is not - // @Operation + @Operation(promptCancellation = true) suspend fun receiveViaSelect(): Any = try { select { c.onReceive { it } } } catch (e: NumberedCancellationException) { diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt index a278985fdd..6e6609d6f6 100644 --- a/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt +++ b/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt @@ -5,28 +5,40 @@ package kotlinx.coroutines.lincheck import kotlinx.coroutines.* +import kotlinx.coroutines.selects.* import kotlinx.coroutines.sync.* import org.jetbrains.kotlinx.lincheck.* +import org.jetbrains.kotlinx.lincheck.annotations.* import org.jetbrains.kotlinx.lincheck.annotations.Operation -import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.* +import org.jetbrains.kotlinx.lincheck.paramgen.* +@Param(name = "owner", gen = IntGen::class, conf = "0:2") class MutexLincheckTest : AbstractLincheckTest() { private val mutex = Mutex() @Operation - fun tryLock() = mutex.tryLock() + fun tryLock(@Param(name = "owner") owner: Int) = mutex.tryLock(owner.asOwnerOrNull) @Operation(promptCancellation = true) - suspend fun lock() = mutex.lock() + suspend fun lock(@Param(name = "owner") owner: Int) = mutex.lock(owner.asOwnerOrNull) + + @Operation(promptCancellation = true) + suspend fun onLock(@Param(name = "owner") owner: Int) = select { mutex.onLock(owner.asOwnerOrNull) {} } @Operation(handleExceptionsAsResult = [IllegalStateException::class]) - fun unlock() = mutex.unlock() + fun unlock(@Param(name = "owner") owner: Int) = mutex.unlock(owner.asOwnerOrNull) + + @Operation + fun isLocked() = mutex.isLocked + + @Operation + fun holdsLock(@Param(name = "owner") owner: Int) = mutex.holdsLock(owner) override fun > O.customize(isStressTest: Boolean): O = actorsBefore(0) - override fun ModelCheckingOptions.customize(isStressTest: Boolean) = - checkObstructionFreedom() + // state[i] == true <=> mutex.holdsLock(i) with the only exception for 0 that specifies `null`. + override fun extractState() = (1..2).map { mutex.holdsLock(it) } + mutex.isLocked - override fun extractState() = mutex.isLocked + private val Int.asOwnerOrNull get() = if (this == 0) null else this } diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/SemaphoreLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/SemaphoreLincheckTest.kt index 2b471d7f26..09dee56c51 100644 --- a/kotlinx-coroutines-core/jvm/test/lincheck/SemaphoreLincheckTest.kt +++ b/kotlinx-coroutines-core/jvm/test/lincheck/SemaphoreLincheckTest.kt @@ -11,7 +11,7 @@ import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.* abstract class SemaphoreLincheckTestBase(permits: Int) : AbstractLincheckTest() { - private val semaphore = Semaphore(permits) + private val semaphore = SemaphoreImpl(permits = permits, acquiredPermits = 0) @Operation fun tryAcquire() = semaphore.tryAcquire() @@ -32,4 +32,4 @@ abstract class SemaphoreLincheckTestBase(permits: Int) : AbstractLincheckTest() } class Semaphore1LincheckTest : SemaphoreLincheckTestBase(1) -class Semaphore2LincheckTest : SemaphoreLincheckTestBase(2) \ No newline at end of file +class Semaphore2LincheckTest : SemaphoreLincheckTestBase(2) diff --git a/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt b/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt index fd1c288238..f6c169b4eb 100644 --- a/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt +++ b/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt @@ -9,7 +9,6 @@ import kotlinx.coroutines.* import kotlinx.coroutines.debug.* import kotlinx.coroutines.selects.* import org.junit.* -import org.junit.Ignore import org.junit.Test import java.util.concurrent.* import kotlin.test.* diff --git a/reactive/kotlinx-coroutines-reactive/api/kotlinx-coroutines-reactive.api b/reactive/kotlinx-coroutines-reactive/api/kotlinx-coroutines-reactive.api index b52df185db..4772be0579 100644 --- a/reactive/kotlinx-coroutines-reactive/api/kotlinx-coroutines-reactive.api +++ b/reactive/kotlinx-coroutines-reactive/api/kotlinx-coroutines-reactive.api @@ -49,7 +49,7 @@ public final class kotlinx/coroutines/reactive/PublishKt { public static final fun publishInternal (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lorg/reactivestreams/Publisher; } -public final class kotlinx/coroutines/reactive/PublisherCoroutine : kotlinx/coroutines/AbstractCoroutine, kotlinx/coroutines/channels/ProducerScope, kotlinx/coroutines/selects/SelectClause2, org/reactivestreams/Subscription { +public final class kotlinx/coroutines/reactive/PublisherCoroutine : kotlinx/coroutines/AbstractCoroutine, kotlinx/coroutines/channels/ProducerScope, org/reactivestreams/Subscription { public fun (Lkotlin/coroutines/CoroutineContext;Lorg/reactivestreams/Subscriber;Lkotlin/jvm/functions/Function2;)V public fun cancel ()V public fun close (Ljava/lang/Throwable;)Z @@ -60,7 +60,6 @@ public final class kotlinx/coroutines/reactive/PublisherCoroutine : kotlinx/coro public fun isClosedForSend ()Z public fun offer (Ljava/lang/Object;)Z public synthetic fun onCompleted (Ljava/lang/Object;)V - public fun registerSelectClause2 (Lkotlinx/coroutines/selects/SelectInstance;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V public fun request (J)V public fun send (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public fun trySend-JP2dKIU (Ljava/lang/Object;)Ljava/lang/Object; diff --git a/reactive/kotlinx-coroutines-reactive/src/Publish.kt b/reactive/kotlinx-coroutines-reactive/src/Publish.kt index 1b8683ce64..ae85e4186a 100644 --- a/reactive/kotlinx-coroutines-reactive/src/Publish.kt +++ b/reactive/kotlinx-coroutines-reactive/src/Publish.kt @@ -6,7 +6,6 @@ package kotlinx.coroutines.reactive import kotlinx.atomicfu.* import kotlinx.coroutines.* import kotlinx.coroutines.channels.* -import kotlinx.coroutines.intrinsics.* import kotlinx.coroutines.selects.* import kotlinx.coroutines.sync.* import org.reactivestreams.* @@ -69,11 +68,9 @@ public class PublisherCoroutine( parentContext: CoroutineContext, private val subscriber: Subscriber, private val exceptionOnCancelHandler: (Throwable, CoroutineContext) -> Unit -) : AbstractCoroutine(parentContext, false, true), ProducerScope, Subscription, SelectClause2> { +) : AbstractCoroutine(parentContext, false, true), ProducerScope, Subscription { override val channel: SendChannel get() = this - // Mutex is locked when either nRequested == 0 or while subscriber.onXXX is being invoked - private val mutex = Mutex(locked = true) private val _nRequested = atomic(0L) // < 0 when closed (CLOSED or SIGNALLED) @Volatile @@ -84,6 +81,42 @@ public class PublisherCoroutine( override fun invokeOnClose(handler: (Throwable?) -> Unit): Nothing = throw UnsupportedOperationException("PublisherCoroutine doesn't support invokeOnClose") + // Mutex is locked when either nRequested == 0 or while subscriber.onXXX is being invoked + private val mutex: Mutex = Mutex(locked = true) + + @Suppress("UNCHECKED_CAST", "INVISIBLE_MEMBER") + override val onSend: SelectClause2> get() = SelectClause2Impl( + clauseObject = this, + regFunc = PublisherCoroutine<*>::registerSelectForSend as RegistrationFunction, + processResFunc = PublisherCoroutine<*>::processResultSelectSend as ProcessResultFunction + ) + + @Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER") + private fun registerSelectForSend(select: SelectInstance<*>, element: Any?) { + // Try to acquire the mutex and complete in the registration phase. + if (mutex.tryLock()) { + select.selectInRegistrationPhase(Unit) + return + } + // Start a new coroutine that waits for the mutex, invoking `trySelect(..)` after that. + // Please note that at the point of the `trySelect(..)` invocation the corresponding + // `select` can still be in the registration phase, making this `trySelect(..)` bound to fail. + // In this case, the `onSend` clause will be re-registered, which alongside with the mutex + // manipulation makes the resulting solution obstruction-free. + launch { + mutex.lock() + if (!select.trySelect(this@PublisherCoroutine, Unit)) { + mutex.unlock() + } + } + } + + @Suppress("RedundantNullableReturnType", "UNUSED_PARAMETER", "UNCHECKED_CAST") + private fun processResultSelectSend(element: Any?, selectResult: Any?): Any? { + doLockedNext(element as T)?.let { throw it } + return this@PublisherCoroutine + } + override fun trySend(element: T): ChannelResult = if (!mutex.tryLock()) { ChannelResult.failure() @@ -99,29 +132,6 @@ public class PublisherCoroutine( doLockedNext(element)?.let { throw it } } - override val onSend: SelectClause2> - get() = this - - // registerSelectSend - @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") - override fun registerSelectClause2(select: SelectInstance, element: T, block: suspend (SendChannel) -> R) { - val clause = suspend { - doLockedNext(element)?.let { throw it } - block(this) - } - - launch(start = CoroutineStart.UNDISPATCHED) { - mutex.lock() - // Already selected -- bail out - if (!select.trySelect()) { - mutex.unlock() - return@launch - } - - clause.startCoroutineCancellable(select.completion) - } - } - /* * This code is not trivial because of the following properties: * 1. It ensures conformance to the reactive specification that mandates that onXXX invocations should not @@ -214,7 +224,7 @@ public class PublisherCoroutine( * We have to recheck `isCompleted` after `unlock` anyway. */ mutex.unlock() - // check isCompleted and and try to regain lock to signal completion + // check isCompleted and try to regain lock to signal completion if (isCompleted && mutex.tryLock()) { doLockedSignalCompleted(completionCause, completionCauseHandled) } diff --git a/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt b/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt index 90e770bb4f..57727fbb81 100644 --- a/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt +++ b/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt @@ -10,7 +10,6 @@ import kotlinx.atomicfu.* import kotlinx.coroutines.* import kotlinx.coroutines.channels.* import kotlinx.coroutines.internal.* -import kotlinx.coroutines.intrinsics.* import kotlinx.coroutines.selects.* import kotlinx.coroutines.sync.* import kotlin.coroutines.* @@ -58,12 +57,9 @@ private const val SIGNALLED = -2 // already signalled subscriber onCompleted/on private class RxObservableCoroutine( parentContext: CoroutineContext, private val subscriber: ObservableEmitter -) : AbstractCoroutine(parentContext, false, true), ProducerScope, SelectClause2> { +) : AbstractCoroutine(parentContext, false, true), ProducerScope { override val channel: SendChannel get() = this - // Mutex is locked while subscriber.onXXX is being invoked - private val mutex = Mutex() - private val _signal = atomic(OPEN) override val isClosedForSend: Boolean get() = !isActive @@ -71,6 +67,42 @@ private class RxObservableCoroutine( override fun invokeOnClose(handler: (Throwable?) -> Unit) = throw UnsupportedOperationException("RxObservableCoroutine doesn't support invokeOnClose") + // Mutex is locked when either nRequested == 0 or while subscriber.onXXX is being invoked + private val mutex: Mutex = Mutex() + + @Suppress("UNCHECKED_CAST", "INVISIBLE_MEMBER") + override val onSend: SelectClause2> get() = SelectClause2Impl( + clauseObject = this, + regFunc = RxObservableCoroutine<*>::registerSelectForSend as RegistrationFunction, + processResFunc = RxObservableCoroutine<*>::processResultSelectSend as ProcessResultFunction + ) + + @Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER") + private fun registerSelectForSend(select: SelectInstance<*>, element: Any?) { + // Try to acquire the mutex and complete in the registration phase. + if (mutex.tryLock()) { + select.selectInRegistrationPhase(Unit) + return + } + // Start a new coroutine that waits for the mutex, invoking `trySelect(..)` after that. + // Please note that at the point of the `trySelect(..)` invocation the corresponding + // `select` can still be in the registration phase, making this `trySelect(..)` bound to fail. + // In this case, the `onSend` clause will be re-registered, which alongside with the mutex + // manipulation makes the resulting solution obstruction-free. + launch { + mutex.lock() + if (!select.trySelect(this@RxObservableCoroutine, Unit)) { + mutex.unlock() + } + } + } + + @Suppress("RedundantNullableReturnType", "UNUSED_PARAMETER", "UNCHECKED_CAST") + private fun processResultSelectSend(element: Any?, selectResult: Any?): Any? { + doLockedNext(element as T)?.let { throw it } + return this@RxObservableCoroutine + } + override fun trySend(element: T): ChannelResult = if (!mutex.tryLock()) { ChannelResult.failure() @@ -86,34 +118,6 @@ private class RxObservableCoroutine( doLockedNext(element)?.let { throw it } } - override val onSend: SelectClause2> - get() = this - - // registerSelectSend - @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") - override fun registerSelectClause2( - select: SelectInstance, - element: T, - block: suspend (SendChannel) -> R - ) { - val clause = suspend { - doLockedNext(element)?.let { throw it } - block(this) - } - - // This is the default replacement proposed in onLock replacement - launch(start = CoroutineStart.UNDISPATCHED) { - mutex.lock() - // Already selected -- bail out - if (!select.trySelect()) { - mutex.unlock() - return@launch - } - - clause.startCoroutineCancellable(select.completion) - } - } - // assert: mutex.isLocked() private fun doLockedNext(elem: T): Throwable? { // check if already closed for send diff --git a/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt b/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt index 1c5f7c0a63..5db7585fca 100644 --- a/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt +++ b/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt @@ -13,7 +13,6 @@ import kotlinx.coroutines.selects.* import kotlinx.coroutines.sync.* import kotlin.coroutines.* import kotlinx.coroutines.internal.* -import kotlinx.coroutines.intrinsics.* /** * Creates cold [observable][Observable] that will run a given [block] in a coroutine. @@ -58,12 +57,9 @@ private const val SIGNALLED = -2 // already signalled subscriber onCompleted/on private class RxObservableCoroutine( parentContext: CoroutineContext, private val subscriber: ObservableEmitter -) : AbstractCoroutine(parentContext, false, true), ProducerScope, SelectClause2> { +) : AbstractCoroutine(parentContext, false, true), ProducerScope { override val channel: SendChannel get() = this - // Mutex is locked while subscriber.onXXX is being invoked - private val mutex = Mutex() - private val _signal = atomic(OPEN) override val isClosedForSend: Boolean get() = !isActive @@ -71,6 +67,42 @@ private class RxObservableCoroutine( override fun invokeOnClose(handler: (Throwable?) -> Unit) = throw UnsupportedOperationException("RxObservableCoroutine doesn't support invokeOnClose") + // Mutex is locked when either nRequested == 0 or while subscriber.onXXX is being invoked + private val mutex: Mutex = Mutex() + + @Suppress("UNCHECKED_CAST", "INVISIBLE_MEMBER") + override val onSend: SelectClause2> get() = SelectClause2Impl( + clauseObject = this, + regFunc = RxObservableCoroutine<*>::registerSelectForSend as RegistrationFunction, + processResFunc = RxObservableCoroutine<*>::processResultSelectSend as ProcessResultFunction + ) + + @Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER") + private fun registerSelectForSend(select: SelectInstance<*>, element: Any?) { + // Try to acquire the mutex and complete in the registration phase. + if (mutex.tryLock()) { + select.selectInRegistrationPhase(Unit) + return + } + // Start a new coroutine that waits for the mutex, invoking `trySelect(..)` after that. + // Please note that at the point of the `trySelect(..)` invocation the corresponding + // `select` can still be in the registration phase, making this `trySelect(..)` bound to fail. + // In this case, the `onSend` clause will be re-registered, which alongside with the mutex + // manipulation makes the resulting solution obstruction-free. + launch { + mutex.lock() + if (!select.trySelect(this@RxObservableCoroutine, Unit)) { + mutex.unlock() + } + } + } + + @Suppress("RedundantNullableReturnType", "UNUSED_PARAMETER", "UNCHECKED_CAST") + private fun processResultSelectSend(element: Any?, selectResult: Any?): Any? { + doLockedNext(element as T)?.let { throw it } + return this@RxObservableCoroutine + } + override fun trySend(element: T): ChannelResult = if (!mutex.tryLock()) { ChannelResult.failure() @@ -86,34 +118,6 @@ private class RxObservableCoroutine( doLockedNext(element)?.let { throw it } } - override val onSend: SelectClause2> - get() = this - - // registerSelectSend - @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE") - override fun registerSelectClause2( - select: SelectInstance, - element: T, - block: suspend (SendChannel) -> R - ) { - val clause = suspend { - doLockedNext(element)?.let { throw it } - block(this) - } - - // This is the default replacement proposed in onLock replacement - launch(start = CoroutineStart.UNDISPATCHED) { - mutex.lock() - // Already selected -- bail out - if (!select.trySelect()) { - mutex.unlock() - return@launch - } - - clause.startCoroutineCancellable(select.completion) - } - } - // assert: mutex.isLocked() private fun doLockedNext(elem: T): Throwable? { // check if already closed for send From 03176c7fdec0d74b2dd39d87e18367e2a4d350dc Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 4 Aug 2022 16:52:58 +0200 Subject: [PATCH 011/106] Recover proper coverage filtering and add few more tests for legacy selects --- kotlinx-coroutines-core/build.gradle | 12 ++++---- .../common/test/selects/SelectOldTest.kt | 28 +++++++++++++++++++ 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index 8924830daa..cf462393a1 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -260,12 +260,12 @@ def commonKoverExcludes = "kotlinx.coroutines.scheduling.ExperimentalCoroutineDispatcher" // Deprecated ] -tasks.koverHtmlReport { - excludes = commonKoverExcludes -} - -tasks.koverVerify { - excludes = commonKoverExcludes +kover { + filters { + classes { + excludes.addAll(commonKoverExcludes) + } + } } task testsJar(type: Jar, dependsOn: jvmTestClasses) { diff --git a/kotlinx-coroutines-core/common/test/selects/SelectOldTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectOldTest.kt index 1f4b95d29b..34694fdea4 100644 --- a/kotlinx-coroutines-core/common/test/selects/SelectOldTest.kt +++ b/kotlinx-coroutines-core/common/test/selects/SelectOldTest.kt @@ -90,6 +90,34 @@ class SelectOldTest : TestBase() { finish(8) } + @Test + fun testSelectUnbiasedComplete() = runTest { + expect(1) + val job = Job() + job.complete() + expect(2) + val res = selectUnbiasedOld { + job.onJoin { + expect(3) + "OK" + } + } + assertEquals("OK", res) + finish(4) + } + + @Test + fun testSelectUnbiasedThrows() = runTest { + try { + select { + expect(1) + throw TestException() + } + } catch (e: TestException) { + finish(2) + } + } + @Test fun testSelectLazy() = runTest { expect(1) From a78dd11f589078eceb96439320b7dca939a20cba Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 4 Aug 2022 18:00:12 +0200 Subject: [PATCH 012/106] Introduce Java Object Layout to the codebase and write tests for core primitives (#3391) It should further simplify reasoning about new fields in core classes --- kotlinx-coroutines-core/build.gradle | 1 + .../jvm/test/MemoryFootprintTest.kt | 25 +++++++++++++++++++ 2 files changed, 26 insertions(+) create mode 100644 kotlinx-coroutines-core/jvm/test/MemoryFootprintTest.kt diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index cf462393a1..c38ed16041 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -168,6 +168,7 @@ kotlin.sourceSets { api "org.jetbrains.kotlinx:lincheck:$lincheck_version" api "org.jetbrains.kotlinx:kotlinx-knit-test:$knit_version" implementation project(":android-unit-tests") + implementation "org.openjdk.jol:jol-core:0.16" } } diff --git a/kotlinx-coroutines-core/jvm/test/MemoryFootprintTest.kt b/kotlinx-coroutines-core/jvm/test/MemoryFootprintTest.kt new file mode 100644 index 0000000000..be467cc5cd --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/MemoryFootprintTest.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import org.junit.Test +import org.openjdk.jol.info.ClassLayout +import kotlin.test.* + + +class MemoryFootprintTest : TestBase(true) { + + @Test + fun testJobLayout() = assertLayout(Job().javaClass, 24) + + @Test + fun testCancellableContinuationFootprint() = assertLayout(CancellableContinuationImpl::class.java, 48) + + private fun assertLayout(clz: Class<*>, expectedSize: Int) { + val size = ClassLayout.parseClass(clz).instanceSize() +// println(ClassLayout.parseClass(clz).toPrintable()) + assertEquals(expectedSize.toLong(), size) + } +} From 73f780c29579f86e211f73a84f6dbe9b98e1d985 Mon Sep 17 00:00:00 2001 From: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> Date: Thu, 8 Sep 2022 15:58:10 +0300 Subject: [PATCH 013/106] Fix cancelled delayed jobs not being disposed of in TestDispatcher (#3441) Fixes #3398 --- .../common/src/TestDispatcher.kt | 9 +++++++- .../jvm/test/MemoryLeakTest.kt | 23 +++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 kotlinx-coroutines-test/jvm/test/MemoryLeakTest.kt diff --git a/kotlinx-coroutines-test/common/src/TestDispatcher.kt b/kotlinx-coroutines-test/common/src/TestDispatcher.kt index 348cc2f185..9616bb0b23 100644 --- a/kotlinx-coroutines-test/common/src/TestDispatcher.kt +++ b/kotlinx-coroutines-test/common/src/TestDispatcher.kt @@ -31,7 +31,14 @@ public abstract class TestDispatcher internal constructor() : CoroutineDispatche /** @suppress */ override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) { val timedRunnable = CancellableContinuationRunnable(continuation, this) - scheduler.registerEvent(this, timeMillis, timedRunnable, continuation.context, ::cancellableRunnableIsCancelled) + val handle = scheduler.registerEvent( + this, + timeMillis, + timedRunnable, + continuation.context, + ::cancellableRunnableIsCancelled + ) + continuation.disposeOnCancellation(handle) } /** @suppress */ diff --git a/kotlinx-coroutines-test/jvm/test/MemoryLeakTest.kt b/kotlinx-coroutines-test/jvm/test/MemoryLeakTest.kt new file mode 100644 index 0000000000..705c97eae4 --- /dev/null +++ b/kotlinx-coroutines-test/jvm/test/MemoryLeakTest.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +import kotlinx.coroutines.* +import kotlinx.coroutines.test.* +import kotlin.test.* + +class MemoryLeakTest { + + @Test + fun testCancellationLeakInTestCoroutineScheduler() = runTest { + val leakingObject = Any() + val job = launch(start = CoroutineStart.UNDISPATCHED) { + delay(1) + // This code is needed to hold a reference to `leakingObject` until the job itself is weakly reachable. + leakingObject.hashCode() + } + job.cancel() + FieldWalker.assertReachableCount(1, testScheduler) { it === leakingObject } + runCurrent() + FieldWalker.assertReachableCount(0, testScheduler) { it === leakingObject } + } +} From 03e6a74d24119fa9247a8ecc09086f53cad34910 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 8 Sep 2022 16:39:58 +0300 Subject: [PATCH 014/106] Properly cancel handles returned by setTimeout in JS dispatchers (#3440) Otherwise, usage of withTimeout lead to excessive memory pressure and OOMs --- kotlinx-coroutines-core/js/src/JSDispatcher.kt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/kotlinx-coroutines-core/js/src/JSDispatcher.kt b/kotlinx-coroutines-core/js/src/JSDispatcher.kt index 603005d5a4..4b1daae353 100644 --- a/kotlinx-coroutines-core/js/src/JSDispatcher.kt +++ b/kotlinx-coroutines-core/js/src/JSDispatcher.kt @@ -47,7 +47,6 @@ internal sealed class SetTimeoutBasedDispatcher: CoroutineDispatcher(), Delay { override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) { val handle = setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, delayToInt(timeMillis)) - // Actually on cancellation, but clearTimeout is idempotent continuation.invokeOnCancellation(handler = ClearTimeout(handle).asHandler) } } @@ -64,7 +63,7 @@ internal object SetTimeoutDispatcher : SetTimeoutBasedDispatcher() { } } -private class ClearTimeout(private val handle: Int) : CancelHandler(), DisposableHandle { +private open class ClearTimeout(protected val handle: Int) : CancelHandler(), DisposableHandle { override fun dispose() { clearTimeout(handle) @@ -83,15 +82,18 @@ internal class WindowDispatcher(private val window: Window) : CoroutineDispatche override fun dispatch(context: CoroutineContext, block: Runnable) = queue.enqueue(block) override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) { - window.setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, delayToInt(timeMillis)) + val handle = window.setTimeout({ with(continuation) { resumeUndispatched(Unit) } }, delayToInt(timeMillis)) + continuation.invokeOnCancellation(handler = WindowClearTimeout(handle).asHandler) } override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle { val handle = window.setTimeout({ block.run() }, delayToInt(timeMillis)) - return object : DisposableHandle { - override fun dispose() { - window.clearTimeout(handle) - } + return WindowClearTimeout(handle) + } + + private inner class WindowClearTimeout(handle: Int) : ClearTimeout(handle) { + override fun dispose() { + window.clearTimeout(handle) } } } From 9e1eb9ecd62657f3030af2378a0bd661fd9fad06 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 8 Sep 2022 16:48:39 +0200 Subject: [PATCH 015/106] Update wording in shared-mutable-state-and-concurrency.md --- docs/topics/shared-mutable-state-and-concurrency.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/topics/shared-mutable-state-and-concurrency.md b/docs/topics/shared-mutable-state-and-concurrency.md index 99cc42bc2e..8e491b3d64 100644 --- a/docs/topics/shared-mutable-state-and-concurrency.md +++ b/docs/topics/shared-mutable-state-and-concurrency.md @@ -130,7 +130,7 @@ Completed 100000 actions in Counter = --> -This code works slower, but we still don't get "Counter = 100000" at the end, because volatile variables guarantee +This code works slower, but we still don't always get "Counter = 100000" at the end, because volatile variables guarantee linearizable (this is a technical term for "atomic") reads and writes to the corresponding variable, but do not provide atomicity of larger actions (increment in our case). From 4a44fef16e2240c8914432af31b029f45458c949 Mon Sep 17 00:00:00 2001 From: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> Date: Thu, 8 Sep 2022 18:39:46 +0300 Subject: [PATCH 016/106] Fix another potential memory leak in WorkerDispatcher (#3445) --- .../native/src/MultithreadedDispatchers.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt index a940dcbdb0..c0b82aece0 100644 --- a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt +++ b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt @@ -26,12 +26,16 @@ internal class WorkerDispatcher(name: String) : CloseableCoroutineDispatcher(), } override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) { - worker.executeAfter(timeMillis.toMicrosSafe()) { + val handle = schedule(timeMillis, Runnable { with(continuation) { resumeUndispatched(Unit) } - } + }) + continuation.disposeOnCancellation(handle) } - override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle { + override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle = + schedule(timeMillis, block) + + private fun schedule(timeMillis: Long, block: Runnable): DisposableHandle { // Workers don't have an API to cancel sent "executeAfter" block, but we are trying // to control the damage and reduce reachable objects by nulling out `block` // that may retain a lot of references, and leaving only an empty shell after a timely disposal From 4e97c83509ff0338bf5042b57c5fb00681f58323 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 9 Sep 2022 13:11:39 +0300 Subject: [PATCH 017/106] Get rid of workaround for KT-16222 that is long time fixed (#3446) * The correctness of the change is verified by ConsumeAsFlowLeakTest * Follow-up cleanup here and there --- .../common/src/CompletionState.kt | 2 +- kotlinx-coroutines-core/common/src/Yield.kt | 1 - .../common/src/channels/Channel.kt | 3 -- .../common/src/channels/ChannelCoroutine.kt | 2 +- .../common/src/channels/Channels.common.kt | 3 -- .../common/src/flow/Channels.kt | 29 ++----------------- .../src/flow/internal/AbstractSharedFlow.kt | 1 - .../common/src/flow/internal/Combine.kt | 5 +--- .../common/src/flow/terminal/Count.kt | 1 - .../common/src/internal/Concurrent.common.kt | 2 +- .../src/internal/DispatchedContinuation.kt | 5 ++-- .../common/src/selects/SelectUnbiased.kt | 1 - .../common/src/selects/WhileSelect.kt | 2 +- .../common/src/sync/Mutex.kt | 2 +- 14 files changed, 10 insertions(+), 49 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/CompletionState.kt b/kotlinx-coroutines-core/common/src/CompletionState.kt index b9042874cd..43330af460 100644 --- a/kotlinx-coroutines-core/common/src/CompletionState.kt +++ b/kotlinx-coroutines-core/common/src/CompletionState.kt @@ -40,7 +40,7 @@ internal data class CompletedWithCancellation( * or artificial [CancellationException] if no cause was provided */ internal open class CompletedExceptionally( - @JvmField public val cause: Throwable, + @JvmField val cause: Throwable, handled: Boolean = false ) { private val _handled = atomic(handled) diff --git a/kotlinx-coroutines-core/common/src/Yield.kt b/kotlinx-coroutines-core/common/src/Yield.kt index 98e210412b..db3bfa5359 100644 --- a/kotlinx-coroutines-core/common/src/Yield.kt +++ b/kotlinx-coroutines-core/common/src/Yield.kt @@ -5,7 +5,6 @@ package kotlinx.coroutines import kotlinx.coroutines.internal.* -import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* /** diff --git a/kotlinx-coroutines-core/common/src/channels/Channel.kt b/kotlinx-coroutines-core/common/src/channels/Channel.kt index b8ee0be366..5ceb515f95 100644 --- a/kotlinx-coroutines-core/common/src/channels/Channel.kt +++ b/kotlinx-coroutines-core/common/src/channels/Channel.kt @@ -448,7 +448,6 @@ public value class ChannelResult override fun toString(): String = "Closed($cause)" } - @Suppress("NOTHING_TO_INLINE") @InternalCoroutinesApi public companion object { private val failed = Failed() @@ -512,7 +511,6 @@ public inline fun ChannelResult.onFailure(action: (exception: Throwable?) contract { callsInPlace(action, InvocationKind.AT_MOST_ONCE) } - @Suppress("UNCHECKED_CAST") if (holder is ChannelResult.Failed) action(exceptionOrNull()) return this } @@ -531,7 +529,6 @@ public inline fun ChannelResult.onClosed(action: (exception: Throwable?) contract { callsInPlace(action, InvocationKind.AT_MOST_ONCE) } - @Suppress("UNCHECKED_CAST") if (holder is ChannelResult.Closed) action(exceptionOrNull()) return this } diff --git a/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt b/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt index 57b2797de6..3fcf388a67 100644 --- a/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt +++ b/kotlinx-coroutines-core/common/src/channels/ChannelCoroutine.kt @@ -7,7 +7,6 @@ package kotlinx.coroutines.channels import kotlinx.coroutines.* import kotlin.coroutines.* -@Suppress("DEPRECATION") internal open class ChannelCoroutine( parentContext: CoroutineContext, protected val _channel: Channel, @@ -17,6 +16,7 @@ internal open class ChannelCoroutine( val channel: Channel get() = this + @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") override fun cancel() { cancelInternal(defaultCancellationException()) } diff --git a/kotlinx-coroutines-core/common/src/channels/Channels.common.kt b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt index a78e2f186d..ac2e4cf6f0 100644 --- a/kotlinx-coroutines-core/common/src/channels/Channels.common.kt +++ b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt @@ -11,7 +11,6 @@ package kotlinx.coroutines.channels import kotlinx.coroutines.* import kotlinx.coroutines.selects.* import kotlin.contracts.* -import kotlin.coroutines.* import kotlin.jvm.* internal const val DEFAULT_CLOSE_MESSAGE = "Channel was closed" @@ -54,7 +53,6 @@ public inline fun BroadcastChannel.consume(block: ReceiveChannel.() ) // Warning since 1.5.0, ERROR in 1.6.0, HIDDEN in 1.7.0 @Suppress("EXTENSION_SHADOWED_BY_MEMBER") public suspend fun ReceiveChannel.receiveOrNull(): E? { - @Suppress("DEPRECATION", "UNCHECKED_CAST") return (this as ReceiveChannel).receiveOrNull() } @@ -66,7 +64,6 @@ public suspend fun ReceiveChannel.receiveOrNull(): E? { level = DeprecationLevel.ERROR ) // Warning since 1.5.0, ERROR in 1.6.0, HIDDEN in 1.7.0 public fun ReceiveChannel.onReceiveOrNull(): SelectClause1 { - @Suppress("DEPRECATION", "UNCHECKED_CAST") return (this as ReceiveChannel).onReceiveOrNull } diff --git a/kotlinx-coroutines-core/common/src/flow/Channels.kt b/kotlinx-coroutines-core/common/src/flow/Channels.kt index 51ed4270c0..aed15913df 100644 --- a/kotlinx-coroutines-core/common/src/flow/Channels.kt +++ b/kotlinx-coroutines-core/common/src/flow/Channels.kt @@ -31,35 +31,10 @@ public suspend fun FlowCollector.emitAll(channel: ReceiveChannel): Uni private suspend fun FlowCollector.emitAllImpl(channel: ReceiveChannel, consume: Boolean) { ensureActive() - // Manually inlined "consumeEach" implementation that does not use iterator but works via "receiveCatching". - // It has smaller and more efficient spilled state which also allows to implement a manual kludge to - // fix retention of the last emitted value. - // See https://youtrack.jetbrains.com/issue/KT-16222 - // See https://github.com/Kotlin/kotlinx.coroutines/issues/1333 var cause: Throwable? = null try { - while (true) { - // :KLUDGE: This "run" call is resolved to an extension function "run" and forces the size of - // spilled state to increase by an additional slot, so there are 4 object local variables spilled here - // which makes the size of spill state equal to the 4 slots that are spilled around subsequent "emit" - // call, ensuring that the previously emitted value is not retained in the state while receiving - // the next one. - // L$0 <- this - // L$1 <- channel - // L$2 <- cause - // L$3 <- this$run (actually equal to this) - val result = run { channel.receiveCatching() } - if (result.isClosed) { - result.exceptionOrNull()?.let { throw it } - break // returns normally when result.closeCause == null - } - // result is spilled here to the coroutine state and retained after the call, even though - // it is not actually needed in the next loop iteration. - // L$0 <- this - // L$1 <- channel - // L$2 <- cause - // L$3 <- result - emit(result.getOrThrow()) + for (element in channel) { + emit(element) } } catch (e: Throwable) { cause = e diff --git a/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt b/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt index e717717cc4..d263d61227 100644 --- a/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt +++ b/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt @@ -19,7 +19,6 @@ internal abstract class AbstractSharedFlowSlot { } internal abstract class AbstractSharedFlow> : SynchronizedObject() { - @Suppress("UNCHECKED_CAST") protected var slots: Array? = null // allocated when needed private set protected var nCollectors = 0 // number of allocated (!free) slots diff --git a/kotlinx-coroutines-core/common/src/flow/internal/Combine.kt b/kotlinx-coroutines-core/common/src/flow/internal/Combine.kt index c924c09025..63ea55a585 100644 --- a/kotlinx-coroutines-core/common/src/flow/internal/Combine.kt +++ b/kotlinx-coroutines-core/common/src/flow/internal/Combine.kt @@ -1,7 +1,7 @@ /* * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("UNCHECKED_CAST", "NON_APPLICABLE_CALL_FOR_BUILDER_INFERENCE") // KT-32203 +@file:Suppress("UNCHECKED_CAST") // KT-32203 package kotlinx.coroutines.flow.internal @@ -9,9 +9,6 @@ import kotlinx.coroutines.* import kotlinx.coroutines.channels.* import kotlinx.coroutines.flow.* import kotlinx.coroutines.internal.* -import kotlin.coroutines.* -import kotlin.coroutines.intrinsics.* - private typealias Update = IndexedValue @PublishedApi diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Count.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Count.kt index 5eb99fc8ef..a15567e222 100644 --- a/kotlinx-coroutines-core/common/src/flow/terminal/Count.kt +++ b/kotlinx-coroutines-core/common/src/flow/terminal/Count.kt @@ -7,7 +7,6 @@ package kotlinx.coroutines.flow -import kotlinx.coroutines.* import kotlin.jvm.* /** diff --git a/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt b/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt index fb254a0ebc..6b42dd15db 100644 --- a/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt +++ b/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt @@ -18,7 +18,7 @@ internal expect fun subscriberList(): SubscribersList internal expect class ReentrantLock() { fun tryLock(): Boolean - fun unlock(): Unit + fun unlock() } internal expect inline fun ReentrantLock.withLock(action: () -> T): T diff --git a/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt b/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt index 63e0677fad..bb2bbf6d92 100644 --- a/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt +++ b/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt @@ -207,7 +207,6 @@ internal class DispatchedContinuation( // We inline it to save an entry on the stack in cases where it shows (unconfined dispatcher) // It is used only in Continuation.resumeCancellableWith - @Suppress("NOTHING_TO_INLINE") inline fun resumeCancellableWith( result: Result, noinline onCancellation: ((cause: Throwable) -> Unit)? @@ -235,7 +234,8 @@ internal class DispatchedContinuation( } } - @Suppress("NOTHING_TO_INLINE") + // inline here is to save us an entry on the stack for the sake of better stacktraces + inline fun resumeCancelled(state: Any?): Boolean { val job = context[Job] if (job != null && !job.isActive) { @@ -247,7 +247,6 @@ internal class DispatchedContinuation( return false } - @Suppress("NOTHING_TO_INLINE") // we need it inline to save us an entry on the stack inline fun resumeUndispatchedWith(result: Result) { withContinuationContext(continuation, countOrElement) { continuation.resumeWith(result) diff --git a/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt b/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt index 0b68e411d5..5329a15a0f 100644 --- a/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt +++ b/kotlinx-coroutines-core/common/src/selects/SelectUnbiased.kt @@ -58,7 +58,6 @@ internal open class UnbiasedSelectImplementation(context: CoroutineContext) : return super.doSelect() } - @Suppress("UNCHECKED_CAST") private fun shuffleAndRegisterClauses() = try { clausesToRegister.shuffle() clausesToRegister.forEach { it.register() } diff --git a/kotlinx-coroutines-core/common/src/selects/WhileSelect.kt b/kotlinx-coroutines-core/common/src/selects/WhileSelect.kt index 98a9c67238..ccda6568ae 100644 --- a/kotlinx-coroutines-core/common/src/selects/WhileSelect.kt +++ b/kotlinx-coroutines-core/common/src/selects/WhileSelect.kt @@ -28,5 +28,5 @@ import kotlinx.coroutines.* */ @ExperimentalCoroutinesApi public suspend inline fun whileSelect(crossinline builder: SelectBuilder.() -> Unit) { - while(select(builder)) {} + while(select(builder)) { /* do nothing */ } } diff --git a/kotlinx-coroutines-core/common/src/sync/Mutex.kt b/kotlinx-coroutines-core/common/src/sync/Mutex.kt index 945bac07b5..40e4308f4e 100644 --- a/kotlinx-coroutines-core/common/src/sync/Mutex.kt +++ b/kotlinx-coroutines-core/common/src/sync/Mutex.kt @@ -165,7 +165,7 @@ internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1 lockSuspend(owner) } - private suspend fun lockSuspend(owner: Any?) = suspendCancellableCoroutineReusable { cont -> + private suspend fun lockSuspend(owner: Any?) = suspendCancellableCoroutineReusable { cont -> val contWithOwner = CancellableContinuationWithOwner(cont, owner) acquire(contWithOwner) } From 1fc01e7ed0a49d8e1cde0000727ece7ae4108f47 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 9 Sep 2022 18:15:37 +0300 Subject: [PATCH 018/106] =?UTF-8?q?Replace=20hand-rolled=20ArrayQueue=20wi?= =?UTF-8?q?th=20ArrayDeque=20in=20standard=20library=20in=20=E2=80=A6=20(#?= =?UTF-8?q?3438)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reduce hand-rolled ArrayQueue with ArrayDeque in standard library in order to (potentially) reduce dex size --- .../common/src/EventLoop.common.kt | 33 ++++++------ .../common/src/internal/ArrayQueue.kt | 52 ------------------- .../js/src/JSDispatcher.kt | 6 +-- kotlinx-coroutines-core/js/src/Window.kt | 5 +- .../js/test/MessageQueueTest.kt | 22 ++++---- 5 files changed, 32 insertions(+), 86 deletions(-) delete mode 100644 kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt diff --git a/kotlinx-coroutines-core/common/src/EventLoop.common.kt b/kotlinx-coroutines-core/common/src/EventLoop.common.kt index f2e70176b6..5308120cc8 100644 --- a/kotlinx-coroutines-core/common/src/EventLoop.common.kt +++ b/kotlinx-coroutines-core/common/src/EventLoop.common.kt @@ -36,7 +36,7 @@ internal abstract class EventLoop : CoroutineDispatcher() { * Queue used by [Dispatchers.Unconfined] tasks. * These tasks are thread-local for performance and take precedence over the rest of the queue. */ - private var unconfinedQueue: ArrayQueue>? = null + private var unconfinedQueue: ArrayDeque>? = null /** * Processes next event in this event loop. @@ -49,7 +49,7 @@ internal abstract class EventLoop : CoroutineDispatcher() { * **NOTE**: Must be invoked only from the event loop's thread * (no check for performance reasons, may be added in the future). */ - public open fun processNextEvent(): Long { + open fun processNextEvent(): Long { if (!processUnconfinedEvent()) return Long.MAX_VALUE return 0 } @@ -59,10 +59,10 @@ internal abstract class EventLoop : CoroutineDispatcher() { protected open val nextTime: Long get() { val queue = unconfinedQueue ?: return Long.MAX_VALUE - return if (queue.isEmpty) Long.MAX_VALUE else 0L + return if (queue.isEmpty()) Long.MAX_VALUE else 0L } - public fun processUnconfinedEvent(): Boolean { + fun processUnconfinedEvent(): Boolean { val queue = unconfinedQueue ?: return false val task = queue.removeFirstOrNull() ?: return false task.run() @@ -74,27 +74,27 @@ internal abstract class EventLoop : CoroutineDispatcher() { * By default, event loop implementation is thread-local and should not processed in the context * (current thread's event loop should be processed instead). */ - public open fun shouldBeProcessedFromContext(): Boolean = false + open fun shouldBeProcessedFromContext(): Boolean = false /** * Dispatches task whose dispatcher returned `false` from [CoroutineDispatcher.isDispatchNeeded] * into the current event loop. */ - public fun dispatchUnconfined(task: DispatchedTask<*>) { + fun dispatchUnconfined(task: DispatchedTask<*>) { val queue = unconfinedQueue ?: - ArrayQueue>().also { unconfinedQueue = it } + ArrayDeque>().also { unconfinedQueue = it } queue.addLast(task) } - public val isActive: Boolean + val isActive: Boolean get() = useCount > 0 - public val isUnconfinedLoopActive: Boolean + val isUnconfinedLoopActive: Boolean get() = useCount >= delta(unconfined = true) // May only be used from the event loop's thread - public val isUnconfinedQueueEmpty: Boolean - get() = unconfinedQueue?.isEmpty ?: true + val isUnconfinedQueueEmpty: Boolean + get() = unconfinedQueue?.isEmpty() ?: true private fun delta(unconfined: Boolean) = if (unconfined) (1L shl 32) else 1L @@ -200,7 +200,7 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay { } } - protected override val nextTime: Long + override val nextTime: Long get() { if (super.nextTime == 0L) return 0L val queue = _queue.value @@ -227,7 +227,7 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay { rescheduleAllDelayed() } - public override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) { + override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) { val timeNanos = delayToNanos(timeMillis) if (timeNanos < MAX_DELAY_NS) { val now = nanoTime() @@ -283,7 +283,7 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay { return nextTime } - public final override fun dispatch(context: CoroutineContext, block: Runnable) = enqueue(block) + final override fun dispatch(context: CoroutineContext, block: Runnable) = enqueue(block) open fun enqueue(task: Runnable) { if (enqueueImpl(task)) { @@ -362,7 +362,7 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay { } - public fun schedule(now: Long, delayedTask: DelayedTask) { + fun schedule(now: Long, delayedTask: DelayedTask) { when (scheduleImpl(now, delayedTask)) { SCHEDULE_OK -> if (shouldUnpark(delayedTask)) unpark() SCHEDULE_COMPLETED -> reschedule(now, delayedTask) @@ -481,7 +481,6 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay { final override fun dispose() { val heap = _heap if (heap === DISPOSED_TASK) return // already disposed - @Suppress("UNCHECKED_CAST") (heap as? DelayedTaskQueue)?.remove(this) // remove if it is in heap (first) _heap = DISPOSED_TASK // never add again to any heap } @@ -530,7 +529,7 @@ internal expect fun createEventLoop(): EventLoop internal expect fun nanoTime(): Long internal expect object DefaultExecutor { - public fun enqueue(task: Runnable) + fun enqueue(task: Runnable) } /** diff --git a/kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt b/kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt deleted file mode 100644 index 6b994b68e7..0000000000 --- a/kotlinx-coroutines-core/common/src/internal/ArrayQueue.kt +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines.internal - -internal open class ArrayQueue { - private var elements = arrayOfNulls(16) - private var head = 0 - private var tail = 0 - - val isEmpty: Boolean get() = head == tail - - public fun addLast(element: T) { - elements[tail] = element - tail = (tail + 1) and elements.size - 1 - if (tail == head) ensureCapacity() - } - - @Suppress("UNCHECKED_CAST") - public fun removeFirstOrNull(): T? { - if (head == tail) return null - val element = elements[head] - elements[head] = null - head = (head + 1) and elements.size - 1 - return element as T - } - - public fun clear() { - head = 0 - tail = 0 - elements = arrayOfNulls(elements.size) - } - - private fun ensureCapacity() { - val currentSize = elements.size - val newCapacity = currentSize shl 1 - val newElements = arrayOfNulls(newCapacity) - elements.copyInto( - destination = newElements, - startIndex = head - ) - elements.copyInto( - destination = newElements, - destinationOffset = elements.size - head, - endIndex = head - ) - elements = newElements - head = 0 - tail = currentSize - } -} diff --git a/kotlinx-coroutines-core/js/src/JSDispatcher.kt b/kotlinx-coroutines-core/js/src/JSDispatcher.kt index 4b1daae353..8ddb903339 100644 --- a/kotlinx-coroutines-core/js/src/JSDispatcher.kt +++ b/kotlinx-coroutines-core/js/src/JSDispatcher.kt @@ -131,7 +131,7 @@ private class WindowMessageQueue(private val window: Window) : MessageQueue() { * * Yet there could be a long tail of "slow" reschedules, but it should be amortized by the queue size. */ -internal abstract class MessageQueue : ArrayQueue() { +internal abstract class MessageQueue : MutableList by ArrayDeque() { val yieldEvery = 16 // yield to JS macrotask event loop after this many processed messages private var scheduled = false @@ -140,7 +140,7 @@ internal abstract class MessageQueue : ArrayQueue() { abstract fun reschedule() fun enqueue(element: Runnable) { - addLast(element) + add(element) if (!scheduled) { scheduled = true schedule() @@ -155,7 +155,7 @@ internal abstract class MessageQueue : ArrayQueue() { element.run() } } finally { - if (isEmpty) { + if (isEmpty()) { scheduled = false } else { reschedule() diff --git a/kotlinx-coroutines-core/js/src/Window.kt b/kotlinx-coroutines-core/js/src/Window.kt index dad0c04b39..7e9932834f 100644 --- a/kotlinx-coroutines-core/js/src/Window.kt +++ b/kotlinx-coroutines-core/js/src/Window.kt @@ -4,7 +4,6 @@ package kotlinx.coroutines -import kotlinx.coroutines.internal.* import org.w3c.dom.Window /** @@ -35,8 +34,8 @@ private fun Window.asWindowAnimationQueue(): WindowAnimationQueue = private class WindowAnimationQueue(private val window: Window) { private val dispatcher = window.asCoroutineDispatcher() private var scheduled = false - private var current = ArrayQueue>() - private var next = ArrayQueue>() + private var current = ArrayDeque>() + private var next = ArrayDeque>() private var timestamp = 0.0 fun enqueue(cont: CancellableContinuation) { diff --git a/kotlinx-coroutines-core/js/test/MessageQueueTest.kt b/kotlinx-coroutines-core/js/test/MessageQueueTest.kt index de514c7628..7ce73804f7 100644 --- a/kotlinx-coroutines-core/js/test/MessageQueueTest.kt +++ b/kotlinx-coroutines-core/js/test/MessageQueueTest.kt @@ -36,41 +36,41 @@ class MessageQueueTest { @Test fun testBasic() { - assertTrue(queue.isEmpty) + assertTrue(queue.isEmpty()) queue.enqueue(Box(1)) - assertFalse(queue.isEmpty) + assertFalse(queue.isEmpty()) assertTrue(scheduled) queue.enqueue(Box(2)) - assertFalse(queue.isEmpty) + assertFalse(queue.isEmpty()) scheduled = false queue.process() assertEquals(listOf(1, 2), processed) assertFalse(scheduled) - assertTrue(queue.isEmpty) + assertTrue(queue.isEmpty()) } @Test fun testRescheduleFromProcess() { - assertTrue(queue.isEmpty) + assertTrue(queue.isEmpty()) queue.enqueue(ReBox(1)) - assertFalse(queue.isEmpty) + assertFalse(queue.isEmpty()) assertTrue(scheduled) queue.enqueue(ReBox(2)) - assertFalse(queue.isEmpty) + assertFalse(queue.isEmpty()) scheduled = false queue.process() assertEquals(listOf(1, 2, 11, 12), processed) assertFalse(scheduled) - assertTrue(queue.isEmpty) + assertTrue(queue.isEmpty()) } @Test fun testResizeAndWrap() { repeat(10) { phase -> val n = 10 * (phase + 1) - assertTrue(queue.isEmpty) + assertTrue(queue.isEmpty()) repeat(n) { queue.enqueue(Box(it)) - assertFalse(queue.isEmpty) + assertFalse(queue.isEmpty()) assertTrue(scheduled) } var countYields = 0 @@ -84,4 +84,4 @@ class MessageQueueTest { processed.clear() } } -} \ No newline at end of file +} From df9421ac00560fce6a2ee3697ca9d9deb695867e Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 13 Sep 2022 15:01:26 +0300 Subject: [PATCH 019/106] Update delay and withTimeout* documentation (#3451) Fixes #3373 --- kotlinx-coroutines-core/common/src/Delay.kt | 2 ++ kotlinx-coroutines-core/common/src/Timeout.kt | 21 +++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/Delay.kt b/kotlinx-coroutines-core/common/src/Delay.kt index 301ed2d322..1742c9625c 100644 --- a/kotlinx-coroutines-core/common/src/Delay.kt +++ b/kotlinx-coroutines-core/common/src/Delay.kt @@ -94,6 +94,7 @@ public suspend fun awaitCancellation(): Nothing = suspendCancellableCoroutine {} /** * Delays coroutine for a given time without blocking a thread and resumes it after a specified time. + * If the given [timeMillis] is non-positive, this function returns immediately. * * This suspending function is cancellable. * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function @@ -120,6 +121,7 @@ public suspend fun delay(timeMillis: Long) { /** * Delays coroutine for a given [duration] without blocking a thread and resumes it after the specified time. + * If the given [duration] is non-positive, this function returns immediately. * * This suspending function is cancellable. * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function diff --git a/kotlinx-coroutines-core/common/src/Timeout.kt b/kotlinx-coroutines-core/common/src/Timeout.kt index 6a6829df7a..aea57546a1 100644 --- a/kotlinx-coroutines-core/common/src/Timeout.kt +++ b/kotlinx-coroutines-core/common/src/Timeout.kt @@ -17,6 +17,7 @@ import kotlin.time.* /** * Runs a given suspending [block] of code inside a coroutine with a specified [timeout][timeMillis] and throws * a [TimeoutCancellationException] if the timeout was exceeded. + * If the given [timeMillis] is non-positive, [TimeoutCancellationException] is thrown immediately. * * The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of * the cancellable suspending function inside the block throws a [TimeoutCancellationException]. @@ -25,8 +26,8 @@ import kotlin.time.* * Note that the timeout action can be specified for a [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause. * * **The timeout event is asynchronous with respect to the code running in the block** and may happen at any time, - * even right before the return from inside of the timeout [block]. Keep this in mind if you open or acquire some - * resource inside the [block] that needs closing or release outside of the block. + * even right before the return from inside the timeout [block]. Keep this in mind if you open or acquire some + * resource inside the [block] that needs closing or release outside the block. * See the * [Asynchronous timeout and resources][https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html#asynchronous-timeout-and-resources] * section of the coroutines guide for details. @@ -48,6 +49,7 @@ public suspend fun withTimeout(timeMillis: Long, block: suspend CoroutineSco /** * Runs a given suspending [block] of code inside a coroutine with the specified [timeout] and throws * a [TimeoutCancellationException] if the timeout was exceeded. + * If the given [timeout] is non-positive, [TimeoutCancellationException] is thrown immediately. * * The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of * the cancellable suspending function inside the block throws a [TimeoutCancellationException]. @@ -56,8 +58,8 @@ public suspend fun withTimeout(timeMillis: Long, block: suspend CoroutineSco * Note that the timeout action can be specified for a [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause. * * **The timeout event is asynchronous with respect to the code running in the block** and may happen at any time, - * even right before the return from inside of the timeout [block]. Keep this in mind if you open or acquire some - * resource inside the [block] that needs closing or release outside of the block. + * even right before the return from inside the timeout [block]. Keep this in mind if you open or acquire some + * resource inside the [block] that needs closing or release outside the block. * See the * [Asynchronous timeout and resources][https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html#asynchronous-timeout-and-resources] * section of the coroutines guide for details. @@ -74,6 +76,7 @@ public suspend fun withTimeout(timeout: Duration, block: suspend CoroutineSc /** * Runs a given suspending block of code inside a coroutine with a specified [timeout][timeMillis] and returns * `null` if this timeout was exceeded. + * If the given [timeMillis] is non-positive, `null` is returned immediately. * * The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of * cancellable suspending function inside the block throws a [TimeoutCancellationException]. @@ -82,8 +85,8 @@ public suspend fun withTimeout(timeout: Duration, block: suspend CoroutineSc * Note that the timeout action can be specified for a [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause. * * **The timeout event is asynchronous with respect to the code running in the block** and may happen at any time, - * even right before the return from inside of the timeout [block]. Keep this in mind if you open or acquire some - * resource inside the [block] that needs closing or release outside of the block. + * even right before the return from inside the timeout [block]. Keep this in mind if you open or acquire some + * resource inside the [block] that needs closing or release outside the block. * See the * [Asynchronous timeout and resources][https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html#asynchronous-timeout-and-resources] * section of the coroutines guide for details. @@ -114,6 +117,7 @@ public suspend fun withTimeoutOrNull(timeMillis: Long, block: suspend Corout /** * Runs a given suspending block of code inside a coroutine with the specified [timeout] and returns * `null` if this timeout was exceeded. + * If the given [timeout] is non-positive, `null` is returned immediately. * * The code that is executing inside the [block] is cancelled on timeout and the active or next invocation of * cancellable suspending function inside the block throws a [TimeoutCancellationException]. @@ -122,8 +126,8 @@ public suspend fun withTimeoutOrNull(timeMillis: Long, block: suspend Corout * Note that the timeout action can be specified for a [select] invocation with [onTimeout][SelectBuilder.onTimeout] clause. * * **The timeout event is asynchronous with respect to the code running in the block** and may happen at any time, - * even right before the return from inside of the timeout [block]. Keep this in mind if you open or acquire some - * resource inside the [block] that needs closing or release outside of the block. + * even right before the return from inside the timeout [block]. Keep this in mind if you open or acquire some + * resource inside the [block] that needs closing or release outside the block. * See the * [Asynchronous timeout and resources][https://kotlinlang.org/docs/reference/coroutines/cancellation-and-timeouts.html#asynchronous-timeout-and-resources] * section of the coroutines guide for details. @@ -177,7 +181,6 @@ public class TimeoutCancellationException internal constructor( TimeoutCancellationException(message ?: "", coroutine).also { it.initCause(this) } } -@Suppress("FunctionName") internal fun TimeoutCancellationException( time: Long, coroutine: Job From 33272732879c7c31ded376fa37e3d41982e95999 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 13 Sep 2022 15:01:38 +0300 Subject: [PATCH 020/106] Update Dispatchers.Main documentation (#3450) Fixes #3381 --- kotlinx-coroutines-core/common/src/Dispatchers.common.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/Dispatchers.common.kt b/kotlinx-coroutines-core/common/src/Dispatchers.common.kt index 28e67a423d..9cafbd3427 100644 --- a/kotlinx-coroutines-core/common/src/Dispatchers.common.kt +++ b/kotlinx-coroutines-core/common/src/Dispatchers.common.kt @@ -27,16 +27,16 @@ public expect object Dispatchers { * Access to this property may throw an [IllegalStateException] if no main dispatchers are present in the classpath. * * Depending on platform and classpath, it can be mapped to different dispatchers: - * - On JS and Native it is equivalent to the [Default] dispatcher. * - On JVM it is either the Android main thread dispatcher, JavaFx or Swing EDT dispatcher. It is chosen by the * [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html). + * - On JS it is equivalent to the [Default] dispatcher with [immediate][MainCoroutineDispatcher.immediate] support. + * - On Native Darwin-based targets, it is a dispatcher backed by Darwin's main queue. + * - On other Native targets, it is a single-threaded dispatcher backed by a standalone worker. * * In order to work with the `Main` dispatcher, the following artifact should be added to the project runtime dependencies: * - `kotlinx-coroutines-android` — for Android Main thread dispatcher * - `kotlinx-coroutines-javafx` — for JavaFx Application thread dispatcher * - `kotlinx-coroutines-swing` — for Swing EDT dispatcher - * - * Implementation note: [MainCoroutineDispatcher.immediate] is not supported on the Native and JS platforms. */ public val Main: MainCoroutineDispatcher From 5322c8d5c56dd412978352b0a1a6bd12c5696ab2 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 16 Sep 2022 16:50:35 +0300 Subject: [PATCH 021/106] Ensure awaitFrame() only awaits a single frame when used from the main looper (#3437) Fixes #3432 --- .../src/HandlerDispatcher.kt | 19 ++++++---- .../test/HandlerDispatcherTest.kt | 8 +--- ui/kotlinx-coroutines-javafx/build.gradle.kts | 37 ------------------- .../src/JavaFxDispatcher.kt | 11 +++--- .../src/SwingDispatcher.kt | 17 +++------ 5 files changed, 26 insertions(+), 66 deletions(-) diff --git a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt index 5e33169dd1..7012c23ecd 100644 --- a/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt +++ b/ui/kotlinx-coroutines-android/src/HandlerDispatcher.kt @@ -185,22 +185,27 @@ private var choreographer: Choreographer? = null public suspend fun awaitFrame(): Long { // fast path when choreographer is already known val choreographer = choreographer - if (choreographer != null) { - return suspendCancellableCoroutine { cont -> + return if (choreographer != null) { + suspendCancellableCoroutine { cont -> postFrameCallback(choreographer, cont) } + } else { + awaitFrameSlowPath() } - // post into looper thread to figure it out - return suspendCancellableCoroutine { cont -> - Dispatchers.Main.dispatch(EmptyCoroutineContext, Runnable { +} + +private suspend fun awaitFrameSlowPath(): Long = suspendCancellableCoroutine { cont -> + if (Looper.myLooper() === Looper.getMainLooper()) { // Check if we are already in the main looper thread + updateChoreographerAndPostFrameCallback(cont) + } else { // post into looper thread to figure it out + Dispatchers.Main.dispatch(cont.context, Runnable { updateChoreographerAndPostFrameCallback(cont) }) } } private fun updateChoreographerAndPostFrameCallback(cont: CancellableContinuation) { - val choreographer = choreographer ?: - Choreographer.getInstance()!!.also { choreographer = it } + val choreographer = choreographer ?: Choreographer.getInstance()!!.also { choreographer = it } postFrameCallback(choreographer, cont) } diff --git a/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt b/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt index fe97ae8d27..afe6cff2f6 100644 --- a/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt +++ b/ui/kotlinx-coroutines-android/test/HandlerDispatcherTest.kt @@ -109,16 +109,12 @@ class HandlerDispatcherTest : TestBase() { launch(Dispatchers.Main, start = CoroutineStart.UNDISPATCHED) { expect(1) awaitFrame() - expect(5) + expect(3) } expect(2) // Run choreographer detection mainLooper.runOneTask() - expect(3) - mainLooper.scheduler.advanceBy(50, TimeUnit.MILLISECONDS) - expect(4) - mainLooper.scheduler.advanceBy(51, TimeUnit.MILLISECONDS) - finish(6) + finish(4) } private fun CoroutineScope.doTestAwaitWithDetectedChoreographer() { diff --git a/ui/kotlinx-coroutines-javafx/build.gradle.kts b/ui/kotlinx-coroutines-javafx/build.gradle.kts index f9f66249eb..456ced137c 100644 --- a/ui/kotlinx-coroutines-javafx/build.gradle.kts +++ b/ui/kotlinx-coroutines-javafx/build.gradle.kts @@ -21,40 +21,3 @@ javafx { modules = listOf("javafx.controls") configuration = "javafx" } - -val JDK_18: String? by lazy { - System.getenv("JDK_18") -} - -val checkJdk8 by tasks.registering { - // only fail w/o JDK_18 when actually trying to test, not during project setup phase - doLast { - if (JDK_18 == null) { - throw GradleException( - """ - JDK_18 environment variable is not defined. - Can't run JDK 8 compatibility tests. - Please ensure JDK 8 is installed and that JDK_18 points to it. - """.trimIndent() - ) - } - } -} - -val jdk8Test by tasks.registering(Test::class) { - // Run these tests only during nightly stress test - onlyIf { project.properties["stressTest"] != null } - - val test = tasks.test.get() - - classpath = test.classpath - testClassesDirs = test.testClassesDirs - executable = "$JDK_18/bin/java" - - dependsOn("compileTestKotlin") - dependsOn(checkJdk8) -} - -tasks.build { - dependsOn(jdk8Test) -} diff --git a/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt b/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt index d158fb745a..61583aad65 100644 --- a/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt +++ b/ui/kotlinx-coroutines-javafx/src/JavaFxDispatcher.kt @@ -34,7 +34,7 @@ public sealed class JavaFxDispatcher : MainCoroutineDispatcher(), Delay { /** @suppress */ override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) { - val timeline = schedule(timeMillis, TimeUnit.MILLISECONDS) { + val timeline = schedule(timeMillis) { with(continuation) { resumeUndispatched(Unit) } } continuation.invokeOnCancellation { timeline.stop() } @@ -42,14 +42,14 @@ public sealed class JavaFxDispatcher : MainCoroutineDispatcher(), Delay { /** @suppress */ override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle { - val timeline = schedule(timeMillis, TimeUnit.MILLISECONDS) { + val timeline = schedule(timeMillis) { block.run() } return DisposableHandle { timeline.stop() } } - private fun schedule(time: Long, unit: TimeUnit, handler: EventHandler): Timeline = - Timeline(KeyFrame(Duration.millis(unit.toMillis(time).toDouble()), handler)).apply { play() } + private fun schedule(timeMillis: Long, handler: EventHandler): Timeline = + Timeline(KeyFrame(Duration.millis(timeMillis.toDouble()), handler)).apply { play() } } internal class JavaFxDispatcherFactory : MainDispatcherFactory { @@ -97,7 +97,7 @@ public suspend fun awaitPulse(): Long = suspendCancellableCoroutine { cont -> } private class PulseTimer : AnimationTimer() { - val next = CopyOnWriteArrayList>() + private val next = CopyOnWriteArrayList>() override fun handle(now: Long) { val cur = next.toTypedArray() @@ -116,6 +116,7 @@ internal fun initPlatform(): Boolean = PlatformInitializer.success // Lazily try to initialize JavaFx platform just once private object PlatformInitializer { + @JvmField val success = run { /* * Try to instantiate JavaFx platform in a way which works diff --git a/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt b/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt index 3b43483dbc..010f18c6c2 100644 --- a/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt +++ b/ui/kotlinx-coroutines-swing/src/SwingDispatcher.kt @@ -7,7 +7,6 @@ package kotlinx.coroutines.swing import kotlinx.coroutines.* import kotlinx.coroutines.internal.* import java.awt.event.* -import java.util.concurrent.* import javax.swing.* import kotlin.coroutines.* @@ -29,26 +28,22 @@ public sealed class SwingDispatcher : MainCoroutineDispatcher(), Delay { /** @suppress */ override fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation) { - val timer = schedule(timeMillis, TimeUnit.MILLISECONDS, ActionListener { + val timer = schedule(timeMillis) { with(continuation) { resumeUndispatched(Unit) } - }) + } continuation.invokeOnCancellation { timer.stop() } } /** @suppress */ override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle { - val timer = schedule(timeMillis, TimeUnit.MILLISECONDS, ActionListener { + val timer = schedule(timeMillis) { block.run() - }) - return object : DisposableHandle { - override fun dispose() { - timer.stop() - } } + return DisposableHandle { timer.stop() } } - private fun schedule(time: Long, unit: TimeUnit, action: ActionListener): Timer = - Timer(unit.toMillis(time).coerceAtMost(Int.MAX_VALUE.toLong()).toInt(), action).apply { + private fun schedule(timeMillis: Long, action: ActionListener): Timer = + Timer(timeMillis.coerceAtMost(Int.MAX_VALUE.toLong()).toInt(), action).apply { isRepeats = false start() } From 7f557e9406cee52ce0259b757dc6ef3600c75db4 Mon Sep 17 00:00:00 2001 From: mvicsokolova <82594708+mvicsokolova@users.noreply.github.com> Date: Tue, 20 Sep 2022 17:32:47 +0200 Subject: [PATCH 022/106] Preparation for atomicfu JVM IR plugin application in 1.7.20 (#3455) * Fix for integration-testing:mavenTest: exclude metadata when checking classes bytecode for ATOMIC_REF * Moved SegmentBasedQueue implementation to the main module * Removed SegmentListTest --- integration-testing/build.gradle | 1 + integration-testing/gradle.properties | 1 + .../MavenPublicationAtomicfuValidator.kt | 41 +++++++++++++++---- .../MavenPublicationVersionValidator.kt | 1 - .../internal/SegmentBasedQueue.kt | 0 .../jvm/test/internal/SegmentListTest.kt | 41 ------------------- .../jvm/test/internal/SegmentQueueTest.kt | 3 +- 7 files changed, 37 insertions(+), 51 deletions(-) rename kotlinx-coroutines-core/jvm/{test => src}/internal/SegmentBasedQueue.kt (100%) delete mode 100644 kotlinx-coroutines-core/jvm/test/internal/SegmentListTest.kt diff --git a/integration-testing/build.gradle b/integration-testing/build.gradle index 6c8d2f326f..04be36fac9 100644 --- a/integration-testing/build.gradle +++ b/integration-testing/build.gradle @@ -18,6 +18,7 @@ java { dependencies { testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" + testImplementation "org.ow2.asm:asm:$asm_version" } sourceSets { diff --git a/integration-testing/gradle.properties b/integration-testing/gradle.properties index 5e68a9d9e2..7dfc532358 100644 --- a/integration-testing/gradle.properties +++ b/integration-testing/gradle.properties @@ -1,4 +1,5 @@ kotlin_version=1.7.10 coroutines_version=1.6.4-SNAPSHOT +asm_version=9.3 kotlin.code.style=official diff --git a/integration-testing/src/mavenTest/kotlin/MavenPublicationAtomicfuValidator.kt b/integration-testing/src/mavenTest/kotlin/MavenPublicationAtomicfuValidator.kt index dbb1921d80..13bac0157a 100644 --- a/integration-testing/src/mavenTest/kotlin/MavenPublicationAtomicfuValidator.kt +++ b/integration-testing/src/mavenTest/kotlin/MavenPublicationAtomicfuValidator.kt @@ -4,12 +4,17 @@ package kotlinx.coroutines.validator -import org.junit.* -import org.junit.Assert.assertTrue +import org.junit.Test +import org.objectweb.asm.* +import org.objectweb.asm.ClassReader.* +import org.objectweb.asm.ClassWriter.* +import org.objectweb.asm.Opcodes.* import java.util.jar.* +import kotlin.test.* class MavenPublicationAtomicfuValidator { private val ATOMIC_FU_REF = "Lkotlinx/atomicfu/".toByteArray() + private val KOTLIN_METADATA_DESC = "Lkotlin/Metadata;" @Test fun testNoAtomicfuInClasspath() { @@ -34,19 +39,39 @@ class MavenPublicationAtomicfuValidator { for (e in entries()) { if (!e.name.endsWith(".class")) continue val bytes = getInputStream(e).use { it.readBytes() } - loop@for (i in 0 until bytes.size - ATOMIC_FU_REF.size) { - for (j in 0 until ATOMIC_FU_REF.size) { - if (bytes[i + j] != ATOMIC_FU_REF[j]) continue@loop - } + // The atomicfu compiler plugin does not remove atomic properties from metadata, + // so for now we check that there are no ATOMIC_FU_REF left in the class bytecode excluding metadata. + // This may be reverted after the fix in the compiler plugin transformer (for Kotlin 1.8.0). + val outBytes = bytes.eraseMetadata() + if (outBytes.checkBytes()) { foundClasses += e.name // report error at the end with all class names - break@loop } } if (foundClasses.isNotEmpty()) { error("Found references to atomicfu in jar file $name in the following class files: ${ - foundClasses.joinToString("") { "\n\t\t" + it } + foundClasses.joinToString("") { "\n\t\t" + it } }") } close() } + + private fun ByteArray.checkBytes(): Boolean { + loop@for (i in 0 until this.size - ATOMIC_FU_REF.size) { + for (j in 0 until ATOMIC_FU_REF.size) { + if (this[i + j] != ATOMIC_FU_REF[j]) continue@loop + } + return true + } + return false + } + + private fun ByteArray.eraseMetadata(): ByteArray { + val cw = ClassWriter(COMPUTE_MAXS or COMPUTE_FRAMES) + ClassReader(this).accept(object : ClassVisitor(ASM9, cw) { + override fun visitAnnotation(descriptor: String?, visible: Boolean): AnnotationVisitor? { + return if (descriptor == KOTLIN_METADATA_DESC) null else super.visitAnnotation(descriptor, visible) + } + }, SKIP_FRAMES) + return cw.toByteArray() + } } diff --git a/integration-testing/src/mavenTest/kotlin/MavenPublicationVersionValidator.kt b/integration-testing/src/mavenTest/kotlin/MavenPublicationVersionValidator.kt index da87d4cc59..11529d2d0d 100644 --- a/integration-testing/src/mavenTest/kotlin/MavenPublicationVersionValidator.kt +++ b/integration-testing/src/mavenTest/kotlin/MavenPublicationVersionValidator.kt @@ -4,7 +4,6 @@ package kotlinx.coroutines.validator -import org.junit.* import org.junit.Test import java.util.jar.* import kotlin.test.* diff --git a/kotlinx-coroutines-core/jvm/test/internal/SegmentBasedQueue.kt b/kotlinx-coroutines-core/jvm/src/internal/SegmentBasedQueue.kt similarity index 100% rename from kotlinx-coroutines-core/jvm/test/internal/SegmentBasedQueue.kt rename to kotlinx-coroutines-core/jvm/src/internal/SegmentBasedQueue.kt diff --git a/kotlinx-coroutines-core/jvm/test/internal/SegmentListTest.kt b/kotlinx-coroutines-core/jvm/test/internal/SegmentListTest.kt deleted file mode 100644 index ff6a346cda..0000000000 --- a/kotlinx-coroutines-core/jvm/test/internal/SegmentListTest.kt +++ /dev/null @@ -1,41 +0,0 @@ -package kotlinx.coroutines.internal - -import kotlinx.atomicfu.* -import org.junit.Test -import kotlin.test.* - -class SegmentListTest { - @Test - fun testRemoveTail() { - val initialSegment = TestSegment(0, null, 2) - val head = AtomicRefHolder(initialSegment) - val tail = AtomicRefHolder(initialSegment) - val s1 = tail.ref.findSegmentAndMoveForward(1, tail.ref.value, ::createTestSegment).segment - assertFalse(s1.removed) - tail.ref.value.onSlotCleaned() - assertFalse(s1.removed) - head.ref.findSegmentAndMoveForward(2, head.ref.value, ::createTestSegment) - assertFalse(s1.removed) - tail.ref.findSegmentAndMoveForward(2, head.ref.value, ::createTestSegment) - assertTrue(s1.removed) - } - - @Test - fun testClose() { - val initialSegment = TestSegment(0, null, 2) - val head = AtomicRefHolder(initialSegment) - val tail = AtomicRefHolder(initialSegment) - tail.ref.findSegmentAndMoveForward(1, tail.ref.value, ::createTestSegment) - assertEquals(tail.ref.value, tail.ref.value.close()) - assertTrue(head.ref.findSegmentAndMoveForward(2, head.ref.value, ::createTestSegment).isClosed) - } -} - -private class AtomicRefHolder(initialValue: T) { - val ref = atomic(initialValue) -} - -private class TestSegment(id: Long, prev: TestSegment?, pointers: Int) : Segment(id, prev, pointers) { - override val maxSlots: Int get() = 1 -} -private fun createTestSegment(id: Long, prev: TestSegment?) = TestSegment(id, prev, 0) \ No newline at end of file diff --git a/kotlinx-coroutines-core/jvm/test/internal/SegmentQueueTest.kt b/kotlinx-coroutines-core/jvm/test/internal/SegmentQueueTest.kt index fd2d329088..1390403a3b 100644 --- a/kotlinx-coroutines-core/jvm/test/internal/SegmentQueueTest.kt +++ b/kotlinx-coroutines-core/jvm/test/internal/SegmentQueueTest.kt @@ -1,6 +1,7 @@ package kotlinx.coroutines.internal import kotlinx.coroutines.* +import kotlinx.coroutines.internal.* import org.junit.Test import java.util.* import java.util.concurrent.CyclicBarrier @@ -106,4 +107,4 @@ class SegmentQueueTest : TestBase() { }.forEach { it.join() } assertEquals(2, q.numberOfSegments) } -} \ No newline at end of file +} From 61ba10d9029e4990636f9a41dce24b462e33026e Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 20 Sep 2022 21:27:09 +0300 Subject: [PATCH 023/106] Introduce non-nullable types in reactive integrations where appropriate (#3393) * Introduce non-nullable types in reactive integrations where appropriate * For RxJava2, use them in internal implementations where appropriate * For RxJava3, introduce & Any bound to a generic argument in our extensions to avoid errors in Kotlin 1.8.0 due to non-nullability rx3 annotations being part of generics upper bound. This change went through committee, and all the "unsound" declarations such as "RxSignature" were properly highlighted as a warning that would become an error. --- build.gradle | 2 - .../kotlinx-coroutines-rx2/src/RxAwait.kt | 50 ++++++++++++++----- .../kotlinx-coroutines-rx2/src/RxChannel.kt | 8 +-- .../src/RxObservable.kt | 4 +- reactive/kotlinx-coroutines-rx3/build.gradle | 2 +- .../kotlinx-coroutines-rx3/src/RxAwait.kt | 46 +++++++++-------- .../kotlinx-coroutines-rx3/src/RxChannel.kt | 15 +++--- .../kotlinx-coroutines-rx3/src/RxConvert.kt | 2 +- .../kotlinx-coroutines-rx3/src/RxMaybe.kt | 6 +-- .../src/RxObservable.kt | 4 +- .../kotlinx-coroutines-rx3/src/RxScheduler.kt | 1 - 11 files changed, 83 insertions(+), 57 deletions(-) diff --git a/build.gradle b/build.gradle index 356de63b35..59c28893ab 100644 --- a/build.gradle +++ b/build.gradle @@ -176,8 +176,6 @@ configure(subprojects.findAll { !sourceless.contains(it.name) }) { tasks.withType(AbstractKotlinCompile).all { kotlinOptions.freeCompilerArgs += OptInPreset.optInAnnotations.collect { "-Xopt-in=" + it } kotlinOptions.freeCompilerArgs += "-progressive" - // Disable KT-36770 for RxJava2 integration - kotlinOptions.freeCompilerArgs += "-XXLanguage:-ProhibitUsingNullableTypeParameterAgainstNotNullAnnotated" // Remove null assertions to get smaller bytecode on Android kotlinOptions.freeCompilerArgs += ["-Xno-param-assertions", "-Xno-receiver-assertions", "-Xno-call-assertions"] } diff --git a/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt b/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt index da9809c9f8..a891b3f5c2 100644 --- a/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt +++ b/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt @@ -24,9 +24,17 @@ import kotlin.coroutines.* */ public suspend fun CompletableSource.await(): Unit = suspendCancellableCoroutine { cont -> subscribe(object : CompletableObserver { - override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) } - override fun onComplete() { cont.resume(Unit) } - override fun onError(e: Throwable) { cont.resumeWithException(e) } + override fun onSubscribe(d: Disposable) { + cont.disposeOnCancellation(d) + } + + override fun onComplete() { + cont.resume(Unit) + } + + override fun onError(e: Throwable) { + cont.resumeWithException(e) + } }) } @@ -41,13 +49,23 @@ public suspend fun CompletableSource.await(): Unit = suspendCancellableCoroutine * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this * function immediately resumes with [CancellationException] and disposes of its subscription. */ -@Suppress("UNCHECKED_CAST") public suspend fun MaybeSource.awaitSingleOrNull(): T? = suspendCancellableCoroutine { cont -> subscribe(object : MaybeObserver { - override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) } - override fun onComplete() { cont.resume(null) } - override fun onSuccess(t: T) { cont.resume(t) } - override fun onError(error: Throwable) { cont.resumeWithException(error) } + override fun onSubscribe(d: Disposable) { + cont.disposeOnCancellation(d) + } + + override fun onComplete() { + cont.resume(null) + } + + override fun onSuccess(t: T & Any) { + cont.resume(t) + } + + override fun onError(error: Throwable) { + cont.resumeWithException(error) + } }) } @@ -119,9 +137,17 @@ public suspend fun MaybeSource.awaitOrDefault(default: T): T = awaitSingl */ public suspend fun SingleSource.await(): T = suspendCancellableCoroutine { cont -> subscribe(object : SingleObserver { - override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) } - override fun onSuccess(t: T) { cont.resume(t) } - override fun onError(error: Throwable) { cont.resumeWithException(error) } + override fun onSubscribe(d: Disposable) { + cont.disposeOnCancellation(d) + } + + override fun onSuccess(t: T & Any) { + cont.resume(t) + } + + override fun onError(error: Throwable) { + cont.resumeWithException(error) + } }) } @@ -225,7 +251,7 @@ private suspend fun ObservableSource.awaitOne( cont.invokeOnCancellation { sub.dispose() } } - override fun onNext(t: T) { + override fun onNext(t: T & Any) { when (mode) { Mode.FIRST, Mode.FIRST_OR_DEFAULT -> { if (!seenValue) { diff --git a/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt b/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt index fc09bf9ee3..8db22799c0 100644 --- a/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt +++ b/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt @@ -60,12 +60,12 @@ private class SubscriptionChannel : _subscription.value = sub } - override fun onSuccess(t: T) { + override fun onSuccess(t: T & Any) { trySend(t) close(cause = null) } - override fun onNext(t: T) { + override fun onNext(t: T & Any) { trySend(t) // Safe to ignore return value here, expectedly racing with cancellation } @@ -80,7 +80,7 @@ private class SubscriptionChannel : /** @suppress */ @Deprecated(message = "Deprecated in the favour of Flow", level = DeprecationLevel.HIDDEN) // ERROR in 1.4.0, HIDDEN in 1.6.0 -public fun ObservableSource.openSubscription(): ReceiveChannel { +public fun ObservableSource.openSubscription(): ReceiveChannel { val channel = SubscriptionChannel() subscribe(channel) return channel @@ -88,7 +88,7 @@ public fun ObservableSource.openSubscription(): ReceiveChannel { /** @suppress */ @Deprecated(message = "Deprecated in the favour of Flow", level = DeprecationLevel.HIDDEN) // ERROR in 1.4.0, HIDDEN in 1.6.0 -public fun MaybeSource.openSubscription(): ReceiveChannel { +public fun MaybeSource.openSubscription(): ReceiveChannel { val channel = SubscriptionChannel() subscribe(channel) return channel diff --git a/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt b/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt index 57727fbb81..ad8fac71d3 100644 --- a/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt +++ b/reactive/kotlinx-coroutines-rx2/src/RxObservable.kt @@ -77,7 +77,7 @@ private class RxObservableCoroutine( processResFunc = RxObservableCoroutine<*>::processResultSelectSend as ProcessResultFunction ) - @Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER") + @Suppress("UNUSED_PARAMETER") private fun registerSelectForSend(select: SelectInstance<*>, element: Any?) { // Try to acquire the mutex and complete in the registration phase. if (mutex.tryLock()) { @@ -113,7 +113,7 @@ private class RxObservableCoroutine( } } - public override suspend fun send(element: T) { + override suspend fun send(element: T) { mutex.lock() doLockedNext(element)?.let { throw it } } diff --git a/reactive/kotlinx-coroutines-rx3/build.gradle b/reactive/kotlinx-coroutines-rx3/build.gradle index 15ef66da18..7676b6e23b 100644 --- a/reactive/kotlinx-coroutines-rx3/build.gradle +++ b/reactive/kotlinx-coroutines-rx3/build.gradle @@ -23,7 +23,7 @@ compileKotlin { tasks.withType(DokkaTaskPartial.class) { dokkaSourceSets.configureEach { externalDocumentationLink { - url.set(new URL('http://reactivex.io/RxJava/3.x/javadoc/')) + url.set(new URL('https://reactivex.io/RxJava/3.x/javadoc/')) packageListUrl.set(projectDir.toPath().resolve("package.list").toUri().toURL()) } } diff --git a/reactive/kotlinx-coroutines-rx3/src/RxAwait.kt b/reactive/kotlinx-coroutines-rx3/src/RxAwait.kt index 754dd79484..1c77bbe4f9 100644 --- a/reactive/kotlinx-coroutines-rx3/src/RxAwait.kt +++ b/reactive/kotlinx-coroutines-rx3/src/RxAwait.kt @@ -41,12 +41,11 @@ public suspend fun CompletableSource.await(): Unit = suspendCancellableCoroutine * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this * function immediately resumes with [CancellationException] and disposes of its subscription. */ -@Suppress("UNCHECKED_CAST") -public suspend fun MaybeSource.awaitSingleOrNull(): T? = suspendCancellableCoroutine { cont -> - subscribe(object : MaybeObserver { +public suspend fun MaybeSource.awaitSingleOrNull(): T? = suspendCancellableCoroutine { cont -> + subscribe(object : MaybeObserver { override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) } override fun onComplete() { cont.resume(null) } - override fun onSuccess(t: T) { cont.resume(t) } + override fun onSuccess(t: T & Any) { cont.resume(t) } override fun onError(error: Throwable) { cont.resumeWithException(error) } }) } @@ -61,7 +60,7 @@ public suspend fun MaybeSource.awaitSingleOrNull(): T? = suspendCancellab * * @throws NoSuchElementException if no elements were produced by this [MaybeSource]. */ -public suspend fun MaybeSource.awaitSingle(): T = awaitSingleOrNull() ?: throw NoSuchElementException() +public suspend fun MaybeSource.awaitSingle(): T = awaitSingleOrNull() ?: throw NoSuchElementException() /** * Awaits for completion of the maybe without blocking a thread. @@ -84,7 +83,7 @@ public suspend fun MaybeSource.awaitSingle(): T = awaitSingleOrNull() ?: level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("this.awaitSingleOrNull()") ) // Warning since 1.5, error in 1.6, hidden in 1.7 -public suspend fun MaybeSource.await(): T? = awaitSingleOrNull() +public suspend fun MaybeSource.await(): T? = awaitSingleOrNull() /** * Awaits for completion of the maybe without blocking a thread. @@ -107,7 +106,7 @@ public suspend fun MaybeSource.await(): T? = awaitSingleOrNull() level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("this.awaitSingleOrNull() ?: default") ) // Warning since 1.5, error in 1.6, hidden in 1.7 -public suspend fun MaybeSource.awaitOrDefault(default: T): T = awaitSingleOrNull() ?: default +public suspend fun MaybeSource.awaitOrDefault(default: T): T = awaitSingleOrNull() ?: default // ------------------------ SingleSource ------------------------ @@ -119,10 +118,10 @@ public suspend fun MaybeSource.awaitOrDefault(default: T): T = awaitSingl * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this * function immediately disposes of its subscription and resumes with [CancellationException]. */ -public suspend fun SingleSource.await(): T = suspendCancellableCoroutine { cont -> - subscribe(object : SingleObserver { +public suspend fun SingleSource.await(): T = suspendCancellableCoroutine { cont -> + subscribe(object : SingleObserver { override fun onSubscribe(d: Disposable) { cont.disposeOnCancellation(d) } - override fun onSuccess(t: T) { cont.resume(t) } + override fun onSuccess(t: T & Any) { cont.resume(t) } override fun onError(error: Throwable) { cont.resumeWithException(error) } }) } @@ -139,7 +138,8 @@ public suspend fun SingleSource.await(): T = suspendCancellableCoroutine * * @throws NoSuchElementException if the observable does not emit any value */ -public suspend fun ObservableSource.awaitFirst(): T = awaitOne(Mode.FIRST) +@Suppress("UNCHECKED_CAST") +public suspend fun ObservableSource.awaitFirst(): T = awaitOne(Mode.FIRST) as T /** * Awaits the first value from the given [Observable], or returns the [default] value if none is emitted, without @@ -150,7 +150,9 @@ public suspend fun ObservableSource.awaitFirst(): T = awaitOne(Mode.FIRST * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this * function immediately disposes of its subscription and resumes with [CancellationException]. */ -public suspend fun ObservableSource.awaitFirstOrDefault(default: T): T = awaitOne(Mode.FIRST_OR_DEFAULT, default) +@Suppress("UNCHECKED_CAST") +public suspend fun ObservableSource.awaitFirstOrDefault(default: T): T = + awaitOne(Mode.FIRST_OR_DEFAULT, default) as T /** * Awaits the first value from the given [Observable], or returns `null` if none is emitted, without blocking the @@ -161,7 +163,7 @@ public suspend fun ObservableSource.awaitFirstOrDefault(default: T): T = * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this * function immediately disposes of its subscription and resumes with [CancellationException]. */ -public suspend fun ObservableSource.awaitFirstOrNull(): T? = awaitOne(Mode.FIRST_OR_DEFAULT) +public suspend fun ObservableSource.awaitFirstOrNull(): T? = awaitOne(Mode.FIRST_OR_DEFAULT) /** * Awaits the first value from the given [Observable], or calls [defaultValue] to get a value if none is emitted, @@ -172,7 +174,7 @@ public suspend fun ObservableSource.awaitFirstOrNull(): T? = awaitOne(Mod * If the [Job] of the current coroutine is cancelled or completed while the suspending function is waiting, this * function immediately disposes of its subscription and resumes with [CancellationException]. */ -public suspend fun ObservableSource.awaitFirstOrElse(defaultValue: () -> T): T = +public suspend fun ObservableSource.awaitFirstOrElse(defaultValue: () -> T): T = awaitOne(Mode.FIRST_OR_DEFAULT) ?: defaultValue() /** @@ -185,7 +187,8 @@ public suspend fun ObservableSource.awaitFirstOrElse(defaultValue: () -> * * @throws NoSuchElementException if the observable does not emit any value */ -public suspend fun ObservableSource.awaitLast(): T = awaitOne(Mode.LAST) +@Suppress("UNCHECKED_CAST") +public suspend fun ObservableSource.awaitLast(): T = awaitOne(Mode.LAST) as T /** * Awaits the single value from the given observable without blocking the thread and returns the resulting value, or, @@ -198,14 +201,15 @@ public suspend fun ObservableSource.awaitLast(): T = awaitOne(Mode.LAST) * @throws NoSuchElementException if the observable does not emit any value * @throws IllegalArgumentException if the observable emits more than one value */ -public suspend fun ObservableSource.awaitSingle(): T = awaitOne(Mode.SINGLE) +@Suppress("UNCHECKED_CAST") +public suspend fun ObservableSource.awaitSingle(): T = awaitOne(Mode.SINGLE) as T // ------------------------ private ------------------------ internal fun CancellableContinuation<*>.disposeOnCancellation(d: Disposable) = invokeOnCancellation { d.dispose() } -private enum class Mode(val s: String) { +private enum class Mode(@JvmField val s: String) { FIRST("awaitFirst"), FIRST_OR_DEFAULT("awaitFirstOrDefault"), LAST("awaitLast"), @@ -213,11 +217,11 @@ private enum class Mode(val s: String) { override fun toString(): String = s } -private suspend fun ObservableSource.awaitOne( +private suspend fun ObservableSource.awaitOne( mode: Mode, default: T? = null -): T = suspendCancellableCoroutine { cont -> - subscribe(object : Observer { +): T? = suspendCancellableCoroutine { cont -> + subscribe(object : Observer { private lateinit var subscription: Disposable private var value: T? = null private var seenValue = false @@ -227,7 +231,7 @@ private suspend fun ObservableSource.awaitOne( cont.invokeOnCancellation { sub.dispose() } } - override fun onNext(t: T) { + override fun onNext(t: T & Any) { when (mode) { Mode.FIRST, Mode.FIRST_OR_DEFAULT -> { if (!seenValue) { diff --git a/reactive/kotlinx-coroutines-rx3/src/RxChannel.kt b/reactive/kotlinx-coroutines-rx3/src/RxChannel.kt index 21238d2491..5d7624a17a 100644 --- a/reactive/kotlinx-coroutines-rx3/src/RxChannel.kt +++ b/reactive/kotlinx-coroutines-rx3/src/RxChannel.kt @@ -19,7 +19,7 @@ import kotlinx.coroutines.flow.* * [MaybeSource] doesn't have a corresponding [Flow] adapter, so it should be transformed to [Observable] first. */ @PublishedApi -internal fun MaybeSource.openSubscription(): ReceiveChannel { +internal fun MaybeSource.openSubscription(): ReceiveChannel { val channel = SubscriptionChannel() subscribe(channel) return channel @@ -33,7 +33,7 @@ internal fun MaybeSource.openSubscription(): ReceiveChannel { * [ObservableSource] doesn't have a corresponding [Flow] adapter, so it should be transformed to [Observable] first. */ @PublishedApi -internal fun ObservableSource.openSubscription(): ReceiveChannel { +internal fun ObservableSource.openSubscription(): ReceiveChannel { val channel = SubscriptionChannel() subscribe(channel) return channel @@ -45,7 +45,7 @@ internal fun ObservableSource.openSubscription(): ReceiveChannel { * If [action] throws an exception at some point or if the [MaybeSource] raises an error, the exception is rethrown from * [collect]. */ -public suspend inline fun MaybeSource.collect(action: (T) -> Unit): Unit = +public suspend inline fun MaybeSource.collect(action: (T) -> Unit): Unit = openSubscription().consumeEach(action) /** @@ -54,12 +54,11 @@ public suspend inline fun MaybeSource.collect(action: (T) -> Unit): Unit * If [action] throws an exception at some point, the subscription is cancelled, and the exception is rethrown from * [collect]. Also, if the [ObservableSource] signals an error, that error is rethrown from [collect]. */ -public suspend inline fun ObservableSource.collect(action: (T) -> Unit): Unit = - openSubscription().consumeEach(action) +public suspend inline fun ObservableSource.collect(action: (T) -> Unit): Unit = openSubscription().consumeEach(action) @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") private class SubscriptionChannel : - LinkedListChannel(null), Observer, MaybeObserver + LinkedListChannel(null), Observer, MaybeObserver { private val _subscription = atomic(null) @@ -73,12 +72,12 @@ private class SubscriptionChannel : _subscription.value = sub } - override fun onSuccess(t: T) { + override fun onSuccess(t: T & Any) { trySend(t) close(cause = null) } - override fun onNext(t: T) { + override fun onNext(t: T & Any) { trySend(t) // Safe to ignore return value here, expectedly racing with cancellation } diff --git a/reactive/kotlinx-coroutines-rx3/src/RxConvert.kt b/reactive/kotlinx-coroutines-rx3/src/RxConvert.kt index b4693a55e7..57d2dfb370 100644 --- a/reactive/kotlinx-coroutines-rx3/src/RxConvert.kt +++ b/reactive/kotlinx-coroutines-rx3/src/RxConvert.kt @@ -42,7 +42,7 @@ public fun Job.asCompletable(context: CoroutineContext): Completable = rxComplet * * @param context -- the coroutine context from which the resulting maybe is going to be signalled */ -public fun Deferred.asMaybe(context: CoroutineContext): Maybe = rxMaybe(context) { +public fun Deferred.asMaybe(context: CoroutineContext): Maybe = rxMaybe(context) { this@asMaybe.await() } diff --git a/reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt b/reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt index 12d0197bf2..2e50481732 100644 --- a/reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt +++ b/reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt @@ -20,7 +20,7 @@ import kotlin.coroutines.* public fun rxMaybe( context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T? -): Maybe { +): Maybe { require(context[Job] === null) { "Maybe context cannot contain job in it." + "Its lifecycle should be managed via Disposable handle. Had $context" } return rxMaybeInternal(GlobalScope, context, block) @@ -30,7 +30,7 @@ private fun rxMaybeInternal( scope: CoroutineScope, // support for legacy rxMaybe in scope context: CoroutineContext, block: suspend CoroutineScope.() -> T? -): Maybe = Maybe.create { subscriber -> +): Maybe = Maybe.create { subscriber -> val newContext = scope.newCoroutineContext(context) val coroutine = RxMaybeCoroutine(newContext, subscriber) subscriber.setCancellable(RxCancellable(coroutine)) @@ -39,7 +39,7 @@ private fun rxMaybeInternal( private class RxMaybeCoroutine( parentContext: CoroutineContext, - private val subscriber: MaybeEmitter + private val subscriber: MaybeEmitter ) : AbstractCoroutine(parentContext, false, true) { override fun onCompleted(value: T) { try { diff --git a/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt b/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt index 5db7585fca..8ea761c979 100644 --- a/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt +++ b/reactive/kotlinx-coroutines-rx3/src/RxObservable.kt @@ -77,7 +77,7 @@ private class RxObservableCoroutine( processResFunc = RxObservableCoroutine<*>::processResultSelectSend as ProcessResultFunction ) - @Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER") + @Suppress("UNUSED_PARAMETER") private fun registerSelectForSend(select: SelectInstance<*>, element: Any?) { // Try to acquire the mutex and complete in the registration phase. if (mutex.tryLock()) { @@ -113,7 +113,7 @@ private class RxObservableCoroutine( } } - public override suspend fun send(element: T) { + override suspend fun send(element: T) { mutex.lock() doLockedNext(element)?.let { throw it } } diff --git a/reactive/kotlinx-coroutines-rx3/src/RxScheduler.kt b/reactive/kotlinx-coroutines-rx3/src/RxScheduler.kt index abaf02450a..e7f93868b1 100644 --- a/reactive/kotlinx-coroutines-rx3/src/RxScheduler.kt +++ b/reactive/kotlinx-coroutines-rx3/src/RxScheduler.kt @@ -9,7 +9,6 @@ import io.reactivex.rxjava3.disposables.* import io.reactivex.rxjava3.plugins.* import kotlinx.atomicfu.* import kotlinx.coroutines.* -import kotlinx.coroutines.CancellationException import kotlinx.coroutines.channels.* import java.util.concurrent.* import kotlin.coroutines.* From bb16214d363b22c1b63522499f63aa1634eb7868 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 27 Sep 2022 14:27:57 +0300 Subject: [PATCH 024/106] Remove Class-Path attribute from debug JAR (#3402) Fixes #3361 --- kotlinx-coroutines-debug/build.gradle | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/kotlinx-coroutines-debug/build.gradle b/kotlinx-coroutines-debug/build.gradle index 9165a41f9b..ded13b7b5a 100644 --- a/kotlinx-coroutines-debug/build.gradle +++ b/kotlinx-coroutines-debug/build.gradle @@ -34,18 +34,6 @@ jar { setEnabled(false) } -// This is a rough estimation of what shadow plugin has been doing with our default configuration prior to -// 1.6.2: https://github.com/johnrengelman/shadow/blob/1ff12fc816629ae5bc331fa3889c8ecfcaee7b27/src/main/groovy/com/github/jengelman/gradle/plugins/shadow/ShadowJavaPlugin.groovy#L72-L82 -// We just emulate it here for backwards compatibility -shadowJar.configure { - def classpath = project.objects.fileCollection().from { -> - project.configurations.findByName('runtimeClasspath') - } - doFirst { - manifest.attributes 'Class-Path': classpath.collect { "${it.name}" }.findAll { it }.join(' ') - } -} - def shadowJarTask = shadowJar { classifier null // Shadow only byte buddy, do not package kotlin stdlib From 4a5892f138c68df38d7d0becd1134f8c8f1fa8c2 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 27 Sep 2022 14:28:04 +0300 Subject: [PATCH 025/106] Properly use UndispatchedCoroutine context for context-specific elements (#3413) Fixes #3411 --- .../common/src/Builders.common.kt | 2 +- .../jvm/test/ThreadContextElementTest.kt | 35 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/common/src/Builders.common.kt b/kotlinx-coroutines-core/common/src/Builders.common.kt index 1219b9d859..3384dddbaa 100644 --- a/kotlinx-coroutines-core/common/src/Builders.common.kt +++ b/kotlinx-coroutines-core/common/src/Builders.common.kt @@ -163,7 +163,7 @@ public suspend fun withContext( if (newContext[ContinuationInterceptor] == oldContext[ContinuationInterceptor]) { val coroutine = UndispatchedCoroutine(newContext, uCont) // There are changes in the context, so this thread needs to be updated - withCoroutineContext(newContext, null) { + withCoroutineContext(coroutine.context, null) { return@sc coroutine.startUndispatchedOrReturn(coroutine, block) } } diff --git a/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt b/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt index ec45406bce..83f5ae17db 100644 --- a/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt +++ b/kotlinx-coroutines-core/jvm/test/ThreadContextElementTest.kt @@ -156,6 +156,41 @@ class ThreadContextElementTest : TestBase() { } } } + + class JobCaptor(val capturees: ArrayList = ArrayList()) : ThreadContextElement { + + companion object Key : CoroutineContext.Key + + override val key: CoroutineContext.Key<*> get() = Key + + override fun updateThreadContext(context: CoroutineContext) { + capturees.add(context.job) + } + + override fun restoreThreadContext(context: CoroutineContext, oldState: Unit) { + } + } + + @Test + fun testWithContextJobAccess() = runTest { + val captor = JobCaptor() + val manuallyCaptured = ArrayList() + runBlocking(captor) { + manuallyCaptured += coroutineContext.job + withContext(CoroutineName("undispatched")) { + manuallyCaptured += coroutineContext.job + withContext(Dispatchers.IO) { + manuallyCaptured += coroutineContext.job + } + // Context restored, captured again + manuallyCaptured += coroutineContext.job + } + // Context restored, captured again + manuallyCaptured += coroutineContext.job + } + + assertEquals(manuallyCaptured, captor.capturees) + } } class MyData From dc5880693f1db46356822db3a1eae26fc5934aea Mon Sep 17 00:00:00 2001 From: Ignat Beresnev Date: Wed, 28 Sep 2022 17:04:03 +0200 Subject: [PATCH 026/106] Allow customization of default scheduler name on JVM (#3465) Fixes #3231 --- .../common/src/internal/SystemProps.common.kt | 11 +++++++++++ kotlinx-coroutines-core/jvm/src/scheduling/Tasks.kt | 9 +++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/internal/SystemProps.common.kt b/kotlinx-coroutines-core/common/src/internal/SystemProps.common.kt index ca84809b4c..281c075b5e 100644 --- a/kotlinx-coroutines-core/common/src/internal/SystemProps.common.kt +++ b/kotlinx-coroutines-core/common/src/internal/SystemProps.common.kt @@ -56,6 +56,17 @@ internal fun systemProp( return parsed } +/** + * Gets the system property indicated by the specified [property name][propertyName], + * or returns [defaultValue] if there is no property with that key. + * + * **Note: this function should be used in JVM tests only, other platforms use the default value.** + */ +internal fun systemProp( + propertyName: String, + defaultValue: String +): String = systemProp(propertyName) ?: defaultValue + /** * Gets the system property indicated by the specified [property name][propertyName], * or returns `null` if there is no property with that key. diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/Tasks.kt b/kotlinx-coroutines-core/jvm/src/scheduling/Tasks.kt index 5403cfc1fd..796dc95df9 100644 --- a/kotlinx-coroutines-core/jvm/src/scheduling/Tasks.kt +++ b/kotlinx-coroutines-core/jvm/src/scheduling/Tasks.kt @@ -9,8 +9,13 @@ import kotlinx.coroutines.internal.* import java.util.concurrent.* -// Internal debuggability name + thread name prefixes -internal const val DEFAULT_SCHEDULER_NAME = "DefaultDispatcher" +/** + * The name of the default scheduler. The names of the worker threads of [Dispatchers.Default] have it as their prefix. + */ +@JvmField +internal val DEFAULT_SCHEDULER_NAME = systemProp( + "kotlinx.coroutines.scheduler.default.name", "DefaultDispatcher" +) // 100us as default @JvmField From 85a26013d25c42e77b7365c0e2830dcdfef3de11 Mon Sep 17 00:00:00 2001 From: Ignat Beresnev Date: Wed, 28 Sep 2022 17:06:17 +0200 Subject: [PATCH 027/106] Add parametrized Flow#filterIsInstance extension (#3464) Fixes #3240 --- .../api/kotlinx-coroutines-core.api | 1 + .../common/src/flow/operators/Transform.kt | 6 ++++ .../test/flow/operators/FilterTrivialTest.kt | 33 +++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index 3dbd211de1..dd038f6557 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -973,6 +973,7 @@ public final class kotlinx/coroutines/flow/FlowKt { public static final fun emitAll (Lkotlinx/coroutines/flow/FlowCollector;Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun emptyFlow ()Lkotlinx/coroutines/flow/Flow; public static final fun filter (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow; + public static final fun filterIsInstance (Lkotlinx/coroutines/flow/Flow;Lkotlin/reflect/KClass;)Lkotlinx/coroutines/flow/Flow; public static final fun filterNot (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow; public static final fun filterNotNull (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow; public static final fun first (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt index 0f9e3959e3..51aae34aaa 100644 --- a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt +++ b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt @@ -11,6 +11,7 @@ package kotlinx.coroutines.flow import kotlinx.coroutines.* import kotlinx.coroutines.flow.internal.* import kotlin.jvm.* +import kotlin.reflect.* import kotlinx.coroutines.flow.internal.unsafeFlow as flow import kotlinx.coroutines.flow.unsafeTransform as transform @@ -34,6 +35,11 @@ public inline fun Flow.filterNot(crossinline predicate: suspend (T) -> Bo @Suppress("UNCHECKED_CAST") public inline fun Flow<*>.filterIsInstance(): Flow = filter { it is R } as Flow +/** + * Returns a flow containing only values that are instances of the given [klass]. + */ +public fun Flow<*>.filterIsInstance(klass: KClass): Flow = filter { klass.isInstance(it) } as Flow + /** * Returns a flow containing only values of the original flow that are not null. */ diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FilterTrivialTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FilterTrivialTest.kt index 1d3c69bc7e..f41fe731bf 100644 --- a/kotlinx-coroutines-core/common/test/flow/operators/FilterTrivialTest.kt +++ b/kotlinx-coroutines-core/common/test/flow/operators/FilterTrivialTest.kt @@ -28,6 +28,33 @@ class FilterTrivialTest : TestBase() { assertEquals("value", flow.filterIsInstance().single()) } + @Test + fun testParametrizedFilterIsInstance() = runTest { + val flow = flowOf("value", 2.0) + assertEquals(2.0, flow.filterIsInstance(Double::class).single()) + assertEquals("value", flow.filterIsInstance(String::class).single()) + } + + @Test + fun testSubtypesFilterIsInstance() = runTest { + open class Super + class Sub : Super() + + val flow = flowOf(Super(), Super(), Super(), Sub(), Sub(), Sub()) + assertEquals(6, flow.filterIsInstance().count()) + assertEquals(3, flow.filterIsInstance().count()) + } + + @Test + fun testSubtypesParametrizedFilterIsInstance() = runTest { + open class Super + class Sub : Super() + + val flow = flowOf(Super(), Super(), Super(), Sub(), Sub(), Sub()) + assertEquals(6, flow.filterIsInstance(Super::class).count()) + assertEquals(3, flow.filterIsInstance(Sub::class).count()) + } + @Test fun testFilterIsInstanceNullable() = runTest { val flow = flowOf(1, 2, null) @@ -40,4 +67,10 @@ class FilterTrivialTest : TestBase() { val sum = emptyFlow().filterIsInstance().sum() assertEquals(0, sum) } + + @Test + fun testEmptyFlowParametrizedIsInstance() = runTest { + val sum = emptyFlow().filterIsInstance(Int::class).sum() + assertEquals(0, sum) + } } From 4f1d41f1fd8803b051c40128b0b65e1dfac3dfb5 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 28 Sep 2022 19:52:08 +0300 Subject: [PATCH 028/106] Adjust Gradle configuration and introduce allWarningsAsErrors (#3466) * Also, add tests that verify our disabled assertions * Fix nullability in AgentPremain that used to work by accident (because we disabled those assertions) * Fix all corresponding warnings Co-authored-by: Dmitry Khalanskiy --- build.gradle | 13 +-------- ...nfigure-compilation-conventions.gradle.kts | 25 ++++++++++++++++ .../common/src/channels/AbstractChannel.kt | 21 +++++++++++--- .../common/src/channels/ArrayChannel.kt | 7 +++-- .../common/src/channels/Broadcast.kt | 3 +- .../common/src/channels/Channel.kt | 1 + .../src/internal/DispatchedContinuation.kt | 4 ++- .../common/src/internal/DispatchedTask.kt | 1 - .../jvm/src/channels/Actor.kt | 8 ++++- .../jvm/src/debug/AgentPremain.kt | 5 ++-- .../jvm/src/internal/MainDispatchers.kt | 3 -- .../jvm/test/NoParamAssertionsTest.kt | 29 +++++++++++++++++++ .../common/src/TestCoroutineScheduler.kt | 6 ++-- .../common/src/TestDispatcher.kt | 2 +- .../src/migration/TestBuildersDeprecated.kt | 2 +- .../src/migration/TestCoroutineDispatcher.kt | 12 ++++++++ .../TestCoroutineExceptionHandler.kt | 1 + .../jvm/src/migration/TestCoroutineScope.kt | 1 + .../src/ReactorFlow.kt | 3 +- .../kotlinx-coroutines-rx3/src/RxMaybe.kt | 1 + 20 files changed, 113 insertions(+), 35 deletions(-) create mode 100644 buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts create mode 100644 kotlinx-coroutines-core/jvm/test/NoParamAssertionsTest.kt diff --git a/build.gradle b/build.gradle index 59c28893ab..d5b231d969 100644 --- a/build.gradle +++ b/build.gradle @@ -168,18 +168,7 @@ configure(subprojects.findAll { !sourceless.contains(it.name) && it.name != core apply plugin: "bom-conventions" // Configure subprojects with Kotlin sources -configure(subprojects.findAll { !sourceless.contains(it.name) }) { - // Use atomicfu plugin, it also adds all the necessary dependencies - apply plugin: 'kotlinx-atomicfu' - - // Configure options for all Kotlin compilation tasks - tasks.withType(AbstractKotlinCompile).all { - kotlinOptions.freeCompilerArgs += OptInPreset.optInAnnotations.collect { "-Xopt-in=" + it } - kotlinOptions.freeCompilerArgs += "-progressive" - // Remove null assertions to get smaller bytecode on Android - kotlinOptions.freeCompilerArgs += ["-Xno-param-assertions", "-Xno-receiver-assertions", "-Xno-call-assertions"] - } -} +apply plugin: "configure-compilation-conventions" if (build_snapshot_train) { println "Hacking test tasks, removing stress and flaky tests" diff --git a/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts b/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts new file mode 100644 index 0000000000..1c3f486a36 --- /dev/null +++ b/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts @@ -0,0 +1,25 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +import org.jetbrains.kotlin.gradle.tasks.* + +configure(subprojects) { + if (name in sourceless) return@configure + apply(plugin = "kotlinx-atomicfu") + val projectName = name + tasks.withType(KotlinCompile::class).all { + val isMainTaskName = name == "compileKotlin" || name == "compileKotlinJvm" + kotlinOptions { + if (isMainTaskName) { + allWarningsAsErrors = true + } + val newOptions = + listOf( + "-progressive", "-Xno-param-assertions", "-Xno-receiver-assertions", + "-Xno-call-assertions" + ) + optInAnnotations.map { "-opt-in=$it" } + freeCompilerArgs = freeCompilerArgs + newOptions + } + } +} diff --git a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt index 0fe07488ed..2f2fe506e0 100644 --- a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt @@ -2,6 +2,8 @@ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +@file:Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER") + package kotlinx.coroutines.channels import kotlinx.atomicfu.* @@ -16,6 +18,7 @@ import kotlin.jvm.* /** * Abstract send channel. It is a base class for all send channel implementations. */ +@Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER") internal abstract class AbstractSendChannel( @JvmField protected val onUndeliveredElement: OnUndeliveredElement? ) : SendChannel { @@ -122,7 +125,12 @@ internal abstract class AbstractSendChannel( return sendSuspend(element) } - @Suppress("DEPRECATION", "DEPRECATION_ERROR") + @Suppress("DEPRECATION_ERROR") + @Deprecated( + level = DeprecationLevel.ERROR, + message = "Deprecated in the favour of 'trySend' method", + replaceWith = ReplaceWith("trySend(element).isSuccess") + ) // see super() override fun offer(element: E): Boolean { // Temporary migration for offer users who rely on onUndeliveredElement try { @@ -705,6 +713,11 @@ internal abstract class AbstractChannel( onCancellationConstructor = onUndeliveredElementReceiveCancellationConstructor ) + @Deprecated( + message = "Deprecated in favor of onReceiveCatching extension", + level = DeprecationLevel.ERROR, + replaceWith = ReplaceWith("onReceiveCatching") + ) // See super() override val onReceiveOrNull: SelectClause1 get() = SelectClause1Impl( clauseObject = this, @@ -726,7 +739,7 @@ internal abstract class AbstractChannel( if (selectResult is Closed<*>) throw selectResult.receiveException else selectResult as E - private fun processResultSelectReceiveCatching(ignoredParam: Any?, selectResult: Any?): Any? = + private fun processResultSelectReceiveCatching(ignoredParam: Any?, selectResult: Any?): Any = if (selectResult is Closed<*>) ChannelResult.closed(selectResult.closeCause) else ChannelResult.success(selectResult as E) @@ -735,8 +748,8 @@ internal abstract class AbstractChannel( else selectResult as E private val onUndeliveredElementReceiveCancellationConstructor: OnCancellationConstructor? = onUndeliveredElement?.let { - { select: SelectInstance<*>, ignoredParam: Any?, element: Any? -> - { cause: Throwable -> if (element !is Closed<*>) onUndeliveredElement.callUndeliveredElement(element as E, select.context) } + { select: SelectInstance<*>, _: Any?, element: Any? -> + { _: Throwable -> if (element !is Closed<*>) onUndeliveredElement.callUndeliveredElement(element as E, select.context) } } } diff --git a/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt b/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt index 93f99cf1f6..abf18ac39a 100644 --- a/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt @@ -158,9 +158,10 @@ internal open class ArrayChannel( // Too late, already cancelled, but we removed it from the queue and need to notify on undelivered element. // The only exception is when this "send" operation is an `onSend` clause that has to be re-registered // in the corresponding `select` invocation. - val send = send!! - if (!(send is SendElementSelectWithUndeliveredHandler<*> && send.trySelectResult == REREGISTER)) - send.undeliveredElement() + send!!.apply { + if (!(this is SendElementSelectWithUndeliveredHandler<*> && trySelectResult == REREGISTER)) + undeliveredElement() + } } } if (replacement !== POLL_FAILED && replacement !is Closed<*>) { diff --git a/kotlinx-coroutines-core/common/src/channels/Broadcast.kt b/kotlinx-coroutines-core/common/src/channels/Broadcast.kt index b1c24b456d..ba39d4ff7e 100644 --- a/kotlinx-coroutines-core/common/src/channels/Broadcast.kt +++ b/kotlinx-coroutines-core/common/src/channels/Broadcast.kt @@ -47,10 +47,11 @@ public fun ReceiveChannel.broadcast( start: CoroutineStart = CoroutineStart.LAZY ): BroadcastChannel { val scope = GlobalScope + Dispatchers.Unconfined + CoroutineExceptionHandler { _, _ -> } + val channel = this // We can run this coroutine in the context that ignores all exceptions, because of `onCompletion = consume()` // which passes all exceptions upstream to the source ReceiveChannel return scope.broadcast(capacity = capacity, start = start, onCompletion = { cancelConsumed(it) }) { - for (e in this@broadcast) { + for (e in channel) { send(e) } } diff --git a/kotlinx-coroutines-core/common/src/channels/Channel.kt b/kotlinx-coroutines-core/common/src/channels/Channel.kt index 5ceb515f95..51c0214fe2 100644 --- a/kotlinx-coroutines-core/common/src/channels/Channel.kt +++ b/kotlinx-coroutines-core/common/src/channels/Channel.kt @@ -360,6 +360,7 @@ public interface ReceiveChannel { * * @suppress **Deprecated**: in favor of onReceiveCatching extension. */ + @Suppress("DEPRECATION_ERROR") @Deprecated( message = "Deprecated in favor of onReceiveCatching extension", level = DeprecationLevel.ERROR, diff --git a/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt b/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt index bb2bbf6d92..c196147333 100644 --- a/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt +++ b/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt @@ -207,6 +207,7 @@ internal class DispatchedContinuation( // We inline it to save an entry on the stack in cases where it shows (unconfined dispatcher) // It is used only in Continuation.resumeCancellableWith + @Suppress("NOTHING_TO_INLINE") inline fun resumeCancellableWith( result: Result, noinline onCancellation: ((cause: Throwable) -> Unit)? @@ -235,7 +236,7 @@ internal class DispatchedContinuation( } // inline here is to save us an entry on the stack for the sake of better stacktraces - + @Suppress("NOTHING_TO_INLINE") inline fun resumeCancelled(state: Any?): Boolean { val job = context[Job] if (job != null && !job.isActive) { @@ -247,6 +248,7 @@ internal class DispatchedContinuation( return false } + @Suppress("NOTHING_TO_INLINE") inline fun resumeUndispatchedWith(result: Result) { withContinuationContext(continuation, countOrElement) { continuation.resumeWith(result) diff --git a/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt b/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt index d982f95bdf..1de1bff479 100644 --- a/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt +++ b/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt @@ -167,7 +167,6 @@ internal fun DispatchedTask.dispatch(mode: Int) { } } -@Suppress("UNCHECKED_CAST") internal fun DispatchedTask.resume(delegate: Continuation, undispatched: Boolean) { // This resume is never cancellable. The result is always delivered to delegate continuation. val state = takeState() diff --git a/kotlinx-coroutines-core/jvm/src/channels/Actor.kt b/kotlinx-coroutines-core/jvm/src/channels/Actor.kt index 76d8aae3eb..e8a9152e09 100644 --- a/kotlinx-coroutines-core/jvm/src/channels/Actor.kt +++ b/kotlinx-coroutines-core/jvm/src/channels/Actor.kt @@ -162,7 +162,12 @@ private class LazyActorCoroutine( return super.send(element) } - @Suppress("DEPRECATION", "DEPRECATION_ERROR") + @Suppress("DEPRECATION_ERROR") + @Deprecated( + level = DeprecationLevel.ERROR, + message = "Deprecated in the favour of 'trySend' method", + replaceWith = ReplaceWith("trySend(element).isSuccess") + ) // See super() override fun offer(element: E): Boolean { start() return super.offer(element) @@ -181,6 +186,7 @@ private class LazyActorCoroutine( return closed } + @Suppress("UNCHECKED_CAST") override val onSend: SelectClause2> get() = SelectClause2Impl( clauseObject = this, regFunc = LazyActorCoroutine<*>::onSendRegFunction as RegistrationFunction, diff --git a/kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt b/kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt index 4b0ce3f31e..fb5c5b1b06 100644 --- a/kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt +++ b/kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt @@ -26,6 +26,7 @@ internal object AgentPremain { }.getOrNull() ?: DebugProbesImpl.enableCreationStackTraces @JvmStatic + @Suppress("UNUSED_PARAMETER") fun premain(args: String?, instrumentation: Instrumentation) { AgentInstallationType.isInstalledStatically = true instrumentation.addTransformer(DebugProbesTransformer) @@ -36,13 +37,13 @@ internal object AgentPremain { internal object DebugProbesTransformer : ClassFileTransformer { override fun transform( - loader: ClassLoader, + loader: ClassLoader?, className: String, classBeingRedefined: Class<*>?, protectionDomain: ProtectionDomain, classfileBuffer: ByteArray? ): ByteArray? { - if (className != "kotlin/coroutines/jvm/internal/DebugProbesKt") { + if (loader == null || className != "kotlin/coroutines/jvm/internal/DebugProbesKt") { return null } /* diff --git a/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt b/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt index e87952b419..0c55d92eb8 100644 --- a/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt +++ b/kotlinx-coroutines-core/jvm/src/internal/MainDispatchers.kt @@ -98,9 +98,6 @@ private class MissingMainCoroutineDispatcher( override fun limitedParallelism(parallelism: Int): CoroutineDispatcher = missing() - override suspend fun delay(time: Long) = - missing() - override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle = missing() diff --git a/kotlinx-coroutines-core/jvm/test/NoParamAssertionsTest.kt b/kotlinx-coroutines-core/jvm/test/NoParamAssertionsTest.kt new file mode 100644 index 0000000000..5e1c4625e7 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/NoParamAssertionsTest.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +package kotlinx.coroutines + +import kotlinx.coroutines.* +import org.junit.Test +import kotlin.test.* + + +class NoParamAssertionsTest : TestBase() { + // These tests verify that we haven't omitted "-Xno-param-assertions" and "-Xno-receiver-assertions" + + @Test + fun testNoReceiverAssertion() { + val function: (ThreadLocal, Int) -> ThreadContextElement = ThreadLocal::asContextElement + @Suppress("UNCHECKED_CAST") + val unsafeCasted = function as ((ThreadLocal?, Int) -> ThreadContextElement) + unsafeCasted(null, 42) + } + + @Test + fun testNoParamAssertion() { + val function: (ThreadLocal, Any) -> ThreadContextElement = ThreadLocal::asContextElement + @Suppress("UNCHECKED_CAST") + val unsafeCasted = function as ((ThreadLocal?, Any?) -> ThreadContextElement) + unsafeCasted(ThreadLocal.withInitial { Any() }, null) + } +} diff --git a/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt b/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt index e735c6d4de..5f7198cfff 100644 --- a/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt +++ b/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt @@ -97,7 +97,7 @@ public class TestCoroutineScheduler : AbstractCoroutineContextElement(TestCorout currentTime = event.time event } - event.dispatcher.processEvent(event.time, event.marker) + event.dispatcher.processEvent(event.marker) return true } @@ -132,7 +132,7 @@ public class TestCoroutineScheduler : AbstractCoroutineContextElement(TestCorout val event = synchronized(lock) { events.removeFirstIf { it.time <= timeMark } ?: return } - event.dispatcher.processEvent(event.time, event.marker) + event.dispatcher.processEvent(event.marker) } } @@ -173,7 +173,7 @@ public class TestCoroutineScheduler : AbstractCoroutineContextElement(TestCorout } } } - event.dispatcher.processEvent(event.time, event.marker) + event.dispatcher.processEvent(event.marker) } } diff --git a/kotlinx-coroutines-test/common/src/TestDispatcher.kt b/kotlinx-coroutines-test/common/src/TestDispatcher.kt index 9616bb0b23..8ed8192b9e 100644 --- a/kotlinx-coroutines-test/common/src/TestDispatcher.kt +++ b/kotlinx-coroutines-test/common/src/TestDispatcher.kt @@ -23,7 +23,7 @@ public abstract class TestDispatcher internal constructor() : CoroutineDispatche public abstract val scheduler: TestCoroutineScheduler /** Notifies the dispatcher that it should process a single event marked with [marker] happening at time [time]. */ - internal fun processEvent(time: Long, marker: Any) { + internal fun processEvent(marker: Any) { check(marker is Runnable) marker.run() } diff --git a/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt b/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt index eabdffb2c8..35d237a7f5 100644 --- a/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt +++ b/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt @@ -159,7 +159,7 @@ public fun runTestWithLegacyScope( context: CoroutineContext = EmptyCoroutineContext, dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS, testBody: suspend TestCoroutineScope.() -> Unit -): TestResult { +) { if (context[RunningInRunTest] != null) throw IllegalStateException("Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details.") val testScope = TestBodyCoroutine(createTestCoroutineScope(context + RunningInRunTest)) diff --git a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineDispatcher.kt b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineDispatcher.kt index 08f428f249..40a0f5dfab 100644 --- a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineDispatcher.kt +++ b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineDispatcher.kt @@ -61,6 +61,10 @@ public class TestCoroutineDispatcher(public override val scheduler: TestCoroutin scheduler.registerEvent(this, 0, block, context) { false } /** @suppress */ + @Deprecated( + "Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.", + level = DeprecationLevel.WARNING + ) override suspend fun pauseDispatcher(block: suspend () -> Unit) { val previous = dispatchImmediately dispatchImmediately = false @@ -72,11 +76,19 @@ public class TestCoroutineDispatcher(public override val scheduler: TestCoroutin } /** @suppress */ + @Deprecated( + "Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.", + level = DeprecationLevel.WARNING + ) override fun pauseDispatcher() { dispatchImmediately = false } /** @suppress */ + @Deprecated( + "Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.", + level = DeprecationLevel.WARNING + ) override fun resumeDispatcher() { dispatchImmediately = true } diff --git a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt index 9da521f05c..aeb0f35882 100644 --- a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt +++ b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt @@ -42,6 +42,7 @@ public interface UncaughtExceptionCaptor { /** * An exception handler that captures uncaught exceptions in tests. */ +@Suppress("DEPRECATION") @Deprecated( "Deprecated for removal without a replacement. " + "It may be to define one's own `CoroutineExceptionHandler` if you just need to handle '" + diff --git a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineScope.kt b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineScope.kt index 4a2cbc5c2c..5af83f5197 100644 --- a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineScope.kt +++ b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineScope.kt @@ -86,6 +86,7 @@ private class TestCoroutineScopeImpl( /** These jobs existed before the coroutine scope was used, so it's alright if they don't get cancelled. */ private val initialJobs = coroutineContext.activeJobs() + @Deprecated("Please call `runTest`, which automatically performs the cleanup, instead of using this function.") override fun cleanupTestCoroutines() { val delayController = coroutineContext.delayController val hasUnfinishedJobs = if (delayController != null) { diff --git a/reactive/kotlinx-coroutines-reactor/src/ReactorFlow.kt b/reactive/kotlinx-coroutines-reactor/src/ReactorFlow.kt index 0fc743f937..6a77bbf3a6 100644 --- a/reactive/kotlinx-coroutines-reactor/src/ReactorFlow.kt +++ b/reactive/kotlinx-coroutines-reactor/src/ReactorFlow.kt @@ -32,8 +32,7 @@ private class FlowAsFlux( private val flow: Flow, private val context: CoroutineContext ) : Flux() { - override fun subscribe(subscriber: CoreSubscriber?) { - if (subscriber == null) throw NullPointerException() + override fun subscribe(subscriber: CoreSubscriber) { val hasContext = !subscriber.currentContext().isEmpty val source = if (hasContext) flow.flowOn(subscriber.currentContext().asCoroutineContext()) else flow subscriber.onSubscribe(FlowSubscription(source, subscriber, context)) diff --git a/reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt b/reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt index 2e50481732..39306e29ce 100644 --- a/reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt +++ b/reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt @@ -43,6 +43,7 @@ private class RxMaybeCoroutine( ) : AbstractCoroutine(parentContext, false, true) { override fun onCompleted(value: T) { try { + @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") // KT-54201 if (value == null) subscriber.onComplete() else subscriber.onSuccess(value) } catch (e: Throwable) { handleUndeliverableException(e, context) From 935faf7573c446092fc7ad3ccde841d123db68fd Mon Sep 17 00:00:00 2001 From: Ilya Goncharov Date: Wed, 5 Oct 2022 11:01:51 +0200 Subject: [PATCH 029/106] Use outputFileProperty instead of outputFile (#3460) --- gradle/test-mocha-js.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle/test-mocha-js.gradle b/gradle/test-mocha-js.gradle index 27d2e5b394..1986cb7f81 100644 --- a/gradle/test-mocha-js.gradle +++ b/gradle/test-mocha-js.gradle @@ -23,7 +23,7 @@ def compileTestJsLegacy = tasks.hasProperty("compileTestKotlinJsLegacy") // todo: use atomicfu-transformed test files here (not critical) task testMochaNode(type: NodeTask, dependsOn: [compileTestJsLegacy, installDependenciesMochaNode]) { script = file("${node.nodeProjectDir.getAsFile().get()}/node_modules/mocha/bin/mocha") - args = [compileTestJsLegacy.outputFile.path, '--require', 'source-map-support/register'] + args = [compileTestJsLegacy.outputFileProperty.get().path, '--require', 'source-map-support/register'] if (project.hasProperty("teamcity")) args.addAll(['--reporter', 'mocha-teamcity-reporter']) } @@ -65,8 +65,8 @@ prepareMochaChrome.doLast { - - + + @@ -75,7 +75,7 @@ prepareMochaChrome.doLast { task testMochaChrome(type: NodeTask, dependsOn: prepareMochaChrome) { script = file("${node.nodeProjectDir.getAsFile().get()}/node_modules/mocha-headless-chrome/bin/start") - args = [compileTestJsLegacy.outputFile.path, '--file', mochaChromeTestPage] + args = [compileTestJsLegacy.outputFileProperty.get().path, '--file', mochaChromeTestPage] if (project.hasProperty("teamcity")) args.addAll(['--reporter', 'mocha-teamcity-reporter']) } @@ -96,7 +96,7 @@ task installDependenciesMochaJsdom(type: NpmTask, dependsOn: [npmInstall]) { task testMochaJsdom(type: NodeTask, dependsOn: [compileTestJsLegacy, installDependenciesMochaJsdom]) { script = file("${node.nodeProjectDir.getAsFile().get()}/node_modules/mocha/bin/mocha") - args = [compileTestJsLegacy.outputFile.path, '--require', 'source-map-support/register', '--require', 'jsdom-global/register'] + args = [compileTestJsLegacy.outputFileProperty.get().path, '--require', 'source-map-support/register', '--require', 'jsdom-global/register'] if (project.hasProperty("teamcity")) args.addAll(['--reporter', 'mocha-teamcity-reporter']) } From cefd9c0bdd1ea8d59e67e3802936694a40b2dcb1 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 5 Oct 2022 16:43:19 +0300 Subject: [PATCH 030/106] Update Kover version to 0.6.1 (#3471) --- buildSrc/src/main/kotlin/kover-conventions.gradle.kts | 4 +--- gradle.properties | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/buildSrc/src/main/kotlin/kover-conventions.gradle.kts b/buildSrc/src/main/kotlin/kover-conventions.gradle.kts index e9490f1ee0..c177c638d3 100644 --- a/buildSrc/src/main/kotlin/kover-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/kover-conventions.gradle.kts @@ -10,14 +10,12 @@ val notCovered = sourceless + internal + unpublished val expectedCoverage = mutableMapOf( // These have lower coverage in general, it can be eventually fixed "kotlinx-coroutines-swing" to 70, // awaitFrame is not tested - "kotlinx-coroutines-javafx" to 39, // JavaFx is not tested on TC because its graphic subsystem cannot be initialized in headless mode + "kotlinx-coroutines-javafx" to 35, // JavaFx is not tested on TC because its graphic subsystem cannot be initialized in headless mode // Reactor has lower coverage in general due to various fatal error handling features "kotlinx-coroutines-reactor" to 75 ) - - subprojects { val projectName = name if (projectName in notCovered) return@subprojects diff --git a/gradle.properties b/gradle.properties index c5234eaa08..cffc2c53b7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -23,7 +23,7 @@ rxjava3_version=3.0.2 javafx_version=11.0.2 javafx_plugin_version=0.0.8 binary_compatibility_validator_version=0.11.0 -kover_version=0.6.0-Beta +kover_version=0.6.1 blockhound_version=1.0.2.RELEASE jna_version=5.9.0 From d2503c0a10c69a95a34d6b9469e4a7cd41bbb691 Mon Sep 17 00:00:00 2001 From: Pablo Baxter Date: Wed, 5 Oct 2022 08:56:55 -0700 Subject: [PATCH 031/106] Add timeout operator to Flow as @FlowPreview (#3148) Fixes #2624 Co-authored-by: Vsevolod Tolstopyatov --- .../api/kotlinx-coroutines-core.api | 1 + .../common/src/flow/operators/Delay.kt | 59 ++++- .../common/test/flow/operators/TimeoutTest.kt | 221 ++++++++++++++++++ .../examples/example-delay-duration-01.kt | 1 - .../examples/example-delay-duration-02.kt | 1 - .../examples/example-delay-duration-03.kt | 1 - .../examples/example-timeout-duration-01.kt | 27 +++ .../jvm/test/examples/test/FlowDelayTest.kt | 7 + 8 files changed, 314 insertions(+), 4 deletions(-) create mode 100644 kotlinx-coroutines-core/common/test/flow/operators/TimeoutTest.kt create mode 100644 kotlinx-coroutines-core/jvm/test/examples/example-timeout-duration-01.kt diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index dd038f6557..ad04fb9c8e 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -1052,6 +1052,7 @@ public final class kotlinx/coroutines/flow/FlowKt { public static final fun switchMap (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow; public static final fun take (Lkotlinx/coroutines/flow/Flow;I)Lkotlinx/coroutines/flow/Flow; public static final fun takeWhile (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow; + public static final fun timeout-HG0u8IE (Lkotlinx/coroutines/flow/Flow;J)Lkotlinx/coroutines/flow/Flow; public static final fun toCollection (Lkotlinx/coroutines/flow/Flow;Ljava/util/Collection;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun toList (Lkotlinx/coroutines/flow/Flow;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static synthetic fun toList$default (Lkotlinx/coroutines/flow/Flow;Ljava/util/List;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object; diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt b/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt index 258dc3eeb1..738fef79be 100644 --- a/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt +++ b/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt @@ -18,7 +18,6 @@ import kotlin.time.* + * + * produces the following emissions + * + * ```text + * 1, 2, 3, -1 + * ``` + * + * + * Note that delaying on the downstream doesn't trigger the timeout. + * + * @param timeout period. If non-positive, the flow is timed out immediately + */ +@FlowPreview +public fun Flow.timeout( + timeout: Duration +): Flow = timeoutInternal(timeout) + +private fun Flow.timeoutInternal( + timeout: Duration +): Flow = scopedFlow { downStream -> + if (timeout <= Duration.ZERO) throw TimeoutCancellationException("Timed out immediately") + val values = buffer(Channel.RENDEZVOUS).produceIn(this) + whileSelect { + values.onReceiveCatching { value -> + value.onSuccess { + downStream.emit(it) + }.onClosed { + return@onReceiveCatching false + } + return@onReceiveCatching true + } + onTimeout(timeout) { + throw TimeoutCancellationException("Timed out waiting for $timeout") + } + } +} diff --git a/kotlinx-coroutines-core/common/test/flow/operators/TimeoutTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/TimeoutTest.kt new file mode 100644 index 0000000000..c2fa8b06dd --- /dev/null +++ b/kotlinx-coroutines-core/common/test/flow/operators/TimeoutTest.kt @@ -0,0 +1,221 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.flow.operators + +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import kotlin.test.* +import kotlin.time.Duration.Companion.milliseconds + +class TimeoutTest : TestBase() { + @Test + fun testBasic() = withVirtualTime { + expect(1) + val flow = flow { + expect(3) + emit("A") + delay(100) + emit("B") + delay(100) + emit("C") + expect(4) + delay(400) + expectUnreached() + } + + expect(2) + val list = mutableListOf() + assertFailsWith(flow.timeout(300.milliseconds).onEach { list.add(it) }) + assertEquals(listOf("A", "B", "C"), list) + finish(5) + } + + @Test + fun testSingleNull() = withVirtualTime { + val flow = flow { + emit(null) + delay(1) + expect(1) + }.timeout(2.milliseconds) + assertNull(flow.single()) + finish(2) + } + + @Test + fun testBasicCustomAction() = withVirtualTime { + expect(1) + val flow = flow { + expect(3) + emit("A") + delay(100) + emit("B") + delay(100) + emit("C") + expect(4) + delay(400) + expectUnreached() + } + + expect(2) + val list = mutableListOf() + flow.timeout(300.milliseconds).catch { if (it is TimeoutCancellationException) emit("-1") }.collect { list.add(it) } + assertEquals(listOf("A", "B", "C", "-1"), list) + finish(5) + } + + @Test + fun testDelayedFirst() = withVirtualTime { + expect(1) + val flow = flow { + expect(3) + delay(100) + emit(1) + expect(4) + }.timeout(250.milliseconds) + expect(2) + assertEquals(1, flow.singleOrNull()) + finish(5) + } + + @Test + fun testEmpty() = withVirtualTime { + val flow = emptyFlow().timeout(1.milliseconds) + assertNull(flow.singleOrNull()) + finish(1) + } + + @Test + fun testScalar() = withVirtualTime { + val flow = flowOf(1, 2, 3).timeout(1.milliseconds) + assertEquals(listOf(1, 2, 3), flow.toList()) + finish(1) + } + + @Test + fun testUpstreamError() = testUpstreamError(TestException()) + + @Test + fun testUpstreamErrorTimeoutException() = testUpstreamError(TimeoutCancellationException(0, Job())) + + private inline fun testUpstreamError(cause: T) = runTest { + try { + // Workaround for JS legacy bug + flow { + emit(1) + throw cause + }.timeout(1.milliseconds).collect() + expectUnreached() + } catch (e: Throwable) { + assertTrue { e is T } + finish(1) + } + } + + @Test + fun testDownstreamError() = runTest { + val flow = flow { + expect(1) + emit(1) + hang { expect(3) } + expectUnreached() + }.timeout(100.milliseconds).map { + expect(2) + yield() + throw TestException() + } + + assertFailsWith(flow) + finish(4) + } + + @Test + fun testUpstreamTimeoutIsolatedContext() = runTest { + val flow = flow { + assertEquals("upstream", NamedDispatchers.name()) + expect(1) + emit(1) + expect(2) + delay(300) + expectUnreached() + }.flowOn(NamedDispatchers("upstream")).timeout(100.milliseconds) + + assertFailsWith(flow) + finish(3) + } + + @Test + fun testUpstreamTimeoutActionIsolatedContext() = runTest { + val flow = flow { + assertEquals("upstream", NamedDispatchers.name()) + expect(1) + emit(1) + expect(2) + delay(300) + expectUnreached() + }.flowOn(NamedDispatchers("upstream")).timeout(100.milliseconds).catch { + expect(3) + emit(2) + } + + assertEquals(listOf(1, 2), flow.toList()) + finish(4) + } + + @Test + fun testUpstreamNoTimeoutIsolatedContext() = runTest { + val flow = flow { + assertEquals("upstream", NamedDispatchers.name()) + expect(1) + emit(1) + expect(2) + delay(10) + }.flowOn(NamedDispatchers("upstream")).timeout(100.milliseconds) + + assertEquals(listOf(1), flow.toList()) + finish(3) + } + + @Test + fun testSharedFlowTimeout() = runTest { + // Workaround for JS legacy bug + try { + MutableSharedFlow().asSharedFlow().timeout(100.milliseconds).collect() + expectUnreached() + } catch (e: TimeoutCancellationException) { + finish(1) + } + } + + @Test + fun testSharedFlowCancelledNoTimeout() = runTest { + val mutableSharedFlow = MutableSharedFlow() + val list = arrayListOf() + + expect(1) + val consumerJob = launch { + expect(3) + mutableSharedFlow.asSharedFlow().timeout(100.milliseconds).collect { list.add(it) } + expectUnreached() + } + val producerJob = launch { + expect(4) + repeat(10) { + delay(50) + mutableSharedFlow.emit(it) + } + yield() + consumerJob.cancel() + expect(5) + } + + expect(2) + + producerJob.join() + consumerJob.join() + + assertEquals((0 until 10).toList(), list) + finish(6) + } +} diff --git a/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-01.kt b/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-01.kt index 8b3d2d2422..954af0662b 100644 --- a/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-01.kt +++ b/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-01.kt @@ -5,7 +5,6 @@ // This file was automatically generated from Delay.kt by Knit tool. Do not edit. package kotlinx.coroutines.examples.exampleDelayDuration01 -import kotlin.time.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import kotlin.time.Duration.Companion.milliseconds diff --git a/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-02.kt b/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-02.kt index 6500ecd35c..45935a0982 100644 --- a/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-02.kt +++ b/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-02.kt @@ -5,7 +5,6 @@ // This file was automatically generated from Delay.kt by Knit tool. Do not edit. package kotlinx.coroutines.examples.exampleDelayDuration02 -import kotlin.time.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import kotlin.time.Duration.Companion.milliseconds diff --git a/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-03.kt b/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-03.kt index 4d5e40d78d..fc389c2474 100644 --- a/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-03.kt +++ b/kotlinx-coroutines-core/jvm/test/examples/example-delay-duration-03.kt @@ -5,7 +5,6 @@ // This file was automatically generated from Delay.kt by Knit tool. Do not edit. package kotlinx.coroutines.examples.exampleDelayDuration03 -import kotlin.time.* import kotlinx.coroutines.* import kotlinx.coroutines.flow.* import kotlin.time.Duration.Companion.milliseconds diff --git a/kotlinx-coroutines-core/jvm/test/examples/example-timeout-duration-01.kt b/kotlinx-coroutines-core/jvm/test/examples/example-timeout-duration-01.kt new file mode 100644 index 0000000000..5db6e6a521 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/examples/example-timeout-duration-01.kt @@ -0,0 +1,27 @@ +/* + * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +// This file was automatically generated from Delay.kt by Knit tool. Do not edit. +package kotlinx.coroutines.examples.exampleTimeoutDuration01 + +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* +import kotlin.time.Duration.Companion.milliseconds + +fun main() = runBlocking { + +flow { + emit(1) + delay(100) + emit(2) + delay(100) + emit(3) + delay(1000) + emit(4) +}.timeout(100.milliseconds).catch { + emit(-1) // Item to emit on timeout +}.onEach { + delay(300) // This will not cause a timeout +} +.toList().joinToString().let { println(it) } } diff --git a/kotlinx-coroutines-core/jvm/test/examples/test/FlowDelayTest.kt b/kotlinx-coroutines-core/jvm/test/examples/test/FlowDelayTest.kt index 99e72eb2c9..f7e93b34d9 100644 --- a/kotlinx-coroutines-core/jvm/test/examples/test/FlowDelayTest.kt +++ b/kotlinx-coroutines-core/jvm/test/examples/test/FlowDelayTest.kt @@ -50,4 +50,11 @@ class FlowDelayTest { "1, 3, 5, 7, 9" ) } + + @Test + fun testExampleTimeoutDuration01() { + test("ExampleTimeoutDuration01") { kotlinx.coroutines.examples.exampleTimeoutDuration01.main() }.verifyLines( + "1, 2, 3, -1" + ) + } } From f77f6ddc5f5cc6ed155fac643ec52696e02a79a7 Mon Sep 17 00:00:00 2001 From: kerr Date: Tue, 11 Oct 2022 17:36:35 +0800 Subject: [PATCH 032/106] Make use of `CompletionStage#handle` instead of `whenComplete` (#3475) It improves the performance of all future-related builders by saving an extra allocation of `CompletionException` on a cancellation/failure path. An isolated improvement is around x2-x3 in terms of throughput, benchmarks and measurements can be found in #3475 discussion --- .../src/future/Future.kt | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/integration/kotlinx-coroutines-jdk8/src/future/Future.kt b/integration/kotlinx-coroutines-jdk8/src/future/Future.kt index caf2a3c359..4d9a01ac70 100644 --- a/integration/kotlinx-coroutines-jdk8/src/future/Future.kt +++ b/integration/kotlinx-coroutines-jdk8/src/future/Future.kt @@ -40,7 +40,7 @@ public fun CoroutineScope.future( val newContext = this.newCoroutineContext(context) val future = CompletableFuture() val coroutine = CompletableFutureCoroutine(newContext, future) - future.whenComplete(coroutine) // Cancel coroutine if future was completed externally + future.handle(coroutine) // Cancel coroutine if future was completed externally coroutine.start(start, coroutine, block) return future } @@ -48,8 +48,8 @@ public fun CoroutineScope.future( private class CompletableFutureCoroutine( context: CoroutineContext, private val future: CompletableFuture -) : AbstractCoroutine(context, initParentJob = true, active = true), BiConsumer { - override fun accept(value: T?, exception: Throwable?) { +) : AbstractCoroutine(context, initParentJob = true, active = true), BiFunction { + override fun apply(value: T?, exception: Throwable?) { cancel() } @@ -97,7 +97,7 @@ public fun Job.asCompletableFuture(): CompletableFuture { } private fun Job.setupCancellation(future: CompletableFuture<*>) { - future.whenComplete { _, exception -> + future.handle { _, exception -> cancel(exception?.let { it as? CancellationException ?: CancellationException("CompletableFuture was completed exceptionally", it) }) @@ -125,7 +125,7 @@ public fun CompletionStage.asDeferred(): Deferred { } } val result = CompletableDeferred() - whenComplete { value, exception -> + handle { value, exception -> try { if (exception == null) { // the future has completed normally @@ -168,8 +168,8 @@ public suspend fun CompletionStage.await(): T { } // slow path -- suspend return suspendCancellableCoroutine { cont: CancellableContinuation -> - val consumer = ContinuationConsumer(cont) - whenComplete(consumer) + val consumer = ContinuationHandler(cont) + handle(consumer) cont.invokeOnCancellation { future.cancel(false) consumer.cont = null // shall clear reference to continuation to aid GC @@ -177,11 +177,11 @@ public suspend fun CompletionStage.await(): T { } } -private class ContinuationConsumer( +private class ContinuationHandler( @Volatile @JvmField var cont: Continuation? -) : BiConsumer { +) : BiFunction { @Suppress("UNCHECKED_CAST") - override fun accept(result: T?, exception: Throwable?) { + override fun apply(result: T?, exception: Throwable?) { val cont = this.cont ?: return // atomically read current value unless null if (exception == null) { // the future has completed normally From 27396e7ad7d8e4e159ed79d751ccb6cf489bd61e Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 11 Oct 2022 18:34:25 +0300 Subject: [PATCH 033/106] Fix flakiness of TimeoutTest (#3477) * Fix flakiness of TimeoutTest Increase timeout, so it's never triggered, as these tests do not require it to race or trigger with the upstream Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- .../common/test/flow/operators/TimeoutTest.kt | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/common/test/flow/operators/TimeoutTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/TimeoutTest.kt index c2fa8b06dd..28c5888277 100644 --- a/kotlinx-coroutines-core/common/test/flow/operators/TimeoutTest.kt +++ b/kotlinx-coroutines-core/common/test/flow/operators/TimeoutTest.kt @@ -99,13 +99,16 @@ class TimeoutTest : TestBase() { @Test fun testUpstreamErrorTimeoutException() = testUpstreamError(TimeoutCancellationException(0, Job())) + @Test + fun testUpstreamErrorCancellationException() = testUpstreamError(CancellationException("")) + private inline fun testUpstreamError(cause: T) = runTest { try { // Workaround for JS legacy bug flow { emit(1) throw cause - }.timeout(1.milliseconds).collect() + }.timeout(1000.milliseconds).collect() expectUnreached() } catch (e: Throwable) { assertTrue { e is T } @@ -113,6 +116,26 @@ class TimeoutTest : TestBase() { } } + @Test + fun testUpstreamExceptionsTakingPriority() = withVirtualTime { + val flow = flow { + expect(2) + withContext(NonCancellable) { + delay(2.milliseconds) + } + assertFalse(currentCoroutineContext().isActive) // cancelled already + expect(3) + throw TestException() + }.timeout(1.milliseconds) + expect(1) + assertFailsWith { + flow.collect { + expectUnreached() + } + } + finish(4) + } + @Test fun testDownstreamError() = runTest { val flow = flow { From 150f185dfbe66c4bb5a21a2dc7f23e0f3d937ddf Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 12 Oct 2022 23:03:34 +0300 Subject: [PATCH 034/106] Update Kotlin to 1.7.20 (#3478) * Update Kotlin to 1.7.20 * Add workaround for compiler bug * Update atomicfu and Dokka --- README.md | 10 +++++----- gradle.properties | 6 +++--- gradle/compile-native-multiplatform.gradle | 10 ++++++---- integration-testing/gradle.properties | 2 +- .../jvm/src/internal/SegmentBasedQueue.kt | 4 +++- 5 files changed, 18 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index b33ee80bff..fac4a1a2e7 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ [![JetBrains official project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) [![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0) [![Download](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.6.4)](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.6.4/pom) -[![Kotlin](https://img.shields.io/badge/kotlin-1.7.10-blue.svg?logo=kotlin)](http://kotlinlang.org) +[![Kotlin](https://img.shields.io/badge/kotlin-1.7.20-blue.svg?logo=kotlin)](http://kotlinlang.org) [![Slack channel](https://img.shields.io/badge/chat-slack-green.svg?logo=slack)](https://kotlinlang.slack.com/messages/coroutines/) Library support for Kotlin coroutines with [multiplatform](#multiplatform) support. -This is a companion version for the Kotlin `1.7.10` release. +This is a companion version for the Kotlin `1.7.20` release. ```kotlin suspend fun main() = coroutineScope { @@ -92,7 +92,7 @@ And make sure that you use the latest Kotlin version: ```xml - 1.7.10 + 1.7.20 ``` @@ -111,10 +111,10 @@ And make sure that you use the latest Kotlin version: ```kotlin plugins { // For build.gradle.kts (Kotlin DSL) - kotlin("jvm") version "1.7.10" + kotlin("jvm") version "1.7.20" // For build.gradle (Groovy DSL) - id "org.jetbrains.kotlin.jvm" version "1.7.10" + id "org.jetbrains.kotlin.jvm" version "1.7.20" } ``` diff --git a/gradle.properties b/gradle.properties index cffc2c53b7..37dfbb0000 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,16 +5,16 @@ # Kotlin version=1.6.4-SNAPSHOT group=org.jetbrains.kotlinx -kotlin_version=1.7.10 +kotlin_version=1.7.20 # Dependencies junit_version=4.12 junit5_version=5.7.0 -atomicfu_version=0.18.2 +atomicfu_version=0.18.4 knit_version=0.4.0 html_version=0.7.2 lincheck_version=2.14.1 -dokka_version=1.6.21 +dokka_version=1.7.20 byte_buddy_version=1.10.9 reactor_version=3.4.1 reactive_streams_version=1.0.3 diff --git a/gradle/compile-native-multiplatform.gradle b/gradle/compile-native-multiplatform.gradle index 0a247ede9a..1aeb8d2c54 100644 --- a/gradle/compile-native-multiplatform.gradle +++ b/gradle/compile-native-multiplatform.gradle @@ -6,10 +6,12 @@ project.ext.nativeMainSets = [] project.ext.nativeTestSets = [] kotlin { - targets.metaClass.addTarget = { preset -> - def target = delegate.fromPreset(preset, preset.name) - project.ext.nativeMainSets.add(target.compilations['main'].kotlinSourceSets.first()) - project.ext.nativeTestSets.add(target.compilations['test'].kotlinSourceSets.first()) + targets { + delegate.metaClass.addTarget = { preset -> + def target = delegate.fromPreset(preset, preset.name) + project.ext.nativeMainSets.add(target.compilations['main'].kotlinSourceSets.first()) + project.ext.nativeTestSets.add(target.compilations['test'].kotlinSourceSets.first()) + } } targets { diff --git a/integration-testing/gradle.properties b/integration-testing/gradle.properties index 7dfc532358..c8f5c78d48 100644 --- a/integration-testing/gradle.properties +++ b/integration-testing/gradle.properties @@ -1,4 +1,4 @@ -kotlin_version=1.7.10 +kotlin_version=1.7.20 coroutines_version=1.6.4-SNAPSHOT asm_version=9.3 diff --git a/kotlinx-coroutines-core/jvm/src/internal/SegmentBasedQueue.kt b/kotlinx-coroutines-core/jvm/src/internal/SegmentBasedQueue.kt index a125bec25c..f3447b7164 100644 --- a/kotlinx-coroutines-core/jvm/src/internal/SegmentBasedQueue.kt +++ b/kotlinx-coroutines-core/jvm/src/internal/SegmentBasedQueue.kt @@ -35,6 +35,7 @@ internal class SegmentBasedQueue { while (true) { val curTail = this.tail.value val enqIdx = this.enqIdx.getAndIncrement() + @Suppress("INFERRED_TYPE_VARIABLE_INTO_POSSIBLE_EMPTY_INTERSECTION") // KT-54411 val segmentOrClosed = this.tail.findSegmentAndMoveForward(id = enqIdx, startFrom = curTail, createNewSegment = ::createSegment) if (segmentOrClosed.isClosed) return null val s = segmentOrClosed.segment @@ -48,6 +49,7 @@ internal class SegmentBasedQueue { if (this.deqIdx.value >= this.enqIdx.value) return null val curHead = this.head.value val deqIdx = this.deqIdx.getAndIncrement() + @Suppress("INFERRED_TYPE_VARIABLE_INTO_POSSIBLE_EMPTY_INTERSECTION") // KT-54411 val segmentOrClosed = this.head.findSegmentAndMoveForward(id = deqIdx, startFrom = curHead, createNewSegment = ::createSegment) if (segmentOrClosed.isClosed) return null val s = segmentOrClosed.segment @@ -122,4 +124,4 @@ internal class OneElementSegment(id: Long, prev: OneElementSegment?, point } } -private val BROKEN = Symbol("BROKEN") \ No newline at end of file +private val BROKEN = Symbol("BROKEN") From 298eb1142583f64c9594b4d663e416f6618fbdae Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 12 Oct 2022 23:04:21 +0300 Subject: [PATCH 035/106] New implementation of K/N multi-threaded dispatchers (#3421) * Get rid of the race on LimitedDispatcher due to @Volatile being no-op on K/N Improve newFixedThreadPoolContext on K/N: * Create workers lazily * Do properly handle rejection * Await termination in parallel * Extract the concurrent structure from MultithreadedDispatchers to test it with Lincheck * Fix a race Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> Co-authored-by: Dmitry Khalanskiy --- kotlinx-coroutines-core/build.gradle | 4 +- .../common/src/internal/LimitedDispatcher.kt | 16 +-- .../src/internal/OnDemandAllocatingPool.kt | 106 ++++++++++++++++++ .../OnDemandAllocatingPoolLincheckTest.kt | 60 ++++++++++ .../native/src/MultithreadedDispatchers.kt | 41 +++++-- 5 files changed, 208 insertions(+), 19 deletions(-) create mode 100644 kotlinx-coroutines-core/concurrent/src/internal/OnDemandAllocatingPool.kt create mode 100644 kotlinx-coroutines-core/jvm/test/internal/OnDemandAllocatingPoolLincheckTest.kt diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index c38ed16041..a415aeec17 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -185,7 +185,7 @@ jvmTest { } // 'stress' is required to be able to run all subpackage tests like ":jvmTests --tests "*channels*" -Pstress=true" if (!Idea.active && rootProject.properties['stress'] == null) { - exclude '**/*LincheckTest.*' + exclude '**/*LincheckTest*' exclude '**/*StressTest.*' } if (Idea.active) { @@ -229,7 +229,7 @@ task jvmStressTest(type: Test, dependsOn: compileTestKotlinJvm) { task jvmLincheckTest(type: Test, dependsOn: compileTestKotlinJvm) { classpath = files { jvmTest.classpath } testClassesDirs = files { jvmTest.testClassesDirs } - include '**/*LincheckTest.*' + include '**/*LincheckTest*' enableAssertions = true testLogging.showStandardStreams = true configureJvmForLincheck(jvmLincheckTest) diff --git a/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt b/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt index 28f37ecf1d..214480ea70 100644 --- a/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt +++ b/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt @@ -4,6 +4,7 @@ package kotlinx.coroutines.internal +import kotlinx.atomicfu.* import kotlinx.coroutines.* import kotlin.coroutines.* import kotlin.jvm.* @@ -18,8 +19,9 @@ internal class LimitedDispatcher( private val parallelism: Int ) : CoroutineDispatcher(), Runnable, Delay by (dispatcher as? Delay ?: DefaultDelay) { - @Volatile - private var runningWorkers = 0 + // Atomic is necessary here for the sake of K/N memory ordering, + // there is no need in atomic operations for this property + private val runningWorkers = atomic(0) private val queue = LockFreeTaskQueue(singleConsumer = false) @@ -54,9 +56,9 @@ internal class LimitedDispatcher( } synchronized(workerAllocationLock) { - --runningWorkers + runningWorkers.decrementAndGet() if (queue.size == 0) return - ++runningWorkers + runningWorkers.incrementAndGet() fairnessCounter = 0 } } @@ -90,15 +92,15 @@ internal class LimitedDispatcher( private fun tryAllocateWorker(): Boolean { synchronized(workerAllocationLock) { - if (runningWorkers >= parallelism) return false - ++runningWorkers + if (runningWorkers.value >= parallelism) return false + runningWorkers.incrementAndGet() return true } } private fun addAndTryDispatching(block: Runnable): Boolean { queue.addLast(block) - return runningWorkers >= parallelism + return runningWorkers.value >= parallelism } } diff --git a/kotlinx-coroutines-core/concurrent/src/internal/OnDemandAllocatingPool.kt b/kotlinx-coroutines-core/concurrent/src/internal/OnDemandAllocatingPool.kt new file mode 100644 index 0000000000..1c2beff283 --- /dev/null +++ b/kotlinx-coroutines-core/concurrent/src/internal/OnDemandAllocatingPool.kt @@ -0,0 +1,106 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.internal + +import kotlinx.atomicfu.* + +/** + * A thread-safe resource pool. + * + * [maxCapacity] is the maximum amount of elements. + * [create] is the function that creates a new element. + * + * This is only used in the Native implementation, + * but is part of the `concurrent` source set in order to test it on the JVM. + */ +internal class OnDemandAllocatingPool( + private val maxCapacity: Int, + private val create: (Int) -> T +) { + /** + * Number of existing elements + isClosed flag in the highest bit. + * Once the flag is set, the value is guaranteed not to change anymore. + */ + private val controlState = atomic(0) + private val elements = atomicArrayOfNulls(maxCapacity) + + /** + * Returns the number of elements that need to be cleaned up due to the pool being closed. + */ + @Suppress("NOTHING_TO_INLINE") + private inline fun tryForbidNewElements(): Int { + controlState.loop { + if (it.isClosed()) return 0 // already closed + if (controlState.compareAndSet(it, it or IS_CLOSED_MASK)) return it + } + } + + @Suppress("NOTHING_TO_INLINE") + private inline fun Int.isClosed(): Boolean = this and IS_CLOSED_MASK != 0 + + /** + * Request that a new element is created. + * + * Returns `false` if the pool is closed. + * + * Note that it will still return `true` even if an element was not created due to reaching [maxCapacity]. + * + * Rethrows the exceptions thrown from [create]. In this case, this operation has no effect. + */ + fun allocate(): Boolean { + controlState.loop { ctl -> + if (ctl.isClosed()) return false + if (ctl >= maxCapacity) return true + if (controlState.compareAndSet(ctl, ctl + 1)) { + elements[ctl].value = create(ctl) + return true + } + } + } + + /** + * Close the pool. + * + * This will prevent any new elements from being created. + * All the elements present in the pool will be returned. + * + * The function is thread-safe. + * + * [close] can be called multiple times, but only a single call will return a non-empty list. + * This is due to the elements being cleaned out from the pool on the first invocation to avoid memory leaks, + * and no new elements being created after. + */ + fun close(): List { + val elementsExisting = tryForbidNewElements() + return (0 until elementsExisting).map { i -> + // we wait for the element to be created, because we know that eventually it is going to be there + loop { + val element = elements[i].getAndSet(null) + if (element != null) { + return@map element + } + } + } + } + + // for tests + internal fun stateRepresentation(): String { + val ctl = controlState.value + val elementsStr = (0 until (ctl and IS_CLOSED_MASK.inv())).map { elements[it].value }.toString() + val closedStr = if (ctl.isClosed()) "[closed]" else "" + return elementsStr + closedStr + } + + override fun toString(): String = "OnDemandAllocatingPool(${stateRepresentation()})" +} + +// KT-25023 +private inline fun loop(block: () -> Unit): Nothing { + while (true) { + block() + } +} + +private const val IS_CLOSED_MASK = 1 shl 31 diff --git a/kotlinx-coroutines-core/jvm/test/internal/OnDemandAllocatingPoolLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/internal/OnDemandAllocatingPoolLincheckTest.kt new file mode 100644 index 0000000000..de9ab8d5cd --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/internal/OnDemandAllocatingPoolLincheckTest.kt @@ -0,0 +1,60 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.internal + +import kotlinx.atomicfu.* +import kotlinx.coroutines.* +import org.jetbrains.kotlinx.lincheck.* +import org.jetbrains.kotlinx.lincheck.annotations.* + +/** + * Test that: + * * All elements allocated in [OnDemandAllocatingPool] get returned when [close] is invoked. + * * After reaching the maximum capacity, new elements are not added. + * * After [close] is invoked, [OnDemandAllocatingPool.allocate] returns `false`. + * * [OnDemandAllocatingPool.close] will return an empty list after the first invocation. + */ +abstract class OnDemandAllocatingPoolLincheckTest(maxCapacity: Int) : AbstractLincheckTest() { + private val counter = atomic(0) + private val pool = OnDemandAllocatingPool(maxCapacity = maxCapacity, create = { + counter.getAndIncrement() + }) + + @Operation + fun allocate(): Boolean = pool.allocate() + + @Operation + fun close(): String = pool.close().sorted().toString() + + override fun extractState(): Any = pool.stateRepresentation() +} + +abstract class OnDemandAllocatingSequentialPool(private val maxCapacity: Int) { + var closed = false + var elements = 0 + + fun allocate() = if (closed) { + false + } else { + if (elements < maxCapacity) { + elements++ + } + true + } + + fun close(): String = if (closed) { + emptyList() + } else { + closed = true + (0 until elements) + }.sorted().toString() +} + +class OnDemandAllocatingPool3LincheckTest : OnDemandAllocatingPoolLincheckTest(3) { + override fun > O.customize(isStressTest: Boolean): O = + this.sequentialSpecification(OnDemandAllocatingSequentialPool3::class.java) +} + +class OnDemandAllocatingSequentialPool3 : OnDemandAllocatingSequentialPool(3) diff --git a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt index c0b82aece0..bf91e7003b 100644 --- a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt +++ b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt @@ -5,6 +5,7 @@ package kotlinx.coroutines import kotlinx.coroutines.channels.* +import kotlinx.coroutines.internal.* import kotlin.coroutines.* import kotlin.native.concurrent.* @@ -14,7 +15,7 @@ public actual fun newSingleThreadContext(name: String): CloseableCoroutineDispat } public actual fun newFixedThreadPoolContext(nThreads: Int, name: String): CloseableCoroutineDispatcher { - require(nThreads >= 1) { "Expected at least one thread, but got: $nThreads"} + require(nThreads >= 1) { "Expected at least one thread, but got: $nThreads" } return MultiWorkerDispatcher(name, nThreads) } @@ -67,28 +68,48 @@ internal class WorkerDispatcher(name: String) : CloseableCoroutineDispatcher(), } } -private class MultiWorkerDispatcher(name: String, workersCount: Int) : CloseableCoroutineDispatcher() { +private class MultiWorkerDispatcher( + private val name: String, + workersCount: Int +) : CloseableCoroutineDispatcher() { private val tasksQueue = Channel(Channel.UNLIMITED) - private val workers = Array(workersCount) { Worker.start(name = "$name-$it") } - - init { - workers.forEach { w -> w.executeAfter(0L) { workerRunLoop() } } + private val workerPool = OnDemandAllocatingPool(workersCount) { + Worker.start(name = "$name-$it").apply { + executeAfter { workerRunLoop() } + } } private fun workerRunLoop() = runBlocking { + // NB: we leverage tail-call optimization in this loop, do not replace it with + // .receive() without proper evaluation for (task in tasksQueue) { - // TODO error handling + /** + * Any unhandled exception here will pass through worker's boundary and will be properly reported. + */ task.run() } } override fun dispatch(context: CoroutineContext, block: Runnable) { - // TODO handle rejections - tasksQueue.trySend(block) + fun throwClosed(block: Runnable) { + throw IllegalStateException("Dispatcher $name was closed, attempted to schedule: $block") + } + + if (!workerPool.allocate()) throwClosed(block) // Do not even try to send to avoid race + + tasksQueue.trySend(block).onClosed { + throwClosed(block) + } } override fun close() { + val workers = workerPool.close() tasksQueue.close() - workers.forEach { it.requestTermination().result } + /* + * Here we cannot avoid waiting on `.result`, otherwise it will lead + * to a native memory leak, including a pthread handle. + */ + val requests = workers.map { it.requestTermination() } + requests.map { it.result } } } From 1656a0d3c221f88027f05101ad2c0809c679a023 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 12 Oct 2022 23:29:58 +0200 Subject: [PATCH 036/106] Get rid of real time in TimeoutTest * To avoid races on very slow Windows-virtualized TC agents --- .../common/test/flow/operators/TimeoutTest.kt | 20 +++---------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/kotlinx-coroutines-core/common/test/flow/operators/TimeoutTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/TimeoutTest.kt index 28c5888277..854e331f1a 100644 --- a/kotlinx-coroutines-core/common/test/flow/operators/TimeoutTest.kt +++ b/kotlinx-coroutines-core/common/test/flow/operators/TimeoutTest.kt @@ -154,7 +154,7 @@ class TimeoutTest : TestBase() { } @Test - fun testUpstreamTimeoutIsolatedContext() = runTest { + fun testUpstreamTimeoutIsolatedContext() = withVirtualTime { val flow = flow { assertEquals("upstream", NamedDispatchers.name()) expect(1) @@ -169,7 +169,7 @@ class TimeoutTest : TestBase() { } @Test - fun testUpstreamTimeoutActionIsolatedContext() = runTest { + fun testUpstreamTimeoutActionIsolatedContext() = withVirtualTime { val flow = flow { assertEquals("upstream", NamedDispatchers.name()) expect(1) @@ -187,21 +187,7 @@ class TimeoutTest : TestBase() { } @Test - fun testUpstreamNoTimeoutIsolatedContext() = runTest { - val flow = flow { - assertEquals("upstream", NamedDispatchers.name()) - expect(1) - emit(1) - expect(2) - delay(10) - }.flowOn(NamedDispatchers("upstream")).timeout(100.milliseconds) - - assertEquals(listOf(1), flow.toList()) - finish(3) - } - - @Test - fun testSharedFlowTimeout() = runTest { + fun testSharedFlowTimeout() = withVirtualTime { // Workaround for JS legacy bug try { MutableSharedFlow().asSharedFlow().timeout(100.milliseconds).collect() From f3527c961e86747f662fce4965d5d574291ef2ca Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 13 Oct 2022 01:41:50 +0300 Subject: [PATCH 037/106] Merge kotlinx-coroutines-core and kotlinx-coroutines-jdk8 modules (#3415) * Configure source sets and compilations for module merger * Programmatically split the structure of compilation for the sake of separate compilation and dependencies * Add new integration test * Merge ABI signatures * Exclude jdk8 from animal sniffer Fixes #3268 --- README.md | 6 +- build.gradle | 6 +- buildSrc/src/main/kotlin/Projects.kt | 1 + .../animalsniffer-conventions.gradle.kts | 14 ++++ integration-testing/build.gradle | 10 +-- .../kotlin/Jdk8InCoreIntegration.kt | 21 ++++++ ...ListAllCoroutineThrowableSubclassesTest.kt | 0 integration/README.md | 1 - integration/kotlinx-coroutines-jdk8/README.md | 69 +------------------ .../api/kotlinx-coroutines-jdk8.api | 22 ------ .../api/kotlinx-coroutines-core.api | 22 ++++++ kotlinx-coroutines-core/build.gradle | 67 ++++++++++++++---- .../jdk8}/src/future/Future.kt | 0 .../jdk8}/src/stream/Stream.kt | 0 .../jdk8}/src/time/Time.kt | 0 .../jvm/test/jdk8}/future/AsFutureTest.kt | 0 ...eferredUnhandledCompletionExceptionTest.kt | 4 +- .../test/jdk8}/future/FutureExceptionsTest.kt | 0 .../jvm/test/jdk8}/future/FutureTest.kt | 0 .../test/jdk8}/stream/ConsumeAsFlowTest.kt | 0 .../test/jdk8}/time/DurationOverflowTest.kt | 0 .../jvm/test/jdk8}/time/FlowDebounceTest.kt | 0 .../jvm/test/jdk8}/time/FlowSampleTest.kt | 0 .../jvm/test/jdk8}/time/WithTimeoutTest.kt | 0 24 files changed, 128 insertions(+), 115 deletions(-) create mode 100644 integration-testing/src/jvmCoreTest/kotlin/Jdk8InCoreIntegration.kt rename integration-testing/src/{withGuavaTest => jvmCoreTest}/kotlin/ListAllCoroutineThrowableSubclassesTest.kt (100%) rename {integration/kotlinx-coroutines-jdk8 => kotlinx-coroutines-core/jdk8}/src/future/Future.kt (100%) rename {integration/kotlinx-coroutines-jdk8 => kotlinx-coroutines-core/jdk8}/src/stream/Stream.kt (100%) rename {integration/kotlinx-coroutines-jdk8 => kotlinx-coroutines-core/jdk8}/src/time/Time.kt (100%) rename {integration/kotlinx-coroutines-jdk8/test => kotlinx-coroutines-core/jvm/test/jdk8}/future/AsFutureTest.kt (100%) rename {integration/kotlinx-coroutines-jdk8/test => kotlinx-coroutines-core/jvm/test/jdk8}/future/FutureAsDeferredUnhandledCompletionExceptionTest.kt (91%) rename {integration/kotlinx-coroutines-jdk8/test => kotlinx-coroutines-core/jvm/test/jdk8}/future/FutureExceptionsTest.kt (100%) rename {integration/kotlinx-coroutines-jdk8/test => kotlinx-coroutines-core/jvm/test/jdk8}/future/FutureTest.kt (100%) rename {integration/kotlinx-coroutines-jdk8/test => kotlinx-coroutines-core/jvm/test/jdk8}/stream/ConsumeAsFlowTest.kt (100%) rename {integration/kotlinx-coroutines-jdk8/test => kotlinx-coroutines-core/jvm/test/jdk8}/time/DurationOverflowTest.kt (100%) rename {integration/kotlinx-coroutines-jdk8/test => kotlinx-coroutines-core/jvm/test/jdk8}/time/FlowDebounceTest.kt (100%) rename {integration/kotlinx-coroutines-jdk8/test => kotlinx-coroutines-core/jvm/test/jdk8}/time/FlowSampleTest.kt (100%) rename {integration/kotlinx-coroutines-jdk8/test => kotlinx-coroutines-core/jvm/test/jdk8}/time/WithTimeoutTest.kt (100%) diff --git a/README.md b/README.md index fac4a1a2e7..c11d97258a 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,7 @@ suspend fun main() = coroutineScope { * [core/jvm](kotlinx-coroutines-core/jvm/) — additional core features available on Kotlin/JVM: * [Dispatchers.IO] dispatcher for blocking coroutines; * [Executor.asCoroutineDispatcher][asCoroutineDispatcher] extension, custom thread pools, and more. + * Integrations with `CompletableFuture` and JVM-specific extensions. * [core/js](kotlinx-coroutines-core/js/) — additional core features available on Kotlin/JS: * Integration with `Promise` via [Promise.await] and [promise] builder; * Integration with `Window` via [Window.asCoroutineDispatcher], etc. @@ -56,7 +57,7 @@ suspend fun main() = coroutineScope { * [ui](ui/README.md) — modules that provide coroutine dispatchers for various single-threaded UI libraries: * Android, JavaFX, and Swing. * [integration](integration/README.md) — modules that provide integration with various asynchronous callback- and future-based libraries: - * JDK8 [CompletionStage.await], Guava [ListenableFuture.await], and Google Play Services [Task.await]; + * Guava [ListenableFuture.await], and Google Play Services [Task.await]; * SLF4J MDC integration via [MDCContext]. ## Documentation @@ -259,9 +260,6 @@ See [Contributing Guidelines](CONTRIBUTING.md). - -[CompletionStage.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/await.html - diff --git a/build.gradle b/build.gradle index d5b231d969..4377240625 100644 --- a/build.gradle +++ b/build.gradle @@ -223,7 +223,9 @@ def core_docs_url = "https://kotlinlang.org/api/kotlinx.coroutines/$coreModule/" def core_docs_file = "$projectDir/kotlinx-coroutines-core/build/dokka/htmlPartial/package-list" apply plugin: "org.jetbrains.dokka" -configure(subprojects.findAll { !unpublished.contains(it.name) && it.name != coreModule }) { +configure(subprojects.findAll { !unpublished.contains(it.name) + && it.name != coreModule + && it.name != jdk8ObsoleteModule}) { if (it.name != 'kotlinx-coroutines-bom') { apply from: rootProject.file('gradle/dokka.gradle.kts') } @@ -232,7 +234,7 @@ configure(subprojects.findAll { !unpublished.contains(it.name) && it.name != cor configure(subprojects.findAll { !unpublished.contains(it.name) }) { if (it.name != "kotlinx-coroutines-bom") { - if (it.name != coreModule) { + if (it.name != coreModule && it.name != jdk8ObsoleteModule) { tasks.withType(DokkaTaskPartial.class) { dokkaSourceSets.configureEach { externalDocumentationLink { diff --git a/buildSrc/src/main/kotlin/Projects.kt b/buildSrc/src/main/kotlin/Projects.kt index af7098935d..2442c50934 100644 --- a/buildSrc/src/main/kotlin/Projects.kt +++ b/buildSrc/src/main/kotlin/Projects.kt @@ -8,6 +8,7 @@ fun Project.version(target: String): String = property("${target}_version") as String val coreModule = "kotlinx-coroutines-core" +val jdk8ObsoleteModule = "kotlinx-coroutines-jdk8" val testModule = "kotlinx-coroutines-test" val multiplatform = setOf(coreModule, testModule) diff --git a/buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts b/buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts index f00a0b315f..639245b6e5 100644 --- a/buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts @@ -17,6 +17,20 @@ configure(subprojects) { signature("net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature") signature("org.codehaus.mojo.signature:java17:1.0@signature") } + + if (project.name == coreModule) { + // Specific files so nothing from core is accidentally skipped + tasks.withType().configureEach { + exclude("**/future/FutureKt*") + exclude("**/future/ContinuationHandler*") + exclude("**/future/CompletableFutureCoroutine*") + + exclude("**/stream/StreamKt*") + exclude("**/stream/StreamFlow*") + + exclude("**/time/TimeKt*") + } + } } } diff --git a/integration-testing/build.gradle b/integration-testing/build.gradle index 04be36fac9..f845816f34 100644 --- a/integration-testing/build.gradle +++ b/integration-testing/build.gradle @@ -22,8 +22,8 @@ dependencies { } sourceSets { - // Test that relies on Guava to reflectively check all Throwable subclasses in coroutines - withGuavaTest { + // An assortment of tests for behavior of the core coroutines module on JVM + jvmCoreTest { kotlin compileClasspath += sourceSets.test.runtimeClasspath runtimeClasspath += sourceSets.test.runtimeClasspath @@ -86,9 +86,9 @@ compileDebugAgentTestKotlin { } } -task withGuavaTest(type: Test) { +task jvmCoreTest(type: Test) { environment "version", coroutines_version - def sourceSet = sourceSets.withGuavaTest + def sourceSet = sourceSets.jvmCoreTest testClassesDirs = sourceSet.output.classesDirs classpath = sourceSet.runtimeClasspath } @@ -128,5 +128,5 @@ compileTestKotlin { } check { - dependsOn([withGuavaTest, debugDynamicAgentTest, mavenTest, debugAgentTest, coreAgentTest, 'smokeTest:build']) + dependsOn([jvmCoreTest, debugDynamicAgentTest, mavenTest, debugAgentTest, coreAgentTest, 'smokeTest:build']) } diff --git a/integration-testing/src/jvmCoreTest/kotlin/Jdk8InCoreIntegration.kt b/integration-testing/src/jvmCoreTest/kotlin/Jdk8InCoreIntegration.kt new file mode 100644 index 0000000000..91eef7e24b --- /dev/null +++ b/integration-testing/src/jvmCoreTest/kotlin/Jdk8InCoreIntegration.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +package kotlinx.coroutines + +import kotlinx.coroutines.future.* +import org.junit.Test +import kotlin.test.* + +/* + * Integration test that ensures signatures from both the jdk8 and the core source sets of the kotlinx-coroutines-core subproject are used. + */ +class Jdk8InCoreIntegration { + + @Test + fun testFuture() = runBlocking { + val future = future { yield(); 42 } + future.whenComplete { r, _ -> assertEquals(42, r) } + assertEquals(42, future.await()) + } +} diff --git a/integration-testing/src/withGuavaTest/kotlin/ListAllCoroutineThrowableSubclassesTest.kt b/integration-testing/src/jvmCoreTest/kotlin/ListAllCoroutineThrowableSubclassesTest.kt similarity index 100% rename from integration-testing/src/withGuavaTest/kotlin/ListAllCoroutineThrowableSubclassesTest.kt rename to integration-testing/src/jvmCoreTest/kotlin/ListAllCoroutineThrowableSubclassesTest.kt diff --git a/integration/README.md b/integration/README.md index 89100179a8..54dd96bbc9 100644 --- a/integration/README.md +++ b/integration/README.md @@ -5,7 +5,6 @@ Module name below corresponds to the artifact name in Maven/Gradle. ## Modules -* [kotlinx-coroutines-jdk8](kotlinx-coroutines-jdk8/README.md) -- integration with JDK8 `CompletableFuture` (Android API level 24). * [kotlinx-coroutines-guava](kotlinx-coroutines-guava/README.md) -- integration with Guava [ListenableFuture](https://github.com/google/guava/wiki/ListenableFutureExplained). * [kotlinx-coroutines-slf4j](kotlinx-coroutines-slf4j/README.md) -- integration with SLF4J [MDC](https://logback.qos.ch/manual/mdc.html). * [kotlinx-coroutines-play-services](kotlinx-coroutines-play-services) -- integration with Google Play Services [Tasks API](https://developers.google.com/android/guides/tasks). diff --git a/integration/kotlinx-coroutines-jdk8/README.md b/integration/kotlinx-coroutines-jdk8/README.md index 321e293414..56e145fc4e 100644 --- a/integration/kotlinx-coroutines-jdk8/README.md +++ b/integration/kotlinx-coroutines-jdk8/README.md @@ -1,68 +1,3 @@ -# Module kotlinx-coroutines-jdk8 +# Stub module -Integration with JDK8 [CompletableFuture] (Android API level 24). - -Coroutine builders: - -| **Name** | **Result** | **Scope** | **Description** -| -------- | ------------------- | ---------------- | --------------- -| [future] | [CompletableFuture] | [CoroutineScope] | Returns a single value with the future result - -Extension functions: - -| **Name** | **Description** -| -------- | --------------- -| [CompletionStage.await][java.util.concurrent.CompletionStage.await] | Awaits for completion of the completion stage -| [CompletionStage.asDeferred][java.util.concurrent.CompletionStage.asDeferred] | Converts completion stage to an instance of [Deferred] -| [Deferred.asCompletableFuture][kotlinx.coroutines.Deferred.asCompletableFuture] | Converts a deferred value to the future - -## Example - -Given the following functions defined in some Java API: - -```java -public CompletableFuture loadImageAsync(String name); // starts async image loading -public Image combineImages(Image image1, Image image2); // synchronously combines two images using some algorithm -``` - -We can consume this API from Kotlin coroutine to load two images and combine then asynchronously. -The resulting function returns `CompletableFuture` for ease of use back from Java. - -```kotlin -fun combineImagesAsync(name1: String, name2: String): CompletableFuture = future { - val future1 = loadImageAsync(name1) // start loading first image - val future2 = loadImageAsync(name2) // start loading second image - combineImages(future1.await(), future2.await()) // wait for both, combine, and return result -} -``` - -Note that this module should be used only for integration with existing Java APIs based on `CompletableFuture`. -Writing pure-Kotlin code that uses `CompletableFuture` is highly not recommended, since the resulting APIs based -on the futures are quite error-prone. See the discussion on -[Asynchronous Programming Styles](https://github.com/Kotlin/KEEP/blob/master/proposals/coroutines.md#asynchronous-programming-styles) -for details on general problems pertaining to any future-based API and keep in mind that `CompletableFuture` exposes -a _blocking_ method -[get](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html#get--) -that makes it especially bad choice for coroutine-based Kotlin code. - -# Package kotlinx.coroutines.future - -Integration with JDK8 [CompletableFuture] (Android API level 24). - -[CompletableFuture]: https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html - - - - -[CoroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html -[Deferred]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html - - - - -[future]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/future.html -[java.util.concurrent.CompletionStage.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/await.html -[java.util.concurrent.CompletionStage.asDeferred]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/as-deferred.html -[kotlinx.coroutines.Deferred.asCompletableFuture]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-jdk8/kotlinx.coroutines.future/as-completable-future.html - - +Stub module for backwards compatibility. Since 1.7.0, this module was merged with core. diff --git a/integration/kotlinx-coroutines-jdk8/api/kotlinx-coroutines-jdk8.api b/integration/kotlinx-coroutines-jdk8/api/kotlinx-coroutines-jdk8.api index 4ee57845b2..e69de29bb2 100644 --- a/integration/kotlinx-coroutines-jdk8/api/kotlinx-coroutines-jdk8.api +++ b/integration/kotlinx-coroutines-jdk8/api/kotlinx-coroutines-jdk8.api @@ -1,22 +0,0 @@ -public final class kotlinx/coroutines/future/FutureKt { - public static final fun asCompletableFuture (Lkotlinx/coroutines/Deferred;)Ljava/util/concurrent/CompletableFuture; - public static final fun asCompletableFuture (Lkotlinx/coroutines/Job;)Ljava/util/concurrent/CompletableFuture; - public static final fun asDeferred (Ljava/util/concurrent/CompletionStage;)Lkotlinx/coroutines/Deferred; - public static final fun await (Ljava/util/concurrent/CompletionStage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun future (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Ljava/util/concurrent/CompletableFuture; - public static synthetic fun future$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/util/concurrent/CompletableFuture; -} - -public final class kotlinx/coroutines/stream/StreamKt { - public static final fun consumeAsFlow (Ljava/util/stream/Stream;)Lkotlinx/coroutines/flow/Flow; -} - -public final class kotlinx/coroutines/time/TimeKt { - public static final fun debounce (Lkotlinx/coroutines/flow/Flow;Ljava/time/Duration;)Lkotlinx/coroutines/flow/Flow; - public static final fun delay (Ljava/time/Duration;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun onTimeout (Lkotlinx/coroutines/selects/SelectBuilder;Ljava/time/Duration;Lkotlin/jvm/functions/Function1;)V - public static final fun sample (Lkotlinx/coroutines/flow/Flow;Ljava/time/Duration;)Lkotlinx/coroutines/flow/Flow; - public static final fun withTimeout (Ljava/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun withTimeoutOrNull (Ljava/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; -} - diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index ad04fb9c8e..3a2d08f428 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -1176,6 +1176,15 @@ public final class kotlinx/coroutines/flow/internal/SendingCollector : kotlinx/c public fun emit (Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class kotlinx/coroutines/future/FutureKt { + public static final fun asCompletableFuture (Lkotlinx/coroutines/Deferred;)Ljava/util/concurrent/CompletableFuture; + public static final fun asCompletableFuture (Lkotlinx/coroutines/Job;)Ljava/util/concurrent/CompletableFuture; + public static final fun asDeferred (Ljava/util/concurrent/CompletionStage;)Lkotlinx/coroutines/Deferred; + public static final fun await (Ljava/util/concurrent/CompletionStage;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun future (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;)Ljava/util/concurrent/CompletableFuture; + public static synthetic fun future$default (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;Lkotlinx/coroutines/CoroutineStart;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Ljava/util/concurrent/CompletableFuture; +} + public final class kotlinx/coroutines/intrinsics/CancellableKt { public static final fun startCoroutineCancellable (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)V } @@ -1292,6 +1301,10 @@ public final class kotlinx/coroutines/selects/WhileSelectKt { public static final fun whileSelect (Lkotlin/jvm/functions/Function1;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class kotlinx/coroutines/stream/StreamKt { + public static final fun consumeAsFlow (Ljava/util/stream/Stream;)Lkotlinx/coroutines/flow/Flow; +} + public abstract interface class kotlinx/coroutines/sync/Mutex { public abstract fun getOnLock ()Lkotlinx/coroutines/selects/SelectClause2; public abstract fun holdsLock (Ljava/lang/Object;)Z @@ -1327,3 +1340,12 @@ public final class kotlinx/coroutines/sync/SemaphoreKt { public static final fun withPermit (Lkotlinx/coroutines/sync/Semaphore;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } +public final class kotlinx/coroutines/time/TimeKt { + public static final fun debounce (Lkotlinx/coroutines/flow/Flow;Ljava/time/Duration;)Lkotlinx/coroutines/flow/Flow; + public static final fun delay (Ljava/time/Duration;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun onTimeout (Lkotlinx/coroutines/selects/SelectBuilder;Ljava/time/Duration;Lkotlin/jvm/functions/Function1;)V + public static final fun sample (Lkotlinx/coroutines/flow/Flow;Ljava/time/Duration;)Lkotlinx/coroutines/flow/Flow; + public static final fun withTimeout (Ljava/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final fun withTimeoutOrNull (Ljava/time/Duration;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; +} + diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index a415aeec17..54883ff233 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -19,23 +19,50 @@ apply from: rootProject.file('gradle/publish.gradle') /* ========================================================================== Configure source sets structure for kotlinx-coroutines-core: - TARGETS SOURCE SETS - ------- ---------------------------------------------- + TARGETS SOURCE SETS + ------- ---------------------------------------------- js -----------------------------------------------------+ | V - jvm -------------------------------> concurrent ---> common - ^ - ios \ | - macos | ---> nativeDarwin ---> native --+ + jvmCore\ --------> jvm ---------> concurrent -------> common + jdk8 / ^ + | + ios \ | + macos | ---> nativeDarwin ---> native ---+ tvos | ^ watchos / | | linux \ ---> nativeOther -------+ mingw / - ========================================================================== */ + +Explanation of JVM source sets structure: + +The overall structure is just a hack to support the scenario we are interested in: + +* We would like to have two source-sets "core" and "jdk8" +* "jdk8" is allowed to use API from Java 8 and from "core" +* "core" is prohibited to use any API from "jdk8" +* It is okay to have tests in a single test source-set +* And we want to publish a **single** artifact kotlinx-coroutines-core.jar that contains classes from both source-sets +* Current limitation: only classes from "core" are checked with animal-sniffer + +For that, we have following compilations: +* jvmMain compilation: [jvmCoreMain, jdk8Main] +* jvmCore compilation: [commonMain] +* jdk8 compilation: [commonMain, jvmCoreMain] + +Theoretically, "jvmCore" could've been "jvmMain", it is not for technical reasons, +here is the explanation from Seb: + +""" +The jvmCore is theoretically not necessary. All code for jdk6 compatibility can be in jvmMain and jdk8 dependent code can be in jdk8Main. +Effectively there is no reason for ever putting code into jvmCoreMain. +However, when creating a new compilation, we have to take care of creating a defaultSourceSet. Without creating the jvmCoreMain source set, + the creation of the compilation fails. That is the only reason for this source set. +""" + ========================================================================== */ project.ext.sourceSetSuffixes = ["Main", "Test"] @@ -68,15 +95,12 @@ if (rootProject.ext.native_targets_enabled) { /* ========================================================================== */ + /* * All platform plugins and configuration magic happens here instead of build.gradle * because JMV-only projects depend on core, thus core should always be initialized before configuration. */ kotlin { - sourceSets.forEach { - SourceSetsKt.configureMultiplatform(it) - } - /* * Configure two test runs: * 1) New memory model, Main thread @@ -104,13 +128,32 @@ kotlin { } } + def jvmMain = sourceSets.jvmMain + def jvmCoreMain = sourceSets.create('jvmCoreMain') + def jdk8Main = sourceSets.create('jdk8Main') + jvmCoreMain.dependsOn(jvmMain) + jdk8Main.dependsOn(jvmMain) + + sourceSets.forEach { + SourceSetsKt.configureMultiplatform(it) + } + jvm { + def main = compilations.main + main.source(jvmCoreMain) + main.source(jdk8Main) + + /* Create compilation for jvmCore to prove that jvmMain does not rely on jdk8 */ + compilations.create('CoreMain') { + /* jvmCore is automatically matched as 'defaultSourceSet' for the compilation, due to its name */ + tasks.getByName('check').dependsOn(compileKotlinTaskProvider) + } + // For animal sniffer withJava() } } - configurations { configureKotlinJvmPlatform(kotlinCompilerPluginClasspath) } diff --git a/integration/kotlinx-coroutines-jdk8/src/future/Future.kt b/kotlinx-coroutines-core/jdk8/src/future/Future.kt similarity index 100% rename from integration/kotlinx-coroutines-jdk8/src/future/Future.kt rename to kotlinx-coroutines-core/jdk8/src/future/Future.kt diff --git a/integration/kotlinx-coroutines-jdk8/src/stream/Stream.kt b/kotlinx-coroutines-core/jdk8/src/stream/Stream.kt similarity index 100% rename from integration/kotlinx-coroutines-jdk8/src/stream/Stream.kt rename to kotlinx-coroutines-core/jdk8/src/stream/Stream.kt diff --git a/integration/kotlinx-coroutines-jdk8/src/time/Time.kt b/kotlinx-coroutines-core/jdk8/src/time/Time.kt similarity index 100% rename from integration/kotlinx-coroutines-jdk8/src/time/Time.kt rename to kotlinx-coroutines-core/jdk8/src/time/Time.kt diff --git a/integration/kotlinx-coroutines-jdk8/test/future/AsFutureTest.kt b/kotlinx-coroutines-core/jvm/test/jdk8/future/AsFutureTest.kt similarity index 100% rename from integration/kotlinx-coroutines-jdk8/test/future/AsFutureTest.kt rename to kotlinx-coroutines-core/jvm/test/jdk8/future/AsFutureTest.kt diff --git a/integration/kotlinx-coroutines-jdk8/test/future/FutureAsDeferredUnhandledCompletionExceptionTest.kt b/kotlinx-coroutines-core/jvm/test/jdk8/future/FutureAsDeferredUnhandledCompletionExceptionTest.kt similarity index 91% rename from integration/kotlinx-coroutines-jdk8/test/future/FutureAsDeferredUnhandledCompletionExceptionTest.kt rename to kotlinx-coroutines-core/jvm/test/jdk8/future/FutureAsDeferredUnhandledCompletionExceptionTest.kt index bf810af7aa..9c9c97ecdf 100644 --- a/integration/kotlinx-coroutines-jdk8/test/future/FutureAsDeferredUnhandledCompletionExceptionTest.kt +++ b/kotlinx-coroutines-core/jvm/test/jdk8/future/FutureAsDeferredUnhandledCompletionExceptionTest.kt @@ -1,8 +1,8 @@ /* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -package future +package kotlinx.coroutines.future import kotlinx.coroutines.* import kotlinx.coroutines.future.* diff --git a/integration/kotlinx-coroutines-jdk8/test/future/FutureExceptionsTest.kt b/kotlinx-coroutines-core/jvm/test/jdk8/future/FutureExceptionsTest.kt similarity index 100% rename from integration/kotlinx-coroutines-jdk8/test/future/FutureExceptionsTest.kt rename to kotlinx-coroutines-core/jvm/test/jdk8/future/FutureExceptionsTest.kt diff --git a/integration/kotlinx-coroutines-jdk8/test/future/FutureTest.kt b/kotlinx-coroutines-core/jvm/test/jdk8/future/FutureTest.kt similarity index 100% rename from integration/kotlinx-coroutines-jdk8/test/future/FutureTest.kt rename to kotlinx-coroutines-core/jvm/test/jdk8/future/FutureTest.kt diff --git a/integration/kotlinx-coroutines-jdk8/test/stream/ConsumeAsFlowTest.kt b/kotlinx-coroutines-core/jvm/test/jdk8/stream/ConsumeAsFlowTest.kt similarity index 100% rename from integration/kotlinx-coroutines-jdk8/test/stream/ConsumeAsFlowTest.kt rename to kotlinx-coroutines-core/jvm/test/jdk8/stream/ConsumeAsFlowTest.kt diff --git a/integration/kotlinx-coroutines-jdk8/test/time/DurationOverflowTest.kt b/kotlinx-coroutines-core/jvm/test/jdk8/time/DurationOverflowTest.kt similarity index 100% rename from integration/kotlinx-coroutines-jdk8/test/time/DurationOverflowTest.kt rename to kotlinx-coroutines-core/jvm/test/jdk8/time/DurationOverflowTest.kt diff --git a/integration/kotlinx-coroutines-jdk8/test/time/FlowDebounceTest.kt b/kotlinx-coroutines-core/jvm/test/jdk8/time/FlowDebounceTest.kt similarity index 100% rename from integration/kotlinx-coroutines-jdk8/test/time/FlowDebounceTest.kt rename to kotlinx-coroutines-core/jvm/test/jdk8/time/FlowDebounceTest.kt diff --git a/integration/kotlinx-coroutines-jdk8/test/time/FlowSampleTest.kt b/kotlinx-coroutines-core/jvm/test/jdk8/time/FlowSampleTest.kt similarity index 100% rename from integration/kotlinx-coroutines-jdk8/test/time/FlowSampleTest.kt rename to kotlinx-coroutines-core/jvm/test/jdk8/time/FlowSampleTest.kt diff --git a/integration/kotlinx-coroutines-jdk8/test/time/WithTimeoutTest.kt b/kotlinx-coroutines-core/jvm/test/jdk8/time/WithTimeoutTest.kt similarity index 100% rename from integration/kotlinx-coroutines-jdk8/test/time/WithTimeoutTest.kt rename to kotlinx-coroutines-core/jvm/test/jdk8/time/WithTimeoutTest.kt From 92a24950f134a0686d4a0202b764efcd6473299c Mon Sep 17 00:00:00 2001 From: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> Date: Wed, 19 Oct 2022 17:47:24 +0300 Subject: [PATCH 038/106] Make Mono.awaitSingleOrNull wait for onComplete() (#3489) Fixes #3487 --- .../kotlinx-coroutines-reactor/src/Mono.kt | 10 ++-- .../test/MonoAwaitStressTest.kt | 54 +++++++++++++++++++ 2 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 reactive/kotlinx-coroutines-reactor/test/MonoAwaitStressTest.kt diff --git a/reactive/kotlinx-coroutines-reactor/src/Mono.kt b/reactive/kotlinx-coroutines-reactor/src/Mono.kt index f31004b665..45f3847364 100644 --- a/reactive/kotlinx-coroutines-reactor/src/Mono.kt +++ b/reactive/kotlinx-coroutines-reactor/src/Mono.kt @@ -45,7 +45,7 @@ public fun mono( */ public suspend fun Mono.awaitSingleOrNull(): T? = suspendCancellableCoroutine { cont -> injectCoroutineContext(cont.context).subscribe(object : Subscriber { - private var seenValue = false + private var value: T? = null override fun onSubscribe(s: Subscription) { cont.invokeOnCancellation { s.cancel() } @@ -53,12 +53,14 @@ public suspend fun Mono.awaitSingleOrNull(): T? = suspendCancellableCorou } override fun onComplete() { - if (!seenValue) cont.resume(null) + cont.resume(value) + value = null } override fun onNext(t: T) { - seenValue = true - cont.resume(t) + // We don't return the value immediately because the process that emitted it may not be finished yet. + // Resuming now could lead to race conditions between emitter and the awaiting code. + value = t } override fun onError(error: Throwable) { cont.resumeWithException(error) } diff --git a/reactive/kotlinx-coroutines-reactor/test/MonoAwaitStressTest.kt b/reactive/kotlinx-coroutines-reactor/test/MonoAwaitStressTest.kt new file mode 100644 index 0000000000..355aa686ea --- /dev/null +++ b/reactive/kotlinx-coroutines-reactor/test/MonoAwaitStressTest.kt @@ -0,0 +1,54 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.reactor + +import kotlinx.coroutines.* +import org.junit.Test +import org.reactivestreams.* +import reactor.core.* +import reactor.core.publisher.* +import kotlin.concurrent.* +import kotlin.test.* + +class MonoAwaitStressTest: TestBase() { + private val N_REPEATS = 10_000 * stressTestMultiplier + + private var completed: Boolean = false + + private var thread: Thread? = null + + /** + * Tests that [Mono.awaitSingleOrNull] does await [CoreSubscriber.onComplete] and does not return + * the value as soon as it has it. + */ + @Test + fun testAwaitingRacingWithCompletion() = runTest { + val mono = object: Mono() { + override fun subscribe(s: CoreSubscriber) { + s.onSubscribe(object : Subscription { + override fun request(n: Long) { + thread = thread { + s.onNext(1) + Thread.yield() + completed = true + s.onComplete() + } + } + + override fun cancel() { + } + }) + } + } + repeat(N_REPEATS) { + thread = null + completed = false + val value = mono.awaitSingleOrNull() + assertTrue(completed, "iteration $it") + assertEquals(1, value) + thread!!.join() + } + } +} From 89478180bddba8e181751cc7a2a0e861f2de274b Mon Sep 17 00:00:00 2001 From: mvicsokolova <82594708+mvicsokolova@users.noreply.github.com> Date: Thu, 20 Oct 2022 12:02:49 +0200 Subject: [PATCH 039/106] Integration test step in Train (#3486) * Set up Integration Tests step in Train * Run debugProbesTest with -Poverwrite.probes=true * Update DebugProbesKt.bin * Fixes for integration-testing README * Update README * Migrating integration-testing/build.gradle to kts WIP * Revert "Migrating integration-testing/build.gradle to kts WIP" This reverts commit fbf966e2 * Add space repo only if build_snapshot_train * Fixup Co-authored-by: Margarita Bobova Co-authored-by: Vsevolod Tolstopyatov --- integration-testing/README.md | 12 ++--- integration-testing/build.gradle | 51 +++++++++++++++++++++- integration-testing/gradle.properties | 1 + integration-testing/settings.gradle | 1 + integration-testing/smokeTest/build.gradle | 9 ++++ 5 files changed, 68 insertions(+), 6 deletions(-) diff --git a/integration-testing/README.md b/integration-testing/README.md index 0ede9b254e..0218b23c47 100644 --- a/integration-testing/README.md +++ b/integration-testing/README.md @@ -3,11 +3,13 @@ This is a supplementary project that provides integration tests. The tests are the following: -* `MavenPublicationValidator` depends on the published artifacts and tests artifacts binary content and absence of atomicfu in the classpath. -* `CoreAgentTest` checks that `kotlinx-coroutines-core` can be run as a Java agent. -* `DebugAgentTest` checks that the coroutine debugger can be run as a Java agent. -* `smokeTest` builds the test project that depends on coroutines. +* `mavenTest` depends on the published artifacts and tests artifacts binary content for absence of atomicfu in the classpath. +* `jvmCoreTest` miscellaneous tests that check the behaviour of `kotlinx-coroutines-core` dependency in a smoke manner. +* `coreAgentTest` checks that `kotlinx-coroutines-core` can be run as a Java agent. +* `debugAgentTest` checks that the coroutine debugger can be run as a Java agent. +* `debugDynamicAgentTest` checks that `kotlinx-coroutines-debug` agent can self-attach dynamically to JVM as a standalone dependency. +* `smokeTest` builds the multiplatform test project that depends on coroutines. The `integration-testing` project is expected to be in a subdirectory of the main `kotlinx.coroutines` project. -To run all the available tests: `cd integration-testing` + `./gradlew check`. +To run all the available tests: `./gradlew publishToMavenLocal` + `cd integration-testing` + `./gradlew check`. diff --git a/integration-testing/build.gradle b/integration-testing/build.gradle index f845816f34..26ee9d99dc 100644 --- a/integration-testing/build.gradle +++ b/integration-testing/build.gradle @@ -2,11 +2,54 @@ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ + +buildscript { + + /* + * These property group is used to build kotlinx.coroutines against Kotlin compiler snapshot. + * How does it work: + * When build_snapshot_train is set to true, kotlin_version property is overridden with kotlin_snapshot_version, + * atomicfu_version is overwritten by TeamCity environment (AFU is built with snapshot and published to mavenLocal + * as previous step or the snapshot build). + * Additionally, mavenLocal and Sonatype snapshots are added to repository list and stress tests are disabled. + * DO NOT change the name of these properties without adapting kotlinx.train build chain. + */ + def prop = rootProject.properties['build_snapshot_train'] + ext.build_snapshot_train = prop != null && prop != "" + if (build_snapshot_train) { + ext.kotlin_version = rootProject.properties['kotlin_snapshot_version'] + if (kotlin_version == null) { + throw new IllegalArgumentException("'kotlin_snapshot_version' should be defined when building with snapshot compiler") + } + } + ext.native_targets_enabled = rootProject.properties['disable_native_targets'] == null + + // Determine if any project dependency is using a snapshot version + ext.using_snapshot_version = build_snapshot_train + rootProject.properties.each { key, value -> + if (key.endsWith("_version") && value instanceof String && value.endsWith("-SNAPSHOT")) { + println("NOTE: USING SNAPSHOT VERSION: $key=$value") + ext.using_snapshot_version = true + } + } + + if (using_snapshot_version) { + repositories { + mavenLocal() + maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" } + } + } + +} + plugins { id "org.jetbrains.kotlin.jvm" version "$kotlin_version" } repositories { + if (build_snapshot_train) { + maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" } + } mavenLocal() mavenCentral() } @@ -19,6 +62,7 @@ java { dependencies { testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version" testImplementation "org.ow2.asm:asm:$asm_version" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8" } sourceSets { @@ -56,7 +100,7 @@ sourceSets { } } - // Checks that kotlinx-coroutines-debug agent can self-attach dynamically to JVM as standalone dependency + // Checks that kotlinx-coroutines-debug agent can self-attach dynamically to JVM as a standalone dependency debugDynamicAgentTest { kotlin compileClasspath += sourceSets.test.runtimeClasspath @@ -130,3 +174,8 @@ compileTestKotlin { check { dependsOn([jvmCoreTest, debugDynamicAgentTest, mavenTest, debugAgentTest, coreAgentTest, 'smokeTest:build']) } +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} diff --git a/integration-testing/gradle.properties b/integration-testing/gradle.properties index c8f5c78d48..1e33bbe1ad 100644 --- a/integration-testing/gradle.properties +++ b/integration-testing/gradle.properties @@ -3,3 +3,4 @@ coroutines_version=1.6.4-SNAPSHOT asm_version=9.3 kotlin.code.style=official +kotlin.mpp.stability.nowarn=true diff --git a/integration-testing/settings.gradle b/integration-testing/settings.gradle index 54b47a02b0..8584c05a9a 100644 --- a/integration-testing/settings.gradle +++ b/integration-testing/settings.gradle @@ -2,6 +2,7 @@ pluginManagement { repositories { mavenCentral() maven { url "https://plugins.gradle.org/m2/" } + maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" } mavenLocal() } } diff --git a/integration-testing/smokeTest/build.gradle b/integration-testing/smokeTest/build.gradle index b200bb2fe8..26cd02b600 100644 --- a/integration-testing/smokeTest/build.gradle +++ b/integration-testing/smokeTest/build.gradle @@ -6,6 +6,7 @@ repositories { // Coroutines from the outer project are published by previous CI buils step mavenLocal() mavenCentral() + maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" } } kotlin { @@ -40,4 +41,12 @@ kotlin { } } } + targets { + configure([]) { + tasks.getByName(compilations.main.compileKotlinTaskName).kotlinOptions { + jvmTarget = "1.8" + } + } + } } + From 449b7a0a5de2b4f97702fb3400c47a7397106615 Mon Sep 17 00:00:00 2001 From: francescotescari <36578133+francescotescari@users.noreply.github.com> Date: Thu, 20 Oct 2022 16:20:12 +0200 Subject: [PATCH 040/106] docs: fix link in immediate documentation (#3491) --- kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt index a7065ccd15..9150bd0604 100644 --- a/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt +++ b/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt @@ -20,7 +20,7 @@ public abstract class MainCoroutineDispatcher : CoroutineDispatcher() { * * Immediate dispatcher is safe from stack overflows and in case of nested invocations forms event-loop similar to [Dispatchers.Unconfined]. * The event loop is an advanced topic and its implications can be found in [Dispatchers.Unconfined] documentation. - * The formed event-loop is shared with [Unconfined] and other immediate dispatchers, potentially overlapping tasks between them. + * The formed event-loop is shared with [Dispatchers.Unconfined] and other immediate dispatchers, potentially overlapping tasks between them. * * Example of usage: * ``` From 0bb8ee8282c831a229fe25a029e068ad9caac99a Mon Sep 17 00:00:00 2001 From: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> Date: Thu, 20 Oct 2022 18:00:26 +0300 Subject: [PATCH 041/106] Display artificial stack frames as calls to functions instead of text (#3073) Fixes https://github.com/Kotlin/kotlinx.coroutines/issues/2291 --- docs/images/after.png | Bin 297058 -> 160118 bytes docs/images/before.png | Bin 161429 -> 98194 bytes .../jvm/src/debug/CoroutineDebugging.kt | 65 ++++++++++++++++++ .../jvm/src/debug/internal/DebugProbesImpl.kt | 34 +++++---- .../jvm/src/internal/StackTraceRecovery.kt | 17 +++-- .../channels/testOfferFromScope.txt | 4 +- .../channels/testOfferWithContextWrapped.txt | 2 +- .../channels/testOfferWithCurrentContext.txt | 2 +- .../channels/testReceiveFromChannel.txt | 4 +- .../channels/testReceiveFromClosedChannel.txt | 4 +- .../channels/testSendFromScope.txt | 4 +- .../channels/testSendToChannel.txt | 4 +- .../channels/testSendToClosedChannel.txt | 4 +- .../resume-mode/testEventLoopDispatcher.txt | 4 +- .../testEventLoopDispatcherSuspending.txt | 4 +- .../testNestedEventLoopChangedContext.txt | 2 +- ...estedEventLoopChangedContextSuspending.txt | 4 +- .../testNestedEventLoopDispatcher.txt | 2 +- ...estNestedEventLoopDispatcherSuspending.txt | 4 +- .../resume-mode/testNestedUnconfined.txt | 4 +- .../testNestedUnconfinedChangedContext.txt | 2 +- ...stedUnconfinedChangedContextSuspending.txt | 4 +- .../testNestedUnconfinedSuspending.txt | 4 +- .../resume-mode/testUnconfined.txt | 4 +- .../resume-mode/testUnconfinedSuspending.txt | 4 +- .../select/testSelectCompletedAwait.txt | 2 +- .../stacktraces/select/testSelectJoin.txt | 4 +- .../select/testSelectOnReceive.txt | 4 +- ...edFromLexicalBlockWhenTriggeredByChild.txt | 4 +- ...acktraceIsRecoveredFromSuspensionPoint.txt | 4 +- ...sRecoveredFromSuspensionPointWithChild.txt | 4 +- .../StackTraceRecoveryNestedScopesTest.kt | 4 +- .../test/exceptions/StackTraceRecoveryTest.kt | 12 ++-- .../jvm/test/exceptions/Stacktraces.kt | 17 +---- kotlinx-coroutines-debug/README.md | 2 +- kotlinx-coroutines-debug/src/DebugProbes.kt | 2 +- .../test/CoroutinesDumpTest.kt | 6 +- .../test/DebugProbesTest.kt | 10 +-- .../test/RunningThreadStackMergeTest.kt | 8 +-- .../test/SanitizedProbesTest.kt | 12 ++-- .../test/ScopedBuildersTest.kt | 4 +- .../test/StacktraceUtils.kt | 39 ++++------- .../CoroutinesTimeoutDisabledTracesTest.kt | 2 +- 43 files changed, 180 insertions(+), 146 deletions(-) create mode 100644 kotlinx-coroutines-core/jvm/src/debug/CoroutineDebugging.kt diff --git a/docs/images/after.png b/docs/images/after.png index 4ce15e8b4e635e033bca77ae40e66ae1f840c1ef..b1e138c68279eee31d03a286d6faa7cb6025773c 100644 GIT binary patch literal 160118 zcmb^3Wl$Z@x+r`cg1fuBOK^90hu{vugS)%CI|~V}3wO7LOM*MW9p3!+-lxuc?>Qgu zr=FgwnV#wD?q}wiUrm&Xk~9)LK0Fv07?P}vgc=waR1_E(_&glM-#sQ={_KAXNLMjg z4LCTsjo-?@z`#hsWF}FO-0G^-2*sFIf zD=%+N7grP&jxg|VLvUaa|7XD@7r0!!9^@hdh6)A+1};)K4p*p5Fl^eOKn~o@;~k9fS)gcTvd=vc+e&@ zWrhil2GdLqY6*d?BELQ(z|@lO=+9*}ymc`;V&g|ACMV0(YyiZWWRfgO_WU2+mAaiZ z)R^&@z+Rtw4~3o@(RoqvKo|%lX+XSWh=Cnem|o-6AnY7p?Lb?Gz(Ma3TE{G@^dyGL zR1Vod2&Ct9FNJCwtYuqoCWj+uQ<+2Rq0uIG%Fd{(7K@>x@{YMGY#j^ySovMqeb>(P z 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 l@NAi`fft@| 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

)Oe348*>)O zPbP`itQ$@Uej(eJ+e0I0=Fe|eC5^T!F;!pzIT;&(Fv!?&AX{s$z;q9DpD0|HRZ-LZ zaPGKrmQa{_|K>?9pvPyVe#5?`RSG$?LSi^nw1Ot~NraJ66XCF~S9_fSMHR|`H4YG* zrJth2A+BOd0wnR^MqP#?08<*aEs>VfDs+GD&Ml&%UYciU@ykTgfS}FumNWL=TUl(M zd}sTzcUd1?C^nyzTjFr`*HMl+$D=y~4*ihk()eJV!un@BSNgAheNYY~Iw)$HoWK1? z08PLHb4T2^#DHu_BCHh+Y(LpO{)jW z*9DM~YYF)q<)qcA}l~6M9r6Af*=mqz0d5TKfy{8B82LYl)`uG!@1tG@62?nIjFhf z46CXv{|P*FD>=Cp&X{U{t?J8HFzaX_?xFwH_1((Oa5{eYmx}GKObx~YamhhxF4jJ6 zq@b!wTPn?8cD0(8akrm3c$$K0Rtd)PEAPI_e6}}( zYH;NIIwmW!hh4gR_;<5~F08K`3pp!l>JRs;_Y0T&u5MvLG^=J2Ei6RDM{jJpQMAT_ zZ$U3h$KQ%6%e(EWi+v(0P>TJ;DBXk4T28r^L_QnL5wnG;Fbz}QZdVhkGnH+*E>)W} zCz7%nx4?!arSOl4J=iIaLOY$zozq$JA6+q#4ltPB>w%6r+GvB;SV`^dLCk2pbZ$D8 z0MuRUeE9|qqR2#+GIH8}ak%jUZuWo>^Hl{=skSGK>!;L{q{G}x%h2M{q9X=e@!#Uq zue^WeXzBLdDGsZ{r)Ij(wc~#Sk#T@h%=|U9g}Fe~=PdN|nlq#1DI(A1@aJk=%Ddc^L%mc}Qa3uEq+ICx?Fj<|O92{|8P&knHcA zMCB(ZAtqG6eS2h8dF9MK3*-!dfK|Q5%r~^Wt;g6PmJTgF1ZxImcyN65UrtyZquM*z z3I%WW|3bD04SdYn7_0KgnpZM5zY>dSEun!OrMaGI{}V7*SytWXDWA7uN_1nfQY5A>8?Hu$A#7MFS@6)R+M4YVhBzG71`HnrP61exBziR(-nb>>SUJhwArN6)Kx` zlGD{Tuo7XdXkKQvXQQO0ERy3!ZJx{>)ea?Z8Jv`_pLRCwI^9;IM&4v#DF+Z_2nt$K zR`+f6&jJ4@$in_VKo-_;^Naj<%6I1}lZ{LPPi5PhAtYBadmhO9b}n~?TPHSUQfDy# zYtn)XgOJ;W^sY!S&a3tmoNLRXuYR%-kv214xL=9ncptkclU-nBoKJ0XjngP#E3f8gDhK;A(m7>RE?1;EH zmsW*=r^GGIpq1OexuvbOSCQ&u8kKTH3e@ECquLcmU&dI+gERplHhR*d(mym*r`n(k zC={p9`K+SF>Y|16czM2MUS**|Uq_@*x9ZcD8I9llB4hT+Y#PEZ1yaPm(`SDOcxbix zq3wX@pdrQaLOxvOHNc{BMGrv9+^}tGouEJ!StR+5v_adG^YB#S%p5O|369j?YsLQf z6fRid%k8_s@xJxho{`PNT`nKI3|HCeld$3)rONi90dt?<7O_cB?AOwZgfIpA8kXkA+Z@lD0157llxIo{Ty#G};rZ%t##4O9$@QZrFjZ)vs+_0p zW;5VT8=MKV406!1_Q*mG_&dE6G=j-lstXahsGx`F$#ZdSeV`Y3$O)c#yZw4QB!Hy0 zWvaGY7lg;`F~d<=L-~uM(fX)5r0iL_&gJf1YO8$OU8(_Ed>h2buwRgNZhF3D@u}l< zr&^eVC1!aOkEzDo?fGcUpevS~Y74c~T{Q19vX87=s>I#$?s{{;C)!cFwQsdgAT_Qz zLNqy<(whj)&!G^4h;i9kB=s1LPw+Ytf&I8Pv+uR0EQa%qR5*!hWEKTFH)O8&+1$Ph*Zc#X5Ovq2X|v8e>uMxcbvMdTz8(F*-bz>9 zR2;KvYnj&Z!uaF5{Pr#rYC0zHx%qHgUVH3^N*N8#cOCE(tDm6F;-;V|QFBf4&$QN1 zYKv~wR&1M9jN^L^K!F70UIk^XuebQBDRR(laMZ&h16BpjS_xb3Y^5Gc13iD1IDi%No73abYq3CFnX0kD*F?)D)%kPx@a@CTgjFboX&Gqon822 zAoBPU4k`0+qg~}{dGasB(Tw*;+qCvp7c3$XQ`Q#@O?Q)2&~L!UqiyGerybb9mA_u0# zC--6#@#7lKYZ+>w*FSiG+{3XhuSAS()&@l#AMPU4-#y2Z)h_!xm7{oL?A%+nDk;dC zUs<11*{%B;sNR@y>blJMvV`;l zU6Oj#!OwWDqWx~^%4ED<8td8#s4nH)r=8MzD03yRF;s(fKMxMSRM7}Dtm>%k{a&8pXd-#~hUq_QR zI}LK>_2_u4#$Wof?CIkcMT}JM$m7$0m&+V*Q>f1)t zgWPq`eWAjBZdDhjFiV>MF$Wp`n1jSNj-Q@LMj_3huQjlfv*{Y?X%?nkilYcsdzSeP zr-bXi-r$9#uo@WdgC5=1dm}L9{f+^v9~f?S+wMJ9Ry2EDWwR8h`7jp%yANL18^@il zw`g@<_G|`Y&bmu?mrcEHO3b;nsm)c89b2eT6r&!gnH&+e>!i%l5?^4Vj0oE2lpmCw zoS`98eHOp{vD|K=%x;eA;B5C7f=(9JMKV?gGDLi|ZX*jiHB9W}zM(a&ayFfC2(Xwu z?Clz_d2M%^YfoT$3X1jl( zpg1t5Ht}=DF37s^^4ZVoP2FlrV#AaAqyzxsp-4S-V4O7w4Z!pdn5j{p{bJc9lfDz5 z&Jw73{TF)v*S73ng~_=FcLLNDUvpN{*_?b+u;&IT5Pscb5bA!89vCB>TW ziM$0P-0|KR@|OtE!FLhmV#KFjTH2?EvCJP+GX=o~r}JmxY4HogudJ@f$-&gEh{%sQ>Dwum6bS zM*8dT`RDa=gbXmxa(+tx?Tqj400vf);r(4;3b$brfGfEGEgwRV762G0m;A_C-A7xK zjI-U*JRx@qrvc`3tZ#VrjCWXa$;|r{3^=FHm&1L*za#`ms{sx0Gvyy29(*GJ2y>!r zz&!x}BKXD_`TCifI$(Z)fB^ri=wFYx{yRwi*YHa+@%cHOCPF*z9=>{d03%*+msdMGqk0rk_WH<&6HH={l-v1p7yZ5dFtX@Z?{8&>^#3RA=?o z-&C0|~)fz8|Ce*IGb#~a!a!_AZNgHIZOV3%_hU{zjn;}kpQY|?#18|GtaRdf0o9oeDnn0H?& z&5wKLupJcb^8p#%VKfVa0KkX0h&TV}`6^d`ShMLL8^7BTdBJ-8g&n;S!PlzeeS+p$ zvwpB2yhS6U?^m~*!@6WpdbjC+eYDivFmSboE6tldw_SvYvKpNn^FgI8QB-|pR1dlD(Con$yCGcQO`lqu+{t|DyF6gmwTL8AFNs?(q=+S#8q;3h`CK z3lhMEgM@9M8iuXK?e$+Vla|&J=8C+R_d_` z$4gw+na)4hA?nSajhs2WSv{og^Uc<=PHpjpf7!*Pqmn-tWlL*PreoUsABFyL2|aQU!W?0O>@gf`OF z+u46YwGNL5`jK=dRMPxNQyVQVqxR3zv%%(4l7SAn3NFMpg8<3i`mP{{8GTZazXXr@ zdw|$NhR9|tFVfBgWT!;(P@zLq7wp{?stAkGAdbKHXeD?tA+h`pot%MKCNVkbL$D38Cu#fqO%GV>s&DPbEEwL%#`6% z^@hwRB#Fiq{BGH>8P%rUZA-Qfp%@u>1=HV|S!WCe!8g>sH?w%`BCFTiD22f&u>jix zZw>opbvBOC`^-NLfUI>WtJU&_g2MA9hi~Q!?EZj%q09WV4|L}p8j7}XWIR2G_F~7? zH^iIjC>&&eu5B4R7TckK0ft4bxE^M?VZ0H6ru7xo(md*U{dGM70CP-4^Ar6!nr@m( zaIxr^g2t!e!k)FA8d@EG>h-FYAL7Io9@3rb(oYfY*ODahyF$B`t%_^@wh+9q=I42JX z#H9+WUS{g^!Ny>55w=?hC(CY{&qQMg^rp40w~$^SiE%S4doh7gl zk9Te_GHkr7{mhFwIn?g(KvP#lq**nbB!^{Dyexu~p&U1p-N#JM<4%PVapnavY>#n5G`DSGC=D2<%%|Zq>lu!Qn_Fk|Xm*G=A`2RGT9L8qmdAbBkkdxFjMr~=<4NGSVXhcUV+HLM>_~8q=2aU zD{px6$y{8SxO~w?)RUAgsEO&*Hus$A{$R#r@qHOIbQE8PjFZP?cM`obFgC&nkK-#I zMY#f=kS4Q>!Clr?{U}imY;87*TjoX;*Qka2BdH^bpf>-$kvv$Z_+j%}+C#5lW3vGY zM$Ig+2ds?aff(6GyfORBdRYRnB94u=c+8V6gL(ETQZ*)S=RW-(RWlfANg5a8L_04q zWX#hyD;f{(UXu2p&#=n)<0AACMp9ZyiY|UY&Yk{}kJs>6Lq{#teLuWOsko07-9PoVn^>#j@8N0| zk4*~G1>zITd(`GQdrZP~?;T=dz%auwE~`%C02GqEn(f| zG1D|PQKA;@yte=0MZGPPD%{ecuQxbR;q1rONhBq(IJY!=U1cex+Rt9L7Uhr+JHGGz zdS?D-lB;6+1ygTd8tNUP{r5fYpGr#cTf3vcpJVY2A8#2Q`(Hfp4Mo>8o& zdag~@JC?a5cAd4awc-b%u6d9cxJ=Pq9X?Y-hr*QJe2~Xo!GP%K`rK+iAURReO=k8Q z(a^LLU90izs2$f&@WE#a(?81P>=nhB~fTybGw*jR$!%(7>z_gN54`ZJ}R zUr2YGtEa#oC4HE^@9Up#A%qkvqO;L9gy>p;q^MN+q}8HosEEVO_C{tVC??PnO$-By zP&sB_5W6%Ty)1rGc)Xil&XjqlgGtFl%$JW>k^wtOMxHkPHecmK@t-&6ix!G?`m?*f zE*H$8bApMTe&A|=EJI0RG#yBAkRgx|zvn}@2PoyT_p80W?_qqlJ{WJ(JwIh3w9erp z+K^{%aShef!@h=woW|>Vl{JdVW26zy>+e?O`d-Ln%Vf`nz^5f<{#W?an~+ zI|3lhh()IJ3e;oPa~rjSI#VirPyA1DsAJY9QyVle*VNLWPAj6ETj zsw66Jy53G{6`XW%F}&c>f%#HL@2|sHTTa29*#9H#bbMyxwtgeAv}x=@3|X-ALt*>sddS$Xu`HdhH5KK?vxKaC zSFjSrC?}G84TJd}6tOSEKc^1=8Hx&lz0^4-LUoW&$QhTORly|aS&HsjkYT!xK2p{R zcER&-lZ9VFvO(9~lj@I4yYYsoq?W#^AW+Jqa^Lw5^TswYr0RwF*;#S03!{9Xp50|2 z(n*6{{-^eF1l8lQzUqtl(>YjkVRcI){}=A*NTGU>52Mt4^-0U{O> z3PC`xopZri5{CK=#k%H627#ai9WkQ{(DEpNfNXJ;k7VudK2qO5pOn=D(rvLRb@S7x zE|?L)7)yTmxg{N%e4sgH5%XSm+5^X{v=7GHut)_10w}P!;4JRwy(%8*VfijoU0E@8 z%K&(cT!GeC$7{G<57JQS;D#mq$VY)0qd6~c;5K#(ffBgpHc5>x!!Io*3&xp7NS1n! z`6izAdd8@Z0W?|nI6LImag46~LA!r+5fx}?6sV2(pm;_y6e!dI+>pP z<~77lB%mv8Eg*|{_ZAo=v-IAwCJUmm-DrjbVH|ko<6iBaXp6fIB`-~XJ~=Cnvyt@n zLvMu+-cn{LjwkWUW1wjWUmiBvHI+Ncg$UYQv4jFebs*{$1Lcg_5K0&7WHsIiRi!+- z9WNvVav#ARj2N`lF!$1=R`AkH>-X)ZNQ`9NS@A#9j~v*R+!>>_#8#A&lXdy^p~T_s zHlNz+dRY|co$IN7+Am=T>Wn~sJGZ?eY|M?DV~1Dt<>Tx*caBoJWubc3Z8rGy5np93 z2w9tUF}p9&#~){deNaY1a4r{Dd3eBPSNs;{AqaB}Kz}Up?CijZWfCYz4C~0k{=#Z@ zLG_bC!4(LMqjBlXo%6$!$w%3S0|GIqi!xa?J#sq!NZ>P6yr?oNt%!}kzCI)y+$C|? zMmD{ECEMjlb#PT?6_|jb7yWe!ygr8HmpgK@UuK&I2<)Ow@I7LNwQk}v8=VC8;b zfU>YU2Xrk~bb`sKF8!~{mOt@`vFJHtl7Rf$(C2QR~c{{0M;ncu4#1Kdg6}3(Q5+3 z*$sqMLPT?NaG&}n#Yz$YBi0(TYR-(}PyU^i@uUS)ceVyIe^R5XiI!K99h?6RX$W-? z-JI6z*FwOF*5J55vV`ud{s=2M(iulZ4{(*}hn@2{O$|KM(9LAP=gHDIae&Whc#F@8 z8C-U#j55r;AP3^4026ugr>hH zr4jlD``C`%(?OY@^^GOv`~Am?l5d1hzvg}{4%?a0jDfx|u-w_`R~eu-VT;6xVjMAw zQ|l2nm3It*gG;RBy+OytNF}%FITn9YxGtsQ|!W_IE zY@Id%`2a>eiBut^*KqPjO{xLD&8fXaAim6!HUdH~a}Jh(QK_wMg=6;6x;(<4qBZhM z9CJ>M!mElM;%aEIMrAJbozs~XSvQt-2GKQ#w#KSxX>U_YrI*Z8ZPBDJKLlO^=tJP;iw!f{)EG_-8k^*FM--30jJYKa;m2()XKEWKaR$CInhT261ru@*83v3sVxQ~P#f^; zp$Wm%l-Dg~vh}0zQDXIBZ^7Ik9U)P%gCGT1hEa1o1=LrV&};I`ToRNYGyUSYcZaFJ zg;@{b7FSkbJSC zKFffAHK-PE$EJ`biMB2BV~tTM3UdS)5bDV!5b&+CT?RGoDHGg zYU4#xH#i8}*eRlOSq2t0VUu6e$TcTV!%FZ_wZ{#3C? z%f#Nxfnv@PfQ~~(Y)ahsCz!6G(_-*nZGgv1nd<*IX=u&S$we?P3 zjHaDrsxQ*ww&sPZCMF|bm^;kL(|iWR3i`;((s1QY~uQ%el-IKk{tu)5oFJgXSelK+Y1MOlj^T`49#W z|IQev`2|65VNrun=Myoyw|(W)3W8^H>ssZ)u%7LbQbtXP^R;*A9r4HPGq{Woq;ef( zO2rJ)smvtfedxbH@Gk)tUBPbgG8ye^3gz(6$(6!N7jDg%kTyE?4n#-+ZuHk^xQw|c z@Adqcmy0#$9w|=@3zL3RpjuAoK*wa>$fk!>sjnViN#_CiGZ1m2B2UZzz`=%6Z={PEvBu%NuFR*Z z{(7?#%*=QtlWnn&ve=`^a{fyu@K!zwTap!b$tk5UfsVnVrW*7?esf53cnz_C;Rhcr z204r&G^FD!7B1HNueS$e+%jW{dRX3~tT!~4Wx>)E8EXH0BAQSeDDL|hmtnfYar@aTCXIsK5qvg1l zxd#iLZl+bf73~}Q(Z3j+phaEkP;fr$ja5>9yPPGZspM32L^bOec`y!DSOsC?-NWCs z@JgRHSH+0Py{CR0RX+HJP@xcHLe$KL#R9}Vy;%$j1| z3yngZ0O`YE!`tud8wFa@*1DoIi%Y`X-DCIAZpvWQsVc|7K7PCpTzmneWn%!C8VEh| z)t~)hf0sb7I6 zb?6|Jh>4W|HG@v`Kw<<40z~fv+#y<03NawHg+IIAM^;?k+kTazmAp$W(ldkEBS9W; z4vUTMB7qtbCXFu2$nGo=ylm>oIx$43sW6V05EMcf7R3U_q{OejaQN$Nw{2v9_=lc?8t>!zu7e|Ch`KP)1{ ze6((MEbjs!>TgvP=)debl^*$mae%>?DV;u;V2>|*2PTItFz}&VT@CWS_$$&-q;I&( z9=&u?$=U0*RoMNJE=&lr@~L^vuAsoMh{1oA$_g1_Ne1)VG5xJo*T9$AWhIh+5L^-B zDo}^R_-l_Jl?96pY*5lV?4kBfzgcBjQ%)6CxVPX*ND>Ma!~5KfDlC*HnM}tzh#E~n zOO0TFuUo<7%*+D}ye>Xz$~#@v0}yV3e+!o)#51^cjM|OQCV(YTC_PJwQiDT~1!Z4Q zovW=A@V3@SbQVfz)Yk|Y6_jj;l%j| z7)`^SegiK}uWw9_ZroT3|Kwu2_(v&~g>uEv{NNd6@Sykv7zwbFt~c5uWHF(CGB)@D zOTfJjX!MHBl2XgQ6ckaLqd&q*1PnT}GJ-!fko!~0AyXg(>F-SP zR=6Lkm$h&$t7DJtt|PcwIq_%B-QeNVYk0Pkqn=#S`6c}M zhwdc8+jQ#i<`8CXFCy$Ik?@2jnDj-l!t?XNs)y{Elk;^D40CXlo`#3%Q4jcAE37e- zDpJgm@!VV%*tTvGD*osy^M$Z%#P7$5#RoYP3m6S`#3kNo_q`4eBxMO>r+kyZ&L8fR z*PKTsi2iW@gR}uB`+t%)dyW6Iw0SdyM#>vYG%^nZo#{8%_iE^d+fzrK` zq(^7pbX+^F0a!cD=A6CD`c=^`aZ5I!c*MTy_z-Ex?rSxnaBs`0rc3yo08P)K(2o8=a~b)};Ap_Qx~5e?n-|9GRd$)& zR|-s&I6x3`n$kw zUqHmmkeQH{6ZCT;=@l|6I!2^?Lu{3m+r^a$4VxJ4<+x#TGER$i;0Bf- z3=YPdmG+lsOhX}(R~Gb)w_|1DFTf_Etl8gHC$R!Z^&zdD7BR1lPc$`8L0gO36 z9k#Ea2Sa|_w)jdbX9PqXL;e<<0Nu(sxA* zSwh7WLppHB41iy>nCWR$iziGQPuDaOG@;n5E=DL0NI&%@&TO_Z&3Nh?!Hf3U!G z^^%~7&YUkQr=n;bE{#@Wiji_A9KX(#^duhFaXXd9>*(>E=(gc}S^`SgmwB=X!w5~H zhlfe(+b8cizY|CKlO>q^sttLt43OX#@TJ*dr_U50Vi#rH4zwCUK34i$Z3g!t3O83d zY1dnkx|axTW==88$0jRaZolM=30&%{`stM4w)TIuNWR?fHHMKq=j#QGt)>zG6 z=9QUeH9vvLJ|lutfA6SV>q2pU=bHec=OZE_MpNxB$IanbW5mcTYn?vTQ0+z@EWMxx z--QUQv4FGLI+wjJ$2LwWV*ltb+I0&74%Q_yuwm{bP#MIow{=&ZEZ))>;GDW?fl|yY zTjm(g;pcwml9S;p+IkK`vn;In$lOUO8h@(c0Hr2U2U=5&QK<%?0X3??#3^ZZV2)jM z&1Yj0bAvlb$CbOref7Cmhd;7opGc}$JDL{dyZXd4<@raiyIvo!99Vf-ao6EO4+4NO z1&aiss!hN@i7WEtHgI4jrRXk;{z%fZbTHwlI}-YwjATHv(ao+L=VgZ$=baU$plfA- zQMJUfez~$_OP_5UdwaF;)R7xZ@L%pt=5)HRc4L{T@xUb;)mO09UYv#+qM z^E;{h4yAO)mRZgn2Z@$lejhJ0SFv5x$lB@SSrz4!SyE#ZxH{b#Z>?rqYM&Ox zw|aF=zmFL=OV5V_3=6=q5%PE}EKq+NT?!C5-zl z)}_35C(o)Q!9l?XWKybZOn(=-Lkxpi1E{`IlhIhl7^#Z0PrF_U4plPoX%O)lAc#?) zRcrGgOTz`c;jUgKBe8;e>y-V&cX$;miLJUUR1z$5JjJ=8%o4arqB6Na5KpJ*nHP~P zd+L%qu?5Y0q@h^r@|SBAn5kOoW{QBiE2GU|?znmXt1+u*q^89QB@LX0Ju#WO%OXzn z>S8rGhh5mWLfIbD1?bmk{WAFTK9%FU1$^dwZuu1o;Zbp>_4JS}S1{n$gd9&z+&ln5 z3c!R0>|b4Vp!~>$2b4zygAU*E!RWeNN0bxr z7M2-c?2@+!=4L8;2MqNLV?S>n_A>BOKt@nD-=)1*@v&8N1WfB><6b*2+Mj1i) z^Xul$4XT%RaOUF?G$Sof=Zi*z%?N(vAKwqzfG`2OUA%Mx1piKEeB%K5Usvt|JAPVk z_V=F$KSHX%f$kCFy+tgJc_C|U_$u}AHpr80=cphA-mGTSO7~0t2+T$0hB@dGdo|y< zTn;}u3Jp~YSNDgPrkmyiQ)eow^-~QUgZNJSe}H+rf|)jf$62Q17%j}f{Ul!%d|Jr? zS72_J%=wWa_7&iXRej+K4c;mu(9qH>_-r*b+pVG45`h3mk@u#yMbI$U7 zlc~Xc;U&MF%Csq1NXnqFf`}mzI{v>1QgB}#pfw8NJ7)KK zYQ(&~x64{xei{1Scjk`v>*o2&WXogv3nnATXOQ*>h)=S&zh@M4xo}>@!Q|ONVEm$) zor@FZ+rD#}ai12^62!pXH|?|{##8!{>Wy)~GTCA1BUvywyBOJ#|`WH0Dx4Ut72FD5t? ziA)g5>(-!QRK>vU-Lg` z(v^c-wFNhLpE&5n5g3Wkvnw$-4#W26oV*eP`NZ&fGr72;tv@?$nn%a+Op+>gxNE1Z3bGvz2cUSVn zV~eUU3eOkO`Ry;nL>7ga8GaVcBWHA&awXM95_=mgjk)a0MZIg{0K- zR9__l?pf`Spfj0^*u4VciaqpNtI5)_S9W;mM zOS)XN1G_ZjQG^Fc3qRGxo*~#~ZC?e)`WQr*F|)B^91OZ4X^|-yu*tL8&*o|C zWM!e6+^V!FMJ&iTzc|$ltbS1`ZhyYG)N)H_g7#7jcIxEBc(sN>cHdtZ_c4GQ0Punl zhpOr7lC8g94SgvgGU()5rv)?ch=dOfZ5@WvFdDs`*_X@5D=l7!Hv6eoyXPiWz@wUE zDwo-B!89`MxHe7q&BqHS)XZ9(W|F2FjOc_M#X2i!#mq?G_qzb%ky>4vV?IRoB1t1w zoV|9kUutu;31p|Fc93`BV?%AnW{puo#QKy{*?=@8J5~-mzkNfNq2y4Rt$R zyFQ!>ho_f}s8CJo*_ZZ)sfj#&>gxEU9|v9>wq1E2r`mlBq{g-cqPgF>yvNTcpXGq@ z92t%JC!tiBv&|Y#stKTXP85N>CZ|JaJx@}z7Jag|@!YnW$X|g|cGVwxG`{^ycxax? zjBZcywT!d3r;m9zI0Cn{XeS?o0Zu)VN)-G}*7eqd;Z;5&>l#Kps@{w_1L<~lFZou< z?JHJ#)BdiDKGBtK)>R>k(;O~sitpwp+F?xg0sUQ$iPfoUL?|-OnkUBXf&5#h$Da0+ zyzE9{B8jqgyHsy5>wY%zMpG3+pzR$jGg^IF36$c(LZCk`HCc_bvp&wY1}iFYyu&%T z_Hz6to1&n09gG4c3`D6+I=Jbd@W4AGp)W6a?(}z=L7<^T{;%ZKt!FZHqPB+TD8%6S z+T2s~@~?!PYz{ibeBZ_P>JjB=_Zai}4eN3iDNMB5pFb;|j}7%)Z#~Mys>PD5a>j>f z6;pck5M``!az>H9myxksmlu<3=`SAq#DXUrS?KN`AJg&D@tb48m_Zk^?e2DOsAzm@ z4&>3W3X)DZv9s%FJ4xojp%^QuQ-y)c?kL0$8spa8*0{V!bJ9XFC=O2tEd zk-nXqF^jHwPtubLy1m_l(6KVM?8C;2hbh=mRIxeypuYuS?sRFf)Ji4o6-B+kmB6MJ z8*v%ET;#nNjHjSdzI!+8Dr?OfAyp(m4#Cm!e7{#*x(g;o0U>)OhvPibtodFsV1s2o4@M8BvvnQ1K;Go4$ z+89PP)ECQl$k7LKL2Gk1BOU@o&Iw&C2iIo}n^>D>v=L2f>CXIKg(ws>Y=8kU6_!r3 zVZ4Q_Q$DQcg#gN88Od`-JlK<%CvU=3q9eAiQmCtCxI3w6@vG)y1)Gl%q>YkrYK;t^ z2KY<-^vMi#^RwW;Rt^{hY7{JVWB-GSDao(*IxvczKi+*iO)TkipUcrgTHo8Nh`D+i zmEmI`rwf)#v;4R1ow{&3KiSn%PJUW7#?R9Q1rcEsFp3k<1>*4;q`gUw18h%RJhqoH z(4li+nJJ=$`GL6fEA5602S)S7{0$a2A04P?g(5keCs|&T*ENyBZ(|i-LGg>T8j6#90r3<|?w0p-i+ODw)~uH2gO1 zD?45-HhmIqBPMru<-fIIng0(qOr(G;Wv3M5KbtQnkwNqnvq$NQ-fE#j_=KCn5Gx>} z!MmR!*(tI{M`ff|Sai3&rRbPDb$}*A*Q)=GB{S@#X|y_nup-_;<0%|l_8S$XEe_+> zcv)MT$5gBWSa%0W*a&Me-gaYR+Fr)KEcO!LictZv6JB32O!gu0etWglow`@q6XErs z#j|JX@dxnMzIPAlK{3_u0kR*p|7}FBCW$2zWvy)1Rl-Gx#nd<*8&&dmHB$phZe>rz zAzJxijmb1_Fqi9?#otazxsCIIT(rfGdPgtt0$#*hTT05bL1q-tU_{77EooMD>yLG8 z4Kre=DjLI?p;|gU3t1>z@u&rQ-837}TuS zz|Qtq=xmFtuWa`w9F%8*%-wy{*uP*_l3%g$ry5GUa^V4#?m}Ym?zD%d{n7Pg0BD6Q zzHF?iUeBC`aiHXTypS6bO(`j8ojC(4S`xccK2%Qd<0uk#8ln)-GSta|q9%_kC8+#+ar`9{?EuwwLcR_<6W{BdqcVW zyUrv30+1NH&SxpSz-<&dkq2CDN;Cv(E&7X!b{x1 z8q3IK25od^3iGb1^h58x0@K{V2Uj@H z!ArbM$)QrcU}g5RrS5^U*k1C7H+}c;h@}iEW(8tue*1w*JEekE@>)Duko#NlpRJwf z2_Oq4RzG`2%I#cXIpqQHg#$zULJhRNm&*M|f17ENV&(mqlSwW!?Q=z?1|q^Fdf8a5 zIg}cXwYZtr3$RjQVD1)QC}|g066VD-|v<1E^l|&W)4Lr zlp}4nSZOTg)2qPA;oC%-pu}qDDV|%mp%CXf^%1Oj8yCLaVWa5FhGx!s4t|%>=Z8*Y zyR4KFcnsvIdP1${@#*iqMh?Y~kTKN#$x~Yq|0sjO`*S$G8-x+R+oJwB@4i((FTe^V zmw<^?0$2veH9lXrr6591Ool1}P)v+%v)L?1*Lo}5gkBdGRi{$i*|yWo5B}-d*j&oy z{&ao@K^_isP2Z<*{20}BvD41oP~*lIg!#)(d$<%jt=s1sRYj|1KljmC%_BG?I$Yg~ zBsv`4nAd%f70cL|pY?cGYzrX^h?YH|uM-6vEI!5FF7;c3>kkh!^ME+rL#OFNz9kO0 zUKRG>6>W-(>*$*XYADcsU{ojR-9oQDIg)to4?FPFSf=ly8`z6LwKnJL z2We0zZwY6JcXL@Bg=^AZ+)bC0LwQW%jm8hD=<1QIv|syT36blId3V$->BhrlhxhB_ z^Q7+X_wsv!Q_^Nc6e8eaUC%>r7!W7g)f(+vYh#(Ti}x+yxO+5waK-h4;q!Cag6i^?GyFrzg#} zf~jZpA`QO0eLi(aHd-gH{{P6{wEhKqTW`YH623-3^oB6JI6`Uj+z1}!0d9lhS>R%| z@Rt!6GMFb+N<978G~N1hE?CQqc(D0{TL0xOTm3BTSN{VenOEnR77Ry4TeJdr?ScM_ zl9l~wEe~p$d+Ne7k#agAHONL66XRA72e|csh)w1PbqrHV=EP<7urhqC*jy+WI)3vA zqjVJ6PD!%hh2HCA__YZP2?qW=A;}uv#tjRn2+6UX`ZDCe#yI%DHF`oZJ;BMzD{N5c zBvu*KBaO{sDh4lOk~z(C70T1hzHaocqRP#aOCF zPPzNiv38VhJ2{E5Nw9+FgMw-}IQ@Ww&u7Yd}o4xpM0u_k$ znB7nqPG71Kupagmc!?pp6Wzb3k{yb&Ja2{apB#fPAY(WEh;o&6T(pMV-9i3I>3swj z6qqgqhzB@3dp~Rp^+*!;O_dN|iwfJ#1Lu0DZtYW*Y?$=?xu^@ppaPg@V9o_f6nNRI zEDAwZF+B25isE{)!j&D{^dlc38<97MHHX_^u#KQ|AM{=8nP>6vKaUv+I5)*EOVjwm zUs4p&Lodsv$04gKdmsapqSYKX#W*<1Z3rlKtB|5vG!{H!)aXvD=%RD2K#;~QoaEA| zdT&J=uBp`n5K7Y+T}&OwJjMN_WGsm%EzU z!qkIYVG~rUZd(A57!x`5pAr&&@%ngKSsyIH^hLko(Zs08N{(B4c;%1x?w{U16J4+X zKa41~P5yqm`(<$<&Of2tA@v|8*?P<*p@4aXrRqi2Bpi3z zt6y-C{1jjq6+qp(Xp5eIX;2h zZe478+L&26>(XYW#N{+XDJWlwa;IK}8wo{junF)N6vFZ{H9@+KlaxY7BCWQZv{Al- zetSY5`~k(4a9IqVtaa-!020JxtYcAkBqiuz0s$l9WF$Z93r7o~A2CkKQ}*t!>F;;8 z$SOR<3AY7T#$EL|j=KWy)pEMqq6#*NMrN;3!tL*dNR0d9u)(9hm-gn7J_)ExCi!QJ zC|nZszCodHi7ew#uStGXg*qudA6Wvj&qUgX;_}9jeVcbB2?Qr)oOu4zMGos=Qc;aV zCbs`*r!TdOBf{I@8B6)>Tb~>Q!;>UHMMIP>;Qi4Qwao7vca3}1raHcQ* zO*@PTo6qn`@{iy8;GyOjxc{UJ15_>+uqQ&rDYS1&&}9(*8(vseZjALC11V1Nt_{i_ z#qG8)-T(xK($t#qGH;gfN~D+OdY-_Fb7OoW;n%c=#|_ z_R*?+%1Arelxta;js8TY;!lZPuoG)qFZ@{|E`UavcW-z*qmKBXxX+O&9apw@B+ibj zach**WS~4!DX=Q*5)@>=(1>pa8r1-ym<3wujk6LigBrAt@%i5POrBFehXb)w&Mt~9 zVv%`i>fz^`z@Nz`Y;(tOA6d+?;66)6PQ7MNe5dNE1dA8<=agTT*jG7>L-=6}{ZY0X z`DBjnUl>RRNV$(X;ZIbRFE0W;DVDov_9Snor?f$7r4U7@+`j-40)spK{{WDjkQ3Y#dPX3!2OUN1AWN!jp@IJ&Qw*B^okB$G;9gO(f=3q7Ux zoUi8vO~NfHwGcV!=+0->YBpmKgt8(+K#JvNm5RS2ic-F3y9!HCwa(0Y!bLC^{L2C# zu~0{+0*Ip-LGvvWVv$Z$(jib-jM*Az?9=soiuMPlOHMu`?kF&gdjg%>G$54np7FW4jXHT;0~QzeZtaWnPaBMejRe0h6wn*V`8>x< z?ll4R&dx?p@l>R``MhcpR@cBD)}?_lfPr$Hz(m7%=Y71APg_LChmuH&o%B?Qpg;D= zm^Xl)<;mCcNT&x(qKIW%iIX_ZyT_v&~Wo4@Ba&IUbFm6vnY$e4+_QjkLm2GwB%IxZzi!%s^|JD*j$el$n?VPPW z^X^njAiX)rdrPcC?}ZFN=Hx-8eI3Z%|`` zR8$KC$(XNv_*XFVmE&1IEOzIB9L#yt5`S2M3p+Nmd}|HG-(244LMalg;IeY1%|_fB z6+NCe$$RJclbBf5S$9mxfZr|atn6q^5;~NMRg@eUhbovnAJNXC7cGS1t3zAXy3aa0 zws|*ITUCbo<@ldz=CCn+Z-2xk8q?MdY^+v#q`RdY?H`glKn6A+X;!o3cr0}JhKEME zDdUVRF_51}h=)YeD&q~Av`|;FN&uWn&cuq!T42-5Vd#vmPr2^o&(fqqb{Ggs?)hj9 zXB+BUn^`6{Q!~Vb^|p<`)}TZW8Oq%ii#7~%%kWf4vK6uVJ8;PgT4{0N=OYBX&+MGW zK|^kiyvVzEnv|asZ|f%+ zleuENyxo$TY~7q87Ha3*vZ@WuqYq1wCGh$uCk&864hvHWCbrvLA41+{2Iia^qSsd$ zr^pG3BMnOu0^>90iq?J=UQb5wnnk(jj5e7g8*@V+-37gI`7nOK+`hQ2@u^F@juxj; zm)fTEqi+6I#BP;p(Ea$sRPX)ahGKG7NsjsI(CJn?07%r|r6x9TTUaYU1qkB^&19xT zR;agzSMf<&E=~fB_^W#aiGJm9))yLk+%PM=5`RbAy{a+`BJ#{s`~KJ)aNAUuyb~rM z9!-SU&Hlqu*Mmy&=F|E;o>p|Apl+oS+$rm743QYEsGO@}>155hLCt$OEQFM3jb6|^ zO%Gdk`QZmCqp|&WS({9>_KBXuu--a4M3PQgef65*>pc+zIZ4O=McrA2#nol&I%sg0 z;O_1gAh}i%g-)SEi4rBH!~Z%m zT^3_IMpHU5fwAbMmg1~iD%0tVBu3w)D-kZ**kw{uGioD!8KF%aCwX&SZz$#s?Ttn7 zEMQtwZ9ZAX0DlTv)|GM}6vj7w=Hj%r%6yQY>%Soh9WO(1xeTO=)~c$5F&(wYK3>EYiIY%WiXMIEm)ey3t_)YQH&z# zFn=L(rcPxoMBOuS|JEa3X~z4yD)(a^%;SU5*|F*Vy0+=XV>bI&5gy>ei@4Myk=p*2 zF>SBMF0M|0^ou2=HFg3+X^Ct}jIOG34+ALWcWZQ`KOR3&I?%AoG>gcsexRd^*&2h3 zC{uyTs(gw4Un8>o@0cOF{4M2&0m=u?A^<{uNC>Q z;dMr?zw|D@I!3Mhni6&aEJC5T89lOx${H+881F3ja0f-}R&{=D)G_^PyCUy+{H>|+ zmkYjEP4=0dXh0{(e(UOOpz7-0|DqI?f$X)T|4&-cYk%Gt>>HCINjnSMbAO8J<&B`V zie-`$_f8FI#d)*AbEfgKB3IKSk*;;Ox;f+A@?W=2*^iE6 z^U`I@0B=U&cZuBSt=jHQIf@z@c|_ZT5REv)C`s ziq=3&)P&z?+O$>^WomQUXa{X&Dx#BAUGV>*Guwstt_4c{2gzvdg?D~ik3iMD|0?8` zrmU-vEzBhtI>)=3w3^oTLDF-?szmGu>MwA&iWU z&o_ISLx0!CJl;)7)&HhVsx)6-O;F5CYT#&zI50lUSsR+0q=@?c-bf`i+e^| z5&Hxw$tyUTzj{KhhJ?Rwpic1DP=|4UW4%2Q(l7qPjzyT7V(HzX{N;Y{+jts}RcqP_ zye`BkmksaC{cg=mJ>vRS1{rL%nL)cBbvu?9N+ZxV>E|l(u9+~??bYn2%l_`z`$TJI zDb;coUO31PO)h0%8$&l#p-5kB__->z@?s+&0(HrCf9Ha*k??5rSP-e+(xjyZH{obr zypZPsF-!Dxk5oez_<8#NWySzAl>$PsZ6|T*dApR%iYlMN;`t{oO zw3c0*&PM7eJp+$jdBTNRXvc_@`;5BJR6(fioRkA4UqA}AC3nri-VBipWV@Tgd6tpb zY-_m2;_7Eg!iaa}ymBv?@D9(kbE0!Uh*U0?O90n{bDE)W(L-Yj)|2t#;Hw`?!Fqpe z!$xE;DicIAwV0)%JLKqQ1igDo zH+RNfzd#mjd`d)@ixYW=_qQZ;BdZ^?(TXO)DxsqXw3@}bmX#`sGZ2biUX|p&5^Bg9 zm%Q^oY9C1BEUbS7Y+-)fg>8N9f&hur##4`C8~SNL;C1d>uPChl);MOL1hZ3cBw?8S z=e#Y&wUWO3UWb0LCt_0u@ua3k2|XpSbvT+-DDxSfT}f6^)5%8kKZshXcgEi1BM&dm z@VMTdGMK7&WH?Lly@|JPo%(e+_@|b zixxMsG298C&u;&$ah=T9ByvO};_@$IFF>hSM_6>HfN{%y$-|74k-<1~O1toM^V6s6 zK#Lz7ex{gkYcqQN4C;w*C!^*2rCUmH#q%-}idbpDug*Sz*i58ysj^Y1d{N^}x4aQh zSzp<-*rcI=#F0}{k^`4W3^ zJ~lnqQ?t;y(R=w9@GYsCt@K)F7PYLGIXWtywDJ2Sa26bxSf5DLflU-fDoLE2*>MDn zVBDHB*iU1LMXG2v@Y$`#YbS%og9?osmm&-ODguMi|3WNFH( zpkj!8H}^C{D5zgW5!Ggn=AON>z(i7&Pop!`JS98$N<39avOg zbaaI5^V8GBu>Gi{|83&d#~>=rnrc5D^}M$2U4W=WO#Ameu$dH}R90%`5UDEFKpz6)TlePVBg3PR#_fgdVzX_pvJCURdL4t&W z`onMUJdDs4Vyl$zhrYeP&holG_NR3zCG#=df<5+aC~cM#OD=ARL(O8v_fe9|rz+aq zv*l?3lUIeiFHcfLNKU?6w=YPvybpVA@$--Ql*pa+YSsv~1c}mM-e&cEG)_rw(T4=5 z{SvbQZ8MWP#2*IRjxWd6?m#+weu4-tmBFO>`FkyNr^9uk*P~XH1)Hd?JWn;j$`Gp9 z1a4V_w5ii`7vJd)F5C2_LORiooA@?k*!dz)OSSK1YpDe+t6#Ure_5M-Z%5PiEHrp$ zu$FP6;-Q(GcXS^5qe+?;wbWX8tg67Kn_Xhn!!D{}DF%5OwSx(pR^_sfVkV<}m+Y(3 zAU>}hjHQ)Y)$7TTMv;1ITuhmH&n=~3Sllz(+0qv1(0)R%RC+paVH;SB zbN{YV)69!3omoHfzF=oHS!V7l?4FjkWX-_kMM_@zXA|x&ygRwFtJ=#LEfaJ;d5VdS zOS8$y)C5nso>lc_Nc}`Bcc=AQaN*b~yz2D?>Nq<*$n{Jf&oI)Ez#RUpVeAW!8_s1Z z{g!*_A2ph;CzUa^kE>+v6)7=;nV*p|=#|E4;|=KAZrKAUd2qDFx4&s4HN~V9w9?mMe&)qJ+EG;E-6mzJUjUk@jl0i2VRVDfnN5_Q) zaPy_YdqN_%pG0+Tis6neMova(`|5iZjfI^>fo}a8-P*u3_|`&}_T}DsK!3RZ^$;DG z75RB9`8a{FfW^;`m`mQ_qyF92yVhW&zc$(=$i>(99?ZzU!24yrB)@HU+En^-cIyKX z34-y2Urx_%>8-bP@vD*=WY;finHMgDwP3-K!HaZnRMn`2i(c98hiTXfRpWbpRUUfW z`%AlPvdDK1#Fu-gixuU0=U!L?p{(9i@kV0O-BZ1Rqj7_$D<A z1mIwxe~U>TgKS0z&7)MzZk)o9k`kK>y8E}ww_qS`H)&iaSMh=iCE5LWV2z_jApT6v*z;BPr_H@4 z(T7X|;)Zg`iOv0LvV#(0df1lDRW8Ywdl1lba7fclf(#8mgZX4|%C-EA2W|U9Q)u6$ zrQ}w`whoP=s{ZLH0#?=f;bfHn(Nqv6aNc9MbuaWZ;?J1)#R{C5c9{Po8{-#0SBWn` zbFbtEKidDwm-v4(E`9+IME&B(va8jj{l9#-MfRB<()0h*8+STMem0?PjBsYoMRd8Q zfdzfgfX{?IR`~ut!}*}O2j$_{`zzZd|CDhu##I?>#}Y5R~0f|iO+20W0aCo(qq)t zr>a!ElU{Zfi%K+G+m91sso5R`O_>fHUDy#FTe=*elT4ru0&;%Zjy|rknJ*zChWmO~ ziN>mzcD!}dJ}#^FQf-f=;D#{C>kia*4;xanvchWvhL(B!$Ytnp&ZH)b_tayYYme(B zk^GO)HFkh=)~^haLo1I@miQc$s_Dwo+2ot;{&VxnC^Lk+8%i>y5ez2UNJK#@U8}p% z=Uh-2a7yOXkZXL|ZopMDb5b&5AA&7W>+N8RC>kViRxg8EtYE{DS*(z=JD_4x&?hs#Ppvv$x5}U^;h_yyf zgqvJL8lA)#BG@suyYU|gtAW-yG77^Efz2P3s89G#bAdl**C;}{WQIsG;EaO?g{Tz` z4N%JT4a-J{NfKWqOBf|GFW`RyfW=3?Fw-{Z@?Zd`)Q2Y{rBRKFB_K=tH2?1?qY{ky zr9kenSdDFDkjk!?;aQ_|bxSZ>kurfk$pluDwb3^Ph856WH1F)h@SzgN3~5{#&5A7D z5gx+%3A)WZ5te=3Qp)a3_bd;dXYiSYkyG&e{F_*MxW%y)J=4(116vCqq);H=`D z;q=jOD8we(gfZ}E;LrH#1ro;17!(EPk9-D@9hAoPvi!{wW?Q-S@ypAc-lz6n8D8F! z&xq-=UwgwZy$V-393;F*{T`ry#6f=6#Dan@gzgiR`M6t+oo3>+Ts&V>!AOc3>I8^* z*<{zrRZLp(1B!*drkIer zJ_|!wlw}Xx0W}p^x+@1$e+9vpO3L8;fFn4e6?8=ULDlpp**ewy8D%F@eAwBD%>Yv_ zLU~L5zCzYLl$mjP6hNLsBHII07MuTz_*dhu7R=rLt<- zGA&t*LG@uMX=O|5oDL+iQtm12!{3(Np--4VQMs=&S_J|IX&`aps2OY(SQOE;PL97K z?tPg#E$+@Vx7z|Tb6r=Y$U_^egr9flBklM)n=7K z-L|X1Jyy@$X!n;jDtk&fnzGQhCF9Zj59!XQ%3hi1 z8L{hm-Nw<=lSjJymHOVkFUv4=D~=N;+R(P1FU!Bobbqh*gt;7*2Y`A$GlE-p)t)|# znzBU~P3o8pn1`)QXnKA+#8e}+hNCzq5MCGUo{1oQfOzGq9a;Rbnn@{^Cg=(AFmS%DJ{RpKx9#K=3(Yo`^=EW_rUt zW_5Ye3%~wr8_x$-fX&HC%kLUfy_Y5^{|4(pU5z(Xybi`A3a}{3`$iE@Fy@WVEVDSG zWOuPftep1-p3`$u>S0B}L^!XmJ{6TCm*O+aWWTyXct78`zh+A>%G=|11on1LA%wMr z8DT!Vyt!jT0N>sbpZ~nK94hyqG%lKMk14(S6Jzw4t#4#ggqb;7u^!oAC5`k} z?i+_n#*0owZwi{v?Fl3nJRwu0Z?8Xy}8vOm((S$9-(Rpk(qs+adgbGtBj^T!gnIrY;HDk*^13t5LJsq(ekR;RN z4O@09m?&JsOCMcxtkBC?J5tDX*B;`I#hC}0&hTL4u8Zgbk5unPI`B?cZb^SP#^pz| zob^_U*{Wm|PhU0n6q9OUlcRQ)M5ES8ZRLeQ_IDOPuR3r(oG-o-KR+n7rYR*)xyT2P zd>=vIOu>A6c!Dc>K2|62#B#*}VME`937-JmrtnOQv}+Oy}Q)G?XA!;2h(a&jvOm zUg*ZC;eWR`%^?Pi>QRv`XW5494{WP@SBxJ08HjF0Wq}&q#KixDXSl(=tyIOVa)sFB z<6b7{vYIGDjV5uRC{_jCh7^%gF-9VVDj9U;>qtjodX$i`dA-Q7cf`>3t*mmiHu+XC z3kaKCSG#o%Ta9{g9nnxh;KRw%XubPp{hYBC+e54~1)STcA@1&0qOe@-SgGB)&Oq{z zyl&Zb10j^75#&-NkPrm(c40I)ZJvN$2lM$xrj3>Sx>)CXhMt@ax7Rh>6Sj@gdY|{2 zM_?k&md}Qc@!G*fE-p)A+Fcs~r43=zKScB%+n;uILEw(s`u@!3$4G^FM)izHFP7Nk z;m89AP+SP-V4qT|d<=*>!8iP1ihLZX`Jkyj|m4m}J~Zy&J|CS}f6-Z~FOdxB@d$>=XcLsL z67P&IHr0~pWZgbjYY>#tnrkCyyBCX8frnEQkZbq+9W1EEi^8ZOy6%wT@?u-S&9ygi z-aM55W^ydW(c$%t0B0yc<385#1wk0NGMqtuNfqvWfTJ^@+v2s`(Q%kpo5py5ySPRn zTnQ~AsYFlCWKHg&`Fox>rL1Ba$507QbTY%bUo>`Xfd(3mXhGzFmek@%Q`}k6Z6#Cm zhN(;7xlU#f(RTQkn0(T05TLV2pp1o`qq=yVUftN`PF&bwb2uD;82Uy>OtikZA(ME9 z+d%d)OM{p_GkYx~5}KS(pQW^*5#e|-Ufy^tD=k{TS`d1V`w-_ILxOExLR)FAPohGm zGFigZ87?S(R}Y*E_Bb9o@g3iwc;-2C@t_sL9UINct1JOyQ&u+2{GO72S6D5ea~amt zGc`GLC`lD?@C(??FgN&XAs%kG;*BOR3$uEex$ws|#?U-1VF?7ESU|e<1Ssg@U`h|v z?;+Z)HtNQ)e*R}vtJA^Ol||DJy7(-vo7?-`$GUfAl2VGVfVXtjwwXnF#nzgtdX=9n z_l$A)FIc2jHo}_{ZS9P>l;DFOu3}F0Doea)e=4URb|+> zYaykneG{uC*M9QyK#Z##`3U}P^P|8@TCBCVhNsn&nc~@}i~e7I28HND=Gyx_$JF@n@Z9?gmDq#td!XLOM@8;?Y=hUP5VlJg@QFqCYHx ziO|HG190P47P)u`dt0%Dml0Dc{;V&)PHYZUshykAMMhaSq?s<(&PEkkS(8=v%`g{n zz}3eBd@7B>yJ^b>30da$Rz0yvGeXtvy(z|C+)9p_CDepkv84X!7W~=am;B0w)x$c! z3ew!Y(|s#Ko4oR}H-l8j>duQY8a5!dB9!f8fd!>5QIH7EDhzVbBBvNF?^JLW9Uk## zQ|9K4U3=__^Ym%Fx^d#!D2J?!9w`}jPf^T*(XUh3L0?9Q%mY5Z?(TftBIpWXOhZM* zII_;T+8F+>HIqZ3mH39Qh;_~2?0UUv*De3FYEXLh!~EzSN}q%hZr+R+cOrkWHF~Us zoh6{7;!5ET$lJp?!;7R#m!V@F17?RCk0`2TZ{b43FTM4N z)jy~(6LYMMO^5iPvs0LWnMT~aaFHDTZW!3~^w5da36V+Zu&!}ZPX;mVjEX{Jr z%cpdvG__wev4GDV)jU?ZM~PX+SrVgC>YGup~<&MWWK22$H%d) z>9^J2;qDN3hOAG1`>XN8W^`Ss@oRd_L<`6SDSDV#G_s^>^VWx2iF=~PENAz`vqa%} zzu7Hgt)+wpe?PXQ%v(L7&sRc31ri|~910f769v+_6^M&n>@fV$`-6HMUsbhi(&wOK zNe1I~LJ|=fY%5@Vy>~x|~Rx1%1oM8`y~X ziTKh&`HfB11WF8-B~koDH^*reo&%MXe)NE2IhE2F}phuDaq!taXDMH%aBiu-uDDlzR; z+2<22Ztbt`befZj+lzo)M0Q?*1_i&Qv>t0NmAWA7xA5=|SE4C>M{}S8?_P3eA^JplR!K_Rs z93l}F^mlwrEOZsD5_;szN(|iiQh-m?i_JX%U!LdiAM_E%31q;&WxK@z~fqe4?G-t;+ z-!xmL)0hCwR}v;ZM{;CHzfh78kf5MC2*AaIGv2JhHSk?+&t0*a9dS~uAIr&sNq~i_ z>Aad>tOHs4U=VhZ0DMn7*Ew{Km`=6du@2QHuVq-q}n`b!Q%E&pS+wZ-(3jHQGg=Gwy$QmTOUJgMzl#4&sPZM} z$9P0-R!J5Xs&@OJ051i1_lVgD>kEiyFp z?9fuAFXy>Ts5*}$c~_DQZSufNAemT6D;|fZ0_MOia;Tnu@7{s*grr)R!8Q&lD`!yi z<(lcNvtP}AOagapE=;I6VRBw^!B>773197^{0fG`lIk&9A6iYn%F!yPKbW8HdQ z2}+1Z$jAu(tVg--IT%|vQ7GVgT_g+lGC3fRfb(95+QrqCm-B$-B2oH%@MDa8lj0Y> zmk0M%HXFGV#1>Q^52;@lzHK|iw!jGs`ZIGrJ-6$q21KrJFNW(YV7@=&sgv8o?XuOMZ4vQ?qS0sflm%cbgN7^Q&6=jph!rJ$g$tPJu%s zC!byJ$F)U`H>&tdkN2>AVVK+ROwi?7rXw=5+ic1|7{Utkn*(f2!3E|ujBTbnxm)*XC!>KNdp%+ zA2CK)5XwlQ8Rf|=j^x~_?%9dCIMp{Z7J{N#OV)(eORV@qT77UX5pH;EuW;M*W9FJ+ z9r!cWBlD^WhLU)e+NC2Putx}j;mSk|g<2}DV|@dh#^}|Dz}QF|x?73I$Y90@m5hp( z{F?c~%?ZyuYft!_IJW;yUzcZxU%T4cm~T|O&0PR2Q#2}z7}kT^uJkvqTbZrReMM(g z#P$2ptR2os#s(#KLsk~THpSst*1kYL&!0UzWQ=-eAe<-w8*__zj}dH&>0N9L3=0g5 zF>2)kPm-uR_mG(NeR=aVY?4jsB;1Vex}|o%#~O~#)1#@b*V5$O^?7Z<(ATt-SHLZ) zot~Is{j^d#|P(9EvT) z@L{I0c18o{r7#}c@+tzGnpK8KqVG3=vZg%1QJheZWQ+V|FV{tLH-Gs$I850p;3vYF zJvr*htv$`%ro=J-XM>A$J=<-Aw;SG;?5FSQkyOb z`Jqj1*gjO_@@YiDwDD{+z}S|TIfR(&nEjQMVS@altA(SC(j=KL z2FQ$5uT!8Ub4N-)J?l&g*`{P4WX#O_wzHYYK=kr1N*+L_x zLKb7xn&xE|8#k$;LJ&8llNFTyi@_QQ$6r(frFyJ?{D1=mYqJrppQ(!->$rA(7nGtw z)esWh-xPX~SF5jIV?~EWJ>DDq<*S}zF;j!7%f>rg-b_$H9~^{KVRJWci_ly=Soyk1 zYXV`499+cZ7`1gVS(|GzncX6(N>O0bDRA?=ST+yoW6%ntfq-$`Mbhk}(zq}@{q56= z)TsWcK3ng2Tuq%&Z{f+~|Iz|9vR91tnIv3Q{J9?V00PByN;psJ#vT|#*WSP7Qp|~M z+q0P4YL=w+I^NxTIa)6T7Zn;FV4bM*$g(16TmBx=ou-9 z!kFd`YDd@OjlkqIojn9J(P$*h?9MkaUQUprTM?Kiisf83`zit^0tI#!e)smaSy!)mBm@Gn=jiw7eNk6>>IcYH1 zOxv25vz3_Y^yK#OPWO~6Z@2WDho=4(S@J5_JVxj(%mb3oGnT)4xV`gPCIDsj2MtSB zagxDyme0I@6UN4Pc-cS~uk{XZ#T!fA0IPn`5!N_IPq;VQ>7G*4hVU<2wo4~H#Y?G7 zv(^E!Uo8vB&*BZ@vQV{JuQ&6@^T4*)+?8@JG;Od{iX#hsMB^=0f&nVkybifM3^6vG zvoB}uFTT_CX}fc?So$C}MUkC8dyy;k9BJAS2kBBQC=P0ESxp_LE@(11JevdSwMQ!H zc&$gQT{&s8_;UeK>3MlY{(RogghG>O>xy9GW@%|9zo$o5Y$dajS!zqc6`#_Aniep4 zJvE}~q(&`LvC3nuZGWm*17$9BI^Ewf)(6Fd)nhXLNG@@Y#Jxzeh{$Nh$KBD^_w@_K)UtI<~ubZt#Bcp2HwH~Ary^k+k#b77QH@m7>X`5^i5LU_yw zU_y;{Nk!qM(Qw}U3UK24g2+xGi$X;U^z3OtNPP1+a@xuM;*Az}6qv%dQv+1rd)WbW zr&}bI0`3kB#L`7l%Aso`T@8BMsK%nLz75UWg zrv+;@P?i; znn4%q5fK2eD8IJe*NE!dsm$ivY}TNEv~jI$s64%l>!y9Tzp?A`w1n+I_KW=AgbX^F z*Y|@Dy~$Ng{Dxs6@L|VL=u(#dR5j=vIe!nEOs=OVS!v#}3BDNYwmod;GrDX($I%?9 z5hrEYYxv(J4`N#a_pi7jHG*V$0$9%?BV;Z>j&_E}#HP_x3r_~b=Oay$Epzh!hfV;H zCl2HPqgE{cF}|NZInF!>(Hh_uG=*|GYmhOvP1Oj6C@C61A^>h$@g24H)It-{C;W4~ zEkNj)V+sW1gKOAdiSCGa2_;tquN_0o-vb+Vvo55GKb^Y)R=u<7fa;psOYkkY&1LHR zYMv|K`iH8;J&wQuX8|}TVlUFvhtN|(Xqu|A&IL&nY~VhxscGO9#s|rAMrfd3C;APn zFB`fey2jSFGkwC-B5;qmsc)nZq`UgW>!;796rLX{^j~WX$u+W{xGYkw78jWJ+L_xx z50d7epLcNkXXRtJ@aV0m$hBjMP?z!MgV4n%6=R4dCpPPDuxPmwm;vI4WXc+5DVorx}V*ZApG6{ZI~!_yKQj!yQiHA^EBSqG|d z2E+t4wd5MZj#v*gMsy5&B=ndK)s6cDqWe2v?z{o?*z*U%!x#6x)Ke^c27_BIzUNlN zni`8UCXyx5@E=C>=tt%PwuUy^@7AiQq~$i>H@s8aFUqf{Qg0PM=S;%txT#>%nNDW0c-UEY;nx*JKp3-R~6X(-L-_6?2CW8v{M~nVUeN)WlJ&0TcOiSgq-2FD$9%t1>O#vT|4c z7I0WnI)vEjicN7MYY!!^HfHBuS>7z@oFqgzFq$+Os_L%cRWcrQ76Z1}+JS&xQh>Pa z$o9DL`+-#QlY*LU{#H-Iyyn+Ud(Lc9V>v<`b<=|-WaNal>*B>bMi*g9%i#$?7Ru9d6<+2Kr~~>Mx+S9uDCbs896&<)0rN4B$O&moIx% zITZiA_>Z4>jez%T0EO=9YvBI}$KS&I#MSp*ZZOBt|MrhxKna*XLH$ciC#)41_!p3W zA^Cm}L0?kRI~lS-JwX4(j=uy6zofrROn%NZpzHs37s!ttZ=x@UCYRyGKePV3^?x(; z57DP+pdN9xy@&l@p7qz0-E^Toi~e85gQEsDlj@C(n5UXN&Y6RuyxAuH2^cVa!_nP- z+^)k`gZ-;0JzxJa<9WChIG4BWN|xm|12tq_@N-)XaG5_<5cLIlfTG7EvAOrG=Dgqx zd3tR?E@)5~OT;`>LcLIiR}z943^Wglk-*sJ)O*EcZ1d4#WddYOJCN7`o~%cL>WQFF?~|yyk-NY)QrhlP}k|sfj!{@neR>HW? zMCxJ&0PE_!i2TTI-nmD9J`fDqy39L5!$ImzPT)%PG!j&>S)IcW!Xbg}?Yl>qpTM*Y0LCP#`fL#?W*B=Sc(NkiOv&vLmtt!in%T*Ka~rH z21u3rhm_-^#i$>0M9zZJKqY4W3CBHSkZoqKtZv-1WfD5z4f%OLl=jcAX!x-zA@iLj zhJ9NV{NqoZ6zGa7p`8=XJle&&XHVOGab=dW8rgg5SlpQ$kSVM$#T^3cx4(yncO)pz z3D?4@@eJq2Qy>l944gI3!%(NvQjzh}sM>bNif*lX_)3Hd48b z3*ci01v>$mOARbIzFuJ*lD9tf1pLd;Aplk8I5L8U8 zJ4ZUtc&&P_AK&YrB5l-Yk;AQKnE!L#d!DY3JhS&?gYDlbW~uOcS3xesKb@;>T3u5X zFkfp{GDPO_Bztz72^oNIp!(8l`;S2f@>fl=w`_=fN~QYC z6F#*@tiTGRh`Tq6opR~;Vyqc4yjAz()sK$zMFN0a#T?J^TJ?o=}B+kK$A#&x&`KX_ekw*pM@P5L2J z#r531HH$wC#tAto5)@hR^MaL3rTDucyJs1(3@Tp)l5q=2xyT!xk8cOxO$L!$wU11I z$tNd^)FhY_xTZh(eOWma%s9Qs-xLbVRllMGRy!?As~0yxGZzD=s!GZ4HIvDr-&yVs zn<3*5JeBhVjQ(DFKLHWFo+A^h!_4ezTkg2*_c9rP+^}u!a27lUWD6kERodG-3w@oX|cBzb1>LU%tPj@@KyfNIW= zM8ji#&Y+`8_u$+2=X^)}uJrqGF6Lp=8Qi_-%ozYZV8kkMrZEi~I zEIf)8#UxQIO1|H_FErI3sIa0}FZ#^ac4qDA64Uk76B;av2tPba6@clxlT+8Gc!pX~ zs+#;?JWM+Nh{oovt$ts=#sNhdKF#w-#(Q_QGDDhD<98D6&B6&Qo;iK1@A>mv$MrKg zyNIaBwh2(!mjdqspfR=8C-y&xY+0k_Pv-DA+YQM{2H$y6r3H4^>suVls=%Wy_lKRo zk>maObCb$!{YjZ9_73UKxdusUeN4`>S7N+-#d%eM!`3*NLopHlZ{QhfJyV=6_|@4ZV5aw;SA?RDZo#HCuNBWwbg z{>TX`?ndTfMD)$gJrhpiVm%d`F3fGsuv%vQ6By?w=Y00FmHQjvE`Aqla^cdtsT+D6MVz;+T z;~On>fNFN`vQWHa?i+R;ZZXB^CS)$r3jv*`LGaw!co8YjbdG)ZKM6!7RENlX8-d#% zg9j7=`&>$20k26TjhF|RZ;XazSe2zUJ4cgD0uI<*FOkN=$C7sGl0ih^`ZoqhXVUZ! zGSYzS4=;lys)fzgX1Aw8dF{&m$d(7DDU<~WgUjEoELBB1X_qdv8oA{3=9`Fkg;5Dc z*Gu&@vNhyZX-yN6@6_CHX3U`G$TKK##5GXiqEltO)i!s@S|GCrsoyJ0u*YOp)fRhx zqTe=*9+jZMz~xeYctM8smZa8OuTUzcdXV|es=&I;o{yZ#9pH5qBPt|XcxGmM@o)=D zdVi*|^ns0R+UNH;*hS^m=qjbWa#$F24ATf7ws1)JQ5N6Vf5Dm-eFZ<4Y)$Rs#>4mB zzs?95e{7pKB%w%4+F(ukwh7P^*Q0Fgy>*ONDiI3R{DgARWx1N)?%LnK0-Nl1 zdUXW7qMuMIcfJXFIKqKJuG{VTiH*rAOo4)ym)X4D;pV&OJJy8#w9;+(&SmNzPjFjF zJT9Y|BPFy+gDvLtkhdJ}=H@Fe#(PyqNNRzjWX6ji&Hp65yjdv%+H=?U`eCG_$YJct zNlQ&Nv1M^(CJQNBI-+{$hBpY6ZSO&ZYD9iKNd-*Z&`G#JDp3vKrefF$=`@jWHLPL- zx^qr&n1xj`Q%btCF`Uoe{keCfZ!4`>Yg$ysHLo#m_abeNIqq>p8I8Uo3paar8bxR! z&4y+Fq`wmQ%Hera{}1{bKCcv6kcF#uXRC7}s466J(APEv0GV20hTy$t5ntJW6VVp4 z$}TSk6$p0p>W&)G@oKCw>cn`^_G}F0`zn|{yXt$5i$8}v!a|dql3>Lc6G#ao>7xXCe)tI&d-DQ%*ws({V1_$7eH;xjCXGbJi4 zC8zsxRLM(_30)g5Z|cxW$vt;BEFzlnko)A5PJiHqd-r;FtQw zfi%#q3jy}mK+t+tnaWBC*F%GYJaUTi1Pd0Dm1W53)iJ?XcS@kW?2{WW*!!QX$`j_n zd4y~gEX&3u+z=|aE0+jzo#dI*VRbU)^d0m3;2l`u;52KpuX*!X>~PL~BqS>CPtJ4f zET$0zVI2A^6%06pIjXJmEo(B3eLg2qQ#X^Vi-KzXqa1>ou;9UZiU%|%Vo`a~=B zVC0y|Qf3B4mQ?}kh4_lpF9=v5_n<9Ri)V9<%GS?!;O#VrPW^#Xh3J!<-QwkYMyO=2 z!C?HyH;M15gcyI{_4VkHFxEJ-s7+Z;f>*nc*FQhHaPe-catJ&K&^cN}qk-YRlPQ_(=a)Vb=W0%L+7xz@J$=~i37tpwF3}55w z>oW+nYzF9XKI3taoxOa7ngp$CLg1*EUhz^I>QhB;cqvT)v6|m!FD8uBEd8PNgJr>+9cY zGNPiG(~`XI=#30ZDl01s%Ziqt3R%?v3bEZ{QRLNx@QbXw1c3gt_KcGf=pwwh!mE&p zrKnT69JIL}t3b5Hg-0%_R5|tfRwXYrfJ|by8cBJ9@UJmVH!eeS971Dfws$^Abv9@B z+d&-2rtd@UOEU{_G$p0ig8JyrD%e_bIbnK8xgz@&j3-dxi&|+&Z!!A&JnN|}0q8Gr zq;yNyWvOfSI)!J8L6ynQ^Nk0C`4n*CL-NxW37aifkz`oSzpH{8$Eu*3emCEDqClX4 z{Cyh^rw9th!x1oZ-QsE4J>HVJ-ec9z@lm6xF*`L4N45)a-HW(}rUhFuTYM!;%6$H= zmC{oS>83V(K_X#vPhPpBjJ8y##woLVTWw3X#Pf7p5PfN&fXO>@j@~1LXW;us_vIdU zWd45WSf|L`G$;@FN$lm{V~aWF)`psZEAE{Co$&_2OPoZytlSm*mN@%jx>`iq@e3i(;j1SU$bEXx1?k^ zn`&+=JU)Rh%RK^~*l8)AF4i||Acqv09q;f=Zp#X}g`R)+5lamUt!-urD@(1k>LAPk zvMX${U1>tLOd&X$O*B z|AMq48JR?qU8LqB2UHi{VO`5%&Td9S<58sI+U~|Qn_dooz{RFs>e`;MD^7Lyn6ued zxM1B8TfZg@s=yBXx`0ORs=H3& zVKf&uJ4^P}G;&n2KFSA{psXpH_|Fvup9db?riPTs^^mWI*vpym8@{X4Y{w#CkAB~| zwggsK0!Sts9t87q|5e5vvzn)e(fIgOJ$9T2f&Ax59UjBjxTVkSJTThFe{HMFJ1*sI z^SY(5ym5HAt>JP3r8}l}6-u};L{nj8-^F+^n^i*X(;=6$qJ@^|0K`OY2SF1yheV(Y zd`{5v8=fdhYX9J^3@-7k-ySW68AUc=XbZU}T<;lmq0HSi-%hX2OIm1D8zYa}8f+s` zGen%JMItI_c3mxgNyDXC<&g%J26ja7;A>NmM_#ZnFrgaE)-<5WEUOdIuclzxqG%EuB|mu;Wu?o(F|!tL*g9qWM3(!RjD^xn9to`M7_c~n#h%P)V4<^ zQLO2y2c||9XMoZHKj@|N^(cfJ8l7J}ETWx(899Xxm)D-HQHIf3nYXQ@<(Gg$bSF>u z9CordUmVoFx)w2k>v$#m@G%%27I%)dDu$O-8)uTB7k#}eAbJ&#y+)aT7J{;h{4YGJ zU|lGZ4|f_BO$8~dxW1sct-`T0v)%|m8nlj8jju{#`gT`E9|kTb=GvcQQ6EA4C~-*% z^GD%k1~4>r5ofYYeHe;W5hu;_w*_)4CAO*EB_qsMt%jzYs-$rkYie36e`VuV1_4Rs zznZ&(jY%RbB8^#DX{+a@hjmU3(2G(}9j7Kd1%smQ`-BmC&`W<9u4(|CBB5@fmeY8m zZsZ9W+csAx8dj!J%z`L#xUTj@{9$Yjiy<2em}|1bVG~9!^_?sSxKTHhRz)rA2u$AH zO~ypfK7@PzPk73aoPp)yI<2sewofRkjklfBbfm!V>nlEDnj1tZ8#~-|)TB3@<4{mm zS~^yOBiaMe3vuiNiGVcT)1hvNkck@3TfXE<;dO>Dm(rR6LlmvhB%Wa!_s`8 zR_g-M0p`nNX4f5%b9m``JsFn%Ct=6D*dc8T|Jr@DGH zgkmR|ip7*PK<1*+;S)S%;0T-vAXY&u9te|;_$qH*0akH&@xj|}7Az$Ou}>zTZYv23 zv!;hp51j2Bpho{n@ILARQ+C~7#&gGH&RZX1rj z&v;Dkaf;V(m7Rlruc-Rgvbt#R))wY10+owb8GqxdBT2c~q=!xsz#DN(0%vm^%-^#BVywvA%?Zop7u@L&5uhzOdFkh`9#qt|PT?7mjt1ff z9ed`=WL=e_ERFw#R(U>VZK?5miuoHh01NN_IWbQDpXh2|_P?R4&MK2XOP-!z8k=oL z3Xk$S9aJ^+w9)=OU42o+ZgE2zEjX^^JXfs-oCDd9BOYgEPJU}sAuX2um*HJhgHXed zfRod&(ZnO8UXwTGdgoV#ZC6F!9JTpyHEWjk<1O{ONeyIITR~~~{hZv0iJHfelg@tL z(jQ8=pBKfs@sZBUvvKFC;Jx=KHy_?dj~@?VM3Rp=*|e~&GstakTMf}!OQD5eJ)#0W zdpL%Zlkyo3)82TWL_yb)S{ zg-pfA175g#;cO=@Ef*z_#i_q&I6V!{jV?h2hHu;6IK}X6KH4Uc<0EDaL{nW{aG;I6 zm^<4cXzK6H{M=l$g_{Z@F8g?@MmyG!`9vTd%<^vyY|@G7k8+AZFJBpOEbB^#k8$!< zY~Egun4PQ^JTZ!|9~TLFY)!kGRJU?Nz_V*H zz~CZdAp}PfoPJPmF%9Q{p<)N~|6a8r)#iL}O(hYdGRN?tW_5Gd#S}%v2b|iHpStgj zx(oGmyRFR0YpjWUR{|~?Iqamou>4xiY#(d#l#rkN8+_>m zMR>?zdB!gN7I|Er054~8SHUJ-(mIdAG$jJ^3UAT-Cl5VDXrSh$=eR#yK~60Qod*mN#EHykf!cN9fw*wsj(` zh-_szXx(Hs99bQAFsU)H-{5@IfBsGNlK51fc1Ix-je(CJ`$>%zDdgZQ#8Ru%DYzc! z_w850YNOG_2TA>Gn!lk;iVqG+Bnlj(xa$0hpxeGS2x{xw`hZOib27M*_viIVPpvN=vGWT44 z7TemyC$(*_ zDFb?*=@BvzB9`WADSb;O&>&D7*Qfm<>UE61!WVAk+IUeNmzskcl!T&)XP9(PXzzbjC5g#*Sz}+?^r=mv6CuAli z&B0_TO!eeaXqiN|ru1?>UFahtaiT#6rEwsm`|!mib{u=e)Yqzp{){Eae}q9^Z(n3% zs)njw4C^sNv@4cPoU?oeapdLxVyQ@jrFiE#^DggfspZ@jRzC}V??~?O%WKqRWv-&U zsa;-u79KKKp1IOs3m*(uyV2>$#&=cQEu(<|Mz9#^fqv8LE%lLNI_p{k^UlW}r3h zEX4l6B6cy{P>kn>`(COHl0|BY%n zKTvIDF$dWViX}I9|0b%gPIaT4tH*bSQ@ez;qHp?e!uY2dpgq^XMj5Xa$=s?eE_X=v zNr?NJo*=-23MFii47a=1d8NgrwOR10tKgsR@LAD^I}BjD7~}6i@h2n)`PlE(&>V%H z{F1m>Q{8w_3D3ei+~RlCylid{5S55EP|ysL(FNG3V8>8>=u<v|`&#yqvp9)mHzEn&fzdT}wYF;_{W(4biSj=gzBcpY$)dzIy_g zi1*s>2dd$QW%ZSfn?Gd3^Yx#rW%E1KDgpToU9Q><&%LpVX)U!|X- zFd=6eQGCqqWF0Si)3adMa+elS&tQMsN_wzaqbI~0UZ`*9eyGRK`~RXIZ~SM1+tNd9 z7Q$|zUcV4P6SeADTAsQw+?CHE2%9j(6Sd;p*dAKA)39R0@zFUF$&Qp+f?;ATl$R%J z16N&#reLSDpEG}Fq}0z&c4u8me24=Ocr77jQ`?F-`3+<8?z7L~s1pnZG8Br1&_sYY=>zFO4W6JL6?-Z ziuO*yiqk8&nI02gsLII;t^A6il%8Hjx`4;7F9UR3<)*}Vm0opz{9T18@Ua#1k~M^& zsuJ&wY}4M4=tf^bM>?#&wdB_#v8)A3C%j+1m>nPo$&CeC<;mWJ8{jv}n?-@w;&{wP zgI*!Y@irQP=x=fJmsXtnmsUK9wRw$}xjxb&IgQz5n6f1lTi0|MQ`5!C$w<(Y8° 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 literal 297058 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\%9r~F@xR0pxIlbm#|}R&!7K# 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 z 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 k?8iUU9@mwxaPGn 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%0DlLDMhm2@vI1ZvbZ*FP+F%Eg+ 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

q%G$yP|- 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�XAwN(TSRPot&c73QtDj#|u0(x^eD& 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
uH$Bd{GLxDUX@S)$zc>BU{6 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 diff --git a/kotlinx-coroutines-core/jvm/src/debug/CoroutineDebugging.kt b/kotlinx-coroutines-core/jvm/src/debug/CoroutineDebugging.kt new file mode 100644 index 0000000000..49a794e046 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/src/debug/CoroutineDebugging.kt @@ -0,0 +1,65 @@ +/* + * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +/* This package name is like this so that +1) the artificial stack frames look pretty, and +2) the IDE reliably navigates to this file. */ +package _COROUTINE + +/** + * A collection of artificial stack trace elements to be included in stack traces by the coroutines machinery. + * + * There are typically two ways in which one can encounter an artificial stack frame: + * 1. By using the debug mode, via the stacktrace recovery mechanism; see + * [stacktrace recovery](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/topics/debugging.md#stacktrace-recovery) + * documentation. The usual way to enable the debug mode is with the [kotlinx.coroutines.DEBUG_PROPERTY_NAME] system + * property. + * 2. By looking at the output of DebugProbes; see the + * [kotlinx-coroutines-debug](https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-debug) module. + */ +internal class ArtificialStackFrames { + /** + * Returns an artificial stack trace element denoting the boundary between coroutine creation and its execution. + * + * Appearance of this function in stack traces does not mean that it was called. Instead, it is used as a marker + * that separates the part of the stack trace with the code executed in a coroutine from the stack trace of the code + * that launched the coroutine. + * + * In earlier versions of kotlinx-coroutines, this was displayed as "(Coroutine creation stacktrace)", which caused + * problems for tooling that processes stack traces: https://github.com/Kotlin/kotlinx.coroutines/issues/2291 + * + * Note that presence of this marker in a stack trace implies that coroutine creation stack traces were enabled. + */ + fun coroutineCreation(): StackTraceElement = Exception().artificialFrame(_CREATION::class.java.simpleName) + + /** + * Returns an artificial stack trace element denoting a coroutine boundary. + * + * Appearance of this function in stack traces does not mean that it was called. Instead, when one coroutine invokes + * another, this is used as a marker in the stack trace to denote where the execution of one coroutine ends and that + * of another begins. + * + * In earlier versions of kotlinx-coroutines, this was displayed as "(Coroutine boundary)", which caused + * problems for tooling that processes stack traces: https://github.com/Kotlin/kotlinx.coroutines/issues/2291 + */ + fun coroutineBoundary(): StackTraceElement = Exception().artificialFrame(_BOUNDARY::class.java.simpleName) +} + +// These are needed for the IDE navigation to detect that this file does contain the definition. +private class _CREATION +private class _BOUNDARY + +internal val ARTIFICIAL_FRAME_PACKAGE_NAME = "_COROUTINE" + +/** + * Forms an artificial stack frame with the given class name. + * + * It consists of the following parts: + * 1. The package name, it seems, is needed for the IDE to detect stack trace elements reliably. It is `_COROUTINE` since + * this is a valid identifier. + * 2. Class names represents what type of artificial frame this is. + * 3. The method name is `_`. The methods not being present in class definitions does not seem to affect navigation. + */ +private fun Throwable.artificialFrame(name: String): StackTraceElement = + with(stackTrace[0]) { StackTraceElement(ARTIFICIAL_FRAME_PACKAGE_NAME + "." + name, "_", fileName, lineNumber) } diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt index d358d49d1e..7080f612b7 100644 --- a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt +++ b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt @@ -6,7 +6,6 @@ package kotlinx.coroutines.debug.internal import kotlinx.atomicfu.* import kotlinx.coroutines.* -import kotlinx.coroutines.internal.* import kotlinx.coroutines.internal.ScopeCoroutine import java.io.* import java.lang.StackTraceElement @@ -17,10 +16,10 @@ import kotlin.concurrent.* import kotlin.coroutines.* import kotlin.coroutines.jvm.internal.CoroutineStackFrame import kotlin.synchronized -import kotlinx.coroutines.internal.artificialFrame as createArtificialFrame // IDEA bug workaround +import _COROUTINE.ArtificialStackFrames internal object DebugProbesImpl { - private const val ARTIFICIAL_FRAME_MESSAGE = "Coroutine creation stacktrace" + private val ARTIFICIAL_FRAME = ArtificialStackFrames().coroutineCreation() private val dateFormat = SimpleDateFormat("yyyy/MM/dd HH:mm:ss") private var weakRefCleanerThread: Thread? = null @@ -298,7 +297,7 @@ internal object DebugProbesImpl { info.state out.print("\n\nCoroutine ${owner.delegate}, state: $state") if (observedStackTrace.isEmpty()) { - out.print("\n\tat ${createArtificialFrame(ARTIFICIAL_FRAME_MESSAGE)}") + out.print("\n\tat $ARTIFICIAL_FRAME") printStackTrace(out, info.creationStackTrace) } else { printStackTrace(out, enhancedStackTrace) @@ -500,15 +499,17 @@ internal object DebugProbesImpl { return createOwner(completion, frame) } - private fun List.toStackTraceFrame(): StackTraceFrame? = - foldRight(null) { frame, acc -> - StackTraceFrame(acc, frame) - } + private fun List.toStackTraceFrame(): StackTraceFrame = + StackTraceFrame( + foldRight(null) { frame, acc -> + StackTraceFrame(acc, frame) + }, ARTIFICIAL_FRAME + ) private fun createOwner(completion: Continuation, frame: StackTraceFrame?): Continuation { if (!isInstalled) return completion val info = DebugCoroutineInfoImpl(completion.context, frame, sequenceNumber.incrementAndGet()) - val owner = CoroutineOwner(completion, info, frame) + val owner = CoroutineOwner(completion, info) capturedCoroutinesMap[owner] = true if (!isInstalled) capturedCoroutinesMap.clear() return owner @@ -531,9 +532,9 @@ internal object DebugProbesImpl { */ private class CoroutineOwner( @JvmField val delegate: Continuation, - @JvmField val info: DebugCoroutineInfoImpl, - private val frame: CoroutineStackFrame? + @JvmField val info: DebugCoroutineInfoImpl ) : Continuation by delegate, CoroutineStackFrame { + private val frame get() = info.creationStackBottom override val callerFrame: CoroutineStackFrame? get() = frame?.callerFrame @@ -551,12 +552,10 @@ internal object DebugProbesImpl { private fun sanitizeStackTrace(throwable: T): List { val stackTrace = throwable.stackTrace val size = stackTrace.size - val probeIndex = stackTrace.indexOfLast { it.className == "kotlin.coroutines.jvm.internal.DebugProbesKt" } + val traceStart = 1 + stackTrace.indexOfLast { it.className == "kotlin.coroutines.jvm.internal.DebugProbesKt" } if (!sanitizeStackTraces) { - return List(size - probeIndex) { - if (it == 0) createArtificialFrame(ARTIFICIAL_FRAME_MESSAGE) else stackTrace[it + probeIndex] - } + return List(size - traceStart) { stackTrace[it + traceStart] } } /* @@ -567,9 +566,8 @@ internal object DebugProbesImpl { * If an interval of internal methods ends in a synthetic method, the outermost non-synthetic method in that * interval will also be included. */ - val result = ArrayList(size - probeIndex + 1) - result += createArtificialFrame(ARTIFICIAL_FRAME_MESSAGE) - var i = probeIndex + 1 + val result = ArrayList(size - traceStart + 1) + var i = traceStart while (i < size) { if (stackTrace[i].isInternalMethod) { result += stackTrace[i] // we include the boundary of the span in any case diff --git a/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt b/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt index 6153862e2a..5193b0dc26 100644 --- a/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt +++ b/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt @@ -7,6 +7,8 @@ package kotlinx.coroutines.internal import kotlinx.coroutines.* +import _COROUTINE.ARTIFICIAL_FRAME_PACKAGE_NAME +import _COROUTINE.ArtificialStackFrames import java.util.* import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* @@ -18,6 +20,8 @@ import kotlin.coroutines.intrinsics.* private const val baseContinuationImplClass = "kotlin.coroutines.jvm.internal.BaseContinuationImpl" private const val stackTraceRecoveryClass = "kotlinx.coroutines.internal.StackTraceRecoveryKt" +private val ARTIFICIAL_FRAME = ArtificialStackFrames().coroutineBoundary() + private val baseContinuationImplClassName = runCatching { Class.forName(baseContinuationImplClass).canonicalName }.getOrElse { baseContinuationImplClass } @@ -42,7 +46,7 @@ private fun E.sanitizeStackTrace(): E { val adjustment = if (endIndex == -1) 0 else size - endIndex val trace = Array(size - lastIntrinsic - adjustment) { if (it == 0) { - artificialFrame("Coroutine boundary") + ARTIFICIAL_FRAME } else { stackTrace[startIndex + it - 1] } @@ -97,13 +101,13 @@ private fun tryCopyAndVerify(exception: E): E? { * IllegalStateException * at foo * at kotlin.coroutines.resumeWith - * (Coroutine boundary) + * at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) * at bar * ...real stackTrace... * caused by "IllegalStateException" (original one) */ private fun createFinalException(cause: E, result: E, resultStackTrace: ArrayDeque): E { - resultStackTrace.addFirst(artificialFrame("Coroutine boundary")) + resultStackTrace.addFirst(ARTIFICIAL_FRAME) val causeTrace = cause.stackTrace val size = causeTrace.frameIndex(baseContinuationImplClassName) if (size == -1) { @@ -193,12 +197,7 @@ private fun createStackTrace(continuation: CoroutineStackFrame): ArrayDeque.frameIndex(methodName: String) = indexOfFirst { methodName == it.className } private fun StackTraceElement.elementWiseEquals(e: StackTraceElement): Boolean { diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testOfferFromScope.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testOfferFromScope.txt index bf3fd3a3ca..aa5a6a17e1 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testOfferFromScope.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testOfferFromScope.txt @@ -1,10 +1,10 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testOfferFromScope$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:109) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.sendInChannel(StackTraceRecoveryChannelsTest.kt:167) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$sendWithContext$2.invokeSuspend(StackTraceRecoveryChannelsTest.kt:162) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$sendFromScope$2.invokeSuspend(StackTraceRecoveryChannelsTest.kt:172) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testOfferFromScope$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:112) Caused by: kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testOfferFromScope$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:109) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) \ No newline at end of file + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testOfferWithContextWrapped.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testOfferWithContextWrapped.txt index 612d00de06..4908d3be38 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testOfferWithContextWrapped.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testOfferWithContextWrapped.txt @@ -1,6 +1,6 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testOfferWithContextWrapped$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:98) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.sendInChannel(StackTraceRecoveryChannelsTest.kt:199) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$sendWithContext$2.invokeSuspend(StackTraceRecoveryChannelsTest.kt:194) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testOfferWithContextWrapped$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:100) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testOfferWithCurrentContext.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testOfferWithCurrentContext.txt index 833afbf8aa..1eb464c71b 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testOfferWithCurrentContext.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testOfferWithCurrentContext.txt @@ -1,6 +1,6 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testOfferWithCurrentContext$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:86) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.sendInChannel(StackTraceRecoveryChannelsTest.kt:210) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$sendWithContext$2.invokeSuspend(StackTraceRecoveryChannelsTest.kt:205) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testOfferWithCurrentContext$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:89) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveFromChannel.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveFromChannel.txt index 66bb5e5e2e..af8c1fd389 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveFromChannel.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveFromChannel.txt @@ -1,8 +1,8 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveFromChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:97) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.channelReceive(StackTraceRecoveryChannelsTest.kt:116) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveFromChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:101) Caused by: kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveFromChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:97) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) \ No newline at end of file + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveFromClosedChannel.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveFromClosedChannel.txt index 76c0b1a8fa..3f392cd31d 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveFromClosedChannel.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveFromClosedChannel.txt @@ -1,8 +1,8 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveFromClosedChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:110) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.channelReceive(StackTraceRecoveryChannelsTest.kt:116) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveFromClosedChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:111) Caused by: kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveFromClosedChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:110) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) \ No newline at end of file + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendFromScope.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendFromScope.txt index 9f932032bd..49c3628bb2 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendFromScope.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendFromScope.txt @@ -1,10 +1,10 @@ kotlinx.coroutines.RecoverableTestCancellationException at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendFromScope$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:136) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.sendInChannel(StackTraceRecoveryChannelsTest.kt:167) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$sendWithContext$2.invokeSuspend(StackTraceRecoveryChannelsTest.kt:162) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$sendFromScope$2.invokeSuspend(StackTraceRecoveryChannelsTest.kt:172) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendFromScope$1$deferred$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:126) Caused by: kotlinx.coroutines.RecoverableTestCancellationException at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendFromScope$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:136) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) \ No newline at end of file + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) 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 dab728fa79..4a8e320e2d 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt @@ -2,11 +2,11 @@ java.util.concurrent.CancellationException: RendezvousChannel was cancelled at kotlinx.coroutines.channels.AbstractChannel.cancel(AbstractChannel.kt:630) at kotlinx.coroutines.channels.ReceiveChannel$DefaultImpls.cancel$default(Channel.kt:311) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:52) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.channelSend(StackTraceRecoveryChannelsTest.kt:73) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:56) Caused by: java.util.concurrent.CancellationException: RendezvousChannel was cancelled at kotlinx.coroutines.channels.AbstractChannel.cancel(AbstractChannel.kt:630) at kotlinx.coroutines.channels.ReceiveChannel$DefaultImpls.cancel$default(Channel.kt:311) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:52) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) \ No newline at end of file + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToClosedChannel.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToClosedChannel.txt index 54fdbb3295..f2609594f3 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToClosedChannel.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToClosedChannel.txt @@ -1,8 +1,8 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToClosedChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:43) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.channelSend(StackTraceRecoveryChannelsTest.kt:74) at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToClosedChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:44) Caused by: kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToClosedChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:43) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) \ No newline at end of file + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testEventLoopDispatcher.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testEventLoopDispatcher.txt index 6b40ec8308..0e75e64511 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testEventLoopDispatcher.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testEventLoopDispatcher.txt @@ -1,7 +1,7 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:61) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testEventLoopDispatcher$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:40) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$withContext$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:76) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.doFastPath(StackTraceRecoveryResumeModeTest.kt:71) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:62) @@ -9,4 +9,4 @@ kotlinx.coroutines.RecoverableTestException Caused by: kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:61) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testEventLoopDispatcher$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:40) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) \ No newline at end of file + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testEventLoopDispatcherSuspending.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testEventLoopDispatcherSuspending.txt index 5afc559fe0..0792646ed4 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testEventLoopDispatcherSuspending.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testEventLoopDispatcherSuspending.txt @@ -1,10 +1,10 @@ otlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testResumeModeSuspending$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:99) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$withContext$4.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:116) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.doSuspendingPath(StackTraceRecoveryResumeModeTest.kt:110) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeSuspending(StackTraceRecoveryResumeModeTest.kt:101) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testEventLoopDispatcherSuspending$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:89) Caused by: kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testResumeModeSuspending$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:99) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) \ No newline at end of file + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedEventLoopChangedContext.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedEventLoopChangedContext.txt index 406b2d1c9c..f3ca1fc4e0 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedEventLoopChangedContext.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedEventLoopChangedContext.txt @@ -1,7 +1,7 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:61) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testNestedEventLoopChangedContext$1$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:54) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$withContext$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:76) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.doFastPath(StackTraceRecoveryResumeModeTest.kt:71) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:62) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedEventLoopChangedContextSuspending.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedEventLoopChangedContextSuspending.txt index 86ec5e4bb2..dbb574fead 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedEventLoopChangedContextSuspending.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedEventLoopChangedContextSuspending.txt @@ -1,6 +1,6 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testResumeModeSuspending$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:113) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$withContext$4.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:130) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.doSuspendingPath(StackTraceRecoveryResumeModeTest.kt:124) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeSuspending(StackTraceRecoveryResumeModeTest.kt:115) @@ -8,4 +8,4 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testNestedEventLoopChangedContextSuspending$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:102) Caused by: kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testResumeModeSuspending$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:113) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) \ No newline at end of file + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedEventLoopDispatcher.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedEventLoopDispatcher.txt index d9098bbaad..e17e2db685 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedEventLoopDispatcher.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedEventLoopDispatcher.txt @@ -1,7 +1,7 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:61) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testNestedEventLoopDispatcher$1$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:47) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$withContext$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:76) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.doFastPath(StackTraceRecoveryResumeModeTest.kt:71) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:62) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedEventLoopDispatcherSuspending.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedEventLoopDispatcherSuspending.txt index 8caed7ac0c..26e035992c 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedEventLoopDispatcherSuspending.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedEventLoopDispatcherSuspending.txt @@ -1,6 +1,6 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testResumeModeSuspending$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:113) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$withContext$4.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:130) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.doSuspendingPath(StackTraceRecoveryResumeModeTest.kt:124) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeSuspending(StackTraceRecoveryResumeModeTest.kt:115) @@ -8,4 +8,4 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testNestedEventLoopDispatcherSuspending$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:95) Caused by: kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testResumeModeSuspending$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:113) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) \ No newline at end of file + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedUnconfined.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedUnconfined.txt index a2cd009dc8..f247920ee5 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedUnconfined.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedUnconfined.txt @@ -1,7 +1,7 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:61) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testNestedUnconfined$1$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:27) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$withContext$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:76) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.doFastPath(StackTraceRecoveryResumeModeTest.kt:71) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:62) @@ -10,4 +10,4 @@ kotlinx.coroutines.RecoverableTestException Caused by: kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:61) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testNestedUnconfined$1$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:27) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) \ No newline at end of file + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedUnconfinedChangedContext.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedUnconfinedChangedContext.txt index a786682b7e..b7ae52c9d3 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedUnconfinedChangedContext.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedUnconfinedChangedContext.txt @@ -1,7 +1,7 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:61) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testNestedUnconfinedChangedContext$1$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:34) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$withContext$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:76) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.doFastPath(StackTraceRecoveryResumeModeTest.kt:71) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:62) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedUnconfinedChangedContextSuspending.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedUnconfinedChangedContextSuspending.txt index 8c937a7c6b..241a3b2342 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedUnconfinedChangedContextSuspending.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedUnconfinedChangedContextSuspending.txt @@ -1,6 +1,6 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testResumeModeSuspending$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:128) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$withContext$4.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:148) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.doSuspendingPath(StackTraceRecoveryResumeModeTest.kt:140) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeSuspending(StackTraceRecoveryResumeModeTest.kt:130) @@ -8,4 +8,4 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testNestedUnconfinedChangedContextSuspending$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:94) Caused by: kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testResumeModeSuspending$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:128) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) \ No newline at end of file + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedUnconfinedSuspending.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedUnconfinedSuspending.txt index b6eef47911..4484c66432 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedUnconfinedSuspending.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testNestedUnconfinedSuspending.txt @@ -1,6 +1,6 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testResumeModeSuspending$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:128) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$withContext$4.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:148) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.doSuspendingPath(StackTraceRecoveryResumeModeTest.kt:140) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeSuspending(StackTraceRecoveryResumeModeTest.kt:130) @@ -8,4 +8,4 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testNestedUnconfinedSuspending$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:87) Caused by: kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testResumeModeSuspending$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:128) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) \ No newline at end of file + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testUnconfined.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testUnconfined.txt index 9b9cba3eb4..4f0103ecc8 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testUnconfined.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testUnconfined.txt @@ -1,7 +1,7 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:61) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testUnconfined$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:40) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$withContext$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:76) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.doFastPath(StackTraceRecoveryResumeModeTest.kt:71) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:62) @@ -9,4 +9,4 @@ kotlinx.coroutines.RecoverableTestException Caused by: kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:61) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testUnconfined$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:40) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) \ No newline at end of file + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testUnconfinedSuspending.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testUnconfinedSuspending.txt index ca0bbe7fb8..fb742a3076 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testUnconfinedSuspending.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testUnconfinedSuspending.txt @@ -1,9 +1,9 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testResumeModeSuspending$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:128) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.doSuspendingPath(StackTraceRecoveryResumeModeTest.kt:140) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeSuspending(StackTraceRecoveryResumeModeTest.kt:130) at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testUnconfinedSuspending$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:82) Caused by: kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testResumeModeSuspending$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:128) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) \ No newline at end of file + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectCompletedAwait.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectCompletedAwait.txt index dbc39ccc55..2e86a7ad18 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectCompletedAwait.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectCompletedAwait.txt @@ -1,6 +1,6 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$testSelectCompletedAwait$1.invokeSuspend(StackTraceRecoverySelectTest.kt:40) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$testSelectCompletedAwait$1.invokeSuspend(StackTraceRecoverySelectTest.kt:41) Caused by: kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$testSelectCompletedAwait$1.invokeSuspend(StackTraceRecoverySelectTest.kt:40) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectJoin.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectJoin.txt index 049002f58e..420aa7e970 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectJoin.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectJoin.txt @@ -1,8 +1,8 @@ kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$doSelect$2$1.invokeSuspend(StackTraceRecoverySelectTest.kt) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.selects.SelectImplementation.processResultAndInvokeBlockRecoveringException(Select.kt) at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$testSelectJoin$1.invokeSuspend(StackTraceRecoverySelectTest.kt) Caused by: kotlinx.coroutines.RecoverableTestException at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$doSelect$2$1.invokeSuspend(StackTraceRecoverySelectTest.kt) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt) \ No newline at end of file + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectOnReceive.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectOnReceive.txt index d00ea5b0a2..ae40259914 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectOnReceive.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectOnReceive.txt @@ -12,7 +12,7 @@ kotlinx.coroutines.channels.ClosedReceiveChannelException: Channel was closed at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest.doSelectOnReceive(StackTraceRecoverySelectTest.kt) at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest.access$doSelectOnReceive(StackTraceRecoverySelectTest.kt) at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$testSelectOnReceive$1.invokeSuspend(StackTraceRecoverySelectTest.kt) - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.selects.SelectImplementation.processResultAndInvokeBlockRecoveringException(Select.kt) at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$testSelectOnReceive$1.invokeSuspend(StackTraceRecoverySelectTest.kt) Caused by: kotlinx.coroutines.channels.ClosedReceiveChannelException: Channel was closed @@ -29,4 +29,4 @@ Caused by: kotlinx.coroutines.channels.ClosedReceiveChannelException: Channel wa at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest.doSelectOnReceive(StackTraceRecoverySelectTest.kt) at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest.access$doSelectOnReceive(StackTraceRecoverySelectTest.kt) at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$testSelectOnReceive$1.invokeSuspend(StackTraceRecoverySelectTest.kt) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt) \ No newline at end of file + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/timeout/testStacktraceIsRecoveredFromLexicalBlockWhenTriggeredByChild.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/timeout/testStacktraceIsRecoveredFromLexicalBlockWhenTriggeredByChild.txt index ab23c9a369..ac40dc152b 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/timeout/testStacktraceIsRecoveredFromLexicalBlockWhenTriggeredByChild.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/timeout/testStacktraceIsRecoveredFromLexicalBlockWhenTriggeredByChild.txt @@ -1,7 +1,7 @@ kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 200 ms - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryWithTimeoutTest.outerChildWithTimeout(StackTraceRecoveryWithTimeoutTest.kt:48) at kotlinx.coroutines.exceptions.StackTraceRecoveryWithTimeoutTest$testStacktraceIsRecoveredFromLexicalBlockWhenTriggeredByChild$1.invokeSuspend(StackTraceRecoveryWithTimeoutTest.kt:40) Caused by: kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 200 ms at kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:116) - at kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:86) \ No newline at end of file + at kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:86) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/timeout/testStacktraceIsRecoveredFromSuspensionPoint.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/timeout/testStacktraceIsRecoveredFromSuspensionPoint.txt index d3497face6..9d5ddb6621 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/timeout/testStacktraceIsRecoveredFromSuspensionPoint.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/timeout/testStacktraceIsRecoveredFromSuspensionPoint.txt @@ -1,5 +1,5 @@ kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 200 ms - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryWithTimeoutTest.suspendForever(StackTraceRecoveryWithTimeoutTest.kt:42) at kotlinx.coroutines.exceptions.StackTraceRecoveryWithTimeoutTest$outerWithTimeout$2.invokeSuspend(StackTraceRecoveryWithTimeoutTest.kt:32) at kotlinx.coroutines.exceptions.StackTraceRecoveryWithTimeoutTest.outerWithTimeout(StackTraceRecoveryWithTimeoutTest.kt:31) @@ -7,4 +7,4 @@ kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 200 ms Caused by: kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 200 ms at kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:116) at kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:86) - at kotlinx.coroutines.EventLoopImplBase$DelayedRunnableTask.run(EventLoop.common.kt:492) \ No newline at end of file + at kotlinx.coroutines.EventLoopImplBase$DelayedRunnableTask.run(EventLoop.common.kt:492) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/timeout/testStacktraceIsRecoveredFromSuspensionPointWithChild.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/timeout/testStacktraceIsRecoveredFromSuspensionPointWithChild.txt index 8ec7691e50..6f21cc6b30 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/timeout/testStacktraceIsRecoveredFromSuspensionPointWithChild.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/timeout/testStacktraceIsRecoveredFromSuspensionPointWithChild.txt @@ -1,9 +1,9 @@ kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 200 ms - (Coroutine boundary) + at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) at kotlinx.coroutines.exceptions.StackTraceRecoveryWithTimeoutTest.suspendForever(StackTraceRecoveryWithTimeoutTest.kt:92) at kotlinx.coroutines.exceptions.StackTraceRecoveryWithTimeoutTest$outerChild$2.invokeSuspend(StackTraceRecoveryWithTimeoutTest.kt:78) at kotlinx.coroutines.exceptions.StackTraceRecoveryWithTimeoutTest.outerChild(StackTraceRecoveryWithTimeoutTest.kt:74) at kotlinx.coroutines.exceptions.StackTraceRecoveryWithTimeoutTest$testStacktraceIsRecoveredFromSuspensionPointWithChild$1.invokeSuspend(StackTraceRecoveryWithTimeoutTest.kt:66) Caused by: kotlinx.coroutines.TimeoutCancellationException: Timed out waiting for 200 ms at kotlinx.coroutines.TimeoutKt.TimeoutCancellationException(Timeout.kt:116) - at kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:86) \ No newline at end of file + at kotlinx.coroutines.TimeoutCoroutine.run(Timeout.kt:86) diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedScopesTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedScopesTest.kt index a85bb7a23c..dbb1ead568 100644 --- a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedScopesTest.kt +++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryNestedScopesTest.kt @@ -12,7 +12,7 @@ class StackTraceRecoveryNestedScopesTest : TestBase() { "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest.failure(StackTraceRecoveryNestedScopesTest.kt:9)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest.access\$failure(StackTraceRecoveryNestedScopesTest.kt:7)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$createFailingAsync\$1.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:12)\n" + - "\t(Coroutine boundary)\n" + + "\tat _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$callWithTimeout\$2.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:23)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$callCoroutineScope\$2.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:29)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$$TEST_MACROS\$1.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:36)\n" + @@ -82,7 +82,7 @@ class StackTraceRecoveryNestedScopesTest : TestBase() { "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest.failure(StackTraceRecoveryNestedScopesTest.kt:23)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest.access\$failure(StackTraceRecoveryNestedScopesTest.kt:7)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$createFailingAsync\$1.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:26)\n" + - "\t(Coroutine boundary)\n" + + "\tat _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$callWithTimeout\$2.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:37)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$callCoroutineScope\$2.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:43)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryNestedScopesTest\$testAwaitNestedScopes\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryNestedScopesTest.kt:68)\n" + diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt index 0a8b6530e2..1db7c1dbfc 100644 --- a/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt +++ b/kotlinx-coroutines-core/jvm/test/exceptions/StackTraceRecoveryTest.kt @@ -35,7 +35,7 @@ class StackTraceRecoveryTest : TestBase() { val traces = listOf( "java.util.concurrent.ExecutionException\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testAsync\$1\$createDeferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:99)\n" + - "\t(Coroutine boundary)\n" + + "\tat _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.oneMoreNestedMethod(StackTraceRecoveryTest.kt:49)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.nestedMethod(StackTraceRecoveryTest.kt:44)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testAsync\$1.invokeSuspend(StackTraceRecoveryTest.kt:17)\n", @@ -57,7 +57,7 @@ class StackTraceRecoveryTest : TestBase() { val stacktrace = listOf( "java.util.concurrent.ExecutionException\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCompletedAsync\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:44)\n" + - "\t(Coroutine boundary)\n" + + "\tat _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.oneMoreNestedMethod(StackTraceRecoveryTest.kt:81)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.nestedMethod(StackTraceRecoveryTest.kt:75)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCompletedAsync\$1.invokeSuspend(StackTraceRecoveryTest.kt:71)", @@ -91,7 +91,7 @@ class StackTraceRecoveryTest : TestBase() { outerMethod(deferred, "kotlinx.coroutines.RecoverableTestException\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testWithContext\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:143)\n" + - "\t(Coroutine boundary)\n" + + "\tat _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.innerMethod(StackTraceRecoveryTest.kt:158)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$outerMethod\$2.invokeSuspend(StackTraceRecoveryTest.kt:151)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.outerMethod(StackTraceRecoveryTest.kt:150)\n" + @@ -128,7 +128,7 @@ class StackTraceRecoveryTest : TestBase() { outerScopedMethod(deferred, "kotlinx.coroutines.RecoverableTestException\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCoroutineScope\$1\$deferred\$1.invokeSuspend(StackTraceRecoveryTest.kt:143)\n" + - "\t(Coroutine boundary)\n" + + "\tat _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.innerMethod(StackTraceRecoveryTest.kt:158)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$outerScopedMethod\$2\$1.invokeSuspend(StackTraceRecoveryTest.kt:193)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$outerScopedMethod\$2.invokeSuspend(StackTraceRecoveryTest.kt:151)\n" + @@ -227,7 +227,7 @@ class StackTraceRecoveryTest : TestBase() { "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.throws(StackTraceRecoveryTest.kt:280)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.access\$throws(StackTraceRecoveryTest.kt:20)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$throws\$1.invokeSuspend(StackTraceRecoveryTest.kt)\n" + - "\t(Coroutine boundary)\n" + + "\tat _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.awaiter(StackTraceRecoveryTest.kt:285)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testNonDispatchedRecovery\$await\$1.invokeSuspend(StackTraceRecoveryTest.kt:291)\n" + "Caused by: kotlinx.coroutines.RecoverableTestException") @@ -244,7 +244,7 @@ class StackTraceRecoveryTest : TestBase() { } catch (e: Throwable) { verifyStackTrace(e, "kotlinx.coroutines.RecoverableTestException\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCancellableContinuation\$1.invokeSuspend(StackTraceRecoveryTest.kt:329)\n" + - "\t(Coroutine boundary)\n" + + "\tat _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest.awaitCallback(StackTraceRecoveryTest.kt:348)\n" + "\tat kotlinx.coroutines.exceptions.StackTraceRecoveryTest\$testCancellableContinuation\$1\$1.invokeSuspend(StackTraceRecoveryTest.kt:322)\n" + "Caused by: kotlinx.coroutines.RecoverableTestException\n" + diff --git a/kotlinx-coroutines-core/jvm/test/exceptions/Stacktraces.kt b/kotlinx-coroutines-core/jvm/test/exceptions/Stacktraces.kt index f79ad4ba74..5d85c9c9f2 100644 --- a/kotlinx-coroutines-core/jvm/test/exceptions/Stacktraces.kt +++ b/kotlinx-coroutines-core/jvm/test/exceptions/Stacktraces.kt @@ -33,25 +33,10 @@ public fun toStackTrace(t: Throwable): String { } public fun String.normalizeStackTrace(): String = - applyBackspace() - .replace(Regex(":[0-9]+"), "") // remove line numbers + replace(Regex(":[0-9]+"), "") // remove line numbers .replace("kotlinx_coroutines_core_main", "") // yay source sets .replace("kotlinx_coroutines_core", "") .replace(Regex("@[0-9a-f]+"), "") // remove hex addresses in debug toStrings .lines().joinToString("\n") // normalize line separators -public fun String.applyBackspace(): String { - val array = toCharArray() - val stack = CharArray(array.size) - var stackSize = -1 - for (c in array) { - if (c != '\b') { - stack[++stackSize] = c - } else { - --stackSize - } - } - return String(stack, 0, stackSize) -} - public fun String.count(substring: String): Int = split(substring).size - 1 \ No newline at end of file diff --git a/kotlinx-coroutines-debug/README.md b/kotlinx-coroutines-debug/README.md index 5f302d2052..758a0922ef 100644 --- a/kotlinx-coroutines-debug/README.md +++ b/kotlinx-coroutines-debug/README.md @@ -123,7 +123,7 @@ Coroutine "coroutine#2":DeferredCoroutine{Active}@289d1c02, state: SUSPENDED at ExampleKt.combineResults(Example.kt:11) at ExampleKt$computeValue$2.invokeSuspend(Example.kt:7) at ExampleKt$main$1$deferred$1.invokeSuspend(Example.kt:25) - (Coroutine creation stacktrace) + at _COROUTINE._CREATION._(CoroutineDebugging.kt) at kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116) at kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:25) at kotlinx.coroutines.BuildersKt.async$default(Unknown Source) diff --git a/kotlinx-coroutines-debug/src/DebugProbes.kt b/kotlinx-coroutines-debug/src/DebugProbes.kt index ed346d8136..6a7d9028a1 100644 --- a/kotlinx-coroutines-debug/src/DebugProbes.kt +++ b/kotlinx-coroutines-debug/src/DebugProbes.kt @@ -134,7 +134,7 @@ public object DebugProbes { * * Coroutine "coroutine#42":StandaloneCoroutine{Active}@58fdd99, state: SUSPENDED * at MyClass$awaitData.invokeSuspend(MyClass.kt:37) - * (Coroutine creation stacktrace) + * at _COROUTINE._CREATION._(CoroutineDebugging.kt) * at MyClass.createIoRequest(MyClass.kt:142) * at MyClass.fetchData(MyClass.kt:154) * at MyClass.showData(MyClass.kt:31) diff --git a/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt b/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt index fd0279123f..4fecb83e47 100644 --- a/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt +++ b/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt @@ -25,7 +25,7 @@ class CoroutinesDumpTest : DebugTestBase() { "Coroutine \"coroutine#1\":DeferredCoroutine{Active}@1e4a7dd4, state: SUSPENDED\n" + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.sleepingNestedMethod(CoroutinesDumpTest.kt:95)\n" + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.sleepingOuterMethod(CoroutinesDumpTest.kt:88)\n" + - "\t(Coroutine creation stacktrace)\n" + + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" + "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" + "\tat kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:99)\n", @@ -51,7 +51,7 @@ class CoroutinesDumpTest : DebugTestBase() { "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.nestedActiveMethod(CoroutinesDumpTest.kt:141)\n" + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.activeMethod(CoroutinesDumpTest.kt:133)\n" + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest\$testRunningCoroutine\$1$deferred\$1.invokeSuspend(CoroutinesDumpTest.kt:41)\n" + - "\t(Coroutine creation stacktrace)\n" + + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" + "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" + "\tat kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:99)\n" + @@ -81,7 +81,7 @@ class CoroutinesDumpTest : DebugTestBase() { "\tat java.lang.Thread.sleep(Native Method)\n" + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.nestedActiveMethod(CoroutinesDumpTest.kt:111)\n" + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.activeMethod(CoroutinesDumpTest.kt:106)\n" + - "\t(Coroutine creation stacktrace)\n" + + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" + "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" + "\tat kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:99)\n" + diff --git a/kotlinx-coroutines-debug/test/DebugProbesTest.kt b/kotlinx-coroutines-debug/test/DebugProbesTest.kt index 4b39438138..bc0c1e3f24 100644 --- a/kotlinx-coroutines-debug/test/DebugProbesTest.kt +++ b/kotlinx-coroutines-debug/test/DebugProbesTest.kt @@ -20,7 +20,7 @@ class DebugProbesTest : DebugTestBase() { val traces = listOf( "java.util.concurrent.ExecutionException\n" + "\tat kotlinx.coroutines.debug.DebugProbesTest\$createDeferred\$1.invokeSuspend(DebugProbesTest.kt:14)\n" + - "\t(Coroutine boundary)\n" + + "\tat _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)\n" + "\tat kotlinx.coroutines.debug.DebugProbesTest.oneMoreNestedMethod(DebugProbesTest.kt:49)\n" + "\tat kotlinx.coroutines.debug.DebugProbesTest.nestedMethod(DebugProbesTest.kt:44)\n" + "\tat kotlinx.coroutines.debug.DebugProbesTest\$testAsync\$1.invokeSuspend(DebugProbesTest.kt:17)\n", @@ -40,11 +40,11 @@ class DebugProbesTest : DebugTestBase() { val traces = listOf( "java.util.concurrent.ExecutionException\n" + "\tat kotlinx.coroutines.debug.DebugProbesTest\$createDeferred\$1.invokeSuspend(DebugProbesTest.kt)\n" + - "\t(Coroutine boundary)\n" + + "\tat _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)\n" + "\tat kotlinx.coroutines.debug.DebugProbesTest.oneMoreNestedMethod(DebugProbesTest.kt)\n" + "\tat kotlinx.coroutines.debug.DebugProbesTest.nestedMethod(DebugProbesTest.kt)\n" + "\tat kotlinx.coroutines.debug.DebugProbesTest\$testAsyncWithProbes\$1\$1.invokeSuspend(DebugProbesTest.kt:62)\n" + - "\t(Coroutine creation stacktrace)\n" + + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt)\n" + "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt)\n" + "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable\$default(Cancellable.kt)\n" + @@ -71,11 +71,11 @@ class DebugProbesTest : DebugTestBase() { val traces = listOf( "java.util.concurrent.ExecutionException\n" + "\tat kotlinx.coroutines.debug.DebugProbesTest\$createDeferred\$1.invokeSuspend(DebugProbesTest.kt:16)\n" + - "\t(Coroutine boundary)\n" + + "\tat _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)\n" + "\tat kotlinx.coroutines.debug.DebugProbesTest.oneMoreNestedMethod(DebugProbesTest.kt:71)\n" + "\tat kotlinx.coroutines.debug.DebugProbesTest.nestedMethod(DebugProbesTest.kt:66)\n" + "\tat kotlinx.coroutines.debug.DebugProbesTest\$testAsyncWithSanitizedProbes\$1\$1.invokeSuspend(DebugProbesTest.kt:87)\n" + - "\t(Coroutine creation stacktrace)\n" + + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" + "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" + "\tat kotlinx.coroutines.debug.DebugProbesTest.testAsyncWithSanitizedProbes(DebugProbesTest.kt:38)", diff --git a/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt b/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt index e7fdeede79..8b5724219e 100644 --- a/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt +++ b/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt @@ -32,7 +32,7 @@ class RunningThreadStackMergeTest : DebugTestBase() { "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$suspendingFunction\$2.invokeSuspend(RunningThreadStackMergeTest.kt:77)\n" + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.suspendingFunction(RunningThreadStackMergeTest.kt:75)\n" + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$launchCoroutine\$1.invokeSuspend(RunningThreadStackMergeTest.kt:68)\n" + - "\t(Coroutine creation stacktrace)\n" + + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)", ignoredCoroutine = ":BlockingCoroutine" ) { @@ -87,7 +87,7 @@ class RunningThreadStackMergeTest : DebugTestBase() { "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$suspendingFunctionWithContext\$2.invokeSuspend(RunningThreadStackMergeTest.kt:124)\n" + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.suspendingFunctionWithContext(RunningThreadStackMergeTest.kt:122)\n" + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$launchEscapingCoroutine\$1.invokeSuspend(RunningThreadStackMergeTest.kt:116)\n" + - "\t(Coroutine creation stacktrace)\n" + + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)", ignoredCoroutine = ":BlockingCoroutine" ) { @@ -126,7 +126,7 @@ class RunningThreadStackMergeTest : DebugTestBase() { "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.nonSuspendingFun(RunningThreadStackMergeTest.kt:83)\n" + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest.suspendingFunctionWithoutContext(RunningThreadStackMergeTest.kt:160)\n" + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$launchEscapingCoroutineWithoutContext\$1.invokeSuspend(RunningThreadStackMergeTest.kt:153)\n" + - "\t(Coroutine creation stacktrace)\n" + + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)", ignoredCoroutine = ":BlockingCoroutine" ) { @@ -158,7 +158,7 @@ class RunningThreadStackMergeTest : DebugTestBase() { "\tat kotlinx.coroutines.debug.StacktraceUtilsKt.verifyDump(StacktraceUtils.kt)\n" + "\tat kotlinx.coroutines.debug.StacktraceUtilsKt.verifyDump\$default(StacktraceUtils.kt)\n" + "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$testRunBlocking\$1.invokeSuspend(RunningThreadStackMergeTest.kt)\n" + - "\t(Coroutine creation stacktrace)\n" + + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt)\n") } diff --git a/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt b/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt index f6c169b4eb..6afda16889 100644 --- a/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt +++ b/kotlinx-coroutines-debug/test/SanitizedProbesTest.kt @@ -26,11 +26,11 @@ class SanitizedProbesTest : DebugTestBase() { val traces = listOf( "java.util.concurrent.ExecutionException\n" + "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest\$createDeferredNested\$1.invokeSuspend(SanitizedProbesTest.kt:97)\n" + - "\t(Coroutine boundary)\n" + + "\tat _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)\n" + "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.oneMoreNestedMethod(SanitizedProbesTest.kt:67)\n" + "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest.nestedMethod(SanitizedProbesTest.kt:61)\n" + "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest\$testRecoveredStackTrace\$1.invokeSuspend(SanitizedProbesTest.kt:50)\n" + - "\t(Coroutine creation stacktrace)\n" + + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" + "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" + "\tat kotlinx.coroutines.TestBase.runTest\$default(TestBase.kt:141)\n" + @@ -50,7 +50,7 @@ class SanitizedProbesTest : DebugTestBase() { verifyDump( "Coroutine \"coroutine#3\":BlockingCoroutine{Active}@7d68ef40, state: RUNNING\n" + "\tat java.lang.Thread.getStackTrace(Thread.java)\n" + - "\t(Coroutine creation stacktrace)\n" + + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" + "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" + "\tat kotlinx.coroutines.TestBase.runTest\$default(TestBase.kt:141)\n" + @@ -58,7 +58,7 @@ class SanitizedProbesTest : DebugTestBase() { "Coroutine \"coroutine#4\":DeferredCoroutine{Active}@75c072cb, state: SUSPENDED\n" + "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest\$createActiveDeferred\$1.invokeSuspend(SanitizedProbesTest.kt:63)\n" + - "\t(Coroutine creation stacktrace)\n" + + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" + "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" + "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.async\$default(Builders.common.kt)\n" + @@ -82,12 +82,12 @@ class SanitizedProbesTest : DebugTestBase() { expect(3) verifyDump("Coroutine \"coroutine#1\":BlockingCoroutine{Active}@35fc6dc4, state: RUNNING\n" + "\tat java.lang.Thread.getStackTrace(Thread.java:1552)\n" + // Skip the rest - "\t(Coroutine creation stacktrace)\n" + + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)", "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@1b68b9a4, state: SUSPENDED\n" + "\tat definitely.not.kotlinx.coroutines.SanitizedProbesTest\$launchSelector\$1\$1\$1.invokeSuspend(SanitizedProbesTest.kt)\n" + - "\t(Coroutine creation stacktrace)\n" + + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" + "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:25)\n" + "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.launch\$default(Builders.common.kt)\n" + diff --git a/kotlinx-coroutines-debug/test/ScopedBuildersTest.kt b/kotlinx-coroutines-debug/test/ScopedBuildersTest.kt index c762725569..801b74b1aa 100644 --- a/kotlinx-coroutines-debug/test/ScopedBuildersTest.kt +++ b/kotlinx-coroutines-debug/test/ScopedBuildersTest.kt @@ -17,7 +17,7 @@ class ScopedBuildersTest : DebugTestBase() { yield() verifyDump( "Coroutine \"coroutine#1\":BlockingCoroutine{Active}@16612a51, state: RUNNING\n" + - "\t(Coroutine creation stacktrace)\n" + + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n", "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@6b53e23f, state: SUSPENDED\n" + @@ -25,7 +25,7 @@ class ScopedBuildersTest : DebugTestBase() { "\tat kotlinx.coroutines.debug.ScopedBuildersTest.doWithContext(ScopedBuildersTest.kt:47)\n" + "\tat kotlinx.coroutines.debug.ScopedBuildersTest\$doInScope\$2.invokeSuspend(ScopedBuildersTest.kt:41)\n" + "\tat kotlinx.coroutines.debug.ScopedBuildersTest\$testNestedScopes\$1\$job\$1.invokeSuspend(ScopedBuildersTest.kt:30)\n" + - "\t(Coroutine creation stacktrace)\n" + + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)") job.cancelAndJoin() finish(4) diff --git a/kotlinx-coroutines-debug/test/StacktraceUtils.kt b/kotlinx-coroutines-debug/test/StacktraceUtils.kt index 9cc626f19a..58f20f7231 100644 --- a/kotlinx-coroutines-debug/test/StacktraceUtils.kt +++ b/kotlinx-coroutines-debug/test/StacktraceUtils.kt @@ -7,6 +7,9 @@ package kotlinx.coroutines.debug import java.io.* import kotlin.test.* +private val coroutineCreationFrameRegex = + Regex("\n\tat _COROUTINE._CREATION._[^\n]*\n") + public fun String.trimStackTrace(): String = trimIndent() .replace(Regex(":[0-9]+"), "") @@ -14,22 +17,6 @@ public fun String.trimStackTrace(): String = .replace(Regex("(?<=\tat )[^\n]*/"), "") .replace(Regex("\t"), "") .replace("sun.misc.Unsafe.", "jdk.internal.misc.Unsafe.") // JDK8->JDK11 - .applyBackspace() - -public fun String.applyBackspace(): String { - val array = toCharArray() - val stack = CharArray(array.size) - var stackSize = -1 - for (c in array) { - if (c != '\b') { - stack[++stackSize] = c - } else { - --stackSize - } - } - - return String(stack, 0, stackSize + 1) -} public fun verifyStackTrace(e: Throwable, traces: List) { val stacktrace = toStackTrace(e) @@ -74,7 +61,7 @@ public fun verifyDump(vararg traces: String, ignoredCoroutine: String? = null, f * `$$BlockHound$$_` prepended at the last component. */ private fun cleanBlockHoundTraces(frames: List): List { - var result = mutableListOf() + val result = mutableListOf() val blockHoundSubstr = "\$\$BlockHound\$\$_" var i = 0 while (i < frames.size) { @@ -98,21 +85,21 @@ public fun verifyDump(vararg traces: String, ignoredCoroutine: String? = null) { assertTrue(filtered[0].startsWith("Coroutines dump")) return } - // Drop "Coroutine dump" line - trace.withIndex().drop(1).forEach { (index, value) -> + // The first line, "Coroutine dump", is dropped. This is not `zip` because not having enough dumps is an error. + trace.drop(1).withIndex().forEach { (index, value) -> if (ignoredCoroutine != null && value.contains(ignoredCoroutine)) { return@forEach } - val expected = traces[index - 1].applyBackspace().split("\n\t(Coroutine creation stacktrace)\n", limit = 2) - val actual = value.applyBackspace().split("\n\t(Coroutine creation stacktrace)\n", limit = 2) + val expected = traces[index].split(coroutineCreationFrameRegex, limit = 2) + val actual = value.split(coroutineCreationFrameRegex, limit = 2) assertEquals(expected.size, actual.size, "Creation stacktrace should be part of the expected input. Whole dump:\n$wholeDump") - expected.withIndex().forEach { (index, trace) -> - val actualTrace = actual[index].trimStackTrace().sanitizeAddresses() - val expectedTrace = trace.trimStackTrace().sanitizeAddresses() - val actualLines = cleanBlockHoundTraces(actualTrace.split("\n")) - val expectedLines = expectedTrace.split("\n") + actual.zip(expected).forEach { (actualTrace, expectedTrace) -> + val sanitizedActualTrace = actualTrace.trimStackTrace().sanitizeAddresses() + val sanitizedExpectedTrace = expectedTrace.trimStackTrace().sanitizeAddresses() + val actualLines = cleanBlockHoundTraces(sanitizedActualTrace.split("\n")) + val expectedLines = sanitizedExpectedTrace.split("\n") for (i in expectedLines.indices) { assertEquals(expectedLines[i], actualLines[i], "Whole dump:\n$wholeDump") } diff --git a/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutDisabledTracesTest.kt b/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutDisabledTracesTest.kt index 2063090c82..4352140bd7 100644 --- a/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutDisabledTracesTest.kt +++ b/kotlinx-coroutines-debug/test/junit4/CoroutinesTimeoutDisabledTracesTest.kt @@ -22,7 +22,7 @@ class CoroutinesTimeoutDisabledTracesTest : TestBase(disableOutCheck = true) { "at kotlinx.coroutines.debug.junit4.CoroutinesTimeoutDisabledTracesTest.hangForever", "at kotlinx.coroutines.debug.junit4.CoroutinesTimeoutDisabledTracesTest.waitForHangJob" ), - notExpectedOutParts = listOf("Coroutine creation stacktrace"), + notExpectedOutParts = listOf("_COROUTINE._CREATION._"), error = TestTimedOutException::class.java ) ) From 8724e63ce59f1e2e9f5231c5b0218ad5cde9ef1c Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 21 Oct 2022 12:18:56 +0300 Subject: [PATCH 042/106] Update binary compatibility validator to 0.12.0 (#3496) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 37dfbb0000..dd4a4ac685 100644 --- a/gradle.properties +++ b/gradle.properties @@ -22,7 +22,7 @@ rxjava2_version=2.2.8 rxjava3_version=3.0.2 javafx_version=11.0.2 javafx_plugin_version=0.0.8 -binary_compatibility_validator_version=0.11.0 +binary_compatibility_validator_version=0.12.0 kover_version=0.6.1 blockhound_version=1.0.2.RELEASE jna_version=5.9.0 From 02bf3569a6c0d59a2257eef47d66a038b0875f32 Mon Sep 17 00:00:00 2001 From: Philip Wedemann <22521688+hfhbd@users.noreply.github.com> Date: Fri, 21 Oct 2022 15:57:15 +0200 Subject: [PATCH 043/106] Add overloads for runTest that accept a Duration (#3483) --- .../api/kotlinx-coroutines-test.api | 3 + .../common/src/TestBuilders.kt | 169 ++++++++++++++++-- .../src/migration/TestBuildersDeprecated.kt | 3 +- 3 files changed, 159 insertions(+), 16 deletions(-) diff --git a/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api b/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api index bf639235d0..53aa355c5b 100644 --- a/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api +++ b/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api @@ -23,6 +23,9 @@ public final class kotlinx/coroutines/test/TestBuildersKt { public static synthetic fun runTest$default (Lkotlin/coroutines/CoroutineContext;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public static synthetic fun runTest$default (Lkotlinx/coroutines/test/TestCoroutineScope;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public static synthetic fun runTest$default (Lkotlinx/coroutines/test/TestScope;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static final fun runTest-8Mi8wO0 (Lkotlin/coroutines/CoroutineContext;JLkotlin/jvm/functions/Function2;)V + public static final fun runTest-8Mi8wO0 (Lkotlinx/coroutines/test/TestScope;JLkotlin/jvm/functions/Function2;)V + public static synthetic fun runTest-8Mi8wO0$default (Lkotlin/coroutines/CoroutineContext;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public static final fun runTestWithLegacyScope (Lkotlin/coroutines/CoroutineContext;JLkotlin/jvm/functions/Function2;)V public static synthetic fun runTestWithLegacyScope$default (Lkotlin/coroutines/CoroutineContext;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V } diff --git a/kotlinx-coroutines-test/common/src/TestBuilders.kt b/kotlinx-coroutines-test/common/src/TestBuilders.kt index cb677811c9..e9d8fec0d3 100644 --- a/kotlinx-coroutines-test/common/src/TestBuilders.kt +++ b/kotlinx-coroutines-test/common/src/TestBuilders.kt @@ -10,6 +10,8 @@ import kotlinx.coroutines.* import kotlinx.coroutines.selects.* import kotlin.coroutines.* import kotlin.jvm.* +import kotlin.time.* +import kotlin.time.Duration.Companion.milliseconds /** * A test result. @@ -41,9 +43,9 @@ public expect class TestResult * @Test * fun exampleTest() = runTest { * val deferred = async { - * delay(1_000) + * delay(1.seconds) * async { - * delay(1_000) + * delay(1.seconds) * }.await() * } * @@ -99,9 +101,9 @@ public expect class TestResult * fun exampleTest() = runTest { * val elapsed = TimeSource.Monotonic.measureTime { * val deferred = async { - * delay(1_000) // will be skipped + * delay(1.seconds) // will be skipped * withContext(Dispatchers.Default) { - * delay(5_000) // Dispatchers.Default doesn't know about TestCoroutineScheduler + * delay(5.seconds) // Dispatchers.Default doesn't know about TestCoroutineScheduler * } * } * deferred.await() @@ -131,7 +133,7 @@ public expect class TestResult * * In the general case, if there are active jobs, it's impossible to detect if they are going to complete eventually due * to the asynchronous nature of coroutines. In order to prevent tests hanging in this scenario, [runTest] will wait - * for [dispatchTimeoutMs] milliseconds (by default, 60 seconds) from the moment when [TestCoroutineScheduler] becomes + * for [dispatchTimeout] (by default, 60 seconds) from the moment when [TestCoroutineScheduler] becomes * idle before throwing [AssertionError]. If some dispatcher linked to [TestCoroutineScheduler] receives a * task during that time, the timer gets reset. * @@ -146,25 +148,149 @@ public expect class TestResult @ExperimentalCoroutinesApi public fun runTest( context: CoroutineContext = EmptyCoroutineContext, - dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS, + dispatchTimeout: Duration = DEFAULT_DISPATCH_TIMEOUT, testBody: suspend TestScope.() -> Unit ): TestResult { if (context[RunningInRunTest] != null) throw IllegalStateException("Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details.") - return TestScope(context + RunningInRunTest).runTest(dispatchTimeoutMs, testBody) + return TestScope(context + RunningInRunTest).runTest(dispatchTimeout, testBody) } +/** + * Executes [testBody] as a test in a new coroutine, returning [TestResult]. + * + * On JVM and Native, this function behaves similarly to `runBlocking`, with the difference that the code that it runs + * will skip delays. This allows to use [delay] in without causing the tests to take more time than necessary. + * On JS, this function creates a `Promise` that executes the test body with the delay-skipping behavior. + * + * ``` + * @Test + * fun exampleTest() = runTest { + * val deferred = async { + * delay(1.seconds) + * async { + * delay(1.seconds) + * }.await() + * } + * + * deferred.await() // result available immediately + * } + * ``` + * + * The platform difference entails that, in order to use this function correctly in common code, one must always + * immediately return the produced [TestResult] from the test method, without doing anything else afterwards. See + * [TestResult] for details on this. + * + * The test is run in a single thread, unless other [CoroutineDispatcher] are used for child coroutines. + * Because of this, child coroutines are not executed in parallel to the test body. + * In order for the spawned-off asynchronous code to actually be executed, one must either [yield] or suspend the + * test body some other way, or use commands that control scheduling (see [TestCoroutineScheduler]). + * + * ``` + * @Test + * fun exampleWaitingForAsyncTasks1() = runTest { + * // 1 + * val job = launch { + * // 3 + * } + * // 2 + * job.join() // the main test coroutine suspends here, so the child is executed + * // 4 + * } + * + * @Test + * fun exampleWaitingForAsyncTasks2() = runTest { + * // 1 + * launch { + * // 3 + * } + * // 2 + * advanceUntilIdle() // runs the tasks until their queue is empty + * // 4 + * } + * ``` + * + * ### Task scheduling + * + * Delay-skipping is achieved by using virtual time. + * If [Dispatchers.Main] is set to a [TestDispatcher] via [Dispatchers.setMain] before the test, + * then its [TestCoroutineScheduler] is used; + * otherwise, a new one is automatically created (or taken from [context] in some way) and can be used to control + * the virtual time, advancing it, running the tasks scheduled at a specific time etc. + * Some convenience methods are available on [TestScope] to control the scheduler. + * + * Delays in code that runs inside dispatchers that don't use a [TestCoroutineScheduler] don't get skipped: + * ``` + * @Test + * fun exampleTest() = runTest { + * val elapsed = TimeSource.Monotonic.measureTime { + * val deferred = async { + * delay(1.seconds) // will be skipped + * withContext(Dispatchers.Default) { + * delay(5.seconds) // Dispatchers.Default doesn't know about TestCoroutineScheduler + * } + * } + * deferred.await() + * } + * println(elapsed) // about five seconds + * } + * ``` + * + * ### Failures + * + * #### Test body failures + * + * If the created coroutine completes with an exception, then this exception will be thrown at the end of the test. + * + * #### Reported exceptions + * + * Unhandled exceptions will be thrown at the end of the test. + * If the uncaught exceptions happen after the test finishes, the error is propagated in a platform-specific manner. + * If the test coroutine completes with an exception, the unhandled exceptions are suppressed by it. + * + * #### Uncompleted coroutines + * + * This method requires that, after the test coroutine has completed, all the other coroutines launched inside + * [testBody] also complete, or are cancelled. + * Otherwise, the test will be failed (which, on JVM and Native, means that [runTest] itself will throw + * [AssertionError], whereas on JS, the `Promise` will fail with it). + * + * In the general case, if there are active jobs, it's impossible to detect if they are going to complete eventually due + * to the asynchronous nature of coroutines. In order to prevent tests hanging in this scenario, [runTest] will wait + * for [dispatchTimeoutMs] from the moment when [TestCoroutineScheduler] becomes + * idle before throwing [AssertionError]. If some dispatcher linked to [TestCoroutineScheduler] receives a + * task during that time, the timer gets reset. + * + * ### Configuration + * + * [context] can be used to affect the environment of the code under test. Beside just being passed to the coroutine + * scope created for the test, [context] also can be used to change how the test is executed. + * See the [TestScope] constructor function documentation for details. + * + * @throws IllegalArgumentException if the [context] is invalid. See the [TestScope] constructor docs for details. + */ +@ExperimentalCoroutinesApi +public fun runTest( + context: CoroutineContext = EmptyCoroutineContext, + dispatchTimeoutMs: Long, + testBody: suspend TestScope.() -> Unit +): TestResult = runTest( + context = context, + dispatchTimeout = dispatchTimeoutMs.milliseconds, + testBody = testBody +) + /** * Performs [runTest] on an existing [TestScope]. */ @ExperimentalCoroutinesApi public fun TestScope.runTest( - dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS, + dispatchTimeout: Duration, testBody: suspend TestScope.() -> Unit ): TestResult = asSpecificImplementation().let { it.enter() createTestResult { - runTestCoroutine(it, dispatchTimeoutMs, TestScopeImpl::tryGetCompletionCause, testBody) { + runTestCoroutine(it, dispatchTimeout, TestScopeImpl::tryGetCompletionCause, testBody) { backgroundScope.cancel() testScheduler.advanceUntilIdleOr { false } it.leave() @@ -172,6 +298,18 @@ public fun TestScope.runTest( } } +/** + * Performs [runTest] on an existing [TestScope]. + */ +@ExperimentalCoroutinesApi +public fun TestScope.runTest( + dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS, + testBody: suspend TestScope.() -> Unit +): TestResult = runTest( + dispatchTimeout = dispatchTimeoutMs.milliseconds, + testBody = testBody +) + /** * Runs [testProcedure], creating a [TestResult]. */ @@ -189,10 +327,11 @@ internal object RunningInRunTest : CoroutineContext.Key, Corou /** The default timeout to use when waiting for asynchronous completions of the coroutines managed by * a [TestCoroutineScheduler]. */ internal const val DEFAULT_DISPATCH_TIMEOUT_MS = 60_000L +internal val DEFAULT_DISPATCH_TIMEOUT = DEFAULT_DISPATCH_TIMEOUT_MS.milliseconds /** * Run the [body][testBody] of the [test coroutine][coroutine], waiting for asynchronous completions for at most - * [dispatchTimeoutMs] milliseconds, and performing the [cleanup] procedure at the end. + * [dispatchTimeout], and performing the [cleanup] procedure at the end. * * [tryGetCompletionCause] is the [JobSupport.completionCause], which is passed explicitly because it is protected. * @@ -201,7 +340,7 @@ internal const val DEFAULT_DISPATCH_TIMEOUT_MS = 60_000L */ internal suspend fun > CoroutineScope.runTestCoroutine( coroutine: T, - dispatchTimeoutMs: Long, + dispatchTimeout: Duration, tryGetCompletionCause: T.() -> Throwable?, testBody: suspend T.() -> Unit, cleanup: () -> List, @@ -258,8 +397,8 @@ internal suspend fun > CoroutineScope.runTestCoroutin scheduler.onDispatchEvent { // we received knowledge that `scheduler` observed a dispatch event, so we reset the timeout } - onTimeout(dispatchTimeoutMs) { - handleTimeout(coroutine, dispatchTimeoutMs, tryGetCompletionCause, cleanup) + onTimeout(dispatchTimeout) { + handleTimeout(coroutine, dispatchTimeout, tryGetCompletionCause, cleanup) } } } finally { @@ -284,7 +423,7 @@ internal suspend fun > CoroutineScope.runTestCoroutin */ private inline fun> handleTimeout( coroutine: T, - dispatchTimeoutMs: Long, + dispatchTimeout: Duration, tryGetCompletionCause: T.() -> Throwable?, cleanup: () -> List, ) { @@ -296,7 +435,7 @@ private inline fun> handleTimeout( } val activeChildren = coroutine.children.filter { it.isActive }.toList() val completionCause = if (coroutine.isCancelled) coroutine.tryGetCompletionCause() else null - var message = "After waiting for $dispatchTimeoutMs ms" + var message = "After waiting for $dispatchTimeout" if (completionCause == null) message += ", the test coroutine is not completing" if (activeChildren.isNotEmpty()) diff --git a/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt b/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt index 35d237a7f5..c0d6c17cb6 100644 --- a/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt +++ b/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt @@ -10,6 +10,7 @@ package kotlinx.coroutines.test import kotlinx.coroutines.* import kotlin.coroutines.* import kotlin.jvm.* +import kotlin.time.Duration.Companion.milliseconds /** * Executes a [testBody] inside an immediate execution dispatcher. @@ -164,7 +165,7 @@ public fun runTestWithLegacyScope( throw IllegalStateException("Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details.") val testScope = TestBodyCoroutine(createTestCoroutineScope(context + RunningInRunTest)) return createTestResult { - runTestCoroutine(testScope, dispatchTimeoutMs, TestBodyCoroutine::tryGetCompletionCause, testBody) { + runTestCoroutine(testScope, dispatchTimeoutMs.milliseconds, TestBodyCoroutine::tryGetCompletionCause, testBody) { try { testScope.cleanup() emptyList() From 4a13d58484e8f9018b62515d4a13adbd5a94e9be Mon Sep 17 00:00:00 2001 From: mvicsokolova <82594708+mvicsokolova@users.noreply.github.com> Date: Tue, 25 Oct 2022 15:04:45 +0200 Subject: [PATCH 044/106] Apply "configure-compilation-conventions" script earlier (#3488) --- build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 4377240625..f02b1949ec 100644 --- a/build.gradle +++ b/build.gradle @@ -76,6 +76,9 @@ def configureKotlinJvmPlatform(configuration) { configuration.attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.jvm) } +// Configure subprojects with Kotlin sources +apply plugin: "configure-compilation-conventions" + allprojects { // the only place where HostManager could be instantiated project.ext.hostManager = new HostManager() @@ -167,9 +170,6 @@ configure(subprojects.findAll { !sourceless.contains(it.name) && it.name != core apply plugin: "bom-conventions" -// Configure subprojects with Kotlin sources -apply plugin: "configure-compilation-conventions" - if (build_snapshot_train) { println "Hacking test tasks, removing stress and flaky tests" allprojects { From bfc6c0b3ba4de3d7c65dc58828ecc249ed9d52a3 Mon Sep 17 00:00:00 2001 From: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> Date: Tue, 25 Oct 2022 16:42:38 +0300 Subject: [PATCH 045/106] Deprecate SharedFlow.toList(dst) and toSet(dst) (#3500) --- kotlinx-coroutines-core/common/src/flow/operators/Lint.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt b/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt index b140e6280c..f7c7528d43 100644 --- a/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt +++ b/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt @@ -144,7 +144,7 @@ public inline fun SharedFlow.retryWhen(noinline predicate: suspend FlowCo level = DeprecationLevel.WARNING ) @InlineOnly -public suspend inline fun SharedFlow.toList(): List = +public suspend inline fun SharedFlow.toList(destination: MutableList = ArrayList()): List = (this as Flow).toList() /** @@ -156,7 +156,7 @@ public suspend inline fun SharedFlow.toList(): List = level = DeprecationLevel.WARNING ) @InlineOnly -public suspend inline fun SharedFlow.toSet(): Set = +public suspend inline fun SharedFlow.toSet(destination: MutableSet = LinkedHashSet()): Set = (this as Flow).toSet() /** From 06b3b3b50408085a6d54a4bae3b66589ae32ea1f Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 31 Oct 2022 13:16:02 +0300 Subject: [PATCH 046/106] Publish empty kotlinx-coroutines-jdk8 artifact (#3510) Fixes #3509 --- build.gradle | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index f02b1949ec..507f59e739 100644 --- a/build.gradle +++ b/build.gradle @@ -224,9 +224,8 @@ def core_docs_file = "$projectDir/kotlinx-coroutines-core/build/dokka/htmlPartia apply plugin: "org.jetbrains.dokka" configure(subprojects.findAll { !unpublished.contains(it.name) - && it.name != coreModule - && it.name != jdk8ObsoleteModule}) { - if (it.name != 'kotlinx-coroutines-bom') { + && it.name != coreModule }) { + if (it.name != 'kotlinx-coroutines-bom' && it.name != jdk8ObsoleteModule) { apply from: rootProject.file('gradle/dokka.gradle.kts') } apply from: rootProject.file('gradle/publish.gradle') From 6c9d358a33746a2ad52f7b1ff9e5319142cc2b31 Mon Sep 17 00:00:00 2001 From: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> Date: Mon, 31 Oct 2022 18:09:25 +0300 Subject: [PATCH 047/106] Remove some broken tests (#3513) These tests haven't been reliable for a very long time for some reason. We could ensure that they pass by a manual check, but it feels like nobody actually relies on this behavior, and it's not clear who would even want to. --- kotlinx-coroutines-test/common/test/RunTestTest.kt | 14 -------------- .../jvm/test/migration/RunTestLegacyScopeTest.kt | 13 ------------- 2 files changed, 27 deletions(-) diff --git a/kotlinx-coroutines-test/common/test/RunTestTest.kt b/kotlinx-coroutines-test/common/test/RunTestTest.kt index dda89e3a31..0315543d54 100644 --- a/kotlinx-coroutines-test/common/test/RunTestTest.kt +++ b/kotlinx-coroutines-test/common/test/RunTestTest.kt @@ -69,20 +69,6 @@ class RunTestTest { deferred.await() } - /** Tests that a dispatch timeout of `0` will fail the test if there are some dispatches outside the scheduler. */ - @Test - fun testRunTestWithZeroTimeoutWithUncontrolledDispatches() = testResultMap({ fn -> - assertFailsWith { fn() } - }) { - runTest(dispatchTimeoutMs = 0) { - withContext(Dispatchers.Default) { - delay(10) - 3 - } - fail("shouldn't be reached") - } - } - /** Tests that too low of a dispatch timeout causes crashes. */ @Test fun testRunTestWithSmallTimeout() = testResultMap({ fn -> diff --git a/kotlinx-coroutines-test/jvm/test/migration/RunTestLegacyScopeTest.kt b/kotlinx-coroutines-test/jvm/test/migration/RunTestLegacyScopeTest.kt index 7f1dd00963..ed5b1577f5 100644 --- a/kotlinx-coroutines-test/jvm/test/migration/RunTestLegacyScopeTest.kt +++ b/kotlinx-coroutines-test/jvm/test/migration/RunTestLegacyScopeTest.kt @@ -66,19 +66,6 @@ class RunTestLegacyScopeTest { deferred.await() } - @Test - fun testRunTestWithZeroTimeoutWithUncontrolledDispatches() = testResultMap({ fn -> - assertFailsWith { fn() } - }) { - runTestWithLegacyScope(dispatchTimeoutMs = 0) { - withContext(Dispatchers.Default) { - delay(10) - 3 - } - fail("shouldn't be reached") - } - } - @Test fun testRunTestWithSmallTimeout() = testResultMap({ fn -> assertFailsWith { fn() } From 4e29357e8f02c742478504f21f3ed827c113c8f2 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 3 Nov 2022 14:26:12 +0300 Subject: [PATCH 048/106] =?UTF-8?q?Disable=20tests=20with=20real=20source?= =?UTF-8?q?=20of=20the=20time=20on=20Java's=20Windows=20to=20preve?= =?UTF-8?q?=E2=80=A6=20(#3516)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Disable tests with the real source of the time on Java's Windows to prevent flakiness We still want to test real code, not virtualized mocked clocks, but Windows itself is virtualized on our TeamCity, making Java's parker resolution really low and unstable for such tests Co-authored-by: dkhalanskyjb <52952525+dkhalanskyjb@users.noreply.github.com> --- .../common/test/JobTest.kt | 6 ++-- .../common/test/TestBase.common.kt | 2 ++ kotlinx-coroutines-core/common/test/Try.kt | 29 ------------------- .../test/WithTimeoutOrNullDurationTest.kt | 1 + .../common/test/WithTimeoutOrNullTest.kt | 1 + kotlinx-coroutines-core/js/test/TestBase.kt | 2 ++ kotlinx-coroutines-core/jvm/test/TestBase.kt | 7 +++++ .../native/test/TestBase.kt | 2 ++ 8 files changed, 18 insertions(+), 32 deletions(-) delete mode 100644 kotlinx-coroutines-core/common/test/Try.kt diff --git a/kotlinx-coroutines-core/common/test/JobTest.kt b/kotlinx-coroutines-core/common/test/JobTest.kt index f009c7e55d..38895830b6 100644 --- a/kotlinx-coroutines-core/common/test/JobTest.kt +++ b/kotlinx-coroutines-core/common/test/JobTest.kt @@ -103,11 +103,11 @@ class JobTest : TestBase() { } assertTrue(job.isActive) for (i in 0 until n) assertEquals(0, fireCount[i]) - val tryCancel = Try { job.cancel() } + val cancelResult = runCatching { job.cancel() } assertTrue(!job.isActive) for (i in 0 until n) assertEquals(1, fireCount[i]) - assertTrue(tryCancel.exception is CompletionHandlerException) - assertTrue(tryCancel.exception!!.cause is TestException) + assertTrue(cancelResult.exceptionOrNull() is CompletionHandlerException) + assertTrue(cancelResult.exceptionOrNull()!!.cause is TestException) } @Test diff --git a/kotlinx-coroutines-core/common/test/TestBase.common.kt b/kotlinx-coroutines-core/common/test/TestBase.common.kt index 8b7024a60a..06e71b45b5 100644 --- a/kotlinx-coroutines-core/common/test/TestBase.common.kt +++ b/kotlinx-coroutines-core/common/test/TestBase.common.kt @@ -104,3 +104,5 @@ class BadClass { override fun hashCode(): Int = error("hashCode") override fun toString(): String = error("toString") } + +public expect val isJavaAndWindows: Boolean diff --git a/kotlinx-coroutines-core/common/test/Try.kt b/kotlinx-coroutines-core/common/test/Try.kt deleted file mode 100644 index 194ea4bafa..0000000000 --- a/kotlinx-coroutines-core/common/test/Try.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines - -public class Try private constructor(private val _value: Any?) { - private class Fail(val exception: Throwable) { - override fun toString(): String = "Failure[$exception]" - } - - public companion object { - public operator fun invoke(block: () -> T): Try = - try { - Success(block()) - } catch(e: Throwable) { - Failure(e) - } - public fun Success(value: T) = Try(value) - public fun Failure(exception: Throwable) = Try(Fail(exception)) - } - - @Suppress("UNCHECKED_CAST") - public val value: T get() = if (_value is Fail) throw _value.exception else _value as T - - public val exception: Throwable? get() = (_value as? Fail)?.exception - - override fun toString(): String = _value.toString() -} diff --git a/kotlinx-coroutines-core/common/test/WithTimeoutOrNullDurationTest.kt b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullDurationTest.kt index 92dba7b32d..1f9ad46f47 100644 --- a/kotlinx-coroutines-core/common/test/WithTimeoutOrNullDurationTest.kt +++ b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullDurationTest.kt @@ -131,6 +131,7 @@ class WithTimeoutOrNullDurationTest : TestBase() { @Test fun testOuterTimeout() = runTest { + if (isJavaAndWindows) return@runTest var counter = 0 val result = withTimeoutOrNull(320.milliseconds) { while (true) { diff --git a/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt index ee896c9bf0..5ab8ae7df9 100644 --- a/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt +++ b/kotlinx-coroutines-core/common/test/WithTimeoutOrNullTest.kt @@ -128,6 +128,7 @@ class WithTimeoutOrNullTest : TestBase() { @Test fun testOuterTimeout() = runTest { + if (isJavaAndWindows) return@runTest var counter = 0 val result = withTimeoutOrNull(320) { while (true) { diff --git a/kotlinx-coroutines-core/js/test/TestBase.kt b/kotlinx-coroutines-core/js/test/TestBase.kt index c930c20030..f0e3a2dc7a 100644 --- a/kotlinx-coroutines-core/js/test/TestBase.kt +++ b/kotlinx-coroutines-core/js/test/TestBase.kt @@ -138,3 +138,5 @@ public actual open class TestBase actual constructor() { private fun Promise.finally(block: () -> Unit): Promise = then(onFulfilled = { value -> block(); value }, onRejected = { ex -> block(); throw ex }) + +public actual val isJavaAndWindows: Boolean get() = false diff --git a/kotlinx-coroutines-core/jvm/test/TestBase.kt b/kotlinx-coroutines-core/jvm/test/TestBase.kt index ce94d33acc..6a013fa1da 100644 --- a/kotlinx-coroutines-core/jvm/test/TestBase.kt +++ b/kotlinx-coroutines-core/jvm/test/TestBase.kt @@ -254,3 +254,10 @@ public actual open class TestBase(private var disableOutCheck: Boolean) { protected suspend fun currentDispatcher() = coroutineContext[ContinuationInterceptor]!! } + +/* + * We ignore tests that test **real** non-virtualized tests with time on Windows, because + * our CI Windows is virtualized itself (oh, the irony) and its clock resolution is dozens of ms, + * which makes such tests flaky. + */ +public actual val isJavaAndWindows: Boolean = System.getProperty("os.name")!!.contains("Windows") diff --git a/kotlinx-coroutines-core/native/test/TestBase.kt b/kotlinx-coroutines-core/native/test/TestBase.kt index 6fef4752a8..d7dfeeaeba 100644 --- a/kotlinx-coroutines-core/native/test/TestBase.kt +++ b/kotlinx-coroutines-core/native/test/TestBase.kt @@ -107,3 +107,5 @@ public actual open class TestBase actual constructor() { error("Too few unhandled exceptions $exCount, expected ${unhandled.size}") } } + +public actual val isJavaAndWindows: Boolean get() = false From 30e905c18e553a613264b0225e8421a0bcffee19 Mon Sep 17 00:00:00 2001 From: mvicsokolova <82594708+mvicsokolova@users.noreply.github.com> Date: Tue, 15 Nov 2022 14:56:29 +0100 Subject: [PATCH 049/106] Update atomicfu to 0.18.5 (#3501) * Update atomicfu to 0.18.5, apply JVM IR plugin * Update Kotlin to 1.7.21 --- gradle.properties | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index dd4a4ac685..fb2d529443 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,12 +5,12 @@ # Kotlin version=1.6.4-SNAPSHOT group=org.jetbrains.kotlinx -kotlin_version=1.7.20 +kotlin_version=1.7.21 # Dependencies junit_version=4.12 junit5_version=5.7.0 -atomicfu_version=0.18.4 +atomicfu_version=0.18.5 knit_version=0.4.0 html_version=0.7.2 lincheck_version=2.14.1 @@ -58,4 +58,5 @@ org.gradle.jvmargs=-Xmx3g kotlin.mpp.enableCompatibilityMetadataVariant=true kotlin.mpp.stability.nowarn=true -kotlinx.atomicfu.enableIrTransformation=true +kotlinx.atomicfu.enableJvmIrTransformation=true +kotlinx.atomicfu.enableJsIrTransformation=true From 9ae476f60abe3e6dcf23d88b66934da9f4cd0bea Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 16 Nov 2022 13:55:46 +0100 Subject: [PATCH 050/106] Stabilize isClosedForSend and isClosedForReceive (#3517) * Stabilize isClosedForSend and isClosedForReceive Fixes #3448 --- .../common/src/channels/Channel.kt | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/channels/Channel.kt b/kotlinx-coroutines-core/common/src/channels/Channel.kt index 51c0214fe2..a9d4d6fb30 100644 --- a/kotlinx-coroutines-core/common/src/channels/Channel.kt +++ b/kotlinx-coroutines-core/common/src/channels/Channel.kt @@ -23,12 +23,17 @@ import kotlin.jvm.* */ public interface SendChannel { /** - * Returns `true` if this channel was closed by an invocation of [close]. This means that - * calling [send] will result in an exception. + * Returns `true` if this channel was closed by an invocation of [close] or its receiving side was [cancelled][ReceiveChannel.cancel]. + * This means that calling [send] will result in an exception. * - * **Note: This is an experimental api.** This property may change its semantics and/or name in the future. + * Note that if this property returns `false`, it does not guarantee that consecutive call to [send] will succeed, as the + * channel can be concurrently closed right after the check. For such scenarios, it is recommended to use [trySend] instead. + * + * @see SendChannel.trySend + * @see SendChannel.close + * @see ReceiveChannel.cancel */ - @ExperimentalCoroutinesApi + @DelicateCoroutinesApi public val isClosedForSend: Boolean /** @@ -174,14 +179,20 @@ public interface SendChannel { public interface ReceiveChannel { /** * Returns `true` if this channel was closed by invocation of [close][SendChannel.close] on the [SendChannel] - * side and all previously sent items were already received. This means that calling [receive] - * will result in a [ClosedReceiveChannelException]. If the channel was closed because of an exception, it - * is considered closed, too, but is called a _failed_ channel. All suspending attempts to receive - * an element from a failed channel throw the original [close][SendChannel.close] cause exception. + * side and all previously sent items were already received, or if the receiving side was [cancelled][ReceiveChannel.cancel]. + * + * This means that calling [receive] will result in a [ClosedReceiveChannelException] or a corresponding cancellation cause. + * If the channel was closed because of an exception, it is considered closed, too, but is called a _failed_ channel. + * All suspending attempts to receive an element from a failed channel throw the original [close][SendChannel.close] cause exception. * - * **Note: This is an experimental api.** This property may change its semantics and/or name in the future. + * Note that if this property returns `false`, it does not guarantee that consecutive call to [receive] will succeed, as the + * channel can be concurrently closed right after the check. For such scenarios, it is recommended to use [receiveCatching] instead. + * + * @see ReceiveChannel.receiveCatching + * @see ReceiveChannel.cancel + * @see SendChannel.close */ - @ExperimentalCoroutinesApi + @DelicateCoroutinesApi public val isClosedForReceive: Boolean /** From c87589d021eb427e3b63070edca6951fa5b60b11 Mon Sep 17 00:00:00 2001 From: Yahor Berdnikau Date: Mon, 21 Nov 2022 12:09:37 +0100 Subject: [PATCH 051/106] Fix Java targetCompatibility was not configured in kotlinx-coroutines-jdk9 module By default Java targetCompatibility is equal to current Gradle JDK version which could be different from specified in this module Kotlin 'jvmTarget' and depend on current user JDK version. Also Java targetCompatability controls Gradle "org.gradle.jvm.version" publication attribute, which is wrongly set to '8' in '1.6.4' release module file. Setting it explicetly fixes publication issue. This change fixes compatibility with Kotlin 1.8.0 release changes from this issue: https://youtrack.jetbrains.com/issue/KT-54993 --- reactive/kotlinx-coroutines-jdk9/build.gradle.kts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/reactive/kotlinx-coroutines-jdk9/build.gradle.kts b/reactive/kotlinx-coroutines-jdk9/build.gradle.kts index be5eb421f1..0853a34d62 100644 --- a/reactive/kotlinx-coroutines-jdk9/build.gradle.kts +++ b/reactive/kotlinx-coroutines-jdk9/build.gradle.kts @@ -6,6 +6,11 @@ dependencies { implementation(project(":kotlinx-coroutines-reactive")) } +java { + sourceCompatibility = JavaVersion.VERSION_1_9 + targetCompatibility = JavaVersion.VERSION_1_9 +} + tasks { compileKotlin { kotlinOptions.jvmTarget = "9" From 287d038f065f23fcbced215809aa3c21e2fce58c Mon Sep 17 00:00:00 2001 From: Alexander Date: Mon, 21 Nov 2022 17:54:30 +0200 Subject: [PATCH 052/106] Fix coroutines dump tests (#3524) CoroutinesDumpTest tests used verifyDump incorrectly because all coroutine traces contain BlockingCoroutine substring. So these tests actually did not check any coroutine stack traces. To reduce the probability of incorrect usage of verifyDump added code that parses coroutine stack traces. --- .../test/CoroutinesDumpTest.kt | 53 +++--- .../test/RunningThreadStackMergeTest.kt | 9 +- .../test/StacktraceUtils.kt | 155 ++++++++++++++---- 3 files changed, 153 insertions(+), 64 deletions(-) diff --git a/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt b/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt index 4fecb83e47..12573cf835 100644 --- a/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt +++ b/kotlinx-coroutines-debug/test/CoroutinesDumpTest.kt @@ -23,12 +23,13 @@ class CoroutinesDumpTest : DebugTestBase() { val found = DebugProbes.dumpCoroutinesInfo().single { it.job === deferred } verifyDump( "Coroutine \"coroutine#1\":DeferredCoroutine{Active}@1e4a7dd4, state: SUSPENDED\n" + - "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.sleepingNestedMethod(CoroutinesDumpTest.kt:95)\n" + - "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.sleepingOuterMethod(CoroutinesDumpTest.kt:88)\n" + + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.sleepingNestedMethod(CoroutinesDumpTest.kt)\n" + + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.sleepingOuterMethod(CoroutinesDumpTest.kt)\n" + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + - "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" + - "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" + - "\tat kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:99)\n", + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt)\n" + + "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt)\n" + + "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable\$default(Cancellable.kt)\n" + + "\tat kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt)\n", ignoredCoroutine = "BlockingCoroutine" ) { deferred.cancel() @@ -48,19 +49,21 @@ class CoroutinesDumpTest : DebugTestBase() { verifyDump( "Coroutine \"coroutine#1\":DeferredCoroutine{Active}@227d9994, state: RUNNING\n" + "\tat java.lang.Thread.sleep(Native Method)\n" + - "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.nestedActiveMethod(CoroutinesDumpTest.kt:141)\n" + - "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.activeMethod(CoroutinesDumpTest.kt:133)\n" + - "\tat kotlinx.coroutines.debug.CoroutinesDumpTest\$testRunningCoroutine\$1$deferred\$1.invokeSuspend(CoroutinesDumpTest.kt:41)\n" + + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.nestedActiveMethod(CoroutinesDumpTest.kt)\n" + + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.activeMethod(CoroutinesDumpTest.kt)\n" + + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.access\$activeMethod(CoroutinesDumpTest.kt)\n" + + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest\$testRunningCoroutine\$1\$deferred\$1.invokeSuspend(CoroutinesDumpTest.kt)\n" + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + - "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" + - "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" + - "\tat kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:99)\n" + - "\tat kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:148)\n" + + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt)\n" + + "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt)\n" + + "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable\$default(Cancellable.kt)\n" + + "\tat kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt)\n" + + "\tat kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt)\n" + "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt)\n" + "\tat kotlinx.coroutines.BuildersKt.async(Unknown Source)\n" + "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.async\$default(Builders.common.kt)\n" + "\tat kotlinx.coroutines.BuildersKt.async\$default(Unknown Source)\n" + - "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.testRunningCoroutine(CoroutinesDumpTest.kt:49)", + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest\$testRunningCoroutine\$1.invokeSuspend(CoroutinesDumpTest.kt)", ignoredCoroutine = "BlockingCoroutine" ) { deferred.cancel() @@ -79,18 +82,19 @@ class CoroutinesDumpTest : DebugTestBase() { verifyDump( "Coroutine \"coroutine#1\":DeferredCoroutine{Active}@1e4a7dd4, state: RUNNING\n" + "\tat java.lang.Thread.sleep(Native Method)\n" + - "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.nestedActiveMethod(CoroutinesDumpTest.kt:111)\n" + - "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.activeMethod(CoroutinesDumpTest.kt:106)\n" + + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.nestedActiveMethod(CoroutinesDumpTest.kt)\n" + + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.activeMethod(CoroutinesDumpTest.kt)\n" + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + - "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)\n" + - "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt:23)\n" + - "\tat kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt:99)\n" + - "\tat kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt:148)\n" + + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt)\n" + + "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable(Cancellable.kt)\n" + + "\tat kotlinx.coroutines.intrinsics.CancellableKt.startCoroutineCancellable\$default(Cancellable.kt)\n" + + "\tat kotlinx.coroutines.CoroutineStart.invoke(CoroutineStart.kt)\n" + + "\tat kotlinx.coroutines.AbstractCoroutine.start(AbstractCoroutine.kt)\n" + "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.async(Builders.common.kt)\n" + "\tat kotlinx.coroutines.BuildersKt.async(Unknown Source)\n" + "\tat kotlinx.coroutines.BuildersKt__Builders_commonKt.async\$default(Builders.common.kt)\n" + "\tat kotlinx.coroutines.BuildersKt.async\$default(Unknown Source)\n" + - "\tat kotlinx.coroutines.debug.CoroutinesDumpTest.testRunningCoroutineWithSuspensionPoint(CoroutinesDumpTest.kt:71)", + "\tat kotlinx.coroutines.debug.CoroutinesDumpTest\$testRunningCoroutineWithSuspensionPoint\$1.invokeSuspend(CoroutinesDumpTest.kt)", ignoredCoroutine = "BlockingCoroutine" ) { deferred.cancel() @@ -126,9 +130,12 @@ class CoroutinesDumpTest : DebugTestBase() { "kotlinx.coroutines.BuildersKt.async\$default(Unknown Source)\n" + "kotlinx.coroutines.debug.CoroutinesDumpTest\$testCreationStackTrace\$1.invokeSuspend(CoroutinesDumpTest.kt)" if (!result.startsWith(expected)) { - println("=== Actual result") - println(result) - error("Does not start with expected lines") + error(""" + |Does not start with expected lines + |=== Actual result: + |$result + """.trimMargin() + ) } } diff --git a/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt b/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt index 8b5724219e..965f17883f 100644 --- a/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt +++ b/kotlinx-coroutines-debug/test/RunningThreadStackMergeTest.kt @@ -20,7 +20,6 @@ class RunningThreadStackMergeTest : DebugTestBase() { launchCoroutine() awaitCoroutineStarted() verifyDump( - "Coroutine \"coroutine#1\":BlockingCoroutine{Active}@62230679", // <- this one is ignored "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@50284dc4, state: RUNNING\n" + "\tat jdk.internal.misc.Unsafe.park(Native Method)\n" + "\tat java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)\n" + @@ -34,7 +33,7 @@ class RunningThreadStackMergeTest : DebugTestBase() { "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$launchCoroutine\$1.invokeSuspend(RunningThreadStackMergeTest.kt:68)\n" + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)", - ignoredCoroutine = ":BlockingCoroutine" + ignoredCoroutine = "BlockingCoroutine" ) { coroutineBlocker.await() } @@ -75,7 +74,6 @@ class RunningThreadStackMergeTest : DebugTestBase() { awaitCoroutineStarted() Thread.sleep(10) verifyDump( - "Coroutine \"coroutine#1\":BlockingCoroutine{Active}@62230679", // <- this one is ignored "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@3aea3c67, state: RUNNING\n" + "\tat jdk.internal.misc.Unsafe.park(Native Method)\n" + "\tat java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)\n" + @@ -89,7 +87,7 @@ class RunningThreadStackMergeTest : DebugTestBase() { "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$launchEscapingCoroutine\$1.invokeSuspend(RunningThreadStackMergeTest.kt:116)\n" + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)", - ignoredCoroutine = ":BlockingCoroutine" + ignoredCoroutine = "BlockingCoroutine" ) { coroutineBlocker.await() } @@ -116,7 +114,6 @@ class RunningThreadStackMergeTest : DebugTestBase() { launchEscapingCoroutineWithoutContext() awaitCoroutineStarted() verifyDump( - "Coroutine \"coroutine#1\":BlockingCoroutine{Active}@62230679", // <- this one is ignored "Coroutine \"coroutine#2\":StandaloneCoroutine{Active}@3aea3c67, state: RUNNING\n" + "\tat jdk.internal.misc.Unsafe.park(Native Method)\n" + "\tat java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)\n" + @@ -128,7 +125,7 @@ class RunningThreadStackMergeTest : DebugTestBase() { "\tat kotlinx.coroutines.debug.RunningThreadStackMergeTest\$launchEscapingCoroutineWithoutContext\$1.invokeSuspend(RunningThreadStackMergeTest.kt:153)\n" + "\tat _COROUTINE._CREATION._(CoroutineDebugging.kt)\n" + "\tat kotlin.coroutines.intrinsics.IntrinsicsKt__IntrinsicsJvmKt.createCoroutineUnintercepted(IntrinsicsJvm.kt:116)", - ignoredCoroutine = ":BlockingCoroutine" + ignoredCoroutine = "BlockingCoroutine" ) { coroutineBlocker.await() } diff --git a/kotlinx-coroutines-debug/test/StacktraceUtils.kt b/kotlinx-coroutines-debug/test/StacktraceUtils.kt index 58f20f7231..55bdd7e0b0 100644 --- a/kotlinx-coroutines-debug/test/StacktraceUtils.kt +++ b/kotlinx-coroutines-debug/test/StacktraceUtils.kt @@ -7,13 +7,13 @@ package kotlinx.coroutines.debug import java.io.* import kotlin.test.* -private val coroutineCreationFrameRegex = - Regex("\n\tat _COROUTINE._CREATION._[^\n]*\n") - public fun String.trimStackTrace(): String = trimIndent() + // Remove source line .replace(Regex(":[0-9]+"), "") + // Remove coroutine id .replace(Regex("#[0-9]+"), "") + // Remove trace prefix: "java.base@11.0.16.1/java.lang.Thread.sleep" => "java.lang.Thread.sleep" .replace(Regex("(?<=\tat )[^\n]*/"), "") .replace(Regex("\t"), "") .replace("sun.misc.Unsafe.", "jdk.internal.misc.Unsafe.") // JDK8->JDK11 @@ -74,36 +74,128 @@ private fun cleanBlockHoundTraces(frames: List): List { return result } -public fun verifyDump(vararg traces: String, ignoredCoroutine: String? = null) { - val baos = ByteArrayOutputStream() - DebugProbes.dumpCoroutines(PrintStream(baos)) - val wholeDump = baos.toString() - val trace = wholeDump.split("\n\n") - if (traces.isEmpty()) { - val filtered = trace.filter { ignoredCoroutine == null || !it.contains(ignoredCoroutine) } - assertEquals(1, filtered.count()) - assertTrue(filtered[0].startsWith("Coroutines dump")) - return +private data class CoroutineDump( + val header: CoroutineDumpHeader, + val coroutineStackTrace: List, + val threadStackTrace: List, + val originDump: String, + val originHeader: String, +) { + companion object { + private val COROUTINE_CREATION_FRAME_REGEX = + "at _COROUTINE\\._CREATION\\._\\(.*\\)".toRegex() + + fun parse(dump: String, traceCleaner: ((List) -> List)? = null): CoroutineDump { + val lines = dump + .trimStackTrace() + .split("\n") + val header = CoroutineDumpHeader.parse(lines[0]) + val traceLines = lines.slice(1 until lines.size) + val cleanedTraceLines = if (traceCleaner != null) { + traceCleaner(traceLines) + } else { + traceLines + } + val coroutineStackTrace = mutableListOf() + val threadStackTrace = mutableListOf() + var trace = coroutineStackTrace + for (line in cleanedTraceLines) { + if (line.isEmpty()) { + continue + } + if (line.matches(COROUTINE_CREATION_FRAME_REGEX)) { + require(trace !== threadStackTrace) { + "Found more than one coroutine creation frame" + } + trace = threadStackTrace + continue + } + trace.add(line) + } + return CoroutineDump(header, coroutineStackTrace, threadStackTrace, dump, lines[0]) + } + } + + fun verify(expected: CoroutineDump) { + assertEquals( + expected.header, header, + "Coroutine stacktrace headers are not matched:\n\t- ${expected.originHeader}\n\t+ ${originHeader}\n" + ) + verifyStackTrace("coroutine stack", coroutineStackTrace, expected.coroutineStackTrace) + verifyStackTrace("thread stack", threadStackTrace, expected.threadStackTrace) + } + + private fun verifyStackTrace(traceName: String, actualStackTrace: List, expectedStackTrace: List) { + // It is possible there are more stack frames in a dump than we check + for ((ix, expectedLine) in expectedStackTrace.withIndex()) { + val actualLine = actualStackTrace[ix] + assertEquals( + expectedLine, actualLine, + "Following lines from $traceName are not matched:\n\t- ${expectedLine}\n\t+ ${actualLine}\nActual dump:\n$originDump\n\n" + ) + } } - // The first line, "Coroutine dump", is dropped. This is not `zip` because not having enough dumps is an error. - trace.drop(1).withIndex().forEach { (index, value) -> - if (ignoredCoroutine != null && value.contains(ignoredCoroutine)) { - return@forEach +} + +private data class CoroutineDumpHeader( + val name: String?, + val className: String, + val state: String, +) { + companion object { + /** + * Parses following strings: + * + * - Coroutine "coroutine#10":DeferredCoroutine{Active}@66d87651, state: RUNNING + * - Coroutine DeferredCoroutine{Active}@66d87651, state: RUNNING + * + * into: + * + * - `CoroutineDumpHeader(name = "coroutine", className = "DeferredCoroutine", state = "RUNNING")` + * - `CoroutineDumpHeader(name = null, className = "DeferredCoroutine", state = "RUNNING")` + */ + fun parse(header: String): CoroutineDumpHeader { + val (identFull, stateFull) = header.split(", ", limit = 2) + val nameAndClassName = identFull.removePrefix("Coroutine ").split('@', limit = 2)[0] + val (name, className) = nameAndClassName.split(':', limit = 2).let { parts -> + val (quotedName, classNameWithState) = if (parts.size == 1) { + null to parts[0] + } else { + parts[0] to parts[1] + } + val name = quotedName?.removeSurrounding("\"")?.split('#', limit = 2)?.get(0) + val className = classNameWithState.replace("\\{.*\\}".toRegex(), "") + name to className + } + val state = stateFull.removePrefix("state: ") + return CoroutineDumpHeader(name, className, state) } + } +} - val expected = traces[index].split(coroutineCreationFrameRegex, limit = 2) - val actual = value.split(coroutineCreationFrameRegex, limit = 2) - assertEquals(expected.size, actual.size, "Creation stacktrace should be part of the expected input. Whole dump:\n$wholeDump") - - actual.zip(expected).forEach { (actualTrace, expectedTrace) -> - val sanitizedActualTrace = actualTrace.trimStackTrace().sanitizeAddresses() - val sanitizedExpectedTrace = expectedTrace.trimStackTrace().sanitizeAddresses() - val actualLines = cleanBlockHoundTraces(sanitizedActualTrace.split("\n")) - val expectedLines = sanitizedExpectedTrace.split("\n") - for (i in expectedLines.indices) { - assertEquals(expectedLines[i], actualLines[i], "Whole dump:\n$wholeDump") +public fun verifyDump(vararg expectedTraces: String, ignoredCoroutine: String? = null) { + val baos = ByteArrayOutputStream() + DebugProbes.dumpCoroutines(PrintStream(baos)) + val wholeDump = baos.toString() + val traces = wholeDump.split("\n\n") + assertTrue(traces[0].startsWith("Coroutines dump")) + + val dumps = traces + // Drop "Coroutine dump" line + .drop(1) + // Parse dumps and filter out ignored coroutines + .mapNotNull { trace -> + val dump = CoroutineDump.parse(trace, traceCleaner = ::cleanBlockHoundTraces) + if (dump.header.className == ignoredCoroutine) { + null + } else { + dump } } + + assertEquals(expectedTraces.size, dumps.size) + dumps.zip(expectedTraces.map(CoroutineDump::parse)).forEach { (dump, expectedDump) -> + dump.verify(expectedDump) } } @@ -121,10 +213,3 @@ public fun verifyPartialDump(createdCoroutinesCount: Int, vararg frames: String) assertEquals(createdCoroutinesCount, DebugProbes.dumpCoroutinesInfo().size) assertTrue(matches) } - -private fun String.sanitizeAddresses(): String { - val index = indexOf("coroutine\"") - val next = indexOf(',', index) - if (index == -1 || next == -1) return this - return substring(0, index) + substring(next, length) -} From 4e258534b93c3487c7d249bfe14a566e1e595129 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 22 Nov 2022 17:59:29 +0100 Subject: [PATCH 053/106] Remove no longer relevant third-party license file that was added as part of our reference documentation generator (an old one, based on Jekyll) Fixes #3536 --- license/third_party/minima_LICENSE.txt | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 license/third_party/minima_LICENSE.txt diff --git a/license/third_party/minima_LICENSE.txt b/license/third_party/minima_LICENSE.txt deleted file mode 100644 index e8c3c2d56b..0000000000 --- a/license/third_party/minima_LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2016 Parker Moore - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. From cb0b51e6f4767eebf1995e810a7fcee55c138f16 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 7 Dec 2022 15:35:32 +0100 Subject: [PATCH 054/106] Update Kotlin to 1.8.0-Beta (#3549) --- gradle.properties | 2 +- js/example-frontend-js/build.gradle.kts | 4 +++- reactive/kotlinx-coroutines-rx3/test/Check.kt | 12 ++++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/gradle.properties b/gradle.properties index fb2d529443..db7cf099b0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ # Kotlin version=1.6.4-SNAPSHOT group=org.jetbrains.kotlinx -kotlin_version=1.7.21 +kotlin_version=1.8.0-Beta # Dependencies junit_version=4.12 diff --git a/js/example-frontend-js/build.gradle.kts b/js/example-frontend-js/build.gradle.kts index a78ac3dc6d..1cc587b740 100644 --- a/js/example-frontend-js/build.gradle.kts +++ b/js/example-frontend-js/build.gradle.kts @@ -10,7 +10,9 @@ kotlin { directory = directory.parentFile.resolve("dist") } commonWebpackConfig { - cssSupport.enabled = true + cssSupport { + enabled.set(true) + } } testTask { useKarma { diff --git a/reactive/kotlinx-coroutines-rx3/test/Check.kt b/reactive/kotlinx-coroutines-rx3/test/Check.kt index 3d4704f490..37a1b324eb 100644 --- a/reactive/kotlinx-coroutines-rx3/test/Check.kt +++ b/reactive/kotlinx-coroutines-rx3/test/Check.kt @@ -7,7 +7,7 @@ package kotlinx.coroutines.rx3 import io.reactivex.rxjava3.core.* import io.reactivex.rxjava3.plugins.* -fun checkSingleValue( +fun checkSingleValue( observable: Observable, checker: (T) -> Unit ) { @@ -16,15 +16,15 @@ fun checkSingleValue( } fun checkErroneous( - observable: Observable<*>, - checker: (Throwable) -> Unit + observable: Observable<*>, + checker: (Throwable) -> Unit ) { val singleNotification = observable.materialize().blockingSingle() val error = singleNotification.error ?: error("Excepted error") checker(error) } -fun checkSingleValue( +fun checkSingleValue( single: Single, checker: (T) -> Unit ) { @@ -45,8 +45,8 @@ fun checkErroneous( } fun checkMaybeValue( - maybe: Maybe, - checker: (T?) -> Unit + maybe: Maybe, + checker: (T?) -> Unit ) { val maybeValue = maybe.toFlowable().blockingIterable().firstOrNull() checker(maybeValue) From ccde0c7d777f2b36a5f770533893b1cd5acedafc Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy Date: Tue, 13 Dec 2022 15:49:58 +0100 Subject: [PATCH 055/106] Fix a suppressed warning This also fixes the build with the in-development 1.8.0 Kotlin compiler. Before, when constructing `Maybe`, we had `T = A?`. This way, `MaybeEmitter` meant `MaybeEmitter`. However, with the new version, type inference must have changed, so it came to be that `T = A`, breaking the code. --- reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt b/reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt index 39306e29ce..defb2b725d 100644 --- a/reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt +++ b/reactive/kotlinx-coroutines-rx3/src/RxMaybe.kt @@ -37,13 +37,12 @@ private fun rxMaybeInternal( coroutine.start(CoroutineStart.DEFAULT, coroutine, block) } -private class RxMaybeCoroutine( +private class RxMaybeCoroutine( parentContext: CoroutineContext, - private val subscriber: MaybeEmitter -) : AbstractCoroutine(parentContext, false, true) { - override fun onCompleted(value: T) { + private val subscriber: MaybeEmitter +) : AbstractCoroutine(parentContext, false, true) { + override fun onCompleted(value: T?) { try { - @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS") // KT-54201 if (value == null) subscriber.onComplete() else subscriber.onSuccess(value) } catch (e: Throwable) { handleUndeliverableException(e, context) From 184d395e7f24b28f31d53860a61613cf7abdcfbf Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 27 Dec 2022 11:43:59 +0100 Subject: [PATCH 056/106] Mention value-based conflation in MutableStateFlow (#3539) Fixes #3379 --- kotlinx-coroutines-core/common/src/flow/StateFlow.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/common/src/flow/StateFlow.kt b/kotlinx-coroutines-core/common/src/flow/StateFlow.kt index 85377de6d2..b65340a836 100644 --- a/kotlinx-coroutines-core/common/src/flow/StateFlow.kt +++ b/kotlinx-coroutines-core/common/src/flow/StateFlow.kt @@ -145,8 +145,9 @@ public interface StateFlow : SharedFlow { * A mutable [StateFlow] that provides a setter for [value]. * An instance of `MutableStateFlow` with the given initial `value` can be created using * `MutableStateFlow(value)` constructor function. - * + * See the [StateFlow] documentation for details on state flows. + * Note that all emission-related operators, such as [value]'s setter, [emit], and [tryEmit], are conflated using [Any.equals]. * * ### Not stable for inheritance * From 7205177a7ea5704f49c3bb70f6c8852fe097f418 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 27 Dec 2022 11:44:14 +0100 Subject: [PATCH 057/106] Update JMH (#3531) * Update JMH * Restore workflow for benchmarks * Simplify JMH setup --- .gitignore | 1 + benchmarks/build.gradle.kts | 24 +++++------------------- settings.gradle | 5 +---- 3 files changed, 7 insertions(+), 23 deletions(-) diff --git a/.gitignore b/.gitignore index 36de0e50da..76d3585d21 100644 --- a/.gitignore +++ b/.gitignore @@ -12,4 +12,5 @@ build out target local.properties +benchmarks.jar /kotlin-js-store diff --git a/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts index f64c4aaa21..40a9ceec65 100644 --- a/benchmarks/build.gradle.kts +++ b/benchmarks/build.gradle.kts @@ -4,12 +4,12 @@ @file:Suppress("UnstableApiUsage") -import me.champeau.gradle.* +import me.champeau.jmh.* import org.jetbrains.kotlin.gradle.tasks.* plugins { id("com.github.johnrengelman.shadow") - id("me.champeau.gradle.jmh") apply false + id("me.champeau.jmh") } repositories { @@ -21,8 +21,6 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } -apply(plugin="me.champeau.gradle.jmh") - tasks.named("compileJmhKotlin") { kotlinOptions { jvmTarget = "1.8" @@ -30,24 +28,12 @@ tasks.named("compileJmhKotlin") { } } -// It is better to use the following to run benchmarks, otherwise you may get unexpected errors: -// ./gradlew --no-daemon cleanJmhJar jmh -Pjmh="MyBenchmark" -extensions.configure("jmh") { - jmhVersion = "1.26" - duplicateClassesStrategy = DuplicatesStrategy.INCLUDE - failOnError = true - resultFormat = "CSV" - project.findProperty("jmh")?.also { - include = listOf(".*$it.*") - } -// includeTests = false -} - val jmhJarTask = tasks.named("jmhJar") { archiveBaseName by "benchmarks" archiveClassifier by null archiveVersion by null - destinationDirectory.file("$rootDir") + archiveVersion.convention(null as String?) + destinationDirectory.set(file("$rootDir")) } tasks { @@ -63,7 +49,7 @@ tasks { } dependencies { - implementation("org.openjdk.jmh:jmh-core:1.26") + implementation("org.openjdk.jmh:jmh-core:1.35") implementation("io.projectreactor:reactor-core:${version("reactor")}") implementation("io.reactivex.rxjava2:rxjava:2.1.9") implementation("com.github.akarnokd:rxjava2-extensions:0.20.8") diff --git a/settings.gradle b/settings.gradle index f0a764898b..151c087fd8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -5,10 +5,7 @@ pluginManagement { plugins { id "org.openjfx.javafxplugin" version javafx_plugin_version - - // JMH - id "net.ltgt.apt" version "0.21" - id "me.champeau.gradle.jmh" version "0.5.3" + id "me.champeau.jmh" version "0.6.8" } repositories { From 28d98e8183ee1fa1a1e61d787b7700fb6ff669b1 Mon Sep 17 00:00:00 2001 From: Anastasiia Spaseeva Date: Fri, 9 Dec 2022 15:44:18 +0100 Subject: [PATCH 058/106] Chore(infra): Prepare coroutines for including to the community projects composite build //KTI-1051 Support passing an url for a kotlin compiler repository, drop space kotlin/dev repo from dependencies: Kotlin compiler artifacts should be downloaded from maven central by default. In case of compiling with not-published into the MC kotlin compiler artifacts, a kotlin_repo_url should be specified as a gradle parameter(E.g. space kotlin/dev repo). --- build.gradle | 4 +- buildSrc/build.gradle.kts | 5 +- .../src/main/kotlin/CommunityProjectsBuild.kt | 46 +++++++++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) create mode 100644 buildSrc/src/main/kotlin/CommunityProjectsBuild.kt diff --git a/build.gradle b/build.gradle index 507f59e739..934d4c220a 100644 --- a/build.gradle +++ b/build.gradle @@ -45,7 +45,7 @@ buildscript { repositories { mavenCentral() maven { url "https://plugins.gradle.org/m2/" } - maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" } + CommunityProjectsBuild.addDevRepositoryIfEnabled(delegate, project) mavenLocal() } @@ -130,7 +130,7 @@ allprojects { */ google() mavenCentral() - maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" } + CommunityProjectsBuild.addDevRepositoryIfEnabled(delegate, project) } } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index eaa03f2f15..785d13fdbc 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -10,6 +10,7 @@ plugins { val cacheRedirectorEnabled = System.getenv("CACHE_REDIRECTOR")?.toBoolean() == true val buildSnapshotTrain = properties["build_snapshot_train"]?.toString()?.toBoolean() == true +val kotlinDevUrl = project.rootProject.properties["kotlin_repo_url"] as? String repositories { mavenCentral() @@ -18,7 +19,9 @@ repositories { } else { maven("https://plugins.gradle.org/m2") } - maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev") + if (!kotlinDevUrl.isNullOrEmpty()) { + maven(kotlinDevUrl) + } if (buildSnapshotTrain) { mavenLocal() } diff --git a/buildSrc/src/main/kotlin/CommunityProjectsBuild.kt b/buildSrc/src/main/kotlin/CommunityProjectsBuild.kt new file mode 100644 index 0000000000..d8a48648fb --- /dev/null +++ b/buildSrc/src/main/kotlin/CommunityProjectsBuild.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ +@file:JvmName("CommunityProjectsBuild") + +import org.gradle.api.* +import org.gradle.api.artifacts.dsl.* +import java.net.* +import java.util.logging.* + +private val LOGGER: Logger = Logger.getLogger("Kotlin settings logger") + + +/** + * Functions in this file are responsible for configuring kotlinx.coroutines build against a custom dev version + * of Kotlin compiler. + * Such configuration is used in a composite community build of Kotlin in order to check whether not-yet-released changes + * are compatible with our libraries (aka "integration testing that substitues lack of unit testing"). + */ + +/** + * Should be used for running against of non-released Kotlin compiler on a system test level + * Kotlin compiler artifacts are expected to be downloaded from maven central by default. + * In case of compiling with not-published into the MC kotlin compiler artifacts, a kotlin_repo_url gradle parameter should be specified. + * To reproduce a build locally, a kotlin/dev repo should be passed + * + * @return an url for a kotlin compiler repository parametrized from command line nor gradle.properties, empty string otherwise + */ +fun getKotlinDevRepositoryUrl(project: Project): URI? { + val url: String? = project.rootProject.properties["kotlin_repo_url"] as? String + if (url != null) { + LOGGER.info("""Configured Kotlin Compiler repository url: '$url' for project ${project.name}""") + return URI.create(url) + } + return null +} + +/** + * Adds a kotlin-dev space repository with dev versions of Kotlin if Kotlin aggregate build is enabled + */ +fun addDevRepositoryIfEnabled(rh: RepositoryHandler, project: Project) { + val devRepoUrl = getKotlinDevRepositoryUrl(project) ?: return + rh.maven { + url = devRepoUrl + } +} From c194877a298337a4475206de6adc28eabe3f682f Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 27 Dec 2022 14:28:39 +0100 Subject: [PATCH 059/106] Make EventLoopImplBase properly synchronized on Kotlin/Native (#3550) Fixes #3547 --- kotlinx-coroutines-core/common/src/EventLoop.common.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/EventLoop.common.kt b/kotlinx-coroutines-core/common/src/EventLoop.common.kt index 5308120cc8..8d9eed21bc 100644 --- a/kotlinx-coroutines-core/common/src/EventLoop.common.kt +++ b/kotlinx-coroutines-core/common/src/EventLoop.common.kt @@ -410,7 +410,7 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay { * into heap to avoid overflow and corruption of heap data structure. */ @JvmField var nanoTime: Long - ) : Runnable, Comparable, DisposableHandle, ThreadSafeHeapNode { + ) : Runnable, Comparable, DisposableHandle, ThreadSafeHeapNode, SynchronizedObject() { @Volatile private var _heap: Any? = null // null | ThreadSafeHeap | DISPOSED_TASK @@ -434,8 +434,7 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay { fun timeToExecute(now: Long): Boolean = now - nanoTime >= 0L - @Synchronized - fun scheduleTask(now: Long, delayed: DelayedTaskQueue, eventLoop: EventLoopImplBase): Int { + fun scheduleTask(now: Long, delayed: DelayedTaskQueue, eventLoop: EventLoopImplBase): Int = synchronized(this) { if (_heap === DISPOSED_TASK) return SCHEDULE_DISPOSED // don't add -- was already disposed delayed.addLastIf(this) { firstTask -> if (eventLoop.isCompleted) return SCHEDULE_COMPLETED // non-local return from scheduleTask @@ -477,8 +476,7 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay { return SCHEDULE_OK } - @Synchronized - final override fun dispose() { + final override fun dispose(): Unit = synchronized(this) { val heap = _heap if (heap === DISPOSED_TASK) return // already disposed (heap as? DelayedTaskQueue)?.remove(this) // remove if it is in heap (first) From b74e039196711512764add6f94af92f99d09d3aa Mon Sep 17 00:00:00 2001 From: Daniil Ovchinnikov <5519549+dovchinnikov@users.noreply.github.com> Date: Mon, 2 Jan 2023 14:53:54 +0100 Subject: [PATCH 060/106] Eliminate unneeded `LimitedDispatcher` instances on `Dispatchers.Default` and `Dispatchers.IO` (#3562) * Handle `Dispatchers.IO.limitedParallelism(Int.MAX_VALUE)` case `LimitedDispatcher.limitedParallelism` returns `this` if requested parallelism is greater or equal to the own parallelism of the said `LimitedDispatcher`. `UnlimitedIoScheduler` has parallelism effectively set to `Int.MAX_VALUE`, so `parallelism >= this.parallelism` check folds into `parallelism == Int.MAX_VALUE`. Before the change `LimitedDispatcher(Int.MAX_VALUE)` was returned. While it does work as expected, any submitted task goes through its queue and `Int.MAX_VALUE` number of workers. The change allows eliminating the `LimitedDispatcher` instance and its queue in this extreme case. * Handle `Dispatchers.Default.limitedParallelism` when requested parallelism >= core pool size (#3442) `LimitedDispatcher.limitedParallelism` returns `this` if requested parallelism is greater or equal to the own parallelism of the said `LimitedDispatcher`. `DefaultScheduler` has parallelism effectively set to `CORE_POOL_SIZE`. Before the change `LimitedDispatcher(parallelism)` was returned. While it does work as expected, any submitted task goes through its queue and `parallelism` number of workers. The change allows eliminating the `LimitedDispatcher` instance and its queue in case the requested parallelism is greater or equal to `CORE_POOL_SIZE`. Fixes #3442 --- .../jvm/src/scheduling/Dispatcher.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt b/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt index d55edec94f..f91125a27c 100644 --- a/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt +++ b/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt @@ -14,6 +14,14 @@ internal object DefaultScheduler : SchedulerCoroutineDispatcher( CORE_POOL_SIZE, MAX_POOL_SIZE, IDLE_WORKER_KEEP_ALIVE_NS, DEFAULT_SCHEDULER_NAME ) { + + @ExperimentalCoroutinesApi + override fun limitedParallelism(parallelism: Int): CoroutineDispatcher { + parallelism.checkParallelism() + if (parallelism >= CORE_POOL_SIZE) return this + return super.limitedParallelism(parallelism) + } + // Shuts down the dispatcher, used only by Dispatchers.shutdown() internal fun shutdown() { super.close() @@ -38,6 +46,13 @@ private object UnlimitedIoScheduler : CoroutineDispatcher() { override fun dispatch(context: CoroutineContext, block: Runnable) { DefaultScheduler.dispatchWithContext(block, BlockingContext, false) } + + @ExperimentalCoroutinesApi + override fun limitedParallelism(parallelism: Int): CoroutineDispatcher { + parallelism.checkParallelism() + if (parallelism >= MAX_POOL_SIZE) return this + return super.limitedParallelism(parallelism) + } } // Dispatchers.IO From 0eb94ddbcf6582aff86e11426b1ae6696cfc6a67 Mon Sep 17 00:00:00 2001 From: mvicsokolova <82594708+mvicsokolova@users.noreply.github.com> Date: Tue, 3 Jan 2023 14:05:53 +0100 Subject: [PATCH 061/106] Update atomicfu to 0.19.0 (#3570) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index db7cf099b0..c23ae62301 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ kotlin_version=1.8.0-Beta # Dependencies junit_version=4.12 junit5_version=5.7.0 -atomicfu_version=0.18.5 +atomicfu_version=0.19.0 knit_version=0.4.0 html_version=0.7.2 lincheck_version=2.14.1 From e6ddc8130e8b834320469fa5b5858049fd806f96 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 3 Jan 2023 17:08:34 +0100 Subject: [PATCH 062/106] Remove unused internal function (#3573) --- .../common/src/intrinsics/Undispatched.kt | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt b/kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt index 38e870ef9c..3fa53c4be9 100644 --- a/kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt +++ b/kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt @@ -20,17 +20,6 @@ internal fun (suspend () -> T).startCoroutineUnintercepted(completion: Conti } } -/** - * Use this function to restart a coroutine directly from inside of [suspendCoroutine], - * when the code is already in the context of this coroutine. - * It does not use [ContinuationInterceptor] and does not update the context of the current thread. - */ -internal fun (suspend (R) -> T).startCoroutineUnintercepted(receiver: R, completion: Continuation) { - startDirect(completion) { actualCompletion -> - startCoroutineUninterceptedOrReturn(receiver, actualCompletion) - } -} - /** * Use this function to start a new coroutine in [CoroutineStart.UNDISPATCHED] mode — * immediately execute the coroutine in the current thread until the next suspension. From 67e21b2424937c234b83dc5acab5d8ae4d033533 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 5 Jan 2023 11:32:57 +0100 Subject: [PATCH 063/106] Fix flaky SharingReferenceTest the same way it was addressed in #2709 (#3577) --- kotlinx-coroutines-core/jvm/test/flow/SharingReferenceTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/jvm/test/flow/SharingReferenceTest.kt b/kotlinx-coroutines-core/jvm/test/flow/SharingReferenceTest.kt index 98240fc911..aba95edd32 100644 --- a/kotlinx-coroutines-core/jvm/test/flow/SharingReferenceTest.kt +++ b/kotlinx-coroutines-core/jvm/test/flow/SharingReferenceTest.kt @@ -50,7 +50,7 @@ class SharingReferenceTest : TestBase() { @Test fun testStateInSuspendingReference() = runTest { - val flow = weakEmitter.stateIn(GlobalScope) + val flow = weakEmitter.stateIn(ContextScope(executor)) linearize() FieldWalker.assertReachableCount(1, flow) { it === token } } From c3b7b20ec31b72f77898a9e666dd7701ef6c6d2a Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 13 Jan 2023 13:56:02 +0300 Subject: [PATCH 064/106] Promote @FlowPreview API to stable/experimental (#3548) * Promote @FlowPreview-marked API to stable * Promote @FlowPreview to @Experimental where appropriate * Delay-related operators are not promoted as they are clearly underdesigned * Promote AbstractFlow * Promote flat* related operators to gather more feedback and to work on them incrementally later in 1.7+ Fixes #3542 Fixes #3097 --- kotlinx-coroutines-core/common/src/flow/Builders.kt | 2 -- kotlinx-coroutines-core/common/src/flow/Channels.kt | 1 - kotlinx-coroutines-core/common/src/flow/Flow.kt | 2 +- .../common/src/flow/operators/Merge.kt | 13 ++++++++----- .../kotlinx-coroutines-reactive/src/Migration.kt | 1 - 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/flow/Builders.kt b/kotlinx-coroutines-core/common/src/flow/Builders.kt index c4b55e104b..350cc4be76 100644 --- a/kotlinx-coroutines-core/common/src/flow/Builders.kt +++ b/kotlinx-coroutines-core/common/src/flow/Builders.kt @@ -65,7 +65,6 @@ private class SafeFlow(private val block: suspend FlowCollector.() -> Unit /** * Creates a _cold_ flow that produces a single value from the given functional type. */ -@FlowPreview public fun (() -> T).asFlow(): Flow = flow { emit(invoke()) } @@ -80,7 +79,6 @@ public fun (() -> T).asFlow(): Flow = flow { * fun remoteCallFlow(): Flow = ::remoteCall.asFlow() * ``` */ -@FlowPreview public fun (suspend () -> T).asFlow(): Flow = flow { emit(invoke()) } diff --git a/kotlinx-coroutines-core/common/src/flow/Channels.kt b/kotlinx-coroutines-core/common/src/flow/Channels.kt index aed15913df..3da0780fe7 100644 --- a/kotlinx-coroutines-core/common/src/flow/Channels.kt +++ b/kotlinx-coroutines-core/common/src/flow/Channels.kt @@ -168,7 +168,6 @@ public fun BroadcastChannel.asFlow(): Flow = flow { * default and to control what happens when data is produced faster than it is consumed, * that is to control backpressure behavior. */ -@FlowPreview public fun Flow.produceIn( scope: CoroutineScope ): ReceiveChannel = diff --git a/kotlinx-coroutines-core/common/src/flow/Flow.kt b/kotlinx-coroutines-core/common/src/flow/Flow.kt index 3520c48b42..92006d469b 100644 --- a/kotlinx-coroutines-core/common/src/flow/Flow.kt +++ b/kotlinx-coroutines-core/common/src/flow/Flow.kt @@ -221,7 +221,7 @@ public interface Flow { * } * ``` */ -@FlowPreview +@ExperimentalCoroutinesApi public abstract class AbstractFlow : Flow, CancellableFlow { public final override suspend fun collect(collector: FlowCollector) { diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt b/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt index 35c44d0895..dfd08b8fe9 100644 --- a/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt +++ b/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt @@ -17,6 +17,7 @@ import kotlinx.coroutines.flow.internal.unsafeFlow as flow /** * Name of the property that defines the value of [DEFAULT_CONCURRENCY]. + * This is a preview API and can be changed in a backwards-incompatible manner within a single release. */ @FlowPreview public const val DEFAULT_CONCURRENCY_PROPERTY_NAME: String = "kotlinx.coroutines.flow.defaultConcurrency" @@ -24,9 +25,11 @@ public const val DEFAULT_CONCURRENCY_PROPERTY_NAME: String = "kotlinx.coroutines /** * Default concurrency limit that is used by [flattenMerge] and [flatMapMerge] operators. * It is 16 by default and can be changed on JVM using [DEFAULT_CONCURRENCY_PROPERTY_NAME] property. + * This is a preview API and can be changed in a backwards-incompatible manner within a single release. */ @FlowPreview -public val DEFAULT_CONCURRENCY: Int = systemProp(DEFAULT_CONCURRENCY_PROPERTY_NAME, +public val DEFAULT_CONCURRENCY: Int = systemProp( + DEFAULT_CONCURRENCY_PROPERTY_NAME, 16, 1, Int.MAX_VALUE ) @@ -39,7 +42,7 @@ public val DEFAULT_CONCURRENCY: Int = systemProp(DEFAULT_CONCURRENCY_PROPERTY_NA * Note that even though this operator looks very familiar, we discourage its usage in a regular application-specific flows. * Most likely, suspending operation in [map] operator will be sufficient and linear transformations are much easier to reason about. */ -@FlowPreview +@ExperimentalCoroutinesApi public fun Flow.flatMapConcat(transform: suspend (value: T) -> Flow): Flow = map(transform).flattenConcat() @@ -63,7 +66,7 @@ public fun Flow.flatMapConcat(transform: suspend (value: T) -> Flow * @param concurrency controls the number of in-flight flows, at most [concurrency] flows are collected * at the same time. By default, it is equal to [DEFAULT_CONCURRENCY]. */ -@FlowPreview +@ExperimentalCoroutinesApi public fun Flow.flatMapMerge( concurrency: Int = DEFAULT_CONCURRENCY, transform: suspend (value: T) -> Flow @@ -75,7 +78,7 @@ public fun Flow.flatMapMerge( * * Inner flows are collected by this operator *sequentially*. */ -@FlowPreview +@ExperimentalCoroutinesApi public fun Flow>.flattenConcat(): Flow = flow { collect { value -> emitAll(value) } } @@ -132,7 +135,7 @@ public fun merge(vararg flows: Flow): Flow = flows.asIterable().merge( * @param concurrency controls the number of in-flight flows, at most [concurrency] flows are collected * at the same time. By default, it is equal to [DEFAULT_CONCURRENCY]. */ -@FlowPreview +@ExperimentalCoroutinesApi public fun Flow>.flattenMerge(concurrency: Int = DEFAULT_CONCURRENCY): Flow { require(concurrency > 0) { "Expected positive concurrency level, but had $concurrency" } return if (concurrency == 1) flattenConcat() else ChannelFlowMerge(this, concurrency) diff --git a/reactive/kotlinx-coroutines-reactive/src/Migration.kt b/reactive/kotlinx-coroutines-reactive/src/Migration.kt index 41927e67ec..858ab00e98 100644 --- a/reactive/kotlinx-coroutines-reactive/src/Migration.kt +++ b/reactive/kotlinx-coroutines-reactive/src/Migration.kt @@ -30,7 +30,6 @@ public fun Publisher.asFlowDeprecated(): Flow = asFlow() public fun Flow.asPublisherDeprecated(): Publisher = asPublisher() /** @suppress */ -@FlowPreview @Deprecated( message = "batchSize parameter is deprecated, use .buffer() instead to control the backpressure", level = DeprecationLevel.HIDDEN, From ebff8855b113fa01b038f4e1a236e560879bbad8 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 16 Jan 2023 19:02:05 +0300 Subject: [PATCH 065/106] Make scan and runningFold documentation example consistent with other operators (#3571) Fixes #3567 --- .../common/src/flow/operators/Transform.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt index 51aae34aaa..4123aed0a3 100644 --- a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt +++ b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt @@ -87,7 +87,7 @@ public fun Flow.onEach(action: suspend (T) -> Unit): Flow = transform * ``` * flowOf(1, 2, 3).scan(emptyList()) { acc, value -> acc + value }.toList() * ``` - * will produce `[], [1], [1, 2], [1, 2, 3]`. + * will produce `[[], [1], [1, 2], [1, 2, 3]]`. * * This function is an alias to [runningFold] operator. */ @@ -100,7 +100,7 @@ public fun Flow.scan(initial: R, @BuilderInference operation: suspend * ``` * flowOf(1, 2, 3).runningFold(emptyList()) { acc, value -> acc + value }.toList() * ``` - * will produce `[], [1], [1, 2], [1, 2, 3]`. + * will produce `[[], [1], [1, 2], [1, 2, 3]]`. */ public fun Flow.runningFold(initial: R, @BuilderInference operation: suspend (accumulator: R, value: T) -> R): Flow = flow { var accumulator: R = initial From 87d1af97131f9cd642c89474a97ddd9cf4ce0d29 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 16 Jan 2023 19:08:00 +0300 Subject: [PATCH 066/106] Introduce a separate slot for stealing tasks into in CoroutineScheduler (#3537) * Introduce a separate slot for stealing tasks into in CoroutineScheduler It solves two problems: * Stealing into exclusively owned local queue does no longer require CAS'es or atomic operations where they were previously not needed. It should save a few cycles on the stealing code path * The overall timing perturbations should be slightly better now: previously it was possible for the stolen task to be immediately got stolen again from the stealer thread because it was actually published to the owner's queue, but its submission time was never updated (#3416) * Move victim argument in WorkQueue into the receiver position to simplify the overall code structure * Fix oversubscription in CoroutineScheduler (-> Dispatchers.Default) (#3418) Previously, a worker thread unconditionally processed tasks from its own local queue, even if tasks were CPU-intensive, but CPU token was not acquired. Fixes #3416 Fixes #3418 --- .../jvm/src/scheduling/CoroutineScheduler.kt | 45 ++++++---- .../jvm/src/scheduling/WorkQueue.kt | 89 ++++++++++++------- .../CoroutineSchedulerOversubscriptionTest.kt | 89 +++++++++++++++++++ .../test/scheduling/WorkQueueStressTest.kt | 23 ++--- .../jvm/test/scheduling/WorkQueueTest.kt | 33 +++++-- 5 files changed, 209 insertions(+), 70 deletions(-) create mode 100644 kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerOversubscriptionTest.kt diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt index ef36ef9d18..2bad139fe6 100644 --- a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt +++ b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt @@ -10,6 +10,7 @@ import kotlinx.coroutines.internal.* import java.io.* import java.util.concurrent.* import java.util.concurrent.locks.* +import kotlin.jvm.internal.Ref.ObjectRef import kotlin.math.* import kotlin.random.* @@ -263,8 +264,8 @@ internal class CoroutineScheduler( val workers = ResizableAtomicArray(corePoolSize + 1) /** - * Long describing state of workers in this pool. - * Currently includes created, CPU-acquired and blocking workers each occupying [BLOCKING_SHIFT] bits. + * The `Long` value describing the state of workers in this pool. + * Currently includes created, CPU-acquired, and blocking workers, each occupying [BLOCKING_SHIFT] bits. */ private val controlState = atomic(corePoolSize.toLong() shl CPU_PERMITS_SHIFT) private val createdWorkers: Int inline get() = (controlState.value and CREATED_MASK).toInt() @@ -272,7 +273,7 @@ internal class CoroutineScheduler( private inline fun createdWorkers(state: Long): Int = (state and CREATED_MASK).toInt() private inline fun blockingTasks(state: Long): Int = (state and BLOCKING_MASK shr BLOCKING_SHIFT).toInt() - public inline fun availableCpuPermits(state: Long): Int = (state and CPU_PERMITS_MASK shr CPU_PERMITS_SHIFT).toInt() + inline fun availableCpuPermits(state: Long): Int = (state and CPU_PERMITS_MASK shr CPU_PERMITS_SHIFT).toInt() // Guarded by synchronization private inline fun incrementCreatedWorkers(): Int = createdWorkers(controlState.incrementAndGet()) @@ -598,6 +599,12 @@ internal class CoroutineScheduler( @JvmField val localQueue: WorkQueue = WorkQueue() + /** + * Slot that is used to steal tasks into to avoid re-adding them + * to the local queue. See [trySteal] + */ + private val stolenTask: ObjectRef = ObjectRef() + /** * Worker state. **Updated only by this worker thread**. * By default, worker is in DORMANT state in the case when it was created, but all CPU tokens or tasks were taken. @@ -617,7 +624,7 @@ internal class CoroutineScheduler( /** * It is set to the termination deadline when started doing [park] and it reset - * when there is a task. It servers as protection against spurious wakeups of parkNanos. + * when there is a task. It serves as protection against spurious wakeups of parkNanos. */ private var terminationDeadline = 0L @@ -719,7 +726,6 @@ internal class CoroutineScheduler( parkedWorkersStackPush(this) return } - assert { localQueue.size == 0 } workerCtl.value = PARKED // Update value once /* * inStack() prevents spurious wakeups, while workerCtl.value == PARKED @@ -866,15 +872,16 @@ internal class CoroutineScheduler( } } - fun findTask(scanLocalQueue: Boolean): Task? { - if (tryAcquireCpuPermit()) return findAnyTask(scanLocalQueue) - // If we can't acquire a CPU permit -- attempt to find blocking task - val task = if (scanLocalQueue) { - localQueue.poll() ?: globalBlockingQueue.removeFirstOrNull() - } else { - globalBlockingQueue.removeFirstOrNull() - } - return task ?: trySteal(blockingOnly = true) + fun findTask(mayHaveLocalTasks: Boolean): Task? { + if (tryAcquireCpuPermit()) return findAnyTask(mayHaveLocalTasks) + /* + * If we can't acquire a CPU permit, attempt to find blocking task: + * * Check if our queue has one (maybe mixed in with CPU tasks) + * * Poll global and try steal + */ + return localQueue.pollBlocking() + ?: globalBlockingQueue.removeFirstOrNull() + ?: trySteal(blockingOnly = true) } private fun findAnyTask(scanLocalQueue: Boolean): Task? { @@ -904,7 +911,6 @@ internal class CoroutineScheduler( } private fun trySteal(blockingOnly: Boolean): Task? { - assert { localQueue.size == 0 } val created = createdWorkers // 0 to await an initialization and 1 to avoid excess stealing on single-core machines if (created < 2) { @@ -918,14 +924,15 @@ internal class CoroutineScheduler( if (currentIndex > created) currentIndex = 1 val worker = workers[currentIndex] if (worker !== null && worker !== this) { - assert { localQueue.size == 0 } val stealResult = if (blockingOnly) { - localQueue.tryStealBlockingFrom(victim = worker.localQueue) + worker.localQueue.tryStealBlocking(stolenTask) } else { - localQueue.tryStealFrom(victim = worker.localQueue) + worker.localQueue.trySteal(stolenTask) } if (stealResult == TASK_STOLEN) { - return localQueue.poll() + val result = stolenTask.element + stolenTask.element = null + return result } else if (stealResult > 0) { minDelay = min(minDelay, stealResult) } diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt b/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt index 6a9a8a5a31..0a93265dfe 100644 --- a/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt +++ b/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt @@ -7,6 +7,7 @@ package kotlinx.coroutines.scheduling import kotlinx.atomicfu.* import kotlinx.coroutines.* import java.util.concurrent.atomic.* +import kotlin.jvm.internal.Ref.ObjectRef internal const val BUFFER_CAPACITY_BASE = 7 internal const val BUFFER_CAPACITY = 1 shl BUFFER_CAPACITY_BASE @@ -31,7 +32,7 @@ internal const val NOTHING_TO_STEAL = -2L * (scheduler workers without a CPU permit steal blocking tasks via this mechanism). Such property enforces us to use CAS in * order to properly claim value from the buffer. * Moreover, [Task] objects are reusable, so it may seem that this queue is prone to ABA problem. - * Indeed it formally has ABA-problem, but the whole processing logic is written in the way that such ABA is harmless. + * Indeed, it formally has ABA-problem, but the whole processing logic is written in the way that such ABA is harmless. * I have discovered a truly marvelous proof of this, which this KDoc is too narrow to contain. */ internal class WorkQueue { @@ -46,10 +47,12 @@ internal class WorkQueue { * [T2] changeProducerIndex (3) * [T3] changeConsumerIndex (4) * - * Which can lead to resulting size bigger than actual size at any moment of time. - * This is in general harmless because steal will be blocked by timer + * Which can lead to resulting size being negative or bigger than actual size at any moment of time. + * This is in general harmless because steal will be blocked by timer. + * Negative sizes can be observed only when non-owner reads the size, which happens only + * for diagnostic toString(). */ - internal val bufferSize: Int get() = producerIndex.value - consumerIndex.value + private val bufferSize: Int get() = producerIndex.value - consumerIndex.value internal val size: Int get() = if (lastScheduledTask.value != null) bufferSize + 1 else bufferSize private val buffer: AtomicReferenceArray = AtomicReferenceArray(BUFFER_CAPACITY) private val lastScheduledTask = atomic(null) @@ -100,41 +103,61 @@ internal class WorkQueue { } /** - * Tries stealing from [victim] queue into this queue. + * Tries stealing from this queue into the [stolenTaskRef] argument. * * Returns [NOTHING_TO_STEAL] if queue has nothing to steal, [TASK_STOLEN] if at least task was stolen * or positive value of how many nanoseconds should pass until the head of this queue will be available to steal. */ - fun tryStealFrom(victim: WorkQueue): Long { - assert { bufferSize == 0 } - val task = victim.pollBuffer() + fun trySteal(stolenTaskRef: ObjectRef): Long { + val task = pollBuffer() if (task != null) { - val notAdded = add(task) - assert { notAdded == null } + stolenTaskRef.element = task return TASK_STOLEN } - return tryStealLastScheduled(victim, blockingOnly = false) + return tryStealLastScheduled(stolenTaskRef, blockingOnly = false) } - fun tryStealBlockingFrom(victim: WorkQueue): Long { - assert { bufferSize == 0 } - var start = victim.consumerIndex.value - val end = victim.producerIndex.value - val buffer = victim.buffer - - while (start != end) { - val index = start and MASK - if (victim.blockingTasksInBuffer.value == 0) break - val value = buffer[index] - if (value != null && value.isBlocking && buffer.compareAndSet(index, value, null)) { - victim.blockingTasksInBuffer.decrementAndGet() - add(value) - return TASK_STOLEN - } else { - ++start + fun tryStealBlocking(stolenTaskRef: ObjectRef): Long { + var start = consumerIndex.value + val end = producerIndex.value + + while (start != end && blockingTasksInBuffer.value > 0) { + stolenTaskRef.element = tryExtractBlockingTask(start++) ?: continue + return TASK_STOLEN + } + return tryStealLastScheduled(stolenTaskRef, blockingOnly = true) + } + + // Polls for blocking task, invoked only by the owner + fun pollBlocking(): Task? { + while (true) { // Poll the slot + val lastScheduled = lastScheduledTask.value ?: break + if (!lastScheduled.isBlocking) break + if (lastScheduledTask.compareAndSet(lastScheduled, null)) { + return lastScheduled + } // Failed -> someone else stole it + } + + val start = consumerIndex.value + var end = producerIndex.value + + while (start != end && blockingTasksInBuffer.value > 0) { + val task = tryExtractBlockingTask(--end) + if (task != null) { + return task } } - return tryStealLastScheduled(victim, blockingOnly = true) + return null + } + + private fun tryExtractBlockingTask(index: Int): Task? { + val arrayIndex = index and MASK + val value = buffer[arrayIndex] + if (value != null && value.isBlocking && buffer.compareAndSet(arrayIndex, value, null)) { + blockingTasksInBuffer.decrementAndGet() + return value + } + return null } fun offloadAllWorkTo(globalQueue: GlobalQueue) { @@ -145,11 +168,11 @@ internal class WorkQueue { } /** - * Contract on return value is the same as for [tryStealFrom] + * Contract on return value is the same as for [trySteal] */ - private fun tryStealLastScheduled(victim: WorkQueue, blockingOnly: Boolean): Long { + private fun tryStealLastScheduled(stolenTaskRef: ObjectRef, blockingOnly: Boolean): Long { while (true) { - val lastScheduled = victim.lastScheduledTask.value ?: return NOTHING_TO_STEAL + val lastScheduled = lastScheduledTask.value ?: return NOTHING_TO_STEAL if (blockingOnly && !lastScheduled.isBlocking) return NOTHING_TO_STEAL // TODO time wraparound ? @@ -163,8 +186,8 @@ internal class WorkQueue { * If CAS has failed, either someone else had stolen this task or the owner executed this task * and dispatched another one. In the latter case we should retry to avoid missing task. */ - if (victim.lastScheduledTask.compareAndSet(lastScheduled, null)) { - add(lastScheduled) + if (lastScheduledTask.compareAndSet(lastScheduled, null)) { + stolenTaskRef.element = lastScheduled return TASK_STOLEN } continue diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerOversubscriptionTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerOversubscriptionTest.kt new file mode 100644 index 0000000000..0fd6159f9e --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerOversubscriptionTest.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.scheduling + +import kotlinx.coroutines.* +import org.junit.Test +import java.util.concurrent.* +import java.util.concurrent.atomic.AtomicInteger + +class CoroutineSchedulerOversubscriptionTest : TestBase() { + + private val inDefault = AtomicInteger(0) + + private fun CountDownLatch.runAndCheck() { + if (inDefault.incrementAndGet() > CORE_POOL_SIZE) { + error("Oversubscription detected") + } + + await() + inDefault.decrementAndGet() + } + + @Test + fun testOverSubscriptionDeterministic() = runTest { + val barrier = CountDownLatch(1) + val threadsOccupiedBarrier = CyclicBarrier(CORE_POOL_SIZE) + // All threads but one + repeat(CORE_POOL_SIZE - 1) { + launch(Dispatchers.Default) { + threadsOccupiedBarrier.await() + barrier.runAndCheck() + } + } + threadsOccupiedBarrier.await() + withContext(Dispatchers.Default) { + // Put a task in a local queue, it will be stolen + launch(Dispatchers.Default) { + barrier.runAndCheck() + } + // Put one more task to trick the local queue check + launch(Dispatchers.Default) { + barrier.runAndCheck() + } + + withContext(Dispatchers.IO) { + try { + // Release the thread + delay(100) + } finally { + barrier.countDown() + } + } + } + } + + @Test + fun testOverSubscriptionStress() = repeat(1000 * stressTestMultiplierSqrt) { + inDefault.set(0) + runTest { + val barrier = CountDownLatch(1) + val threadsOccupiedBarrier = CyclicBarrier(CORE_POOL_SIZE) + // All threads but one + repeat(CORE_POOL_SIZE - 1) { + launch(Dispatchers.Default) { + threadsOccupiedBarrier.await() + barrier.runAndCheck() + } + } + threadsOccupiedBarrier.await() + withContext(Dispatchers.Default) { + // Put a task in a local queue + launch(Dispatchers.Default) { + barrier.runAndCheck() + } + // Put one more task to trick the local queue check + launch(Dispatchers.Default) { + barrier.runAndCheck() + } + + withContext(Dispatchers.IO) { + yield() + barrier.countDown() + } + } + } + } +} diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueStressTest.kt index 5e170c9f6b..e2562b57ba 100644 --- a/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueStressTest.kt @@ -9,6 +9,7 @@ import org.junit.* import org.junit.Test import java.util.concurrent.* import kotlin.concurrent.* +import kotlin.jvm.internal.* import kotlin.test.* class WorkQueueStressTest : TestBase() { @@ -40,7 +41,7 @@ class WorkQueueStressTest : TestBase() { threads += thread(name = "producer") { startLatch.await() for (i in 1..offerIterations) { - while (producerQueue.bufferSize > BUFFER_CAPACITY / 2) { + while (producerQueue.size > BUFFER_CAPACITY / 2) { Thread.yield() } @@ -52,17 +53,18 @@ class WorkQueueStressTest : TestBase() { for (i in 0 until stealersCount) { threads += thread(name = "stealer $i") { + val ref = Ref.ObjectRef() val myQueue = WorkQueue() startLatch.await() while (!producerFinished || producerQueue.size != 0) { - stolenTasks[i].addAll(myQueue.drain().map { task(it) }) - myQueue.tryStealFrom(victim = producerQueue) + stolenTasks[i].addAll(myQueue.drain(ref).map { task(it) }) + producerQueue.trySteal(ref) } // Drain last element which is not counted in buffer - stolenTasks[i].addAll(myQueue.drain().map { task(it) }) - myQueue.tryStealFrom(producerQueue) - stolenTasks[i].addAll(myQueue.drain().map { task(it) }) + stolenTasks[i].addAll(myQueue.drain(ref).map { task(it) }) + producerQueue.trySteal(ref) + stolenTasks[i].addAll(myQueue.drain(ref).map { task(it) }) } } @@ -77,7 +79,7 @@ class WorkQueueStressTest : TestBase() { threads += thread(name = "producer") { startLatch.await() for (i in 1..offerIterations) { - while (producerQueue.bufferSize == BUFFER_CAPACITY - 1) { + while (producerQueue.size == BUFFER_CAPACITY - 1) { Thread.yield() } @@ -89,13 +91,14 @@ class WorkQueueStressTest : TestBase() { val stolen = GlobalQueue() threads += thread(name = "stealer") { val myQueue = WorkQueue() + val ref = Ref.ObjectRef() startLatch.await() while (stolen.size != offerIterations) { - if (myQueue.tryStealFrom(producerQueue) != NOTHING_TO_STEAL) { - stolen.addAll(myQueue.drain().map { task(it) }) + if (producerQueue.trySteal(ref) != NOTHING_TO_STEAL) { + stolen.addAll(myQueue.drain(ref).map { task(it) }) } } - stolen.addAll(myQueue.drain().map { task(it) }) + stolen.addAll(myQueue.drain(ref).map { task(it) }) } startLatch.countDown() diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueTest.kt index 7acd1620f4..864e65a3a9 100644 --- a/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueTest.kt +++ b/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueTest.kt @@ -7,6 +7,7 @@ package kotlinx.coroutines.scheduling import kotlinx.coroutines.* import org.junit.* import org.junit.Test +import kotlin.jvm.internal.Ref.ObjectRef import kotlin.test.* class WorkQueueTest : TestBase() { @@ -27,7 +28,7 @@ class WorkQueueTest : TestBase() { fun testLastScheduledComesFirst() { val queue = WorkQueue() (1L..4L).forEach { queue.add(task(it)) } - assertEquals(listOf(4L, 1L, 2L, 3L), queue.drain()) + assertEquals(listOf(4L, 1L, 2L, 3L), queue.drain(ObjectRef())) } @Test @@ -38,9 +39,9 @@ class WorkQueueTest : TestBase() { (0 until size).forEach { queue.add(task(it))?.let { t -> offload.addLast(t) } } val expectedResult = listOf(129L) + (0L..126L).toList() - val actualResult = queue.drain() + val actualResult = queue.drain(ObjectRef()) assertEquals(expectedResult, actualResult) - assertEquals((0L until size).toSet().minus(expectedResult), offload.drain().toSet()) + assertEquals((0L until size).toSet().minus(expectedResult.toSet()), offload.drain().toSet()) } @Test @@ -61,23 +62,39 @@ class WorkQueueTest : TestBase() { timeSource.step(3) val stealer = WorkQueue() - assertEquals(TASK_STOLEN, stealer.tryStealFrom(victim)) - assertEquals(arrayListOf(1L), stealer.drain()) + val ref = ObjectRef() + assertEquals(TASK_STOLEN, victim.trySteal(ref)) + assertEquals(arrayListOf(1L), stealer.drain(ref)) - assertEquals(TASK_STOLEN, stealer.tryStealFrom(victim)) - assertEquals(arrayListOf(2L), stealer.drain()) + assertEquals(TASK_STOLEN, victim.trySteal(ref)) + assertEquals(arrayListOf(2L), stealer.drain(ref)) + } + + @Test + fun testPollBlocking() { + val queue = WorkQueue() + assertNull(queue.pollBlocking()) + val blockingTask = blockingTask(1L) + queue.add(blockingTask) + queue.add(task(1L)) + assertSame(blockingTask, queue.pollBlocking()) } } internal fun task(n: Long) = TaskImpl(Runnable {}, n, NonBlockingContext) +internal fun blockingTask(n: Long) = TaskImpl(Runnable {}, n, BlockingContext) -internal fun WorkQueue.drain(): List { +internal fun WorkQueue.drain(ref: ObjectRef): List { var task: Task? = poll() val result = arrayListOf() while (task != null) { result += task.submissionTime task = poll() } + if (ref.element != null) { + result += ref.element!!.submissionTime + ref.element = null + } return result } From 4102f9084c30b0fe3563503c7aadaa5e501ad40e Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 16 Jan 2023 19:10:45 +0300 Subject: [PATCH 067/106] Increment WorkQueue.blockingTasksInBuffer only after capacity check (#3569) Otherwise, it is possible to leave dangling blockingTasksInBuffer increment that will never be decremented back, leading to unnecessary full-scans on any attempt to steal only blocking task. This change does not have a corresponding unit-test because it fixes the performance regression that is not observable semantically --- kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt b/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt index 0a93265dfe..611f2b108a 100644 --- a/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt +++ b/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt @@ -83,8 +83,8 @@ internal class WorkQueue { * `null` if task was added, task that wasn't added otherwise. */ private fun addLast(task: Task): Task? { - if (task.isBlocking) blockingTasksInBuffer.incrementAndGet() if (bufferSize == BUFFER_CAPACITY - 1) return task + if (task.isBlocking) blockingTasksInBuffer.incrementAndGet() val nextIndex = producerIndex.value and MASK /* * If current element is not null then we're racing with a really slow consumer that committed the consumer index, From 71125e3883952abca51470b42ec2e1a059b63350 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 16 Jan 2023 19:11:15 +0300 Subject: [PATCH 068/106] Do not propagate exceptions to CoroutineExceptionHandler in 'future' builder if it has been cancelled in order to be consistent with other future implementations (#3580) Fixes #3452 --- kotlinx-coroutines-core/jdk8/src/future/Future.kt | 10 ++++++---- .../jvm/test/jdk8/future/FutureTest.kt | 6 +----- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/kotlinx-coroutines-core/jdk8/src/future/Future.kt b/kotlinx-coroutines-core/jdk8/src/future/Future.kt index 4d9a01ac70..f7b4fdca0d 100644 --- a/kotlinx-coroutines-core/jdk8/src/future/Future.kt +++ b/kotlinx-coroutines-core/jdk8/src/future/Future.kt @@ -58,10 +58,12 @@ private class CompletableFutureCoroutine( } override fun onCancelled(cause: Throwable, handled: Boolean) { - if (!future.completeExceptionally(cause) && !handled) { - // prevents loss of exception that was not handled by parent & could not be set to CompletableFuture - handleCoroutineException(context, cause) - } + /* + * Here we can potentially lose the cause if the failure is racing with future's + * external cancellation. We are consistent with other future implementations + * (LF, FT, CF) and give up on such exception. + */ + future.completeExceptionally(cause) } } diff --git a/kotlinx-coroutines-core/jvm/test/jdk8/future/FutureTest.kt b/kotlinx-coroutines-core/jvm/test/jdk8/future/FutureTest.kt index 372e79ef1d..eda3816511 100644 --- a/kotlinx-coroutines-core/jvm/test/jdk8/future/FutureTest.kt +++ b/kotlinx-coroutines-core/jvm/test/jdk8/future/FutureTest.kt @@ -392,11 +392,7 @@ class FutureTest : TestBase() { } @Test - fun testUnhandledExceptionOnExternalCompletion() = runTest( - unhandled = listOf( - { it -> it is TestException } // exception is unhandled because there is no parent - ) - ) { + fun testUnhandledExceptionOnExternalCompletionIsNotReported() = runTest { expect(1) // No parent here (NonCancellable), so nowhere to propagate exception val result = future(NonCancellable + Dispatchers.Unconfined) { From eb21974cb7c5e2b80b599e533e252037eb52b586 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Tue, 17 Jan 2023 14:13:22 +0300 Subject: [PATCH 069/106] Prevent `runBlocking` failure when the Worker it runs on terminates (#3585) --- .../native/src/Builders.kt | 41 ++++++++++++++++++- .../native/test/WorkerTest.kt | 28 +++++++++++++ 2 files changed, 67 insertions(+), 2 deletions(-) diff --git a/kotlinx-coroutines-core/native/src/Builders.kt b/kotlinx-coroutines-core/native/src/Builders.kt index f5b2222409..1f1d352dab 100644 --- a/kotlinx-coroutines-core/native/src/Builders.kt +++ b/kotlinx-coroutines-core/native/src/Builders.kt @@ -52,8 +52,45 @@ public actual fun runBlocking(context: CoroutineContext, block: suspend Coro newContext = GlobalScope.newCoroutineContext(context) } val coroutine = BlockingCoroutine(newContext, eventLoop) - coroutine.start(CoroutineStart.DEFAULT, coroutine, block) - return coroutine.joinBlocking() + var completed = false + ThreadLocalKeepAlive.addCheck { !completed } + try { + coroutine.start(CoroutineStart.DEFAULT, coroutine, block) + return coroutine.joinBlocking() + } finally { + completed = true + } +} + +@ThreadLocal +private object ThreadLocalKeepAlive { + /** If any of these checks passes, this means this [Worker] is still used. */ + private var checks = mutableListOf<() -> Boolean>() + + /** 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) + if (!keepAliveLoopActive) keepAlive() + } + + /** + * 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() + if (keepAliveLoopActive) { + Worker.current.executeAfter(afterMicroseconds = 100_000) { + keepAlive() + } + } + } } private class BlockingCoroutine( diff --git a/kotlinx-coroutines-core/native/test/WorkerTest.kt b/kotlinx-coroutines-core/native/test/WorkerTest.kt index 8252ca656a..7ae31b2656 100644 --- a/kotlinx-coroutines-core/native/test/WorkerTest.kt +++ b/kotlinx-coroutines-core/native/test/WorkerTest.kt @@ -4,6 +4,7 @@ package kotlinx.coroutines +import kotlinx.coroutines.channels.* import kotlin.coroutines.* import kotlin.native.concurrent.* import kotlin.test.* @@ -34,4 +35,31 @@ class WorkerTest : TestBase() { }.result worker.requestTermination() } + + /** + * Test that [runBlocking] does not crash after [Worker.requestTermination] is called on the worker that runs it. + */ + @Test + fun testRunBlockingInTerminatedWorker() { + val workerInRunBlocking = Channel() + val workerTerminated = Channel() + val checkResumption = Channel() + val finished = Channel() + val worker = Worker.start() + worker.executeAfter(0) { + runBlocking { + workerInRunBlocking.send(Unit) + workerTerminated.receive() + checkResumption.receive() + finished.send(Unit) + } + } + runBlocking { + workerInRunBlocking.receive() + worker.requestTermination() + workerTerminated.send(Unit) + checkResumption.send(Unit) + finished.receive() + } + } } From 6a6e62de39b965a0340e82d6ba56a1e4e93964ce Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 19 Jan 2023 14:24:29 +0300 Subject: [PATCH 070/106] Remove obsolete and misleading line from the documentation (#3590) --- kotlinx-coroutines-core/common/src/Job.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/kotlinx-coroutines-core/common/src/Job.kt b/kotlinx-coroutines-core/common/src/Job.kt index 81fe978b19..65646dc59f 100644 --- a/kotlinx-coroutines-core/common/src/Job.kt +++ b/kotlinx-coroutines-core/common/src/Job.kt @@ -382,7 +382,6 @@ public interface Job : CoroutineContext.Element { * * If [parent] job is specified, then this job becomes a child job of its parent and * is cancelled when its parent fails or is cancelled. All this job's children are cancelled in this case, too. - * The invocation of [cancel][Job.cancel] with exception (other than [CancellationException]) on this job also cancels parent. * * Conceptually, the resulting job works in the same way as the job created by the `launch { body }` invocation * (see [launch]), but without any code in the body. It is active until cancelled or completed. Invocation of From 79d885f1c55aa8835b3b7a31353e31b84639a8ae Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 24 Jan 2023 20:47:02 +0300 Subject: [PATCH 071/106] =?UTF-8?q?Remove=20ThreadLocal=20from=20ThreadLoc?= =?UTF-8?q?alMap=20when=20finishing=20UndispatchedCor=E2=80=A6=20(#3593)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove ThreadLocal from ThreadLocalMap when finishing UndispatchedCoroutine * It addresses the problem with ThreadLocalMap.entries that may outlive the coroutine lifecycle and interfere with CPU consumption of other thread-locals on the same thread * No test provided as this is a non-functioanl change. The only reasonable way to check it is to reflectively walk over Thread class which is prohibited by Java since 11+. The only way is to eyeball Thread.currentThread().threadLocals size in debugger in the properly crafted unit test * Do not touch thread-locals if they were never set in UndispatchedCoroutine Fixes #3592 --- .../jvm/src/CoroutineContext.kt | 57 +++++++++++++++---- 1 file changed, 47 insertions(+), 10 deletions(-) diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt index 7209bee803..59695a055a 100644 --- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt +++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt @@ -167,9 +167,11 @@ internal actual class UndispatchedCoroutineactual constructor ( uCont: Continuation ) : ScopeCoroutine(if (context[UndispatchedMarker] == null) context + UndispatchedMarker else context, uCont) { - /* - * The state is thread-local because this coroutine can be used concurrently. - * Scenario of usage (withContinuationContext): + /** + * The state of [ThreadContextElement]s associated with the current undispatched coroutine. + * It is stored in a thread local because this coroutine can be used concurrently in suspend-resume race scenario. + * See the followin, boiled down example with inlined `withContinuationContext` body: + * ``` * val state = saveThreadContext(ctx) * try { * invokeSmthWithThisCoroutineAsCompletion() // Completion implies that 'afterResume' will be called @@ -178,8 +180,40 @@ internal actual class UndispatchedCoroutineactual constructor ( * thisCoroutine().clearThreadContext() // Concurrently the "smth" could've been already resumed on a different thread * // and it also calls saveThreadContext and clearThreadContext * } + * ``` + * + * Usage note: + * + * This part of the code is performance-sensitive. + * It is a well-established pattern to wrap various activities into system-specific undispatched + * `withContext` for the sake of logging, MDC, tracing etc., meaning that there exists thousands of + * undispatched coroutines. + * Each access to Java's [ThreadLocal] leaves a footprint in the corresponding Thread's `ThreadLocalMap` + * that is cleared automatically as soon as the associated thread-local (-> UndispatchedCoroutine) is garbage collected. + * When such coroutines are promoted to old generation, `ThreadLocalMap`s become bloated and an arbitrary accesses to thread locals + * start to consume significant amount of CPU because these maps are open-addressed and cleaned up incrementally on each access. + * (You can read more about this effect as "GC nepotism"). + * + * To avoid that, we attempt to narrow down the lifetime of this thread local as much as possible: + * * It's never accessed when we are sure there are no thread context elements + * * It's cleaned up via [ThreadLocal.remove] as soon as the coroutine is suspended or finished. + */ + private val threadStateToRecover = ThreadLocal>() + + /* + * Indicates that a coroutine has at least one thread context element associated with it + * and that 'threadStateToRecover' is going to be set in case of dispatchhing in order to preserve them. + * Better than nullable thread-local for easier debugging. + * + * It is used as a performance optimization to avoid 'threadStateToRecover' initialization + * (note: tl.get() initializes thread local), + * and is prone to false-positives as it is never reset: otherwise + * it may lead to logical data races between suspensions point where + * coroutine is yet being suspended in one thread while already being resumed + * in another. */ - private var threadStateToRecover = ThreadLocal>() + @Volatile + private var threadLocalIsSet = false init { /* @@ -213,19 +247,22 @@ internal actual class UndispatchedCoroutineactual constructor ( } fun saveThreadContext(context: CoroutineContext, oldValue: Any?) { + threadLocalIsSet = true // Specify that thread-local is touched at all threadStateToRecover.set(context to oldValue) } fun clearThreadContext(): Boolean { - if (threadStateToRecover.get() == null) return false - threadStateToRecover.set(null) - return true + return !(threadLocalIsSet && threadStateToRecover.get() == null).also { + threadStateToRecover.remove() + } } override fun afterResume(state: Any?) { - threadStateToRecover.get()?.let { (ctx, value) -> - restoreThreadContext(ctx, value) - threadStateToRecover.set(null) + if (threadLocalIsSet) { + threadStateToRecover.get()?.let { (ctx, value) -> + restoreThreadContext(ctx, value) + } + threadStateToRecover.remove() } // resume undispatched -- update context but stay on the same dispatcher val result = recoverResult(state, uCont) From eac0b0714d1d6c2116ff80c98f8fd1c53a5e9ce5 Mon Sep 17 00:00:00 2001 From: Margarita Bobova <32216159+woainikk@users.noreply.github.com> Date: Wed, 25 Jan 2023 15:46:03 +0100 Subject: [PATCH 072/106] KotlinxTrain: add mavenLocal repo (#3599) --- build.gradle | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/build.gradle b/build.gradle index 934d4c220a..3e824a2eff 100644 --- a/build.gradle +++ b/build.gradle @@ -42,6 +42,12 @@ buildscript { } } + if (using_snapshot_version) { + repositories { + mavenLocal() + } + } + repositories { mavenCentral() maven { url "https://plugins.gradle.org/m2/" } @@ -97,6 +103,12 @@ allprojects { kotlin_version = rootProject.properties['kotlin_snapshot_version'] } + if (using_snapshot_version) { + repositories { + mavenLocal() + } + } + ext.unpublished = unpublished // This project property is set during nightly stress test From cca82e79030128a03e38df9c2d154e4137949adc Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 1 Feb 2023 17:14:25 +0300 Subject: [PATCH 073/106] Improve DebugProbes performance (#3534) * Get rid of RW lock that was contended enough on reads to be observable by non-concurrent coroutines-heavy code (up to 30% of throughput on IDEA-specific benchmark) * Tweak the code to be DRF in the absence of RW lock * Document snapshots' weak consistency guarantee Fixes #3527 --- benchmarks/build.gradle.kts | 1 + .../debug/DebugProbesConcurrentBenchmark.kt | 71 +++++++++++++++++++ .../debug/internal/DebugCoroutineInfoImpl.kt | 4 ++ .../jvm/src/debug/internal/DebugProbesImpl.kt | 58 +++++++-------- kotlinx-coroutines-debug/src/DebugProbes.kt | 15 ++-- 5 files changed, 111 insertions(+), 38 deletions(-) create mode 100644 benchmarks/src/jmh/kotlin/benchmarks/debug/DebugProbesConcurrentBenchmark.kt diff --git a/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts index 40a9ceec65..b4629809db 100644 --- a/benchmarks/build.gradle.kts +++ b/benchmarks/build.gradle.kts @@ -56,6 +56,7 @@ dependencies { implementation("com.typesafe.akka:akka-actor_2.12:2.5.0") implementation(project(":kotlinx-coroutines-core")) + implementation(project(":kotlinx-coroutines-debug")) implementation(project(":kotlinx-coroutines-reactive")) // add jmh dependency on main diff --git a/benchmarks/src/jmh/kotlin/benchmarks/debug/DebugProbesConcurrentBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/debug/DebugProbesConcurrentBenchmark.kt new file mode 100644 index 0000000000..4c1a67a4d0 --- /dev/null +++ b/benchmarks/src/jmh/kotlin/benchmarks/debug/DebugProbesConcurrentBenchmark.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package benchmarks.debug + +import kotlinx.coroutines.* +import kotlinx.coroutines.debug.* +import org.openjdk.jmh.annotations.* +import org.openjdk.jmh.annotations.State +import java.util.concurrent.* + +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@Fork(value = 1) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MICROSECONDS) +@State(Scope.Benchmark) +open class DebugProbesConcurrentBenchmark { + + @Setup + fun setup() { + DebugProbes.sanitizeStackTraces = false + DebugProbes.enableCreationStackTraces = false + DebugProbes.install() + } + + @TearDown + fun tearDown() { + DebugProbes.uninstall() + } + + + @Benchmark + fun run() = runBlocking { + var sum = 0L + repeat(8) { + launch(Dispatchers.Default) { + val seq = stressSequenceBuilder((1..100).asSequence()) { + (1..it).asSequence() + } + + for (i in seq) { + sum += i.toLong() + } + } + } + sum + } + + private fun stressSequenceBuilder(initialSequence: Sequence, children: (Node) -> Sequence): Sequence { + return sequence { + val initialIterator = initialSequence.iterator() + if (!initialIterator.hasNext()) { + return@sequence + } + val visited = HashSet() + val sequences = ArrayDeque>() + sequences.addLast(initialIterator.asSequence()) + while (sequences.isNotEmpty()) { + val currentSequence = sequences.removeFirst() + for (node in currentSequence) { + if (visited.add(node)) { + yield(node) + sequences.addLast(children(node)) + } + } + } + } + } +} diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugCoroutineInfoImpl.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugCoroutineInfoImpl.kt index 07b9419f1b..45d8d94b6c 100644 --- a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugCoroutineInfoImpl.kt +++ b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugCoroutineInfoImpl.kt @@ -14,6 +14,7 @@ internal const val SUSPENDED = "SUSPENDED" /** * Internal implementation class where debugger tracks details it knows about each coroutine. + * Its mutable fields can be updated concurrently, thus marked with `@Volatile` */ internal class DebugCoroutineInfoImpl( context: CoroutineContext?, @@ -40,15 +41,18 @@ internal class DebugCoroutineInfoImpl( * Can be CREATED, RUNNING, SUSPENDED. */ public val state: String get() = _state + @Volatile private var _state: String = CREATED @JvmField + @Volatile internal var lastObservedThread: Thread? = null /** * We cannot keep a strong reference to the last observed frame of the coroutine, because this will * prevent garbage-collection of a coroutine that was lost. */ + @Volatile private var _lastObservedFrame: WeakReference? = null internal var lastObservedFrame: CoroutineStackFrame? get() = _lastObservedFrame?.get() diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt index 7080f612b7..3357c43278 100644 --- a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt +++ b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt @@ -28,32 +28,23 @@ internal object DebugProbesImpl { private val capturedCoroutinesMap = ConcurrentWeakMap, Boolean>() private val capturedCoroutines: Set> get() = capturedCoroutinesMap.keys - @Volatile - private var installations = 0 + private val installations = atomic(0) /** * This internal method is used by IDEA debugger under the JVM name of * "isInstalled$kotlinx_coroutines_debug". */ - internal val isInstalled: Boolean get() = installations > 0 + internal val isInstalled: Boolean get() = installations.value > 0 // To sort coroutines by creation order, used as unique id private val sequenceNumber = atomic(0L) - /* - * RW-lock that guards all debug probes state changes. - * All individual coroutine state transitions are guarded by read-lock - * and do not interfere with each other. - * All state reads are guarded by the write lock to guarantee a strongly-consistent - * snapshot of the system. - */ - private val coroutineStateLock = ReentrantReadWriteLock() public var sanitizeStackTraces: Boolean = true public var enableCreationStackTraces: Boolean = true /* * Substitute for service loader, DI between core and debug modules. - * If the agent was installed via command line -javaagent parameter, do not use byte-byddy to avoud + * If the agent was installed via command line -javaagent parameter, do not use byte-buddy to avoid dynamic attach. */ private val dynamicAttach = getDynamicAttach() @@ -77,16 +68,16 @@ internal object DebugProbesImpl { */ private val callerInfoCache = ConcurrentWeakMap(weakRefQueue = true) - public fun install(): Unit = coroutineStateLock.write { - if (++installations > 1) return + fun install() { + if (installations.incrementAndGet() > 1) return startWeakRefCleanerThread() if (AgentInstallationType.isInstalledStatically) return dynamicAttach?.invoke(true) // attach } - public fun uninstall(): Unit = coroutineStateLock.write { + fun uninstall() { check(isInstalled) { "Agent was not installed" } - if (--installations != 0) return + if (installations.decrementAndGet() != 0) return stopWeakRefCleanerThread() capturedCoroutinesMap.clear() callerInfoCache.clear() @@ -107,7 +98,7 @@ internal object DebugProbesImpl { thread.join() } - public fun hierarchyToString(job: Job): String = coroutineStateLock.write { + fun hierarchyToString(job: Job): String { check(isInstalled) { "Debug probes are not installed" } val jobToStack = capturedCoroutines .filter { it.delegate.context[Job] != null } @@ -149,20 +140,19 @@ internal object DebugProbesImpl { * Private method that dumps coroutines so that different public-facing method can use * to produce different result types. */ - private inline fun dumpCoroutinesInfoImpl(crossinline create: (CoroutineOwner<*>, CoroutineContext) -> R): List = - coroutineStateLock.write { - check(isInstalled) { "Debug probes are not installed" } - capturedCoroutines - .asSequence() - // Stable ordering of coroutines by their sequence number - .sortedBy { it.info.sequenceNumber } - // Leave in the dump only the coroutines that were not collected while we were dumping them - .mapNotNull { owner -> - // Fuse map and filter into one operation to save an inline - if (owner.isFinished()) null - else owner.info.context?.let { context -> create(owner, context) } - }.toList() - } + private inline fun dumpCoroutinesInfoImpl(crossinline create: (CoroutineOwner<*>, CoroutineContext) -> R): List { + check(isInstalled) { "Debug probes are not installed" } + return capturedCoroutines + .asSequence() + // Stable ordering of coroutines by their sequence number + .sortedBy { it.info.sequenceNumber } + // Leave in the dump only the coroutines that were not collected while we were dumping them + .mapNotNull { owner -> + // Fuse map and filter into one operation to save an inline + if (owner.isFinished()) null + else owner.info.context?.let { context -> create(owner, context) } + }.toList() + } /* * This method optimises the number of packages sent by the IDEA debugger @@ -280,7 +270,7 @@ internal object DebugProbesImpl { return true } - private fun dumpCoroutinesSynchronized(out: PrintStream): Unit = coroutineStateLock.write { + private fun dumpCoroutinesSynchronized(out: PrintStream) { check(isInstalled) { "Debug probes are not installed" } out.print("Coroutines dump ${dateFormat.format(System.currentTimeMillis())}") capturedCoroutines @@ -441,7 +431,7 @@ internal object DebugProbesImpl { } // See comment to callerInfoCache - private fun updateRunningState(frame: CoroutineStackFrame, state: String): Unit = coroutineStateLock.read { + private fun updateRunningState(frame: CoroutineStackFrame, state: String) { if (!isInstalled) return // Lookup coroutine info in cache or by traversing stack frame val info: DebugCoroutineInfoImpl @@ -466,7 +456,7 @@ internal object DebugProbesImpl { return if (caller.getStackTraceElement() != null) caller else caller.realCaller() } - private fun updateState(owner: CoroutineOwner<*>, frame: Continuation<*>, state: String) = coroutineStateLock.read { + private fun updateState(owner: CoroutineOwner<*>, frame: Continuation<*>, state: String) { if (!isInstalled) return owner.info.updateState(state, frame) } diff --git a/kotlinx-coroutines-debug/src/DebugProbes.kt b/kotlinx-coroutines-debug/src/DebugProbes.kt index 6a7d9028a1..d62f5d19b3 100644 --- a/kotlinx-coroutines-debug/src/DebugProbes.kt +++ b/kotlinx-coroutines-debug/src/DebugProbes.kt @@ -20,13 +20,20 @@ import kotlin.coroutines.* * asynchronous stack-traces and coroutine dumps (similar to [ThreadMXBean.dumpAllThreads] and `jstack` via [DebugProbes.dumpCoroutines]. * All introspecting methods throw [IllegalStateException] if debug probes were not installed. * - * Installed hooks: + * ### Consistency guarantees * + * All snapshotting operations (e.g. [dumpCoroutines]) are *weakly-consistent*, meaning that they happen + * concurrently with coroutines progressing their own state. These operations are guaranteed to observe + * each coroutine's state exactly once, but the state is not guaranteed to be the most recent before the operation. + * In practice, it means that for snapshotting operations in progress, for each concurrent coroutine either + * the state prior to the operation or the state that was reached during the current operation is observed. + * + * ### Installed hooks * * `probeCoroutineResumed` is invoked on every [Continuation.resume]. * * `probeCoroutineSuspended` is invoked on every continuation suspension. - * * `probeCoroutineCreated` is invoked on every coroutine creation using stdlib intrinsics. + * * `probeCoroutineCreated` is invoked on every coroutine creation. * - * Overhead: + * ### Overhead * * Every created coroutine is stored in a concurrent hash map and hash map is looked up and * updated on each suspension and resumption. * * If [DebugProbes.enableCreationStackTraces] is enabled, stack trace of the current thread is captured on @@ -118,7 +125,7 @@ public object DebugProbes { printJob(scope.coroutineContext[Job] ?: error("Job is not present in the scope"), out) /** - * Returns all existing coroutines info. + * Returns all existing coroutines' info. * The resulting collection represents a consistent snapshot of all existing coroutines at the moment of invocation. */ public fun dumpCoroutinesInfo(): List = DebugProbesImpl.dumpCoroutinesInfo().map { CoroutineInfo(it) } From 33220fcde834fcaddebb50297225436fffd4a86d Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Thu, 2 Feb 2023 16:31:17 +0300 Subject: [PATCH 074/106] Remove docs about Dispatchers.kt that replicate the common code (#3614) We update the docs in Dispatchers.common.kt, but JVM's Dispatchers.kt also has its own version of it, which is accessible via the "JVM" tab of the docs: The docs there are outdated, so we remove them. --- .../common/src/Dispatchers.common.kt | 5 +- .../jvm/src/Dispatchers.kt | 64 ------------------- 2 files changed, 3 insertions(+), 66 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/Dispatchers.common.kt b/kotlinx-coroutines-core/common/src/Dispatchers.common.kt index 9cafbd3427..f3d4bc2cc5 100644 --- a/kotlinx-coroutines-core/common/src/Dispatchers.common.kt +++ b/kotlinx-coroutines-core/common/src/Dispatchers.common.kt @@ -15,7 +15,7 @@ public expect object Dispatchers { * [launch][CoroutineScope.launch], [async][CoroutineScope.async], etc * if neither a dispatcher nor any other [ContinuationInterceptor] is specified in their context. * - * It is backed by a shared pool of threads on JVM. By default, the maximum number of threads used + * It is backed by a shared pool of threads on JVM and Native. By default, the maximum number of threads used * by this dispatcher is equal to the number of CPU cores, but is at least two. */ public val Default: CoroutineDispatcher @@ -27,7 +27,7 @@ public expect object Dispatchers { * Access to this property may throw an [IllegalStateException] if no main dispatchers are present in the classpath. * * Depending on platform and classpath, it can be mapped to different dispatchers: - * - On JVM it is either the Android main thread dispatcher, JavaFx or Swing EDT dispatcher. It is chosen by the + * - On JVM it is either the Android main thread dispatcher, JavaFx, or Swing EDT dispatcher. It is chosen by the * [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html). * - On JS it is equivalent to the [Default] dispatcher with [immediate][MainCoroutineDispatcher.immediate] support. * - On Native Darwin-based targets, it is a dispatcher backed by Darwin's main queue. @@ -37,6 +37,7 @@ public expect object Dispatchers { * - `kotlinx-coroutines-android` — for Android Main thread dispatcher * - `kotlinx-coroutines-javafx` — for JavaFx Application thread dispatcher * - `kotlinx-coroutines-swing` — for Swing EDT dispatcher + * - `kotlinx-coroutines-test` — for mocking the `Main` dispatcher in tests via `Dispatchers.setMain` */ public val Main: MainCoroutineDispatcher diff --git a/kotlinx-coroutines-core/jvm/src/Dispatchers.kt b/kotlinx-coroutines-core/jvm/src/Dispatchers.kt index 251a567c54..6a4724e325 100644 --- a/kotlinx-coroutines-core/jvm/src/Dispatchers.kt +++ b/kotlinx-coroutines-core/jvm/src/Dispatchers.kt @@ -19,76 +19,12 @@ public const val IO_PARALLELISM_PROPERTY_NAME: String = "kotlinx.coroutines.io.p * Groups various implementations of [CoroutineDispatcher]. */ public actual object Dispatchers { - /** - * The default [CoroutineDispatcher] that is used by all standard builders like - * [launch][CoroutineScope.launch], [async][CoroutineScope.async], etc. - * if no dispatcher nor any other [ContinuationInterceptor] is specified in their context. - * - * It is backed by a shared pool of threads on JVM. By default, the maximal level of parallelism used - * by this dispatcher is equal to the number of CPU cores, but is at least two. - * Level of parallelism X guarantees that no more than X tasks can be executed in this dispatcher in parallel. - */ @JvmStatic public actual val Default: CoroutineDispatcher = DefaultScheduler - /** - * A coroutine dispatcher that is confined to the Main thread operating with UI objects. - * This dispatcher can be used either directly or via [MainScope] factory. - * Usually such dispatcher is single-threaded. - * - * Access to this property may throw [IllegalStateException] if no main thread dispatchers are present in the classpath. - * - * Depending on platform and classpath it can be mapped to different dispatchers: - * - On JS and Native it is equivalent of [Default] dispatcher. - * - On JVM it is either Android main thread dispatcher, JavaFx or Swing EDT dispatcher. It is chosen by - * [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html). - * - * In order to work with `Main` dispatcher, the following artifacts should be added to project runtime dependencies: - * - `kotlinx-coroutines-android` for Android Main thread dispatcher - * - `kotlinx-coroutines-javafx` for JavaFx Application thread dispatcher - * - `kotlinx-coroutines-swing` for Swing EDT dispatcher - * - * In order to set a custom `Main` dispatcher for testing purposes, add the `kotlinx-coroutines-test` artifact to - * project test dependencies. - * - * Implementation note: [MainCoroutineDispatcher.immediate] is not supported on Native and JS platforms. - */ @JvmStatic public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher - /** - * A coroutine dispatcher that is not confined to any specific thread. - * It executes initial continuation of the coroutine in the current call-frame - * and lets the coroutine resume in whatever thread that is used by the corresponding suspending function, without - * mandating any specific threading policy. Nested coroutines launched in this dispatcher form an event-loop to avoid - * stack overflows. - * - * ### Event loop - * Event loop semantics is a purely internal concept and have no guarantees on the order of execution - * except that all queued coroutines will be executed on the current thread in the lexical scope of the outermost - * unconfined coroutine. - * - * For example, the following code: - * ``` - * withContext(Dispatchers.Unconfined) { - * println(1) - * withContext(Dispatchers.Unconfined) { // Nested unconfined - * println(2) - * } - * println(3) - * } - * println("Done") - * ``` - * Can print both "1 2 3" and "1 3 2", this is an implementation detail that can be changed. - * But it is guaranteed that "Done" will be printed only when both `withContext` are completed. - * - * - * Note that if you need your coroutine to be confined to a particular thread or a thread-pool after resumption, - * but still want to execute it in the current call-frame until its first suspension, then you can use - * an optional [CoroutineStart] parameter in coroutine builders like - * [launch][CoroutineScope.launch] and [async][CoroutineScope.async] setting it to - * the value of [CoroutineStart.UNDISPATCHED]. - */ @JvmStatic public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined From 32af157c611980a0f5ccd1d9a7d5a3a6ba1ca9cd Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 2 Feb 2023 17:16:57 +0300 Subject: [PATCH 075/106] Fix suspend-resume race in DebugProbes (#3514) ### Reproducing scenario For the following code: ``` suspend fun foo() = yield() ``` the following bytecode is produced: ``` fun foo(...) { uCont.intercepted().dispatchUsingDispatcher() // 1 probeCoroutineSuspended() // 2 return COROUTINE_SUSPENDED // Unroll the stack } ``` And it is possible to observe the next 'probeCoroutineSuspended' **prior** to receiving 'probeCoroutineSuspended'. ### Steps taken To address this, a dedicated 'unmatchedResumes' field is introduced to the coroutine state, which keeps track of consecutive 'probeCoroutineResumed' without matching 'probeCoroutineSuspended' and attributes lately arrived probes to it. Unfortunately, it introduces a much more unlikely race when **two** 'probeCoroutineSuspended' are reordered, then we misattribute 'lastObservedFrame', but it is still better than misattributing the actual coroutine state. @Volatile and @Synchronized are also introduced to DebugCoroutineInfoImpl as previously they have been subject to data races as well Fixes #3193 --- .../debug/internal/DebugCoroutineInfoImpl.kt | 93 ++++++++++++++++--- .../jvm/src/debug/internal/DebugProbesImpl.kt | 8 +- .../test/DebugProbesTest.kt | 56 +++++++++++ 3 files changed, 141 insertions(+), 16 deletions(-) diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugCoroutineInfoImpl.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugCoroutineInfoImpl.kt index 45d8d94b6c..4562572d66 100644 --- a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugCoroutineInfoImpl.kt +++ b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugCoroutineInfoImpl.kt @@ -41,9 +41,86 @@ internal class DebugCoroutineInfoImpl( * Can be CREATED, RUNNING, SUSPENDED. */ public val state: String get() = _state + @Volatile private var _state: String = CREATED + /* + * How many consecutive unmatched 'updateState(RESUMED)' this object has received. + * It can be `> 1` in two cases: + * + * * The coroutine is finishing and its state is being unrolled in BaseContinuationImpl, see comment to DebugProbesImpl#callerInfoCache + * Such resumes are not expected to be matched and are ignored. + * * We encountered suspend-resume race explained above, and we do wait for a match. + */ + private var unmatchedResume = 0 + + /** + * Here we orchestrate overlapping state updates that are coming asynchronously. + * In a nutshell, `probeCoroutineSuspended` can arrive **later** than its matching `probeCoroutineResumed`, + * e.g. for the following code: + * ``` + * suspend fun foo() = yield() + * ``` + * + * we have this sequence: + * ``` + * fun foo(...) { + * uCont.intercepted().dispatchUsingDispatcher() // 1 + * // Notify the debugger the coroutine is suspended + * probeCoroutineSuspended() // 2 + * return COROUTINE_SUSPENDED // Unroll the stack + * } + * ``` + * Nothing prevents coroutine to be dispatched and invoke `probeCoroutineResumed` right between '1' and '2'. + * See also: https://github.com/Kotlin/kotlinx.coroutines/issues/3193 + * + * [shouldBeMatched] -- `false` if it is an expected consecutive `probeCoroutineResumed` from BaseContinuationImpl, + * `true` otherwise. + */ + @Synchronized + internal fun updateState(state: String, frame: Continuation<*>, shouldBeMatched: Boolean) { + /** + * We observe consecutive resume that had to be matched, but it wasn't, + * increment + */ + if (_state == RUNNING && state == RUNNING && shouldBeMatched) { + ++unmatchedResume + } else if (unmatchedResume > 0 && state == SUSPENDED) { + /* + * We received late 'suspend' probe for unmatched resume, skip it. + * Here we deliberately allow the very unlikely race; + * Consider the following scenario ('[r:a]' means "probeCoroutineResumed at a()"): + * ``` + * [r:a] a() -> b() [s:b] [r:b] -> (back to a) a() -> c() [s:c] + * ``` + * We can, in theory, observe the following probes interleaving: + * ``` + * r:a + * r:b // Unmatched resume + * s:c // Matched suspend, discard + * s:b + * ``` + * Thus mis-attributing 'lastObservedFrame' to a previously-observed. + * It is possible in theory (though I've failed to reproduce it), yet + * is more preferred than indefinitely mismatched state (-> mismatched real/enhanced stacktrace) + */ + --unmatchedResume + return + } + + // Propagate only non-duplicating transitions to running, see KT-29997 + if (_state == state && state == SUSPENDED && lastObservedFrame != null) return + + _state = state + lastObservedFrame = frame as? CoroutineStackFrame + lastObservedThread = if (state == RUNNING) { + Thread.currentThread() + } else { + null + } + } + @JvmField @Volatile internal var lastObservedThread: Thread? = null @@ -56,7 +133,9 @@ internal class DebugCoroutineInfoImpl( private var _lastObservedFrame: WeakReference? = null internal var lastObservedFrame: CoroutineStackFrame? get() = _lastObservedFrame?.get() - set(value) { _lastObservedFrame = value?.let { WeakReference(it) } } + set(value) { + _lastObservedFrame = value?.let { WeakReference(it) } + } /** * Last observed stacktrace of the coroutine captured on its suspension or resumption point. @@ -88,17 +167,5 @@ internal class DebugCoroutineInfoImpl( } } - internal fun updateState(state: String, frame: Continuation<*>) { - // Propagate only duplicating transitions to running for KT-29997 - if (_state == state && state == SUSPENDED && lastObservedFrame != null) return - _state = state - lastObservedFrame = frame as? CoroutineStackFrame - lastObservedThread = if (state == RUNNING) { - Thread.currentThread() - } else { - null - } - } - override fun toString(): String = "DebugCoroutineInfo(state=$state,context=$context)" } diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt index 3357c43278..ade48bbb5e 100644 --- a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt +++ b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt @@ -436,16 +436,18 @@ internal object DebugProbesImpl { // Lookup coroutine info in cache or by traversing stack frame val info: DebugCoroutineInfoImpl val cached = callerInfoCache.remove(frame) + val shouldBeMatchedWithProbeSuspended: Boolean if (cached != null) { info = cached + shouldBeMatchedWithProbeSuspended = false } else { info = frame.owner()?.info ?: return + shouldBeMatchedWithProbeSuspended = true // Guard against improper implementations of CoroutineStackFrame and bugs in the compiler val realCaller = info.lastObservedFrame?.realCaller() if (realCaller != null) callerInfoCache.remove(realCaller) } - - info.updateState(state, frame as Continuation<*>) + info.updateState(state, frame as Continuation<*>, shouldBeMatchedWithProbeSuspended) // Do not cache it for proxy-classes such as ScopeCoroutines val caller = frame.realCaller() ?: return callerInfoCache[caller] = info @@ -458,7 +460,7 @@ internal object DebugProbesImpl { private fun updateState(owner: CoroutineOwner<*>, frame: Continuation<*>, state: String) { if (!isInstalled) return - owner.info.updateState(state, frame) + owner.info.updateState(state, frame, true) } private fun Continuation<*>.owner(): CoroutineOwner<*>? = (this as? CoroutineStackFrame)?.owner() diff --git a/kotlinx-coroutines-debug/test/DebugProbesTest.kt b/kotlinx-coroutines-debug/test/DebugProbesTest.kt index bc0c1e3f24..cbeeb31171 100644 --- a/kotlinx-coroutines-debug/test/DebugProbesTest.kt +++ b/kotlinx-coroutines-debug/test/DebugProbesTest.kt @@ -6,6 +6,7 @@ package kotlinx.coroutines.debug import kotlinx.coroutines.* import org.junit.Test import java.util.concurrent.* +import java.util.concurrent.atomic.AtomicBoolean import kotlin.test.* class DebugProbesTest : DebugTestBase() { @@ -100,4 +101,59 @@ class DebugProbesTest : DebugTestBase() { verifyStackTrace(e, traces) } } + + @Test + fun testMultipleConsecutiveProbeResumed() = runTest { + val job = launch { + expect(1) + foo() + expect(4) + delay(Long.MAX_VALUE) + expectUnreached() + } + yield() + yield() + expect(5) + val infos = DebugProbes.dumpCoroutinesInfo() + assertEquals(2, infos.size) + assertEquals(setOf(State.RUNNING, State.SUSPENDED), infos.map { it.state }.toSet()) + job.cancel() + finish(6) + } + + @Test + fun testMultipleConsecutiveProbeResumedAndLaterRunning() = runTest { + val reachedActiveStage = AtomicBoolean(false) + val job = launch(Dispatchers.Default) { + expect(1) + foo() + expect(4) + yield() + reachedActiveStage.set(true) + while (isActive) { + // Spin until test is done + } + } + while (!reachedActiveStage.get()) { + delay(10) + } + expect(5) + val infos = DebugProbes.dumpCoroutinesInfo() + assertEquals(2, infos.size) + assertEquals(setOf(State.RUNNING, State.RUNNING), infos.map { it.state }.toSet()) + job.cancel() + finish(6) + } + + private suspend fun foo() { + bar() + // Kill TCO + expect(3) + } + + + private suspend fun bar() { + yield() + expect(2) + } } From e946cd76e5359ac1e7f7b23d3334056681576466 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Mon, 13 Feb 2023 12:22:34 +0300 Subject: [PATCH 076/106] Don't allocate threads on every dispatch in Native's thread pools (#3595) Related to #3576 --- .../src/CloseableCoroutineDispatcher.kt | 4 +- .../test/MultithreadedDispatcherStressTest.kt | 35 ++++++++ .../native/src/MultithreadedDispatchers.kt | 81 +++++++++++++++---- .../test/MultithreadedDispatchersTest.kt | 66 +++++++++++++++ 4 files changed, 168 insertions(+), 18 deletions(-) create mode 100644 kotlinx-coroutines-core/concurrent/test/MultithreadedDispatcherStressTest.kt create mode 100644 kotlinx-coroutines-core/native/test/MultithreadedDispatchersTest.kt diff --git a/kotlinx-coroutines-core/common/src/CloseableCoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/CloseableCoroutineDispatcher.kt index 9c6703291a..541b3082e2 100644 --- a/kotlinx-coroutines-core/common/src/CloseableCoroutineDispatcher.kt +++ b/kotlinx-coroutines-core/common/src/CloseableCoroutineDispatcher.kt @@ -19,8 +19,8 @@ public expect abstract class CloseableCoroutineDispatcher() : CoroutineDispatche /** * Initiate the closing sequence of the coroutine dispatcher. - * After a successful call to [close], no new tasks will - * be accepted to be [dispatched][dispatch], but the previously dispatched tasks will be run. + * After a successful call to [close], no new tasks will be accepted to be [dispatched][dispatch]. + * The previously-submitted tasks will still be run, but [close] is not guaranteed to wait for them to finish. * * Invocations of `close` are idempotent and thread-safe. */ diff --git a/kotlinx-coroutines-core/concurrent/test/MultithreadedDispatcherStressTest.kt b/kotlinx-coroutines-core/concurrent/test/MultithreadedDispatcherStressTest.kt new file mode 100644 index 0000000000..4e4583f20a --- /dev/null +++ b/kotlinx-coroutines-core/concurrent/test/MultithreadedDispatcherStressTest.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import kotlinx.atomicfu.* +import kotlin.coroutines.* +import kotlin.test.* + +class MultithreadedDispatcherStressTest { + val shared = atomic(0) + + /** + * Tests that [newFixedThreadPoolContext] will not drop tasks when closed. + */ + @Test + fun testClosingNotDroppingTasks() { + repeat(7) { + shared.value = 0 + val nThreads = it + 1 + val dispatcher = newFixedThreadPoolContext(nThreads, "testMultiThreadedContext") + repeat(1_000) { + dispatcher.dispatch(EmptyCoroutineContext, Runnable { + shared.incrementAndGet() + }) + } + dispatcher.close() + while (shared.value < 1_000) { + // spin. + // the test will hang here if the dispatcher drops tasks. + } + } + } +} diff --git a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt index bf91e7003b..0012ff65db 100644 --- a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt +++ b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt @@ -4,6 +4,7 @@ package kotlinx.coroutines +import kotlinx.atomicfu.* import kotlinx.coroutines.channels.* import kotlinx.coroutines.internal.* import kotlin.coroutines.* @@ -73,38 +74,78 @@ private class MultiWorkerDispatcher( workersCount: Int ) : CloseableCoroutineDispatcher() { private val tasksQueue = Channel(Channel.UNLIMITED) + private val availableWorkers = Channel>(Channel.UNLIMITED) private val workerPool = OnDemandAllocatingPool(workersCount) { Worker.start(name = "$name-$it").apply { executeAfter { workerRunLoop() } } } + /** + * (number of tasks - number of workers) * 2 + (1 if closed) + */ + private val tasksAndWorkersCounter = atomic(0L) + + private inline fun Long.isClosed() = this and 1L == 1L + private inline fun Long.hasTasks() = this >= 2 + private inline fun Long.hasWorkers() = this < 0 + private fun workerRunLoop() = runBlocking { - // NB: we leverage tail-call optimization in this loop, do not replace it with - // .receive() without proper evaluation - for (task in tasksQueue) { - /** - * Any unhandled exception here will pass through worker's boundary and will be properly reported. - */ - task.run() + while (true) { + val state = tasksAndWorkersCounter.getAndUpdate { + if (it.isClosed() && !it.hasTasks()) return@runBlocking + it - 2 + } + if (state.hasTasks()) { + // we promised to process a task, and there are some + tasksQueue.receive().run() + } else { + try { + suspendCancellableCoroutine { + val result = availableWorkers.trySend(it) + checkChannelResult(result) + }.run() + } catch (e: CancellationException) { + /** we are cancelled from [close] and thus will never get back to this branch of code, + but there may still be pending work, so we can't just exit here. */ + } + } } } + // a worker that promised to be here and should actually arrive, so we wait for it in a blocking manner. + private fun obtainWorker(): CancellableContinuation = + availableWorkers.tryReceive().getOrNull() ?: runBlocking { availableWorkers.receive() } + override fun dispatch(context: CoroutineContext, block: Runnable) { - fun throwClosed(block: Runnable) { - throw IllegalStateException("Dispatcher $name was closed, attempted to schedule: $block") + val state = tasksAndWorkersCounter.getAndUpdate { + if (it.isClosed()) + throw IllegalStateException("Dispatcher $name was closed, attempted to schedule: $block") + it + 2 } - - if (!workerPool.allocate()) throwClosed(block) // Do not even try to send to avoid race - - tasksQueue.trySend(block).onClosed { - throwClosed(block) + if (state.hasWorkers()) { + // there are workers that have nothing to do, let's grab one of them + obtainWorker().resume(block) + } else { + workerPool.allocate() + // no workers are available, we must queue the task + val result = tasksQueue.trySend(block) + checkChannelResult(result) } } override fun close() { - val workers = workerPool.close() - tasksQueue.close() + tasksAndWorkersCounter.getAndUpdate { if (it.isClosed()) it else it or 1L } + val workers = workerPool.close() // no new workers will be created + while (true) { + // check if there are workers that await tasks in their personal channels, we need to wake them up + val state = tasksAndWorkersCounter.getAndUpdate { + if (it.hasWorkers()) it + 2 else it + } + if (!state.hasWorkers()) + break + obtainWorker().cancel() + } /* * Here we cannot avoid waiting on `.result`, otherwise it will lead * to a native memory leak, including a pthread handle. @@ -112,4 +153,12 @@ private class MultiWorkerDispatcher( val requests = workers.map { it.requestTermination() } requests.map { it.result } } + + private fun checkChannelResult(result: ChannelResult<*>) { + if (!result.isSuccess) + throw IllegalStateException( + "Internal invariants of $this were violated, please file a bug to kotlinx.coroutines", + result.exceptionOrNull() + ) + } } diff --git a/kotlinx-coroutines-core/native/test/MultithreadedDispatchersTest.kt b/kotlinx-coroutines-core/native/test/MultithreadedDispatchersTest.kt new file mode 100644 index 0000000000..ce433cc3e3 --- /dev/null +++ b/kotlinx-coroutines-core/native/test/MultithreadedDispatchersTest.kt @@ -0,0 +1,66 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import kotlinx.atomicfu.* +import kotlinx.coroutines.channels.* +import kotlinx.coroutines.internal.* +import kotlin.native.concurrent.* +import kotlin.test.* + +private class BlockingBarrier(val n: Int) { + val counter = atomic(0) + val wakeUp = Channel(n - 1) + fun await() { + val count = counter.addAndGet(1) + if (count == n) { + repeat(n - 1) { + runBlocking { + wakeUp.send(Unit) + } + } + } else if (count < n) { + runBlocking { + wakeUp.receive() + } + } + } +} + +class MultithreadedDispatchersTest { + /** + * Test that [newFixedThreadPoolContext] does not allocate more dispatchers than it needs to. + * Incidentally also tests that it will allocate enough workers for its needs. Otherwise, the test will hang. + */ + @Test + fun testNotAllocatingExtraDispatchers() { + val barrier = BlockingBarrier(2) + val lock = SynchronizedObject() + suspend fun spin(set: MutableSet) { + repeat(100) { + synchronized(lock) { set.add(Worker.current) } + delay(1) + } + } + val dispatcher = newFixedThreadPoolContext(64, "test") + try { + runBlocking { + val encounteredWorkers = mutableSetOf() + val coroutine1 = launch(dispatcher) { + barrier.await() + spin(encounteredWorkers) + } + val coroutine2 = launch(dispatcher) { + barrier.await() + spin(encounteredWorkers) + } + listOf(coroutine1, coroutine2).joinAll() + assertEquals(2, encounteredWorkers.size) + } + } finally { + dispatcher.close() + } + } +} From e7cf6321f875e6ccf2cc90028877551ce7478856 Mon Sep 17 00:00:00 2001 From: Nikita Koval Date: Mon, 13 Feb 2023 12:06:46 +0100 Subject: [PATCH 077/106] Introduce fast and scalable channels (#3103) See #3621 for detailed description and rationale Fixes #3621 --- .idea/dictionaries/shared.xml | 5 + .../ChannelProducerConsumerBenchmark.kt | 55 +- .../kotlin/benchmarks/SemaphoreBenchmark.kt | 19 +- gradle.properties | 6 +- gradle/test-mocha-js.gradle | 7 +- .../api/kotlinx-coroutines-core.api | 2 +- kotlinx-coroutines-core/build.gradle | 29 +- .../common/src/CancellableContinuation.kt | 14 - .../common/src/CancellableContinuationImpl.kt | 3 +- .../common/src/channels/AbstractChannel.kt | 1110 ------ .../src/channels/ArrayBroadcastChannel.kt | 329 -- .../common/src/channels/ArrayChannel.kt | 209 -- .../common/src/channels/Broadcast.kt | 4 +- .../common/src/channels/BroadcastChannel.kt | 343 +- .../common/src/channels/BufferOverflow.kt | 2 - .../common/src/channels/BufferedChannel.kt | 3152 +++++++++++++++++ .../common/src/channels/Channel.kt | 24 +- .../src/channels/ConflatedBroadcastChannel.kt | 296 -- .../src/channels/ConflatedBufferedChannel.kt | 118 + .../common/src/channels/ConflatedChannel.kt | 96 - .../common/src/channels/LinkedListChannel.kt | 58 - .../common/src/channels/RendezvousChannel.kt | 23 - .../common/src/internal/Concurrent.common.kt | 12 - .../src/internal/ConcurrentLinkedList.kt | 61 +- .../common/src/selects/Select.kt | 8 +- .../common/src/sync/Mutex.kt | 3 +- .../common/src/sync/Semaphore.kt | 4 +- .../test/channels/BasicOperationsTest.kt | 4 +- .../channels/BroadcastChannelFactoryTest.kt | 8 +- .../common/test/channels/BroadcastTest.kt | 3 +- ...est.kt => BufferedBroadcastChannelTest.kt} | 2 +- ...yChannelTest.kt => BufferedChannelTest.kt} | 13 +- .../test/channels/ChannelFactoryTest.kt | 24 +- .../ChannelUndeliveredElementFailureTest.kt | 62 +- .../channels/ChannelUndeliveredElementTest.kt | 59 + .../common/test/channels/ChannelsTest.kt | 39 +- .../ConflatedChannelArrayModelTest.kt | 11 - .../test/channels/SendReceiveStressTest.kt | 5 +- .../test/channels/TestBroadcastChannelKind.kt | 4 +- .../common/test/channels/TestChannelKind.kt | 20 +- ...ChannelTest.kt => UnlimitedChannelTest.kt} | 2 +- ...elTest.kt => SelectBufferedChannelTest.kt} | 3 +- ...lTest.kt => SelectUnlimitedChannelTest.kt} | 2 +- ...annelCancelUndeliveredElementStressTest.kt | 1 + .../test/selects/SelectChannelStressTest.kt | 5 +- .../js/src/internal/Concurrent.kt | 2 - .../jvm/src/internal/Concurrent.kt | 2 - .../jvm/src/internal/SegmentBasedQueue.kt | 127 - .../channels/testReceiveFromChannel.txt | 14 +- .../channels/testSendToChannel.txt | 30 +- .../resume-mode/testUnconfined.txt | 18 +- .../select/testSelectOnReceive.txt | 20 +- .../jvm/test/AbstractLincheckTest.kt | 11 +- .../jvm/test/MutexCancellationStressTest.kt | 2 +- .../test/channels/BroadcastChannelLeakTest.kt | 4 +- ...ssTest.kt => BufferedChannelStressTest.kt} | 2 +- .../channels/ChannelMemoryLeakStressTest.kt | 25 + .../channels/ChannelSendReceiveStressTest.kt | 12 +- ...elUndeliveredElementSelectOldStressTest.kt | 2 +- .../ChannelUndeliveredElementStressTest.kt | 3 +- .../test/channels/InvokeOnCloseStressTest.kt | 3 +- .../jvm/test/internal/SegmentQueueTest.kt | 110 - .../jvm/test/lincheck/ChannelsLincheckTest.kt | 143 +- .../jvm/test/lincheck/MutexLincheckTest.kt | 3 +- .../lincheck/SegmentListRemoveLincheckTest.kt | 43 - .../test/lincheck/SegmentQueueLincheckTest.kt | 44 - .../selects/SelectMemoryLeakStressTest.kt | 2 +- .../native/src/internal/Concurrent.kt | 2 - .../src/CoroutinesBlockHoundIntegration.kt | 44 +- .../test/BlockHoundTest.kt | 20 +- .../src/Channel.kt | 7 +- .../kotlinx-coroutines-rx2/src/RxChannel.kt | 5 +- .../kotlinx-coroutines-rx3/src/RxChannel.kt | 5 +- ui/coroutines-guide-ui.md | 6 +- 74 files changed, 4190 insertions(+), 2780 deletions(-) delete mode 100644 kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt delete mode 100644 kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt delete mode 100644 kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt create mode 100644 kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt delete mode 100644 kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt create mode 100644 kotlinx-coroutines-core/common/src/channels/ConflatedBufferedChannel.kt delete mode 100644 kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt delete mode 100644 kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt delete mode 100644 kotlinx-coroutines-core/common/src/channels/RendezvousChannel.kt rename kotlinx-coroutines-core/common/test/channels/{ArrayBroadcastChannelTest.kt => BufferedBroadcastChannelTest.kt} (99%) rename kotlinx-coroutines-core/common/test/channels/{ArrayChannelTest.kt => BufferedChannelTest.kt} (89%) delete mode 100644 kotlinx-coroutines-core/common/test/channels/ConflatedChannelArrayModelTest.kt rename kotlinx-coroutines-core/common/test/channels/{LinkedListChannelTest.kt => UnlimitedChannelTest.kt} (96%) rename kotlinx-coroutines-core/common/test/selects/{SelectArrayChannelTest.kt => SelectBufferedChannelTest.kt} (99%) rename kotlinx-coroutines-core/common/test/selects/{SelectLinkedListChannelTest.kt => SelectUnlimitedChannelTest.kt} (93%) delete mode 100644 kotlinx-coroutines-core/jvm/src/internal/SegmentBasedQueue.kt rename kotlinx-coroutines-core/jvm/test/channels/{ArrayChannelStressTest.kt => BufferedChannelStressTest.kt} (95%) create mode 100644 kotlinx-coroutines-core/jvm/test/channels/ChannelMemoryLeakStressTest.kt delete mode 100644 kotlinx-coroutines-core/jvm/test/internal/SegmentQueueTest.kt delete mode 100644 kotlinx-coroutines-core/jvm/test/lincheck/SegmentListRemoveLincheckTest.kt delete mode 100644 kotlinx-coroutines-core/jvm/test/lincheck/SegmentQueueLincheckTest.kt diff --git a/.idea/dictionaries/shared.xml b/.idea/dictionaries/shared.xml index 3da8e22952..45cbedcf1f 100644 --- a/.idea/dictionaries/shared.xml +++ b/.idea/dictionaries/shared.xml @@ -1,8 +1,13 @@ + Alistarh + Elizarov + Koval kotlinx lincheck + linearizability + linearizable redirector diff --git a/benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt index 0fa5048983..0aa218e824 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/benchmarks/ChannelProducerConsumerBenchmark.kt @@ -4,15 +4,14 @@ package benchmarks +import benchmarks.common.* import kotlinx.coroutines.* import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.scheduling.* import kotlinx.coroutines.selects.select import org.openjdk.jmh.annotations.* -import org.openjdk.jmh.infra.Blackhole import java.lang.Integer.max -import java.util.concurrent.ForkJoinPool import java.util.concurrent.Phaser -import java.util.concurrent.ThreadLocalRandom import java.util.concurrent.TimeUnit @@ -26,14 +25,14 @@ import java.util.concurrent.TimeUnit * Please, be patient, this benchmark takes quite a lot of time to complete. */ @Warmup(iterations = 3, time = 500, timeUnit = TimeUnit.MICROSECONDS) -@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MICROSECONDS) -@Fork(value = 3) -@BenchmarkMode(Mode.AverageTime) +@Measurement(iterations = 20, time = 500, timeUnit = TimeUnit.MICROSECONDS) +@Fork(value = 1) +@BenchmarkMode(Mode.Throughput) @OutputTimeUnit(TimeUnit.MILLISECONDS) @State(Scope.Benchmark) open class ChannelProducerConsumerBenchmark { @Param - private var _0_dispatcher: DispatcherCreator = DispatcherCreator.FORK_JOIN + private var _0_dispatcher: DispatcherCreator = DispatcherCreator.DEFAULT @Param private var _1_channel: ChannelCreator = ChannelCreator.RENDEZVOUS @@ -44,12 +43,13 @@ open class ChannelProducerConsumerBenchmark { @Param("false", "true") private var _3_withSelect: Boolean = false - @Param("1", "2", "4") // local machine -// @Param("1", "2", "4", "8", "12") // local machine -// @Param("1", "2", "4", "8", "16", "32", "64", "128", "144") // dasquad -// @Param("1", "2", "4", "8", "16", "32", "64", "96") // Google Cloud + @Param("1", "2", "4", "8", "16") // local machine +// @Param("1", "2", "4", "8", "16", "32", "64", "128") // Server private var _4_parallelism: Int = 0 + @Param("50") + private var _5_workSize: Int = 0 + private lateinit var dispatcher: CoroutineDispatcher private lateinit var channel: Channel @@ -61,13 +61,21 @@ open class ChannelProducerConsumerBenchmark { } @Benchmark - fun spmc() { + fun mcsp() { if (_2_coroutines != 0) return val producers = max(1, _4_parallelism - 1) val consumers = 1 run(producers, consumers) } + @Benchmark + fun spmc() { + if (_2_coroutines != 0) return + val producers = 1 + val consumers = max(1, _4_parallelism - 1) + run(producers, consumers) + } + @Benchmark fun mpmc() { val producers = if (_2_coroutines == 0) (_4_parallelism + 1) / 2 else _2_coroutines / 2 @@ -76,7 +84,7 @@ open class ChannelProducerConsumerBenchmark { } private fun run(producers: Int, consumers: Int) { - val n = APPROX_BATCH_SIZE / producers * producers + val n = (APPROX_BATCH_SIZE / producers * producers) / consumers * consumers val phaser = Phaser(producers + consumers + 1) // Run producers repeat(producers) { @@ -111,7 +119,7 @@ open class ChannelProducerConsumerBenchmark { } else { channel.send(element) } - doWork() + doWork(_5_workSize) } private suspend fun consume(dummy: Channel?) { @@ -123,28 +131,25 @@ open class ChannelProducerConsumerBenchmark { } else { channel.receive() } - doWork() + doWork(_5_workSize) } } enum class DispatcherCreator(val create: (parallelism: Int) -> CoroutineDispatcher) { - FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }) + //FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }), + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + DEFAULT({ parallelism -> ExperimentalCoroutineDispatcher(corePoolSize = parallelism, maxPoolSize = parallelism) }) } enum class ChannelCreator(private val capacity: Int) { RENDEZVOUS(Channel.RENDEZVOUS), -// BUFFERED_1(1), - BUFFERED_2(2), -// BUFFERED_4(4), - BUFFERED_32(32), - BUFFERED_128(128), + BUFFERED_16(16), + BUFFERED_64(64), BUFFERED_UNLIMITED(Channel.UNLIMITED); fun create(): Channel = Channel(capacity) } -private fun doWork(): Unit = Blackhole.consumeCPU(ThreadLocalRandom.current().nextLong(WORK_MIN, WORK_MAX)) +private fun doWork(workSize: Int): Unit = doGeomDistrWork(workSize) -private const val WORK_MIN = 50L -private const val WORK_MAX = 100L -private const val APPROX_BATCH_SIZE = 100000 +private const val APPROX_BATCH_SIZE = 100_000 diff --git a/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt index 9e1bfc43bb..6826b7a1a3 100644 --- a/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt +++ b/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt @@ -7,6 +7,7 @@ package benchmarks import benchmarks.common.* import kotlinx.coroutines.* import kotlinx.coroutines.channels.* +import kotlinx.coroutines.scheduling.* import kotlinx.coroutines.sync.* import org.openjdk.jmh.annotations.* import java.util.concurrent.* @@ -19,7 +20,7 @@ import java.util.concurrent.* @State(Scope.Benchmark) open class SemaphoreBenchmark { @Param - private var _1_dispatcher: SemaphoreBenchDispatcherCreator = SemaphoreBenchDispatcherCreator.FORK_JOIN + private var _1_dispatcher: SemaphoreBenchDispatcherCreator = SemaphoreBenchDispatcherCreator.DEFAULT @Param("0", "1000") private var _2_coroutines: Int = 0 @@ -27,9 +28,8 @@ open class SemaphoreBenchmark { @Param("1", "2", "4", "8", "32", "128", "100000") private var _3_maxPermits: Int = 0 - @Param("1", "2", "4") // local machine -// @Param("1", "2", "4", "8", "16", "32", "64", "128", "144") // dasquad -// @Param("1", "2", "4", "8", "16", "32", "64", "96") // Google Cloud + @Param("1", "2", "4", "8", "16") // local machine +// @Param("1", "2", "4", "8", "16", "32", "64", "128") // Server private var _4_parallelism: Int = 0 private lateinit var dispatcher: CoroutineDispatcher @@ -80,10 +80,11 @@ open class SemaphoreBenchmark { } enum class SemaphoreBenchDispatcherCreator(val create: (parallelism: Int) -> CoroutineDispatcher) { - FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }), - EXPERIMENTAL({ parallelism -> Dispatchers.Default }) // TODO doesn't take parallelism into account + // FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }), + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") + DEFAULT({ parallelism -> ExperimentalCoroutineDispatcher(corePoolSize = parallelism, maxPoolSize = parallelism) }) } -private const val WORK_INSIDE = 80 -private const val WORK_OUTSIDE = 40 -private const val BATCH_SIZE = 1000000 +private const val WORK_INSIDE = 50 +private const val WORK_OUTSIDE = 50 +private const val BATCH_SIZE = 100000 diff --git a/gradle.properties b/gradle.properties index c23ae62301..229904ead4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -13,7 +13,7 @@ junit5_version=5.7.0 atomicfu_version=0.19.0 knit_version=0.4.0 html_version=0.7.2 -lincheck_version=2.14.1 +lincheck_version=2.16 dokka_version=1.7.20 byte_buddy_version=1.10.9 reactor_version=3.4.1 @@ -59,4 +59,6 @@ org.gradle.jvmargs=-Xmx3g kotlin.mpp.enableCompatibilityMetadataVariant=true kotlin.mpp.stability.nowarn=true kotlinx.atomicfu.enableJvmIrTransformation=true -kotlinx.atomicfu.enableJsIrTransformation=true +# When the flag below is set to `true`, AtomicFU cannot process +# usages of `moveForward` in `ConcurrentLinkedList.kt` correctly. +kotlinx.atomicfu.enableJsIrTransformation=false diff --git a/gradle/test-mocha-js.gradle b/gradle/test-mocha-js.gradle index 1986cb7f81..1ec297e415 100644 --- a/gradle/test-mocha-js.gradle +++ b/gradle/test-mocha-js.gradle @@ -29,7 +29,8 @@ task testMochaNode(type: NodeTask, dependsOn: [compileTestJsLegacy, installDepen def jsLegacyTestTask = project.tasks.findByName('jsLegacyTest') ? jsLegacyTest : jsTest -jsLegacyTestTask.dependsOn testMochaNode +// TODO +//jsLegacyTestTask.dependsOn testMochaNode // -- Testing with Mocha under headless Chrome @@ -100,5 +101,5 @@ task testMochaJsdom(type: NodeTask, dependsOn: [compileTestJsLegacy, installDepe if (project.hasProperty("teamcity")) args.addAll(['--reporter', 'mocha-teamcity-reporter']) } -jsLegacyTestTask.dependsOn testMochaJsdom - +// TODO +//jsLegacyTestTask.dependsOn testMochaJsdom diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index 3a2d08f428..def21f8130 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -51,7 +51,7 @@ public final class kotlinx/coroutines/CancellableContinuation$DefaultImpls { public static synthetic fun tryResume$default (Lkotlinx/coroutines/CancellableContinuation;Ljava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;)Ljava/lang/Object; } -public class kotlinx/coroutines/CancellableContinuationImpl : kotlin/coroutines/jvm/internal/CoroutineStackFrame, kotlinx/coroutines/CancellableContinuation { +public class kotlinx/coroutines/CancellableContinuationImpl : kotlin/coroutines/jvm/internal/CoroutineStackFrame, kotlinx/coroutines/CancellableContinuation, kotlinx/coroutines/channels/Waiter { public fun (Lkotlin/coroutines/Continuation;I)V public final fun callCancelHandler (Lkotlinx/coroutines/CancelHandler;Ljava/lang/Throwable;)V public final fun callOnCancellation (Lkotlin/jvm/functions/Function1;Ljava/lang/Throwable;)V diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index 54883ff233..84d9b0485d 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -265,8 +265,11 @@ task jvmStressTest(type: Test, dependsOn: compileTestKotlinJvm) { enableAssertions = true testLogging.showStandardStreams = true systemProperty 'kotlinx.coroutines.scheduler.keep.alive.sec', '100000' // any unpark problem hangs test - systemProperty 'kotlinx.coroutines.semaphore.segmentSize', '2' + // Adjust internal algorithmic parameters to increase the testing quality instead of performance. + systemProperty 'kotlinx.coroutines.semaphore.segmentSize', '1' systemProperty 'kotlinx.coroutines.semaphore.maxSpinCycles', '10' + systemProperty 'kotlinx.coroutines.bufferedChannel.segmentSize', '2' + systemProperty 'kotlinx.coroutines.bufferedChannel.expandBufferCompletionWaitIterations', '1' } task jvmLincheckTest(type: Test, dependsOn: compileTestKotlinJvm) { @@ -278,17 +281,35 @@ task jvmLincheckTest(type: Test, dependsOn: compileTestKotlinJvm) { configureJvmForLincheck(jvmLincheckTest) } -static void configureJvmForLincheck(task) { +// Additional Lincheck tests with `segmentSize = 2`. +// Some bugs cannot be revealed when storing one request per segment, +// and some are hard to detect when storing multiple requests. +task jvmLincheckTestAdditional(type: Test, dependsOn: compileTestKotlinJvm) { + classpath = files { jvmTest.classpath } + testClassesDirs = files { jvmTest.testClassesDirs } + include '**/RendezvousChannelLincheckTest*' + include '**/Buffered1ChannelLincheckTest*' + include '**/Semaphore*LincheckTest*' + enableAssertions = true + testLogging.showStandardStreams = true + configureJvmForLincheck(jvmLincheckTestAdditional, true) +} + +static void configureJvmForLincheck(task, additional = false) { task.minHeapSize = '1g' task.maxHeapSize = '4g' // we may need more space for building an interleaving tree in the model checking mode task.jvmArgs = ['--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED', // required for transformation '--add-exports', 'java.base/jdk.internal.util=ALL-UNNAMED'] // in the model checking mode - task.systemProperty 'kotlinx.coroutines.semaphore.segmentSize', '2' + // Adjust internal algorithmic parameters to increase the testing quality instead of performance. + var segmentSize = additional ? '2' : '1' + task.systemProperty 'kotlinx.coroutines.semaphore.segmentSize', segmentSize task.systemProperty 'kotlinx.coroutines.semaphore.maxSpinCycles', '1' // better for the model checking mode + task.systemProperty 'kotlinx.coroutines.bufferedChannel.segmentSize', segmentSize + task.systemProperty 'kotlinx.coroutines.bufferedChannel.expandBufferCompletionWaitIterations', '1' } // Always check additional test sets -task moreTest(dependsOn: [jvmStressTest, jvmLincheckTest]) +task moreTest(dependsOn: [jvmStressTest, jvmLincheckTest, jvmLincheckTestAdditional]) check.dependsOn moreTest tasks.jvmLincheckTest { diff --git a/kotlinx-coroutines-core/common/src/CancellableContinuation.kt b/kotlinx-coroutines-core/common/src/CancellableContinuation.kt index 2c2f1b8ff6..5e8d7f9102 100644 --- a/kotlinx-coroutines-core/common/src/CancellableContinuation.kt +++ b/kotlinx-coroutines-core/common/src/CancellableContinuation.kt @@ -358,13 +358,6 @@ internal fun getOrCreateCancellableContinuation(delegate: Continuation): ?: return CancellableContinuationImpl(delegate, MODE_CANCELLABLE_REUSABLE) } -/** - * Removes the specified [node] on cancellation. This function assumes that this node is already - * removed on successful resume and does not try to remove it if the continuation is cancelled during dispatch. - */ -internal fun CancellableContinuation<*>.removeOnCancellation(node: LockFreeLinkedListNode) = - invokeOnCancellation(handler = RemoveOnCancel(node).asHandler) - /** * Disposes the specified [handle] when this continuation is cancelled. * @@ -379,13 +372,6 @@ internal fun CancellableContinuation<*>.removeOnCancellation(node: LockFreeLinke public fun CancellableContinuation<*>.disposeOnCancellation(handle: DisposableHandle): Unit = invokeOnCancellation(handler = DisposeOnCancel(handle).asHandler) -// --------------- implementation details --------------- - -private class RemoveOnCancel(private val node: LockFreeLinkedListNode) : BeforeResumeCancelHandler() { - override fun invoke(cause: Throwable?) { node.remove() } - override fun toString() = "RemoveOnCancel[$node]" -} - private class DisposeOnCancel(private val handle: DisposableHandle) : CancelHandler() { override fun invoke(cause: Throwable?) = handle.dispose() override fun toString(): String = "DisposeOnCancel[$handle]" diff --git a/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt index ef49da23df..423cb05d18 100644 --- a/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt +++ b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt @@ -5,6 +5,7 @@ package kotlinx.coroutines import kotlinx.atomicfu.* +import kotlinx.coroutines.channels.Waiter import kotlinx.coroutines.internal.* import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* @@ -24,7 +25,7 @@ internal val RESUME_TOKEN = Symbol("RESUME_TOKEN") internal open class CancellableContinuationImpl( final override val delegate: Continuation, resumeMode: Int -) : DispatchedTask(resumeMode), CancellableContinuation, CoroutineStackFrame { +) : DispatchedTask(resumeMode), CancellableContinuation, CoroutineStackFrame, Waiter { init { assert { resumeMode != MODE_UNINITIALIZED } // invalid mode for CancellableContinuationImpl } diff --git a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt deleted file mode 100644 index 2f2fe506e0..0000000000 --- a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt +++ /dev/null @@ -1,1110 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -@file:Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER") - -package kotlinx.coroutines.channels - -import kotlinx.atomicfu.* -import kotlinx.coroutines.* -import kotlinx.coroutines.internal.* -import kotlinx.coroutines.intrinsics.* -import kotlinx.coroutines.selects.* -import kotlinx.coroutines.selects.TrySelectDetailedResult.* -import kotlin.coroutines.* -import kotlin.jvm.* - -/** - * Abstract send channel. It is a base class for all send channel implementations. - */ -@Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER") -internal abstract class AbstractSendChannel( - @JvmField protected val onUndeliveredElement: OnUndeliveredElement? -) : SendChannel { - /** @suppress **This is unstable API and it is subject to change.** */ - protected val queue = LockFreeLinkedListHead() - - // ------ extension points for buffered channels ------ - - /** - * Returns `true` if [isBufferFull] is always `true`. - * @suppress **This is unstable API and it is subject to change.** - */ - protected abstract val isBufferAlwaysFull: Boolean - - /** - * Returns `true` if this channel's buffer is full. - * This operation should be atomic if it is invoked by [enqueueSend]. - * @suppress **This is unstable API and it is subject to change.** - */ - protected abstract val isBufferFull: Boolean - - // State transitions: null -> handler -> HANDLER_INVOKED - private val onCloseHandler = atomic(null) - - // ------ internal functions for override by buffered channels ------ - - /** - * Tries to add element to buffer or to queued receiver. - * Return type is `OFFER_SUCCESS | OFFER_FAILED | Closed`. - * @suppress **This is unstable API and it is subject to change.** - */ - protected open fun offerInternal(element: E): Any { - while (true) { - val receive = takeFirstReceiveOrPeekClosed() ?: return OFFER_FAILED - val token = receive.tryResumeReceive(element) - if (token != null) { - assert { token === RESUME_TOKEN } - receive.completeResumeReceive(element) - return receive.offerResult - } - } - } - - - // ------ state functions & helpers for concrete implementations ------ - - /** - * Returns non-null closed token if it is last in the queue. - * @suppress **This is unstable API and it is subject to change.** - */ - protected val closedForSend: Closed<*>? get() = (queue.prevNode as? Closed<*>)?.also { helpClose(it) } - - /** - * Returns non-null closed token if it is first in the queue. - * @suppress **This is unstable API and it is subject to change.** - */ - protected val closedForReceive: Closed<*>? get() = (queue.nextNode as? Closed<*>)?.also { helpClose(it) } - - /** - * Retrieves first sending waiter from the queue or returns closed token. - * @suppress **This is unstable API and it is subject to change.** - */ - protected fun takeFirstSendOrPeekClosed(): Send? = - queue.removeFirstIfIsInstanceOfOrPeekIf { it is Closed<*> } - - /** - * Queues buffered element, returns null on success or - * returns node reference if it was already closed or is waiting for receive. - * @suppress **This is unstable API and it is subject to change.** - */ - protected fun sendBuffered(element: E): ReceiveOrClosed<*>? { - queue.addLastIfPrev(SendBuffered(element)) { prev -> - if (prev is ReceiveOrClosed<*>) return@sendBuffered prev - true - } - return null - } - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - protected fun describeSendBuffered(element: E): AddLastDesc<*> = SendBufferedDesc(queue, element) - - private open class SendBufferedDesc( - queue: LockFreeLinkedListHead, - element: E - ) : AddLastDesc>(queue, SendBuffered(element)) { - override fun failure(affected: LockFreeLinkedListNode): Any? = when (affected) { - is Closed<*> -> affected - is ReceiveOrClosed<*> -> OFFER_FAILED - else -> null - } - } - - // ------ SendChannel ------ - - public final override val isClosedForSend: Boolean get() = closedForSend != null - private val isFullImpl: Boolean get() = queue.nextNode !is ReceiveOrClosed<*> && isBufferFull - - public final override suspend fun send(element: E) { - // fast path -- try offer non-blocking - if (offerInternal(element) === OFFER_SUCCESS) return - // slow-path does suspend or throws exception - return sendSuspend(element) - } - - @Suppress("DEPRECATION_ERROR") - @Deprecated( - level = DeprecationLevel.ERROR, - message = "Deprecated in the favour of 'trySend' method", - replaceWith = ReplaceWith("trySend(element).isSuccess") - ) // see super() - override fun offer(element: E): Boolean { - // Temporary migration for offer users who rely on onUndeliveredElement - try { - return super.offer(element) - } catch (e: Throwable) { - onUndeliveredElement?.callUndeliveredElementCatchingException(element)?.let { - // If it crashes, add send exception as suppressed for better diagnostics - it.addSuppressed(e) - throw it - } - throw e - } - } - - public final override fun trySend(element: E): ChannelResult { - val result = offerInternal(element) - return when { - result === OFFER_SUCCESS -> ChannelResult.success(Unit) - result === OFFER_FAILED -> { - // We should check for closed token on trySend as well, otherwise trySend won't be linearizable - // in the face of concurrent close() - // See https://github.com/Kotlin/kotlinx.coroutines/issues/359 - val closedForSend = closedForSend ?: return ChannelResult.failure() - ChannelResult.closed(helpCloseAndGetSendException(closedForSend)) - } - result is Closed<*> -> { - ChannelResult.closed(helpCloseAndGetSendException(result)) - } - else -> error("trySend returned $result") - } - } - - private fun helpCloseAndGetSendException(closed: Closed<*>): Throwable { - helpClose(closed) - return closed.sendException - } - - private fun helpCloseAndGetSendException(element: E, closed: Closed<*>): Throwable { - // To ensure linearizablity we must ALWAYS help close the channel when we observe that it was closed - // See https://github.com/Kotlin/kotlinx.coroutines/issues/1419 - helpClose(closed) - // Element was not delivered -> cals onUndeliveredElement - onUndeliveredElement?.callUndeliveredElementCatchingException(element)?.let { - // If it crashes, add send exception as suppressed for better diagnostics - it.addSuppressed(closed.sendException) - throw it - } - return closed.sendException - } - - private suspend fun sendSuspend(element: E): Unit = suspendCancellableCoroutineReusable sc@ { cont -> - sendSlowPath(element, cont) - } - - // waiter is either CancellableContinuation or SelectInstance - private fun sendSlowPath(element: E, waiter: Any) { - loop@ while (true) { - if (isFullImpl) { - val send: Send = if (waiter is SelectInstance<*>) { - if (onUndeliveredElement == null) SendElementSelect(element, waiter, this) - else SendElementSelectWithUndeliveredHandler(element, waiter, this, onUndeliveredElement) - } else { - if (onUndeliveredElement == null) SendElement(element, waiter as CancellableContinuation) - else SendElementWithUndeliveredHandler(element, waiter as CancellableContinuation, onUndeliveredElement) - } - val enqueueResult = enqueueSend(send) - when { - enqueueResult == null -> { // enqueued successfully - if (waiter is SelectInstance<*>) { - waiter.disposeOnCompletion { send.remove() } - } else { - waiter as CancellableContinuation - waiter.removeOnCancellation(send) - } - return - } - enqueueResult is Closed<*> -> { - if (waiter is SelectInstance<*>) { - waiter.helpCloseAndSelectInRegistrationPhaseClosed(element, enqueueResult) - } else { - waiter as CancellableContinuation - waiter.helpCloseAndResumeWithSendException(element, enqueueResult) - } - return - } - enqueueResult === ENQUEUE_FAILED -> {} // try to offer instead - enqueueResult is Receive<*> -> {} // try to offer instead - else -> error("enqueueSend returned $enqueueResult") - } - } - // hm... receiver is waiting or buffer is not full. try to offer - val offerResult = offerInternal(element) - when { - offerResult === OFFER_SUCCESS -> { - if (waiter is SelectInstance<*>) { - waiter.selectInRegistrationPhase(Unit) - } else { - waiter as CancellableContinuation - waiter.resume(Unit) - } - return - } - offerResult === OFFER_FAILED -> continue@loop - offerResult is Closed<*> -> { - if (waiter is SelectInstance<*>) { - waiter.helpCloseAndSelectInRegistrationPhaseClosed(element, offerResult) - } else { - waiter as CancellableContinuation - waiter.helpCloseAndResumeWithSendException(element, offerResult) - } - return - } - else -> error("offerInternal returned $offerResult") - } - } - } - - private fun Continuation<*>.helpCloseAndResumeWithSendException(element: E, closed: Closed<*>) { - helpClose(closed) - val sendException = closed.sendException - onUndeliveredElement?.callUndeliveredElementCatchingException(element)?.let { - it.addSuppressed(sendException) - resumeWithException(it) - return - } - resumeWithException(sendException) - } - - private fun SelectInstance<*>.helpCloseAndSelectInRegistrationPhaseClosed(element: E, closed: Closed<*>) { - helpClose(closed) - onUndeliveredElement?.callUndeliveredElementCatchingException(element)?.let { - it.addSuppressed(closed.sendException) - selectInRegistrationPhase(Closed(it)) - return - } - selectInRegistrationPhase(closed) - } - - /** - * Result is: - * * null -- successfully enqueued - * * ENQUEUE_FAILED -- buffer is not full (should not enqueue) - * * ReceiveOrClosed<*> -- receiver is waiting or it is closed (should not enqueue) - */ - protected open fun enqueueSend(send: Send): Any? { - if (isBufferAlwaysFull) { - queue.addLastIfPrev(send) { prev -> - if (prev is ReceiveOrClosed<*>) return@enqueueSend prev - true - } - } else { - if (!queue.addLastIfPrevAndIf(send, { prev -> - if (prev is ReceiveOrClosed<*>) return@enqueueSend prev - true - }, { isBufferFull })) - return ENQUEUE_FAILED - } - return null - } - - public override fun close(cause: Throwable?): Boolean { - val closed = Closed(cause) - /* - * Try to commit close by adding a close token to the end of the queue. - * Successful -> we're now responsible for closing receivers - * Not successful -> help closing pending receivers to maintain invariant - * "if (!close()) next send will throw" - */ - val closeAdded = queue.addLastIfPrev(closed) { it !is Closed<*> } - val actuallyClosed = if (closeAdded) closed else queue.prevNode as Closed<*> - helpClose(actuallyClosed) - if (closeAdded) invokeOnCloseHandler(cause) - return closeAdded // true if we have closed - } - - private fun invokeOnCloseHandler(cause: Throwable?) { - val handler = onCloseHandler.value - if (handler !== null && handler !== HANDLER_INVOKED - && onCloseHandler.compareAndSet(handler, HANDLER_INVOKED)) { - // CAS failed -> concurrent invokeOnClose() invoked handler - @Suppress("UNCHECKED_CAST") - (handler as Handler)(cause) - } - } - - override fun invokeOnClose(handler: Handler) { - // Intricate dance for concurrent invokeOnClose and close calls - if (!onCloseHandler.compareAndSet(null, handler)) { - val value = onCloseHandler.value - if (value === HANDLER_INVOKED) { - throw IllegalStateException("Another handler was already registered and successfully invoked") - } - - throw IllegalStateException("Another handler was already registered: $value") - } else { - val closedToken = closedForSend - if (closedToken != null && onCloseHandler.compareAndSet(handler, HANDLER_INVOKED)) { - // CAS failed -> close() call invoked handler - (handler)(closedToken.closeCause) - } - } - } - - private fun helpClose(closed: Closed<*>) { - /* - * It's important to traverse list from right to left to avoid races with sender. - * Consider channel state: head -> [receive_1] -> [receive_2] -> head - * - T1 calls receive() - * - T2 calls close() - * - T3 calls close() + send(value) - * - * If both will traverse list from left to right, following non-linearizable history is possible: - * [close -> false], [send -> transferred 'value' to receiver] - * - * Another problem with linearizability of close is that we cannot resume closed receives until all - * receivers are removed from the list. - * Consider channel state: head -> [receive_1] -> [receive_2] -> head - * - T1 called receive_2, and will call send() when it's receive call resumes - * - T2 calls close() - * - * Now if T2's close resumes T1's receive_2 then it's receive gets "closed for receive" exception, but - * its subsequent attempt to send successfully rendezvous with receive_1, producing non-linearizable execution. - */ - var closedList = InlineList>() - while (true) { - // Break when channel is empty or has no receivers - @Suppress("UNCHECKED_CAST") - val previous = closed.prevNode as? Receive ?: break - if (!previous.remove()) { - // failed to remove the node (due to race) -- retry finding non-removed prevNode - // NOTE: remove() DOES NOT help pending remove operation (that marked next pointer) - previous.helpRemove() // make sure remove is complete before continuing - continue - } - // add removed nodes to a separate list - closedList += previous - } - /* - * Now notify all removed nodes that the channel was closed - * in the order they were added to the channel - */ - closedList.forEachReversed { it.resumeReceiveClosed(closed) } - // and do other post-processing - onClosedIdempotent(closed) - } - - /** - * Invoked when channel is closed as the last action of [close] invocation. - * This method should be idempotent and can be called multiple times. - */ - protected open fun onClosedIdempotent(closed: LockFreeLinkedListNode) {} - - /** - * Retrieves first receiving waiter from the queue or returns closed token. - * @suppress **This is unstable API and it is subject to change.** - */ - protected open fun takeFirstReceiveOrPeekClosed(): ReceiveOrClosed? = - queue.removeFirstIfIsInstanceOfOrPeekIf>({ it is Closed<*> }) - - final override val onSend - get() = SelectClause2Impl>( - clauseObject = this, - regFunc = AbstractSendChannel<*>::registerSelectForSend as RegistrationFunction, - processResFunc = AbstractSendChannel<*>::processResultSelectSend as ProcessResultFunction - ) - - private fun registerSelectForSend(select: SelectInstance<*>, element: Any?) { - element as E - if (offerInternal(element) === OFFER_SUCCESS) { - select.selectInRegistrationPhase(Unit) - return - } - sendSlowPath(element, select) - } - - private fun processResultSelectSend(ignoredParam: Any?, selectResult: Any?): Any? = - if (selectResult is Closed<*>) throw selectResult.sendException - else this - - // ------ debug ------ - - public override fun toString() = - "$classSimpleName@$hexAddress{$queueDebugStateString}$bufferDebugString" - - private val queueDebugStateString: String - get() { - val head = queue.nextNode - if (head === queue) return "EmptyQueue" - var result = when (head) { - is Closed<*> -> head.toString() - is Receive<*> -> "ReceiveQueued" - is Send -> "SendQueued" - else -> "UNEXPECTED:$head" // should not happen - } - val tail = queue.prevNode - if (tail !== head) { - result += ",queueSize=${countQueueSize()}" - if (tail is Closed<*>) result += ",closedForSend=$tail" - } - return result - } - - private fun countQueueSize(): Int { - var size = 0 - queue.forEach { size++ } - return size - } - - protected open val bufferDebugString: String get() = "" - - // ------ private ------ - - internal class SendBuffered( - @JvmField val element: E - ) : Send() { - override val pollResult: Any? get() = element - override fun tryResumeSend(otherOp: PrepareOp?): Symbol? = RESUME_TOKEN.also { otherOp?.finishPrepare() } - override fun completeResumeSend() {} - - /** - * This method should be never called, see special logic in [LinkedListChannel.onCancelIdempotentList]. - */ - override fun resumeSendClosed(closed: Closed<*>) { - assert { false } - } - - override fun toString(): String = "SendBuffered@$hexAddress($element)" - } -} - -/** - * Abstract send/receive channel. It is a base class for all channel implementations. - */ -internal abstract class AbstractChannel( - onUndeliveredElement: OnUndeliveredElement? -) : AbstractSendChannel(onUndeliveredElement), Channel { - // ------ extension points for buffered channels ------ - - /** - * Returns `true` if [isBufferEmpty] is always `true`. - * @suppress **This is unstable API and it is subject to change.** - */ - protected abstract val isBufferAlwaysEmpty: Boolean - - /** - * Returns `true` if this channel's buffer is empty. - * This operation should be atomic if it is invoked by [enqueueReceive]. - * @suppress **This is unstable API and it is subject to change.** - */ - protected abstract val isBufferEmpty: Boolean - - // ------ internal functions for override by buffered channels ------ - - /** - * Tries to remove element from buffer or from queued sender. - * Return type is `E | POLL_FAILED | Closed` - * @suppress **This is unstable API and it is subject to change.** - */ - protected open fun pollInternal(): Any? { - while (true) { - val send = takeFirstSendOrPeekClosed() ?: return POLL_FAILED - val token = send.tryResumeSend(null) - if (token != null) { - assert { token === RESUME_TOKEN } - send.completeResumeSend() - return send.pollResult - } - // Too late, already cancelled, but we removed it from the queue and need to notify on undelivered element. - // The only exception is when this "send" operation is an `onSend` clause that has to be re-registered - // in the corresponding `select` invocation. - if (!(send is SendElementSelectWithUndeliveredHandler<*> && send.trySelectResult == REREGISTER)) - send.undeliveredElement() - } - } - - // ------ state functions & helpers for concrete implementations ------ - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - protected val hasReceiveOrClosed: Boolean get() = queue.nextNode is ReceiveOrClosed<*> - - // ------ ReceiveChannel ------ - - public override val isClosedForReceive: Boolean get() = closedForReceive != null && isBufferEmpty - public override val isEmpty: Boolean get() = isEmptyImpl - protected val isEmptyImpl: Boolean get() = queue.nextNode !is Send && isBufferEmpty - - public final override suspend fun receive(): E { - // fast path -- try poll non-blocking - val result = pollInternal() - /* - * If result is Closed -- go to tail-call slow-path that will allow us to - * properly recover stacktrace without paying a performance cost on fast path. - * We prefer to recover stacktrace using suspending path to have a more precise stacktrace. - */ - @Suppress("UNCHECKED_CAST") - if (result !== POLL_FAILED && result !is Closed<*>) return result as E - // slow-path does suspend - return receiveSuspend(RECEIVE_THROWS_ON_CLOSE) - } - - @Suppress("UNCHECKED_CAST") - private suspend fun receiveSuspend(receiveMode: Int): R = suspendCancellableCoroutineReusable sc@ { cont -> - receiveSlowPath(receiveMode, cont) - } - - @Suppress("UNCHECKED_CAST") - private fun receiveSlowPath(receiveMode: Int, waiter: Any) { - val receive = if (waiter is SelectInstance<*>) { - if (onUndeliveredElement == null) ReceiveElementSelect(waiter) - else ReceiveElementSelectWithUndeliveredHandler(waiter, onUndeliveredElement) - } else { - waiter as CancellableContinuation - if (onUndeliveredElement == null) ReceiveElement(waiter, receiveMode) - else ReceiveElementWithUndeliveredHandler(waiter, receiveMode, onUndeliveredElement) - } - while (true) { - if (enqueueReceive(receive)) { - if (waiter is SelectInstance<*>) { - waiter.disposeOnCompletion(RemoveReceiveOnCancel(receive)) - } else { - waiter as CancellableContinuation - removeReceiveOnCancel(waiter, receive) - } - return - } - // hm... something is not right. try to poll - val result = pollInternal() - if (result is Closed<*>) { - if (waiter is SelectInstance<*>) { - waiter.selectInRegistrationPhase(result) - } else { // CancellableContinuation - receive.resumeReceiveClosed(result) - } - return - } - if (result !== POLL_FAILED) { - if (waiter is SelectInstance<*>) { - waiter.selectInRegistrationPhase(result as E) - } else { - waiter as CancellableContinuation - receive as ReceiveElement - waiter.resume(receive.resumeValue(result as E), receive.resumeOnCancellationFun(result as E)) - } - return - } - } - } - - protected open fun enqueueReceiveInternal(receive: Receive): Boolean = if (isBufferAlwaysEmpty) - queue.addLastIfPrev(receive) { it !is Send } else - queue.addLastIfPrevAndIf(receive, { it !is Send }, { isBufferEmpty }) - - private fun enqueueReceive(receive: Receive) = enqueueReceiveInternal(receive).also { result -> - if (result) onReceiveEnqueued() - } - - @Suppress("UNCHECKED_CAST") - public final override suspend fun receiveCatching(): ChannelResult { - // fast path -- try poll non-blocking - val result = pollInternal() - if (result !== POLL_FAILED) return result.toResult() - // slow-path does suspend - return receiveSuspend(RECEIVE_RESULT) - } - - @Suppress("UNCHECKED_CAST") - public final override fun tryReceive(): ChannelResult { - val result = pollInternal() - if (result === POLL_FAILED) return ChannelResult.failure() - if (result is Closed<*>) return ChannelResult.closed(result.closeCause) - return ChannelResult.success(result as E) - } - - @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") - final override fun cancel(cause: Throwable?): Boolean = - cancelInternal(cause) - - final override fun cancel(cause: CancellationException?) { - /* - * Do not create an exception if channel is already cancelled. - * Channel is closed for receive when either it is cancelled (then we are free to bail out) - * or was closed and elements were received. - * Then `onCancelIdempotent` does nothing for all implementations. - */ - if (isClosedForReceive) return - cancelInternal(cause ?: CancellationException("$classSimpleName was cancelled")) - } - - // It needs to be internal to support deprecated cancel(Throwable?) API - internal fun cancelInternal(cause: Throwable?): Boolean = - close(cause).also { - onCancelIdempotent(it) - } - - /** - * Method that is invoked right after [close] in [cancel] sequence. - * [wasClosed] is directly mapped to the value returned by [close]. - */ - protected open fun onCancelIdempotent(wasClosed: Boolean) { - /* - * See the comment to helpClose, all these machinery (reversed order of iteration, postponed resume) - * has the same rationale. - */ - val closed = closedForSend ?: error("Cannot happen") - var list = InlineList() - while (true) { - val previous = closed.prevNode - if (previous is LockFreeLinkedListHead) { - break - } - assert { previous is Send } - if (!previous.remove()) { - previous.helpRemove() // make sure remove is complete before continuing - continue - } - // Add to the list only **after** successful removal - list += previous as Send - } - onCancelIdempotentList(list, closed) - } - - /** - * This method is overridden by [LinkedListChannel] to handle cancellation of [SendBuffered] elements from the list. - */ - protected open fun onCancelIdempotentList(list: InlineList, closed: Closed<*>) { - list.forEachReversed { it.resumeSendClosed(closed) } - } - - public final override fun iterator(): ChannelIterator = Itr(this) - - // ------ registerSelectReceive ------ - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - protected fun describeTryPoll(): TryPollDesc = TryPollDesc(queue) - - /** - * @suppress **This is unstable API and it is subject to change.** - */ - protected class TryPollDesc(queue: LockFreeLinkedListHead) : RemoveFirstDesc(queue) { - override fun failure(affected: LockFreeLinkedListNode): Any? = when (affected) { - is Closed<*> -> affected - !is Send -> POLL_FAILED - else -> null - } - - @Suppress("UNCHECKED_CAST") - override fun onPrepare(prepareOp: PrepareOp): Any? { - val affected = prepareOp.affected as Send // see "failure" impl - val token = affected.tryResumeSend(prepareOp) ?: return REMOVE_PREPARED - if (token === RETRY_ATOMIC) return RETRY_ATOMIC - assert { token === RESUME_TOKEN } - return null - } - - override fun onRemoved(affected: LockFreeLinkedListNode) { - // Called when we removed it from the queue but were too late to resume, so we have undelivered element - (affected as Send).undeliveredElement() - } - } - - // ------ the `select` expression support ------ - - final override val onReceive - get() = SelectClause1Impl( - clauseObject = this, - regFunc = AbstractChannel<*>::registerSelectForReceive as RegistrationFunction, - processResFunc = AbstractChannel<*>::processResultSelectReceive as ProcessResultFunction, - onCancellationConstructor = onUndeliveredElementReceiveCancellationConstructor - ) - - final override val onReceiveCatching - get() = SelectClause1Impl>( - clauseObject = this, - regFunc = AbstractChannel<*>::registerSelectForReceive as RegistrationFunction, - processResFunc = AbstractChannel<*>::processResultSelectReceiveCatching as ProcessResultFunction, - onCancellationConstructor = onUndeliveredElementReceiveCancellationConstructor - ) - - @Deprecated( - message = "Deprecated in favor of onReceiveCatching extension", - level = DeprecationLevel.ERROR, - replaceWith = ReplaceWith("onReceiveCatching") - ) // See super() - override val onReceiveOrNull: SelectClause1 - get() = SelectClause1Impl( - clauseObject = this, - regFunc = AbstractChannel<*>::registerSelectForReceive as RegistrationFunction, - processResFunc = AbstractChannel<*>::processResultSelectReceiveOrNull as ProcessResultFunction, - onCancellationConstructor = onUndeliveredElementReceiveCancellationConstructor - ) - - private fun registerSelectForReceive(select: SelectInstance<*>, ignoredParam: Any?) { - val result = pollInternal() - if (result !== POLL_FAILED && result !is Closed<*>) { - select.selectInRegistrationPhase(result as E) - return - } - receiveSlowPath(RECEIVE_RESULT, select) - } - - private fun processResultSelectReceive(ignoredParam: Any?, selectResult: Any?): Any? = - if (selectResult is Closed<*>) throw selectResult.receiveException - else selectResult as E - - private fun processResultSelectReceiveCatching(ignoredParam: Any?, selectResult: Any?): Any = - if (selectResult is Closed<*>) ChannelResult.closed(selectResult.closeCause) - else ChannelResult.success(selectResult as E) - - private fun processResultSelectReceiveOrNull(ignoredParam: Any?, selectResult: Any?): Any? = - if (selectResult is Closed<*>) null - else selectResult as E - - private val onUndeliveredElementReceiveCancellationConstructor: OnCancellationConstructor? = onUndeliveredElement?.let { - { select: SelectInstance<*>, _: Any?, element: Any? -> - { _: Throwable -> if (element !is Closed<*>) onUndeliveredElement.callUndeliveredElement(element as E, select.context) } - } - } - - // ------ protected ------ - - override fun takeFirstReceiveOrPeekClosed(): ReceiveOrClosed? = - super.takeFirstReceiveOrPeekClosed().also { - if (it != null && it !is Closed<*>) onReceiveDequeued() - } - - /** - * Invoked when receiver is successfully enqueued to the queue of waiting receivers. - * @suppress **This is unstable API and it is subject to change.** - */ - protected open fun onReceiveEnqueued() {} - - /** - * Invoked when enqueued receiver was successfully removed from the queue of waiting receivers. - * @suppress **This is unstable API and it is subject to change.** - */ - protected open fun onReceiveDequeued() {} - - // ------ private ------ - - private fun removeReceiveOnCancel(cont: CancellableContinuation<*>, receive: Receive<*>) = - cont.invokeOnCancellation(handler = RemoveReceiveOnCancel(receive).asHandler) - - private inner class RemoveReceiveOnCancel(private val receive: Receive<*>) : BeforeResumeCancelHandler(), DisposableHandle { - override fun invoke(cause: Throwable?) = dispose() - - override fun dispose() { - if (receive.remove()) - onReceiveDequeued() - } - - override fun toString(): String = "RemoveReceiveOnCancel[$receive]" - } - - private class Itr(@JvmField val channel: AbstractChannel) : ChannelIterator { - var result: Any? = POLL_FAILED // E | POLL_FAILED | Closed - - override suspend fun hasNext(): Boolean { - // check for repeated hasNext - if (result !== POLL_FAILED) return hasNextResult(result) - // fast path -- try poll non-blocking - result = channel.pollInternal() - if (result !== POLL_FAILED) return hasNextResult(result) - // slow-path does suspend - return hasNextSuspend() - } - - private fun hasNextResult(result: Any?): Boolean { - if (result is Closed<*>) { - if (result.closeCause != null) throw recoverStackTrace(result.receiveException) - return false - } - return true - } - - private suspend fun hasNextSuspend(): Boolean = suspendCancellableCoroutineReusable sc@ { cont -> - val receive = ReceiveHasNext(this, cont) - while (true) { - if (channel.enqueueReceive(receive)) { - channel.removeReceiveOnCancel(cont, receive) - return@sc - } - // hm... something is not right. try to poll - val result = channel.pollInternal() - this.result = result - if (result is Closed<*>) { - if (result.closeCause == null) - cont.resume(false) - else - cont.resumeWithException(result.receiveException) - return@sc - } - if (result !== POLL_FAILED) { - @Suppress("UNCHECKED_CAST") - cont.resume(true, channel.onUndeliveredElement?.bindCancellationFun(result as E, cont.context)) - return@sc - } - } - } - - @Suppress("UNCHECKED_CAST") - override fun next(): E { - val result = this.result - if (result is Closed<*>) throw recoverStackTrace(result.receiveException) - if (result !== POLL_FAILED) { - this.result = POLL_FAILED - return result as E - } - - throw IllegalStateException("'hasNext' should be called prior to 'next' invocation") - } - } - - private open class ReceiveElement( - @JvmField val cont: CancellableContinuation, - @JvmField val receiveMode: Int - ) : Receive() { - fun resumeValue(value: E): Any? = when (receiveMode) { - RECEIVE_RESULT -> ChannelResult.success(value) - else -> value - } - - override fun tryResumeReceive(value: E): Symbol? { - val token = cont.tryResume(resumeValue(value), null, resumeOnCancellationFun(value)) ?: return null - assert { token === RESUME_TOKEN } // the only other possible result - return RESUME_TOKEN - } - - override fun completeResumeReceive(value: E) = cont.completeResume(RESUME_TOKEN) - - override fun resumeReceiveClosed(closed: Closed<*>) { - when (receiveMode) { - RECEIVE_RESULT -> cont.resume(closed.toResult()) - else -> cont.resumeWithException(closed.receiveException) - } - } - override fun toString(): String = "ReceiveElement@$hexAddress[receiveMode=$receiveMode]" - } - - private class ReceiveElementWithUndeliveredHandler( - cont: CancellableContinuation, - receiveMode: Int, - @JvmField val onUndeliveredElement: OnUndeliveredElement - ) : ReceiveElement(cont, receiveMode) { - override fun resumeOnCancellationFun(value: E): ((Throwable) -> Unit)? = - onUndeliveredElement.bindCancellationFun(value, cont.context) - } - - internal open inner class ReceiveElementSelect( - @JvmField val select: SelectInstance<*>, - ) : Receive() { - private val lock = ReentrantLock() - private var success: Boolean? = null - - override fun tryResumeReceive(value: E): Symbol? = lock.withLock { - if (success == null) success = select.trySelect(this@AbstractChannel, value) - if (success!!) RESUME_TOKEN else null - } - override fun completeResumeReceive(value: E) {} - - override fun resumeReceiveClosed(closed: Closed<*>) { - select.trySelect(this@AbstractChannel, closed) - } - override fun toString(): String = "ReceiveElementSelect@$hexAddress" - } - - private open inner class ReceiveElementSelectWithUndeliveredHandler( - select: SelectInstance<*>, - @JvmField val onUndeliveredElement: OnUndeliveredElement - ) : ReceiveElementSelect(select) { - override fun resumeOnCancellationFun(value: E): ((Throwable) -> Unit)? = - onUndeliveredElement.bindCancellationFun(value, select.context) - } - - private open class ReceiveHasNext( - @JvmField val iterator: Itr, - @JvmField val cont: CancellableContinuation - ) : Receive() { - override fun tryResumeReceive(value: E): Symbol? { - val token = cont.tryResume(true, null, resumeOnCancellationFun(value)) - ?: return null - assert { token === RESUME_TOKEN } // the only other possible result - return RESUME_TOKEN - } - - override fun completeResumeReceive(value: E) { - iterator.result = value - cont.completeResume(RESUME_TOKEN) - } - - override fun resumeReceiveClosed(closed: Closed<*>) { - val token = if (closed.closeCause == null) { - cont.tryResume(false) - } else { - cont.tryResumeWithException(closed.receiveException) - } - if (token != null) { - iterator.result = closed - cont.completeResume(token) - } - } - - override fun resumeOnCancellationFun(value: E): ((Throwable) -> Unit)? = - iterator.channel.onUndeliveredElement?.bindCancellationFun(value, cont.context) - - override fun toString(): String = "ReceiveHasNext@$hexAddress" - } -} - -// receiveMode values -internal const val RECEIVE_THROWS_ON_CLOSE = 0 -internal const val RECEIVE_RESULT = 1 - -@JvmField -internal val EMPTY = Symbol("EMPTY") // marker for Conflated & Buffered channels - -@JvmField -internal val OFFER_SUCCESS = Symbol("OFFER_SUCCESS") - -@JvmField -internal val OFFER_FAILED = Symbol("OFFER_FAILED") - -@JvmField -internal val POLL_FAILED = Symbol("POLL_FAILED") - -@JvmField -internal val ENQUEUE_FAILED = Symbol("ENQUEUE_FAILED") - -@JvmField -internal val HANDLER_INVOKED = Symbol("ON_CLOSE_HANDLER_INVOKED") - -internal typealias Handler = (Throwable?) -> Unit - -/** - * Represents sending waiter in the queue. - */ -internal abstract class -Send : LockFreeLinkedListNode() { - abstract val pollResult: Any? // E | Closed - the result pollInternal returns when it rendezvous with this node - // Returns: null - failure, - // RETRY_ATOMIC for retry (only when otherOp != null), - // RESUME_TOKEN on success (call completeResumeSend) - // Must call otherOp?.finishPrepare() after deciding on result other than RETRY_ATOMIC - abstract fun tryResumeSend(otherOp: PrepareOp?): Symbol? - abstract fun completeResumeSend() - abstract fun resumeSendClosed(closed: Closed<*>) - open fun undeliveredElement() {} -} - -/** - * Represents receiver waiter in the queue or closed token. - */ -internal interface ReceiveOrClosed { - val offerResult: Any // OFFER_SUCCESS | Closed - // Returns: null - failure, - // RESUME_TOKEN on success (call completeResumeReceive) - fun tryResumeReceive(value: E): Symbol? - fun completeResumeReceive(value: E) -} - -/** - * Represents sender for a specific element. - */ -internal open class SendElement( - override val pollResult: E, - @JvmField val cont: CancellableContinuation -) : Send() { - override fun tryResumeSend(otherOp: PrepareOp?): Symbol? { - val token = cont.tryResume(Unit, otherOp?.desc) ?: return null - assert { token === RESUME_TOKEN } // the only other possible result - // We can call finishPrepare only after successful tryResume, so that only good affected node is saved - otherOp?.finishPrepare() // finish preparations - return RESUME_TOKEN - } - - override fun completeResumeSend() = cont.completeResume(RESUME_TOKEN) - override fun resumeSendClosed(closed: Closed<*>) = cont.resumeWithException(closed.sendException) - override fun toString(): String = "$classSimpleName@$hexAddress($pollResult)" -} - -internal open class SendElementSelect( - override val pollResult: E, - @JvmField val select: SelectInstance<*>, - private val channel: SendChannel -) : Send() { - private val lock = ReentrantLock() - private var _trySelectResult: TrySelectDetailedResult? = null - - override fun tryResumeSend(otherOp: PrepareOp?): Symbol? = lock.withLock { - select as SelectImplementation<*> - if (_trySelectResult == null) { - _trySelectResult = select.trySelectDetailed(channel, Unit) - } - return@withLock if (_trySelectResult == SUCCESSFUL) { - otherOp?.finishPrepare() // finish preparations - RESUME_TOKEN - } else null - } - - val trySelectResult: TrySelectDetailedResult - get() = checkNotNull(_trySelectResult) { "trySelect(..) has not been invoked yet" } - - override fun completeResumeSend() {} - override fun resumeSendClosed(closed: Closed<*>) { - select.trySelect(channel, closed) - } - override fun toString(): String = "$classSimpleName@$hexAddress($pollResult)" -} - -internal class SendElementWithUndeliveredHandler( - pollResult: E, - cont: CancellableContinuation, - @JvmField val onUndeliveredElement: OnUndeliveredElement -) : SendElement(pollResult, cont) { - override fun remove(): Boolean { - if (!super.remove()) return false - // if the node was successfully removed (meaning it was added but was not received) then we have undelivered element - undeliveredElement() - return true - } - - override fun undeliveredElement() { - onUndeliveredElement.callUndeliveredElement(pollResult, cont.context) - } -} - -internal class SendElementSelectWithUndeliveredHandler( - pollResult: E, - select: SelectInstance<*>, - channel: SendChannel, - onUndeliveredElement: OnUndeliveredElement -) : SendElementSelect(pollResult, select, channel) { - private val onUndeliveredElement = atomic?>(onUndeliveredElement) - - override fun remove(): Boolean { - undeliveredElement() - return super.remove() - } - - override fun undeliveredElement() { - onUndeliveredElement.getAndSet(null)?.callUndeliveredElement(pollResult, select.context) - } -} -/** - * Represents closed channel. - */ -internal class Closed( - @JvmField val closeCause: Throwable? -) : Send(), ReceiveOrClosed { - val sendException: Throwable get() = closeCause ?: ClosedSendChannelException(DEFAULT_CLOSE_MESSAGE) - val receiveException: Throwable get() = closeCause ?: ClosedReceiveChannelException(DEFAULT_CLOSE_MESSAGE) - - override val offerResult get() = this - override val pollResult get() = this - override fun tryResumeSend(otherOp: PrepareOp?): Symbol = RESUME_TOKEN.also { otherOp?.finishPrepare() } - override fun completeResumeSend() {} - override fun tryResumeReceive(value: E): Symbol = RESUME_TOKEN - override fun completeResumeReceive(value: E) {} - override fun resumeSendClosed(closed: Closed<*>) = assert { false } // "Should be never invoked" - override fun toString(): String = "Closed@$hexAddress[$closeCause]" -} - -internal abstract class Receive : LockFreeLinkedListNode(), ReceiveOrClosed { - override val offerResult get() = OFFER_SUCCESS - abstract fun resumeReceiveClosed(closed: Closed<*>) - open fun resumeOnCancellationFun(value: E): ((Throwable) -> Unit)? = null -} - -@Suppress("NOTHING_TO_INLINE", "UNCHECKED_CAST") -private inline fun Any?.toResult(): ChannelResult = - if (this is Closed<*>) ChannelResult.closed(closeCause) else ChannelResult.success(this as E) - -@Suppress("NOTHING_TO_INLINE") -private inline fun Closed<*>.toResult(): ChannelResult = ChannelResult.closed(closeCause) diff --git a/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt deleted file mode 100644 index a9aaf08a53..0000000000 --- a/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt +++ /dev/null @@ -1,329 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines.channels - -import kotlinx.atomicfu.* -import kotlinx.coroutines.* -import kotlinx.coroutines.internal.* -import kotlinx.coroutines.selects.* - -/** - * Broadcast channel with array buffer of a fixed [capacity]. - * Sender suspends only when buffer is full due to one of the receives being slow to consume and - * receiver suspends only when buffer is empty. - * - * **Note**, that elements that are sent to this channel while there are no - * [openSubscription] subscribers are immediately lost. - * - * This channel is created by `BroadcastChannel(capacity)` factory function invocation. - * - * This implementation uses lock to protect the buffer, which is held only during very short buffer-update operations. - * The lock at each subscription is also used to manage concurrent attempts to receive from the same subscriber. - * The lists of suspended senders or receivers are lock-free. - */ -internal class ArrayBroadcastChannel( - /** - * Buffer capacity. - */ - val capacity: Int -) : AbstractSendChannel(null), BroadcastChannel { - init { - require(capacity >= 1) { "ArrayBroadcastChannel capacity must be at least 1, but $capacity was specified" } - } - - /** - * NB: prior to changing any logic of ArrayBroadcastChannel internals, please ensure that - * you do not break internal invariants of the SubscriberList implementation on K/N and KJS - */ - - /* - * Writes to buffer are guarded by bufferLock, but reads from buffer are concurrent with writes - * - Write element to buffer then write "tail" (volatile) - * - Read "tail" (volatile), then read element from buffer - * So read/writes to buffer need not be volatile - */ - private val bufferLock = ReentrantLock() - private val buffer = arrayOfNulls(capacity) - - // head & tail are Long (64 bits) and we assume that they never wrap around - // head, tail, and size are guarded by bufferLock - - private val _head = atomic(0L) - private var head: Long // do modulo on use of head - get() = _head.value - set(value) { _head.value = value } - - private val _tail = atomic(0L) - private var tail: Long // do modulo on use of tail - get() = _tail.value - set(value) { _tail.value = value } - - private val _size = atomic(0) - private var size: Int - get() = _size.value - set(value) { _size.value = value } - - @Suppress("DEPRECATION") - private val subscribers = subscriberList>() - - override val isBufferAlwaysFull: Boolean get() = false - override val isBufferFull: Boolean get() = size >= capacity - - public override fun openSubscription(): ReceiveChannel = - Subscriber(this).also { - updateHead(addSub = it) - } - - public override fun close(cause: Throwable?): Boolean { - if (!super.close(cause)) return false - checkSubOffers() - return true - } - - @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") - override fun cancel(cause: Throwable?): Boolean = - cancelInternal(cause) - - override fun cancel(cause: CancellationException?) { - cancelInternal(cause) - } - - private fun cancelInternal(cause: Throwable?): Boolean = - close(cause).also { - for (sub in subscribers) sub.cancelInternal(cause) - } - - // result is `OFFER_SUCCESS | OFFER_FAILED | Closed` - override fun offerInternal(element: E): Any { - bufferLock.withLock { - // check if closed for send (under lock, so size cannot change) - closedForSend?.let { return it } - val size = this.size - if (size >= capacity) return OFFER_FAILED - val tail = this.tail - buffer[(tail % capacity).toInt()] = element - this.size = size + 1 - this.tail = tail + 1 - } - // if offered successfully, then check subscribers outside of lock - checkSubOffers() - return OFFER_SUCCESS - } - - private fun checkSubOffers() { - var updated = false - var hasSubs = false - @Suppress("LoopToCallChain") // must invoke `checkOffer` on every sub - for (sub in subscribers) { - hasSubs = true - if (sub.checkOffer()) updated = true - } - if (updated || !hasSubs) - updateHead() - } - - // updates head if needed and optionally adds / removes subscriber under the same lock - private tailrec fun updateHead(addSub: Subscriber? = null, removeSub: Subscriber? = null) { - // update head in a tail rec loop - var send: Send? = null - bufferLock.withLock { - if (addSub != null) { - addSub.subHead = tail // start from last element - val wasEmpty = subscribers.isEmpty() - subscribers.add(addSub) - if (!wasEmpty) return // no need to update when adding second and etc sub - } - if (removeSub != null) { - subscribers.remove(removeSub) - if (head != removeSub.subHead) return // no need to update - } - val minHead = computeMinHead() - val tail = this.tail - var head = this.head - val targetHead = minHead.coerceAtMost(tail) - if (targetHead <= head) return // nothing to do -- head was already moved - var size = this.size - // clean up removed (on not need if we don't have any subscribers anymore) - while (head < targetHead) { - buffer[(head % capacity).toInt()] = null - val wasFull = size >= capacity - // update the size before checking queue (no more senders can queue up) - this.head = ++head - this.size = --size - if (wasFull) { - while (true) { - send = takeFirstSendOrPeekClosed() ?: break // when when no sender - if (send is Closed<*>) break // break when closed for send - val token = send!!.tryResumeSend(null) - if (token != null) { - assert { token === RESUME_TOKEN } - // put sent element to the buffer - buffer[(tail % capacity).toInt()] = (send as Send).pollResult - this.size = size + 1 - this.tail = tail + 1 - return@withLock // go out of lock to wakeup this sender - } - // Too late, already cancelled, but we removed it from the queue and need to release resources. - // However, ArrayBroadcastChannel does not support onUndeliveredElement, so nothing to do - } - } - } - return // done updating here -> return - } - // we only get out of the lock normally when there is a sender to resume - send!!.completeResumeSend() - // since we've just sent an element, we might need to resume some receivers - checkSubOffers() - // tailrec call to recheck - updateHead() - } - - private fun computeMinHead(): Long { - var minHead = Long.MAX_VALUE - for (sub in subscribers) - minHead = minHead.coerceAtMost(sub.subHead) // volatile (atomic) reads of subHead - return minHead - } - - @Suppress("UNCHECKED_CAST") - private fun elementAt(index: Long): E = buffer[(index % capacity).toInt()] as E - - private class Subscriber( - private val broadcastChannel: ArrayBroadcastChannel - ) : AbstractChannel(null), ReceiveChannel { - private val subLock = ReentrantLock() - - private val _subHead = atomic(0L) - var subHead: Long // guarded by subLock - get() = _subHead.value - set(value) { _subHead.value = value } - - override val isBufferAlwaysEmpty: Boolean get() = false - override val isBufferEmpty: Boolean get() = subHead >= broadcastChannel.tail - override val isBufferAlwaysFull: Boolean get() = error("Should not be used") - override val isBufferFull: Boolean get() = error("Should not be used") - - override fun close(cause: Throwable?): Boolean { - val wasClosed = super.close(cause) - if (wasClosed) { - broadcastChannel.updateHead(removeSub = this) - subLock.withLock { - subHead = broadcastChannel.tail - } - } - return wasClosed - } - - // returns true if subHead was updated and broadcast channel's head must be checked - // this method is lock-free (it never waits on lock) - @Suppress("UNCHECKED_CAST") - fun checkOffer(): Boolean { - var updated = false - var closed: Closed<*>? = null - loop@ - while (needsToCheckOfferWithoutLock()) { - // just use `tryLock` here and break when some other thread is checking under lock - // it means that `checkOffer` must be retried after every `unlock` - if (!subLock.tryLock()) break - val receive: ReceiveOrClosed? - var result: Any? - try { - result = peekUnderLock() - when { - result === POLL_FAILED -> continue@loop // must retest `needsToCheckOfferWithoutLock` outside of the lock - result is Closed<*> -> { - closed = result - break@loop // was closed - } - } - // find a receiver for an element - receive = takeFirstReceiveOrPeekClosed() ?: break // break when no one's receiving - if (receive is Closed<*>) break // noting more to do if this sub already closed - val token = receive.tryResumeReceive(result as E) ?: continue - assert { token === RESUME_TOKEN } - val subHead = this.subHead - this.subHead = subHead + 1 // retrieved element for this subscriber - updated = true - } finally { - subLock.unlock() - } - receive!!.completeResumeReceive(result as E) - } - // do close outside of lock if needed - closed?.also { close(cause = it.closeCause) } - return updated - } - - // result is `E | POLL_FAILED | Closed` - override fun pollInternal(): Any? { - var updated = false - val result = subLock.withLock { - val result = peekUnderLock() - when { - result is Closed<*> -> { /* just bail out of lock */ } - result === POLL_FAILED -> { /* just bail out of lock */ } - else -> { - // update subHead after retrieiving element from buffer - val subHead = this.subHead - this.subHead = subHead + 1 - updated = true - } - } - result - } - // do close outside of lock - (result as? Closed<*>)?.also { close(cause = it.closeCause) } - // there could have been checkOffer attempt while we were holding lock - // now outside the lock recheck if anything else to offer - if (checkOffer()) - updated = true - // and finally update broadcast's channel head if needed - if (updated) - broadcastChannel.updateHead() - return result - } - - // Must invoke this check this after lock, because offer's invocation of `checkOffer` might have failed - // to `tryLock` just before the lock was about to unlocked, thus loosing notification to this - // subscription about an element that was just offered - private fun needsToCheckOfferWithoutLock(): Boolean { - if (closedForReceive != null) - return false // already closed -> nothing to do - if (isBufferEmpty && broadcastChannel.closedForReceive == null) - return false // no data for us && broadcast channel was not closed yet -> nothing to do - return true // check otherwise - } - - // guarded by lock, returns: - // E - the element from the buffer at subHead - // Closed<*> when closed; - // POLL_FAILED when there seems to be no data, but must retest `needsToCheckOfferWithoutLock` outside of lock - private fun peekUnderLock(): Any? { - val subHead = this.subHead // guarded read (can be non-volatile read) - // note: from the broadcastChannel we must read closed token first, then read its tail - // because it is Ok if tail moves in between the reads (we make decision based on tail first) - val closedBroadcast = broadcastChannel.closedForReceive // unguarded volatile read - val tail = broadcastChannel.tail // unguarded volatile read - if (subHead >= tail) { - // no elements to poll from the queue -- check if closed broads & closed this sub - // must retest `needsToCheckOfferWithoutLock` outside of the lock - return closedBroadcast ?: this.closedForReceive ?: POLL_FAILED - } - // Get tentative result. This result may be wrong (completely invalid value, including null), - // because this subscription might get closed, moving channel's head past this subscription's head. - val result = broadcastChannel.elementAt(subHead) - // now check if this subscription was closed - val closedSub = this.closedForReceive - if (closedSub != null) return closedSub - // we know the subscription was not closed, so this tentative result is Ok to return - return result - } - } - - // ------ debug ------ - - override val bufferDebugString: String - get() = "(buffer:capacity=${buffer.size},size=$size)" -} diff --git a/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt b/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt deleted file mode 100644 index abf18ac39a..0000000000 --- a/kotlinx-coroutines-core/common/src/channels/ArrayChannel.kt +++ /dev/null @@ -1,209 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines.channels - -import kotlinx.atomicfu.* -import kotlinx.coroutines.* -import kotlinx.coroutines.internal.* -import kotlinx.coroutines.selects.TrySelectDetailedResult.* -import kotlin.math.* - -/** - * Channel with array buffer of a fixed [capacity]. - * Sender suspends only when buffer is full and receiver suspends only when buffer is empty. - * - * This channel is created by `Channel(capacity)` factory function invocation. - * - * This implementation uses lock to protect the buffer, which is held only during very short buffer-update operations. - * The lists of suspended senders or receivers are lock-free. - **/ -internal open class ArrayChannel( - /** - * Buffer capacity. - */ - private val capacity: Int, - private val onBufferOverflow: BufferOverflow, - onUndeliveredElement: OnUndeliveredElement? -) : AbstractChannel(onUndeliveredElement) { - init { - // This check is actually used by the Channel(...) constructor function which checks only for known - // capacities and calls ArrayChannel constructor for everything else. - require(capacity >= 1) { "ArrayChannel capacity must be at least 1, but $capacity was specified" } - } - - private val lock = ReentrantLock() - - /* - * Guarded by lock. - * Allocate minimum of capacity and 16 to avoid excess memory pressure for large channels when it's not necessary. - */ - private var buffer: Array = arrayOfNulls(min(capacity, 8)).apply { fill(EMPTY) } - - private var head: Int = 0 - private val size = atomic(0) // Invariant: size <= capacity - - protected final override val isBufferAlwaysEmpty: Boolean get() = false - protected final override val isBufferEmpty: Boolean get() = size.value == 0 - protected final override val isBufferAlwaysFull: Boolean get() = false - protected final override val isBufferFull: Boolean get() = size.value == capacity && onBufferOverflow == BufferOverflow.SUSPEND - - override val isEmpty: Boolean get() = lock.withLock { isEmptyImpl } - override val isClosedForReceive: Boolean get() = lock.withLock { super.isClosedForReceive } - - // result is `OFFER_SUCCESS | OFFER_FAILED | Closed` - protected override fun offerInternal(element: E): Any { - var receive: ReceiveOrClosed? = null - lock.withLock { - val size = this.size.value - closedForSend?.let { return it } - // update size before checking queue (!!!) - updateBufferSize(size)?.let { return it } - // check for receivers that were waiting on empty queue - if (size == 0) { - loop@ while (true) { - receive = takeFirstReceiveOrPeekClosed() ?: break@loop // break when no receivers queued - if (receive is Closed) { - this.size.value = size // restore size - return receive!! - } - val token = receive!!.tryResumeReceive(element) - if (token != null) { - assert { token === RESUME_TOKEN } - this.size.value = size // restore size - return@withLock - } - } - } - enqueueElement(size, element) - return OFFER_SUCCESS - } - // breaks here if offer meets receiver - receive!!.completeResumeReceive(element) - return receive!!.offerResult - } - - override fun enqueueSend(send: Send): Any? = lock.withLock { - super.enqueueSend(send) - } - - // Guarded by lock - // Result is `OFFER_SUCCESS | OFFER_FAILED | null` - private fun updateBufferSize(currentSize: Int): Symbol? { - if (currentSize < capacity) { - size.value = currentSize + 1 // tentatively put it into the buffer - return null // proceed - } - // buffer is full - return when (onBufferOverflow) { - BufferOverflow.SUSPEND -> OFFER_FAILED - BufferOverflow.DROP_LATEST -> OFFER_SUCCESS - BufferOverflow.DROP_OLDEST -> null // proceed, will drop oldest in enqueueElement - } - } - - // Guarded by lock - private fun enqueueElement(currentSize: Int, element: E) { - if (currentSize < capacity) { - ensureCapacity(currentSize) - buffer[(head + currentSize) % buffer.size] = element // actually queue element - } else { - // buffer is full - assert { onBufferOverflow == BufferOverflow.DROP_OLDEST } // the only way we can get here - buffer[head % buffer.size] = null // drop oldest element - buffer[(head + currentSize) % buffer.size] = element // actually queue element - head = (head + 1) % buffer.size - } - } - - // Guarded by lock - private fun ensureCapacity(currentSize: Int) { - if (currentSize >= buffer.size) { - val newSize = min(buffer.size * 2, capacity) - val newBuffer = arrayOfNulls(newSize) - for (i in 0 until currentSize) { - newBuffer[i] = buffer[(head + i) % buffer.size] - } - newBuffer.fill(EMPTY, currentSize, newSize) - buffer = newBuffer - head = 0 - } - } - - // result is `E | POLL_FAILED | Closed` - protected override fun pollInternal(): Any? { - var send: Send? = null - var resumed = false - var result: Any? = null - lock.withLock { - val size = this.size.value - if (size == 0) return closedForSend ?: POLL_FAILED // when nothing can be read from buffer - // size > 0: not empty -- retrieve element - result = buffer[head] - buffer[head] = null - this.size.value = size - 1 // update size before checking queue (!!!) - // check for senders that were waiting on full queue - var replacement: Any? = POLL_FAILED - if (size == capacity) { - loop@ while (true) { - send = takeFirstSendOrPeekClosed() ?: break - val token = send!!.tryResumeSend(null) - if (token != null) { - assert { token === RESUME_TOKEN } - resumed = true - replacement = send!!.pollResult - break@loop - } - // Too late, already cancelled, but we removed it from the queue and need to notify on undelivered element. - // The only exception is when this "send" operation is an `onSend` clause that has to be re-registered - // in the corresponding `select` invocation. - send!!.apply { - if (!(this is SendElementSelectWithUndeliveredHandler<*> && trySelectResult == REREGISTER)) - undeliveredElement() - } - } - } - if (replacement !== POLL_FAILED && replacement !is Closed<*>) { - this.size.value = size // restore size - buffer[(head + size) % buffer.size] = replacement - } - head = (head + 1) % buffer.size - } - // complete send the we're taken replacement from - if (resumed) - send!!.completeResumeSend() - return result - } - - override fun enqueueReceiveInternal(receive: Receive): Boolean = lock.withLock { - super.enqueueReceiveInternal(receive) - } - - // Note: this function is invoked when channel is already closed - override fun onCancelIdempotent(wasClosed: Boolean) { - // clear buffer first, but do not wait for it in helpers - val onUndeliveredElement = onUndeliveredElement - var undeliveredElementException: UndeliveredElementException? = null // first cancel exception, others suppressed - lock.withLock { - repeat(size.value) { - val value = buffer[head] - if (onUndeliveredElement != null && value !== EMPTY) { - @Suppress("UNCHECKED_CAST") - undeliveredElementException = onUndeliveredElement.callUndeliveredElementCatchingException(value as E, undeliveredElementException) - } - buffer[head] = EMPTY - head = (head + 1) % buffer.size - } - size.value = 0 - } - // then clean all queued senders - super.onCancelIdempotent(wasClosed) - undeliveredElementException?.let { throw it } // throw UndeliveredElementException at the end if there was one - } - - // ------ debug ------ - - override val bufferDebugString: String - get() = "(buffer:capacity=$capacity,size=${size.value})" -} diff --git a/kotlinx-coroutines-core/common/src/channels/Broadcast.kt b/kotlinx-coroutines-core/common/src/channels/Broadcast.kt index ba39d4ff7e..0a76c82f6a 100644 --- a/kotlinx-coroutines-core/common/src/channels/Broadcast.kt +++ b/kotlinx-coroutines-core/common/src/channels/Broadcast.kt @@ -16,7 +16,7 @@ import kotlin.coroutines.intrinsics.* * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. * * The kind of the resulting channel depends on the specified [capacity] parameter: - * when `capacity` is positive (1 by default), but less than [UNLIMITED] -- uses `ArrayBroadcastChannel` with a buffer of given capacity, + * when `capacity` is positive (1 by default), but less than [UNLIMITED] -- uses [BroadcastChannel] with a buffer of given capacity, * when `capacity` is [CONFLATED] -- uses [ConflatedBroadcastChannel] that conflates back-to-back sends; * Note that resulting channel behaves like [ConflatedBroadcastChannel] but is not an instance of [ConflatedBroadcastChannel]. * otherwise -- throws [IllegalArgumentException]. @@ -76,7 +76,7 @@ public fun ReceiveChannel.broadcast( * the resulting channel becomes _failed_, so that any attempt to receive from such a channel throws exception. * * The kind of the resulting channel depends on the specified [capacity] parameter: - * * when `capacity` is positive (1 by default), but less than [UNLIMITED] -- uses `ArrayBroadcastChannel` with a buffer of given capacity, + * * when `capacity` is positive (1 by default), but less than [UNLIMITED] -- uses [BroadcastChannel] with a buffer of given capacity, * * when `capacity` is [CONFLATED] -- uses [ConflatedBroadcastChannel] that conflates back-to-back sends; * Note that resulting channel behaves like [ConflatedBroadcastChannel] but is not an instance of [ConflatedBroadcastChannel]. * * otherwise -- throws [IllegalArgumentException]. diff --git a/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt index c82b8dbd63..de67b60a2d 100644 --- a/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt @@ -7,10 +7,14 @@ package kotlinx.coroutines.channels import kotlinx.coroutines.* +import kotlinx.coroutines.channels.BufferOverflow.* import kotlinx.coroutines.channels.Channel.Factory.BUFFERED import kotlinx.coroutines.channels.Channel.Factory.CHANNEL_DEFAULT_CAPACITY import kotlinx.coroutines.channels.Channel.Factory.CONFLATED import kotlinx.coroutines.channels.Channel.Factory.UNLIMITED +import kotlinx.coroutines.internal.* +import kotlinx.coroutines.selects.* +import kotlin.native.concurrent.* /** * Broadcast channel is a non-blocking primitive for communication between the sender and multiple receivers @@ -55,7 +59,7 @@ public interface BroadcastChannel : SendChannel { * The resulting channel type depends on the specified [capacity] parameter: * * * when `capacity` positive, but less than [UNLIMITED] -- creates `ArrayBroadcastChannel` with a buffer of given capacity. - * **Note:** this channel looses all items that are send to it until the first subscriber appears; + * **Note:** this channel looses all items that have been sent to it until the first subscriber appears; * * when `capacity` is [CONFLATED] -- creates [ConflatedBroadcastChannel] that conflates back-to-back sends; * * when `capacity` is [BUFFERED] -- creates `ArrayBroadcastChannel` with a default capacity. * * otherwise -- throws [IllegalArgumentException]. @@ -70,6 +74,339 @@ public fun BroadcastChannel(capacity: Int): BroadcastChannel = 0 -> throw IllegalArgumentException("Unsupported 0 capacity for BroadcastChannel") UNLIMITED -> throw IllegalArgumentException("Unsupported UNLIMITED capacity for BroadcastChannel") CONFLATED -> ConflatedBroadcastChannel() - BUFFERED -> ArrayBroadcastChannel(CHANNEL_DEFAULT_CAPACITY) - else -> ArrayBroadcastChannel(capacity) + BUFFERED -> BroadcastChannelImpl(CHANNEL_DEFAULT_CAPACITY) + else -> BroadcastChannelImpl(capacity) } + +/** + * Broadcasts the most recently sent element (aka [value]) to all [openSubscription] subscribers. + * + * Back-to-send sent elements are _conflated_ -- only the most recently sent value is received, + * while previously sent elements **are lost**. + * Every subscriber immediately receives the most recently sent element. + * Sender to this broadcast channel never suspends and [trySend] always succeeds. + * + * A secondary constructor can be used to create an instance of this class that already holds a value. + * This channel is also created by `BroadcastChannel(Channel.CONFLATED)` factory function invocation. + * + * In this implementation, [opening][openSubscription] and [closing][ReceiveChannel.cancel] subscription + * takes linear time in the number of subscribers. + * + * **Note: This API is obsolete since 1.5.0.** It will be deprecated with warning in 1.7.0 + * and with error in 1.8.0. It is replaced with [StateFlow][kotlinx.coroutines.flow.StateFlow]. + */ +@ObsoleteCoroutinesApi +public class ConflatedBroadcastChannel private constructor( + private val broadcast: BroadcastChannelImpl +) : BroadcastChannel by broadcast { + public constructor(): this(BroadcastChannelImpl(capacity = CONFLATED)) + /** + * Creates an instance of this class that already holds a value. + * + * It is as a shortcut to creating an instance with a default constructor and + * immediately sending an element: `ConflatedBroadcastChannel().apply { offer(value) }`. + */ + public constructor(value: E) : this() { + trySend(value) + } + + /** + * The most recently sent element to this channel. + * + * Access to this property throws [IllegalStateException] when this class is constructed without + * initial value and no value was sent yet or if it was [closed][close] without a cause. + * It throws the original [close][SendChannel.close] cause exception if the channel has _failed_. + */ + public val value: E get() = broadcast.value + /** + * The most recently sent element to this channel or `null` when this class is constructed without + * initial value and no value was sent yet or if it was [closed][close]. + */ + public val valueOrNull: E? get() = broadcast.valueOrNull +} + +/** + * A common implementation for both the broadcast channel with a buffer of fixed [capacity] + * and the conflated broadcast channel (see [ConflatedBroadcastChannel]). + * + * **Note**, that elements that are sent to this channel while there are no + * [openSubscription] subscribers are immediately lost. + * + * This channel is created by `BroadcastChannel(capacity)` factory function invocation. + */ +internal class BroadcastChannelImpl( + /** + * Buffer capacity; [Channel.CONFLATED] when this broadcast is conflated. + */ + val capacity: Int +) : BufferedChannel(capacity = Channel.RENDEZVOUS, onUndeliveredElement = null), BroadcastChannel { + init { + require(capacity >= 1 || capacity == CONFLATED) { + "BroadcastChannel capacity must be positive or Channel.CONFLATED, but $capacity was specified" + } + } + + // This implementation uses coarse-grained synchronization, + // as, reputedly, it is the simplest synchronization scheme. + // All operations are protected by this lock. + private val lock = ReentrantLock() + // The list of subscribers; all accesses should be protected by lock. + // Each change must create a new list instance to avoid `ConcurrentModificationException`. + private var subscribers: List> = emptyList() + // When this broadcast is conflated, this field stores the last sent element. + // If this channel is empty or not conflated, it stores a special `NO_ELEMENT` marker. + private var lastConflatedElement: Any? = NO_ELEMENT // NO_ELEMENT or E + + // ########################### + // # Subscription Management # + // ########################### + + override fun openSubscription(): ReceiveChannel = lock.withLock { // protected by lock + // Is this broadcast conflated or buffered? + // Create the corresponding subscription channel. + val s = if (capacity == CONFLATED) SubscriberConflated() else SubscriberBuffered() + // If this broadcast is already closed or cancelled, + // and the last sent element is not available in case + // this broadcast is conflated, close the created + // subscriber immediately and return it. + if (isClosedForSend && lastConflatedElement === NO_ELEMENT) { + s.close(closeCause) + return s + } + // Is this broadcast conflated? If so, send + // the last sent element to the subscriber. + if (lastConflatedElement !== NO_ELEMENT) { + s.trySend(value) + } + // Add the subscriber to the list and return it. + subscribers += s + s + } + + private fun removeSubscriber(s: ReceiveChannel) = lock.withLock { // protected by lock + subscribers = subscribers.filter { it !== s } + } + + // ############################# + // # The `send(..)` Operations # + // ############################# + + /** + * Sends the specified element to all subscribers. + * + * **!!! THIS IMPLEMENTATION IS NOT LINEARIZABLE !!!** + * + * As the operation should send the element to multiple + * subscribers simultaneously, it is non-trivial to + * implement it in an atomic way. Specifically, this + * would require a special implementation that does + * not transfer the element until all parties are able + * to resume it (this `send(..)` can be cancelled + * or the broadcast can become closed in the meantime). + * As broadcasts are obsolete, we keep this implementation + * as simple as possible, allowing non-linearizability + * in corner cases. + */ + override suspend fun send(element: E) { + val subs = lock.withLock { // protected by lock + // Is this channel closed for send? + if (isClosedForSend) throw sendException + // Update the last sent element if this broadcast is conflated. + if (capacity == CONFLATED) lastConflatedElement = element + // Get a reference to the list of subscribers under the lock. + subscribers + } + // The lock has been released. Send the element to the + // subscribers one-by-one, and finish immediately + // when this broadcast discovered in the closed state. + // Note that this implementation is non-linearizable; + // see this method documentation for details. + subs.forEach { + // We use special function to send the element, + // which returns `true` on success and `false` + // if the subscriber is closed. + val success = it.sendBroadcast(element) + // The sending attempt has failed. + // Check whether the broadcast is closed. + if (!success && isClosedForSend) throw sendException + } + } + + override fun trySend(element: E): ChannelResult = lock.withLock { // protected by lock + // Is this channel closed for send? + if (isClosedForSend) return super.trySend(element) + // Check whether the plain `send(..)` operation + // should suspend and fail in this case. + val shouldSuspend = subscribers.any { it.shouldSendSuspend() } + if (shouldSuspend) return ChannelResult.failure() + // Update the last sent element if this broadcast is conflated. + if (capacity == CONFLATED) lastConflatedElement = element + // Send the element to all subscribers. + // It is guaranteed that the attempt cannot fail, + // as both the broadcast closing and subscription + // cancellation are guarded by lock, which is held + // by the current operation. + subscribers.forEach { it.trySend(element) } + // Finish with success. + return ChannelResult.success(Unit) + } + + // ########################################### + // # The `select` Expression: onSend { ... } # + // ########################################### + + override fun registerSelectForSend(select: SelectInstance<*>, element: Any?) { + // It is extremely complicated to support sending via `select` for broadcasts, + // as the operation should wait on multiple subscribers simultaneously. + // At the same time, broadcasts are obsolete, so we need a simple implementation + // that works somehow. Here is a tricky work-around. First, we launch a new + // coroutine that performs plain `send(..)` operation and tries to complete + // this `select` via `trySelect`, independently on whether it is in the + // registration or in the waiting phase. On success, the operation finishes. + // On failure, if another clause is already selected or the `select` operation + // has been cancelled, we observe non-linearizable behaviour, as this `onSend` + // clause is completed as well. However, we believe that such a non-linearizability + // is fine for obsolete API. The last case is when the `select` operation is still + // in the registration case, so this `onSend` clause should be re-registered. + // The idea is that we keep information that this `onSend` clause is already selected + // and finish immediately. + @Suppress("UNCHECKED_CAST") + element as E + // First, check whether this `onSend` clause is already + // selected, finishing immediately in this case. + lock.withLock { + val result = onSendInternalResult.remove(select) + if (result != null) { // already selected! + // `result` is either `Unit` ot `CHANNEL_CLOSED`. + select.selectInRegistrationPhase(result) + return + } + } + // Start a new coroutine that performs plain `send(..)` + // and tries to select this `onSend` clause at the end. + CoroutineScope(select.context).launch(start = CoroutineStart.UNDISPATCHED) { + val success: Boolean = try { + send(element) + // The element has been successfully sent! + true + } catch (t: Throwable) { + // This broadcast must be closed. However, it is possible that + // an unrelated exception, such as `OutOfMemoryError` has been thrown. + // This implementation checks that the channel is actually closed, + // re-throwing the caught exception otherwise. + if (isClosedForSend && (t is ClosedSendChannelException || sendException === t)) false + else throw t + } + // Mark this `onSend` clause as selected and + // try to complete the `select` operation. + lock.withLock { + // Status of this `onSend` clause should not be presented yet. + assert { onSendInternalResult[select] == null } + // Success or fail? Put the corresponding result. + onSendInternalResult[select] = if (success) Unit else CHANNEL_CLOSED + // Try to select this `onSend` clause. + select as SelectImplementation<*> + val trySelectResult = select.trySelectDetailed(this@BroadcastChannelImpl, Unit) + if (trySelectResult !== TrySelectDetailedResult.REREGISTER) { + // In case of re-registration (this `select` was still + // in the registration phase), the algorithm will invoke + // `registerSelectForSend`. As we stored an information that + // this `onSend` clause is already selected (in `onSendInternalResult`), + // the algorithm, will complete immediately. Otherwise, to avoid memory + // leaks, we must remove this information from the hashmap. + onSendInternalResult.remove(select) + } + } + + } + } + private val onSendInternalResult = HashMap, Any?>() // select -> Unit or CHANNEL_CLOSED + + // ############################ + // # Closing and Cancellation # + // ############################ + + override fun close(cause: Throwable?): Boolean = lock.withLock { // protected by lock + // Close all subscriptions first. + subscribers.forEach { it.close(cause) } + // Remove all subscriptions that do not contain + // buffered elements or waiting send-s to avoid + // memory leaks. We must keep other subscriptions + // in case `broadcast.cancel(..)` is called. + subscribers = subscribers.filter { it.hasElements() } + // Delegate to the parent implementation. + super.close(cause) + } + + override fun cancelImpl(cause: Throwable?): Boolean = lock.withLock { // protected by lock + // Cancel all subscriptions. As part of cancellation procedure, + // subscriptions automatically remove themselves from this broadcast. + subscribers.forEach { it.cancelImpl(cause) } + // For the conflated implementation, clear the last sent element. + lastConflatedElement = NO_ELEMENT + // Finally, delegate to the parent implementation. + super.cancelImpl(cause) + } + + override val isClosedForSend: Boolean + // Protect by lock to synchronize with `close(..)` / `cancel(..)`. + get() = lock.withLock { super.isClosedForSend } + + // ############################## + // # Subscriber Implementations # + // ############################## + + private inner class SubscriberBuffered : BufferedChannel(capacity = capacity) { + public override fun cancelImpl(cause: Throwable?): Boolean = lock.withLock { + // Remove this subscriber from the broadcast on cancellation. + removeSubscriber(this@SubscriberBuffered ) + super.cancelImpl(cause) + } + } + + private inner class SubscriberConflated : ConflatedBufferedChannel(capacity = 1, onBufferOverflow = DROP_OLDEST) { + public override fun cancelImpl(cause: Throwable?): Boolean { + // Remove this subscriber from the broadcast on cancellation. + removeSubscriber(this@SubscriberConflated ) + return super.cancelImpl(cause) + } + } + + // ######################################## + // # ConflatedBroadcastChannel Operations # + // ######################################## + + @Suppress("UNCHECKED_CAST") + val value: E get() = lock.withLock { + // Is this channel closed for sending? + if (isClosedForSend) { + throw closeCause ?: IllegalStateException("This broadcast channel is closed") + } + // Is there sent element? + if (lastConflatedElement === NO_ELEMENT) error("No value") + // Return the last sent element. + lastConflatedElement as E + } + + @Suppress("UNCHECKED_CAST") + val valueOrNull: E? get() = lock.withLock { + // Is this channel closed for sending? + if (isClosedForReceive) null + // Is there sent element? + else if (lastConflatedElement === NO_ELEMENT) null + // Return the last sent element. + else lastConflatedElement as E + } + + // ################# + // # For Debugging # + // ################# + + override fun toString() = + (if (lastConflatedElement !== NO_ELEMENT) "CONFLATED_ELEMENT=$lastConflatedElement; " else "") + + "BROADCAST=<${super.toString()}>; " + + "SUBSCRIBERS=${subscribers.joinToString(separator = ";", prefix = "<", postfix = ">")}" +} + +@SharedImmutable +private val NO_ELEMENT = Symbol("NO_ELEMENT") \ No newline at end of file diff --git a/kotlinx-coroutines-core/common/src/channels/BufferOverflow.kt b/kotlinx-coroutines-core/common/src/channels/BufferOverflow.kt index 48b89ce6fe..8af2c845e7 100644 --- a/kotlinx-coroutines-core/common/src/channels/BufferOverflow.kt +++ b/kotlinx-coroutines-core/common/src/channels/BufferOverflow.kt @@ -4,8 +4,6 @@ package kotlinx.coroutines.channels -import kotlinx.coroutines.* - /** * A strategy for buffer overflow handling in [channels][Channel] and [flows][kotlinx.coroutines.flow.Flow] that * controls what is going to be sacrificed on buffer overflow: diff --git a/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt b/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt new file mode 100644 index 0000000000..01b5a16b9c --- /dev/null +++ b/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt @@ -0,0 +1,3152 @@ +@file:Suppress("PrivatePropertyName") + +package kotlinx.coroutines.channels + +import kotlinx.atomicfu.* +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.ChannelResult.Companion.closed +import kotlinx.coroutines.channels.ChannelResult.Companion.failure +import kotlinx.coroutines.channels.ChannelResult.Companion.success +import kotlinx.coroutines.flow.internal.* +import kotlinx.coroutines.internal.* +import kotlinx.coroutines.selects.* +import kotlinx.coroutines.selects.TrySelectDetailedResult.* +import kotlin.contracts.* +import kotlin.coroutines.* +import kotlin.js.* +import kotlin.jvm.* +import kotlin.math.* +import kotlin.random.* +import kotlin.reflect.* + +/** + * The buffered channel implementation, which also serves as a rendezvous channel when the capacity is zero. + * The high-level structure bases on a conceptually infinite array for storing elements and waiting requests, + * separate counters of [send] and [receive] invocations that were ever performed, and an additional counter + * that indicates the end of the logical buffer by counting the number of array cells it ever contained. + * The key idea is that both [send] and [receive] start by incrementing their counters, assigning the array cell + * referenced by the counter. In case of rendezvous channels, the operation either suspends and stores its continuation + * in the cell or makes a rendezvous with the opposite request. Each cell can be processed by exactly one [send] and + * one [receive]. As for buffered channels, [send]-s can also add elements without suspension if the logical buffer + * contains the cell, while the [receive] operation updates the end of the buffer when its synchronization finishes. + * + * Please see the ["Fast and Scalable Channels in Kotlin Coroutines"](https://arxiv.org/abs/2211.04986) + * paper by Nikita Koval, Roman Elizarov, and Dan Alistarh for the detailed algorithm description. + */ +internal open class BufferedChannel( + /** + * Channel capacity; `Channel.RENDEZVOUS` for rendezvous channel + * and `Channel.UNLIMITED` for unlimited capacity. + */ + private val capacity: Int, + @JvmField + internal val onUndeliveredElement: OnUndeliveredElement? = null +) : Channel { + init { + require(capacity >= 0) { "Invalid channel capacity: $capacity, should be >=0" } + // This implementation has second `init`. + } + + // Maintenance note: use `Buffered1ChannelLincheckTest` to check hypotheses. + + /* + The counters indicate the total numbers of send, receive, and buffer expansion calls + ever performed. The counters are incremented in the beginning of the corresponding + operation; thus, acquiring a unique (for the operation type) cell to process. + The segments reference to the last working one for each operation type. + + Notably, the counter for send is combined with the channel closing status + for synchronization simplicity and performance reasons. + + The logical end of the buffer is initialized with the channel capacity. + If the channel is rendezvous or unlimited, the counter equals `BUFFER_END_RENDEZVOUS` + or `BUFFER_END_RENDEZVOUS`, respectively, and never updates. The `bufferEndSegment` + point to a special `NULL_SEGMENT` in this case. + */ + private val sendersAndCloseStatus = atomic(0L) + private val receivers = atomic(0L) + private val bufferEnd = atomic(initialBufferEnd(capacity)) + + internal val sendersCounter: Long get() = sendersAndCloseStatus.value.sendersCounter + internal val receiversCounter: Long get() = receivers.value + private val bufferEndCounter: Long get() = bufferEnd.value + + /* + Additionally to the counters above, we need an extra one that + tracks the number of cells processed by `expandBuffer()`. + When a receiver aborts, the corresponding cell might be + physically removed from the data structure to avoid memory + leaks, while it still can be unprocessed by `expandBuffer()`. + In this case, `expandBuffer()` cannot know whether the + removed cell contained sender or receiver and, therefore, + cannot proceed. To solve the race, we ensure that cells + correspond to cancelled receivers cannot be physically + removed until the cell is processed. + This additional counter enables the synchronization, + */ + private val completedExpandBuffersAndPauseFlag = atomic(bufferEndCounter) + + private val isRendezvousOrUnlimited + get() = bufferEndCounter.let { it == BUFFER_END_RENDEZVOUS || it == BUFFER_END_UNLIMITED } + + private val sendSegment: AtomicRef> + private val receiveSegment: AtomicRef> + private val bufferEndSegment: AtomicRef> + + init { + @Suppress("LeakingThis") + val firstSegment = ChannelSegment(id = 0, prev = null, channel = this, pointers = 3) + sendSegment = atomic(firstSegment) + receiveSegment = atomic(firstSegment) + // If this channel is rendezvous or has unlimited capacity, the algorithm never + // invokes the buffer expansion procedure, and the corresponding segment reference + // points to a special `NULL_SEGMENT` one and never updates. + @Suppress("UNCHECKED_CAST") + bufferEndSegment = atomic(if (isRendezvousOrUnlimited) (NULL_SEGMENT as ChannelSegment) else firstSegment) + } + + // ######################### + // ## The send operations ## + // ######################### + + override suspend fun send(element: E): Unit = + sendImpl( // <-- this is an inline function + element = element, + // Do not create a continuation until it is required; + // it is created later via [onNoWaiterSuspend], if needed. + waiter = null, + // Finish immediately if a rendezvous happens + // or the element has been buffered. + onRendezvousOrBuffered = {}, + // As no waiter is provided, suspension is impossible. + onSuspend = { _, _ -> assert { false } }, + // According to the `send(e)` contract, we need to call + // `onUndeliveredElement(..)` handler and throw an exception + // if the channel is already closed. + onClosed = { onClosedSend(element) }, + // When `send(e)` decides to suspend, the corresponding + // `onNoWaiterSuspend` function that creates a continuation + // is called. The tail-call optimization is applied here. + onNoWaiterSuspend = { segm, i, elem, s -> sendOnNoWaiterSuspend(segm, i, elem, s) } + ) + + private suspend fun onClosedSend(element: E): Nothing = suspendCancellableCoroutine { continuation -> + onUndeliveredElement?.callUndeliveredElementCatchingException(element)?.let { + // If it crashes, add send exception as suppressed for better diagnostics + it.addSuppressed(sendException) + continuation.resumeWithStackTrace(it) + return@suspendCancellableCoroutine + } + continuation.resumeWithStackTrace(sendException) + } + + private suspend fun sendOnNoWaiterSuspend( + /* The working cell is specified by + the segment and the index in it. */ + segment: ChannelSegment, + index: Int, + /** The element to be inserted. */ + element: E, + /** The global index of the cell. */ + s: Long + ) = suspendCancellableCoroutineReusable sc@{ cont -> + sendImplOnNoWaiter( // <-- this is an inline function + segment = segment, index = index, element = element, s = s, + // Store the created continuation as a waiter. + waiter = cont, + // If a rendezvous happens or the element has been buffered, + // resume the continuation and finish. In case of prompt + // cancellation, it is guaranteed that the element + // has been already buffered or passed to receiver. + onRendezvousOrBuffered = { cont.resume(Unit) }, + // Clean the cell on suspension and invoke + // `onUndeliveredElement(..)` if needed. + onSuspend = { segm, i -> cont.prepareSenderForSuspension(segm, i) }, + // If the channel is closed, call `onUndeliveredElement(..)` and complete the + // continuation with the corresponding exception. + onClosed = { onClosedSendOnNoWaiterSuspend(element, cont) }, + ) + } + + private fun CancellableContinuation<*>.prepareSenderForSuspension( + /* The working cell is specified by + the segment and the index in it. */ + segment: ChannelSegment, + index: Int + ) { + if (onUndeliveredElement == null) { + invokeOnCancellation(SenderOrReceiverCancellationHandler(segment, index).asHandler) + } else { + invokeOnCancellation(SenderWithOnUndeliveredElementCancellationHandler(segment, index, context).asHandler) + } + } + + // TODO: Replace with a more efficient cancellation mechanism for segments when #3084 is finished. + private inner class SenderOrReceiverCancellationHandler( + private val segment: ChannelSegment, + private val index: Int + ) : BeforeResumeCancelHandler(), DisposableHandle { + override fun dispose() { + segment.onCancellation(index) + } + + override fun invoke(cause: Throwable?) = dispose() + } + + private inner class SenderWithOnUndeliveredElementCancellationHandler( + private val segment: ChannelSegment, + private val index: Int, + private val context: CoroutineContext + ) : BeforeResumeCancelHandler(), DisposableHandle { + override fun dispose() { + segment.onSenderCancellationWithOnUndeliveredElement(index, context) + } + + override fun invoke(cause: Throwable?) = dispose() + } + + private fun onClosedSendOnNoWaiterSuspend(element: E, cont: CancellableContinuation) { + onUndeliveredElement?.callUndeliveredElement(element, cont.context) + cont.resumeWithException(recoverStackTrace(sendException, cont)) + } + + override fun trySend(element: E): ChannelResult { + // Do not try to send the element if the plain `send(e)` operation would suspend. + if (shouldSendSuspend(sendersAndCloseStatus.value)) return failure() + // This channel either has waiting receivers or is closed. + // Let's try to send the element! + // The logic is similar to the plain `send(e)` operation, with + // the only difference that we install `INTERRUPTED_SEND` in case + // the operation decides to suspend. + return sendImpl( // <-- this is an inline function + element = element, + // Store an already interrupted sender in case of suspension. + waiter = INTERRUPTED_SEND, + // Finish successfully when a rendezvous happens + // or the element has been buffered. + onRendezvousOrBuffered = { success(Unit) }, + // On suspension, the `INTERRUPTED_SEND` token has been installed, + // and this `trySend(e)` fails. According to the contract, + // we do not need to call [onUndeliveredElement] handler. + onSuspend = { segm, _ -> + segm.onSlotCleaned() + failure() + }, + // If the channel is closed, return the corresponding result. + onClosed = { closed(sendException) } + ) + } + + /** + * This is a special `send(e)` implementation that returns `true` if the element + * has been successfully sent, and `false` if the channel is closed. + * + * In case of coroutine cancellation, the element may be undelivered -- + * the [onUndeliveredElement] feature is unsupported in this implementation. + * + * Note that this implementation always invokes [suspendCancellableCoroutineReusable], + * as we do not care about broadcasts performance -- they are already deprecated. + */ + internal open suspend fun sendBroadcast(element: E): Boolean = suspendCancellableCoroutineReusable { cont -> + check(onUndeliveredElement == null) { + "the `onUndeliveredElement` feature is unsupported for `sendBroadcast(e)`" + } + sendImpl( + element = element, + waiter = SendBroadcast(cont), + onRendezvousOrBuffered = { cont.resume(true) }, + onSuspend = { segm, i -> cont.prepareSenderForSuspension(segm, i) }, + onClosed = { cont.resume(false) } + ) + } + + /** + * Specifies waiting [sendBroadcast] operation. + */ + private class SendBroadcast(val cont: CancellableContinuation) : Waiter + + /** + * Abstract send implementation. + */ + protected inline fun sendImpl( + /* The element to be sent. */ + element: E, + /* The waiter to be stored in case of suspension, + or `null` if the waiter is not created yet. + In the latter case, when the algorithm decides + to suspend, [onNoWaiterSuspend] is called. */ + waiter: Any?, + /* This lambda is invoked when the element has been + buffered or a rendezvous with a receiver happens. */ + onRendezvousOrBuffered: () -> R, + /* This lambda is called when the operation suspends in the + cell specified by the segment and the index in it. */ + onSuspend: (segm: ChannelSegment, i: Int) -> R, + /* This lambda is called when the channel + is observed in the closed state. */ + onClosed: () -> R, + /* This lambda is called when the operation decides + to suspend, but the waiter is not provided (equals `null`). + It should create a waiter and delegate to `sendImplOnNoWaiter`. */ + onNoWaiterSuspend: ( + segm: ChannelSegment, + i: Int, + element: E, + s: Long + ) -> R = { _, _, _, _ -> error("unexpected") } + ): R { + // Read the segment reference before the counter increment; + // it is crucial to be able to find the required segment later. + var segment = sendSegment.value + while (true) { + // Atomically increment the `senders` counter and obtain the + // value right before the increment along with the close status. + val sendersAndCloseStatusCur = sendersAndCloseStatus.getAndIncrement() + val s = sendersAndCloseStatusCur.sendersCounter + // Is this channel already closed? Keep the information. + val closed = sendersAndCloseStatusCur.isClosedForSend0 + // Count the required segment id and the cell index in it. + val id = s / SEGMENT_SIZE + val i = (s % SEGMENT_SIZE).toInt() + // Try to find the required segment if the initially obtained + // one (in the beginning of this function) has lower id. + if (segment.id != id) { + // Find the required segment. + segment = findSegmentSend(id, segment) ?: + // The required segment has not been found. + // Finish immediately if this channel is closed, + // restarting the operation otherwise. + // In the latter case, the required segment was full + // of interrupted waiters and, therefore, removed + // physically to avoid memory leaks. + if (closed) { + return onClosed() + } else { + continue + } + } + // Update the cell according to the algorithm. Importantly, when + // the channel is already closed, storing a waiter is illegal, so + // the algorithm stores the `INTERRUPTED_SEND` token in this case. + when (updateCellSend(segment, i, element, s, waiter, closed)) { + RESULT_RENDEZVOUS -> { + // A rendezvous with a receiver has happened. + // The previous segments are no longer needed + // for the upcoming requests, so the algorithm + // resets the link to the previous segment. + segment.cleanPrev() + return onRendezvousOrBuffered() + } + RESULT_BUFFERED -> { + // The element has been buffered. + return onRendezvousOrBuffered() + } + RESULT_SUSPEND -> { + // The operation has decided to suspend and installed the + // specified waiter. If the channel was already closed, + // and the `INTERRUPTED_SEND` token has been installed as a waiter, + // this request finishes with the `onClosed()` action. + if (closed) { + segment.onSlotCleaned() + return onClosed() + } + return onSuspend(segment, i) + } + RESULT_CLOSED -> { + // This channel is closed. + // In case this segment is already or going to be + // processed by a receiver, ensure that all the + // previous segments are unreachable. + if (s < receiversCounter) segment.cleanPrev() + return onClosed() + } + RESULT_FAILED -> { + // Either the cell stores an interrupted receiver, + // or it was poisoned by a concurrent receiver. + // In both cases, all the previous segments are already processed, + segment.cleanPrev() + continue + } + RESULT_SUSPEND_NO_WAITER -> { + // The operation has decided to suspend, + // but no waiter has been provided. + return onNoWaiterSuspend(segment, i, element, s) + } + } + } + } + + private inline fun sendImplOnNoWaiter( + /* The working cell is specified by + the segment and the index in it. */ + segment: ChannelSegment, + index: Int, + /* The element to be sent. */ + element: E, + /* The global index of the cell. */ + s: Long, + /* The waiter to be stored in case of suspension. */ + waiter: Any, + /* This lambda is invoked when the element has been + buffered or a rendezvous with a receiver happens.*/ + onRendezvousOrBuffered: () -> R, + /* This lambda is called when the operation suspends in the + cell specified by the segment and the index in it. */ + onSuspend: (segm: ChannelSegment, i: Int) -> R, + /* This lambda is called when the channel + is observed in the closed state. */ + onClosed: () -> R, + ): R = + // Update the cell again, now with the non-null waiter, + // restarting the operation from the beginning on failure. + // Check the `sendImpl(..)` function for the comments. + when(updateCellSend(segment, index, element, s, waiter, false)) { + RESULT_RENDEZVOUS -> { + segment.cleanPrev() + onRendezvousOrBuffered() + } + RESULT_BUFFERED -> { + onRendezvousOrBuffered() + } + RESULT_SUSPEND -> { + onSuspend(segment, index) + } + RESULT_CLOSED -> { + if (s < receiversCounter) segment.cleanPrev() + onClosed() + } + RESULT_FAILED -> { + segment.cleanPrev() + sendImpl( + element = element, + waiter = waiter, + onRendezvousOrBuffered = onRendezvousOrBuffered, + onSuspend = onSuspend, + onClosed = onClosed, + ) + } + else -> error("unexpected") + } + + private fun updateCellSend( + /* The working cell is specified by + the segment and the index in it. */ + segment: ChannelSegment, + index: Int, + /* The element to be sent. */ + element: E, + /* The global index of the cell. */ + s: Long, + /* The waiter to be stored in case of suspension. */ + waiter: Any?, + closed: Boolean + ): Int { + // This is a fast-path of `updateCellSendSlow(..)`. + // + // First, the algorithm stores the element, + // performing the synchronization after that. + // This way, receivers safely retrieve the + // element, following the safe publication pattern. + segment.storeElement(index, element) + if (closed) return updateCellSendSlow(segment, index, element, s, waiter, closed) + // Read the current cell state. + val state = segment.getState(index) + when { + // The cell is empty. + state === null -> { + // If the element should be buffered, or a rendezvous should happen + // while the receiver is still coming, try to buffer the element. + // Otherwise, try to store the specified waiter in the cell. + if (bufferOrRendezvousSend(s)) { + // Move the cell state to `BUFFERED`. + if (segment.casState(index, null, BUFFERED)) { + // The element has been successfully buffered, finish. + return RESULT_BUFFERED + } + } else { + // This `send(e)` operation should suspend. + // However, in case the channel has already + // been observed closed, `INTERRUPTED_SEND` + // is installed instead. + if (waiter == null) { + // The waiter is not specified; return the corresponding result. + return RESULT_SUSPEND_NO_WAITER + } else { + // Try to install the waiter. + if (segment.casState(index, null, waiter)) return RESULT_SUSPEND + } + } + } + // A waiting receiver is stored in the cell. + state is Waiter -> { + // As the element will be passed directly to the waiter, + // the algorithm cleans the element slot in the cell. + segment.cleanElement(index) + // Try to make a rendezvous with the suspended receiver. + return if (state.tryResumeReceiver(element)) { + // Rendezvous! Move the cell state to `DONE_RCV` and finish. + segment.setState(index, DONE_RCV) + onReceiveDequeued() + RESULT_RENDEZVOUS + } else { + // The resumption has failed. Update the cell state correspondingly + // and clean the element field. It is also possible for a concurrent + // cancellation handler to update the cell state; we can safely + // ignore these updates. + if (segment.getAndSetState(index, INTERRUPTED_RCV) !== INTERRUPTED_RCV) { + segment.onCancelledRequest(index, true) + } + RESULT_FAILED + } + } + } + return updateCellSendSlow(segment, index, element, s, waiter, closed) + } + + /** + * Updates the working cell of an abstract send operation. + */ + private fun updateCellSendSlow( + /* The working cell is specified by + the segment and the index in it. */ + segment: ChannelSegment, + index: Int, + /* The element to be sent. */ + element: E, + /* The global index of the cell. */ + s: Long, + /* The waiter to be stored in case of suspension. */ + waiter: Any?, + closed: Boolean + ): Int { + // Then, the cell state should be updated according to + // its state machine; see the paper mentioned in the very + // beginning for the cell life-cycle and the algorithm details. + while (true) { + // Read the current cell state. + val state = segment.getState(index) + when { + // The cell is empty. + state === null -> { + // If the element should be buffered, or a rendezvous should happen + // while the receiver is still coming, try to buffer the element. + // Otherwise, try to store the specified waiter in the cell. + if (bufferOrRendezvousSend(s) && !closed) { + // Move the cell state to `BUFFERED`. + if (segment.casState(index, null, BUFFERED)) { + // The element has been successfully buffered, finish. + return RESULT_BUFFERED + } + } else { + // This `send(e)` operation should suspend. + // However, in case the channel has already + // been observed closed, `INTERRUPTED_SEND` + // is installed instead. + when { + // The channel is closed + closed -> if (segment.casState(index, null, INTERRUPTED_SEND)) { + segment.onCancelledRequest(index, false) + return RESULT_CLOSED + } + // The waiter is not specified; return the corresponding result. + waiter == null -> return RESULT_SUSPEND_NO_WAITER + // Try to install the waiter. + else -> if (segment.casState(index, null, waiter)) return RESULT_SUSPEND + } + } + } + // This cell is in the logical buffer. + state === IN_BUFFER -> { + // Try to buffer the element. + if (segment.casState(index, state, BUFFERED)) { + // The element has been successfully buffered, finish. + return RESULT_BUFFERED + } + } + // The cell stores a cancelled receiver. + state === INTERRUPTED_RCV -> { + // Clean the element slot to avoid memory leaks and finish. + segment.cleanElement(index) + return RESULT_FAILED + } + // The cell is poisoned by a concurrent receive. + state === POISONED -> { + // Clean the element slot to avoid memory leaks and finish. + segment.cleanElement(index) + return RESULT_FAILED + } + // The channel is already closed. + state === CHANNEL_CLOSED -> { + // Clean the element slot to avoid memory leaks, + // ensure that the closing/cancellation procedure + // has been completed, and finish. + segment.cleanElement(index) + completeCloseOrCancel() + return RESULT_CLOSED + } + // A waiting receiver is stored in the cell. + else -> { + assert { state is Waiter || state is WaiterEB } + // As the element will be passed directly to the waiter, + // the algorithm cleans the element slot in the cell. + segment.cleanElement(index) + // Unwrap the waiting receiver from `WaiterEB` if needed. + // As a receiver is stored in the cell, the buffer expansion + // procedure would finish, so senders simply ignore the "EB" marker. + val receiver = if (state is WaiterEB) state.waiter else state + // Try to make a rendezvous with the suspended receiver. + return if (receiver.tryResumeReceiver(element)) { + // Rendezvous! Move the cell state to `DONE_RCV` and finish. + segment.setState(index, DONE_RCV) + onReceiveDequeued() + RESULT_RENDEZVOUS + } else { + // The resumption has failed. Update the cell state correspondingly + // and clean the element field. It is also possible for a concurrent + // `expandBuffer()` or the cancellation handler to update the cell state; + // we can safely ignore these updates as senders do not help `expandBuffer()`. + if (segment.getAndSetState(index, INTERRUPTED_RCV) !== INTERRUPTED_RCV) { + segment.onCancelledRequest(index, true) + } + RESULT_FAILED + } + } + } + } + } + + /** + * Checks whether a [send] invocation is bound to suspend if it is called + * with the specified [sendersAndCloseStatus], [receivers], and [bufferEnd] + * values. When this channel is already closed, the function returns `false`. + * + * Specifically, [send] suspends if the channel is not unlimited, + * the number of receivers is greater than then index of the working cell of the + * potential [send] invocation, and the buffer does not cover this cell + * in case of buffered channel. + * When the channel is already closed, [send] does not suspend. + */ + @JsName("shouldSendSuspend0") + private fun shouldSendSuspend(curSendersAndCloseStatus: Long): Boolean { + // Does not suspend if the channel is already closed. + if (curSendersAndCloseStatus.isClosedForSend0) return false + // Does not suspend if a rendezvous may happen or the buffer is not full. + return !bufferOrRendezvousSend(curSendersAndCloseStatus.sendersCounter) + } + + /** + * Returns `true` when the specified [send] should place + * its element to the working cell without suspension. + */ + private fun bufferOrRendezvousSend(curSenders: Long): Boolean = + curSenders < bufferEndCounter || curSenders < receiversCounter + capacity + + /** + * Checks whether a [send] invocation is bound to suspend if it is called + * with the current counter and close status values. See [shouldSendSuspend] for details. + * + * Note that this implementation is _false positive_ in case of rendezvous channels, + * so it can return `false` when a [send] invocation is bound to suspend. Specifically, + * the counter of `receive()` operations may indicate that there is a waiting receiver, + * while it has already been cancelled, so the potential rendezvous is bound to fail. + */ + internal open fun shouldSendSuspend(): Boolean = shouldSendSuspend(sendersAndCloseStatus.value) + + /** + * Tries to resume this receiver with the specified [element] as a result. + * Returns `true` on success and `false` otherwise. + */ + @Suppress("UNCHECKED_CAST") + private fun Any.tryResumeReceiver(element: E): Boolean = when(this) { + is SelectInstance<*> -> { // `onReceiveXXX` select clause + trySelect(this@BufferedChannel, element) + } + is ReceiveCatching<*> -> { + this as ReceiveCatching + cont.tryResume0(success(element), onUndeliveredElement?.bindCancellationFun(element, cont.context)) + } + is BufferedChannel<*>.BufferedChannelIterator -> { + this as BufferedChannel.BufferedChannelIterator + tryResumeHasNext(element) + } + is CancellableContinuation<*> -> { // `receive()` + this as CancellableContinuation + tryResume0(element, onUndeliveredElement?.bindCancellationFun(element, context)) + } + else -> error("Unexpected receiver type: $this") + } + + // ########################## + // # The receive operations # + // ########################## + + /** + * This function is invoked when a receiver is added as a waiter in this channel. + */ + protected open fun onReceiveEnqueued() {} + + /** + * This function is invoked when a waiting receiver is no longer stored in this channel; + * independently on whether it is caused by rendezvous, cancellation, or channel closing. + */ + protected open fun onReceiveDequeued() {} + + override suspend fun receive(): E = + receiveImpl( // <-- this is an inline function + // Do not create a continuation until it is required; + // it is created later via [onNoWaiterSuspend], if needed. + waiter = null, + // Return the received element on successful retrieval from + // the buffer or rendezvous with a suspended sender. + // Also, inform `BufferedChannel` extensions that + // synchronization of this receive operation is completed. + onElementRetrieved = { element -> + return element + }, + // As no waiter is provided, suspension is impossible. + onSuspend = { _, _, _ -> error("unexpected") }, + // Throw an exception if the channel is already closed. + onClosed = { throw recoverStackTrace(receiveException) }, + // If `receive()` decides to suspend, the corresponding + // `suspend` function that creates a continuation is called. + // The tail-call optimization is applied here. + onNoWaiterSuspend = { segm, i, r -> receiveOnNoWaiterSuspend(segm, i, r) } + ) + + private suspend fun receiveOnNoWaiterSuspend( + /* The working cell is specified by + the segment and the index in it. */ + segment: ChannelSegment, + index: Int, + /* The global index of the cell. */ + r: Long + ) = suspendCancellableCoroutineReusable { cont -> + receiveImplOnNoWaiter( // <-- this is an inline function + segment = segment, index = index, r = r, + // Store the created continuation as a waiter. + waiter = cont, + // In case of successful element retrieval, resume + // the continuation with the element and inform the + // `BufferedChannel` extensions that the synchronization + // is completed. Importantly, the receiver coroutine + // may be cancelled after it is successfully resumed but + // not dispatched yet. In case `onUndeliveredElement` is + // specified, we need to invoke it in the latter case. + onElementRetrieved = { element -> + val onCancellation = onUndeliveredElement?.bindCancellationFun(element, cont.context) + cont.resume(element, onCancellation) + }, + onSuspend = { segm, i, _ -> cont.prepareReceiverForSuspension(segm, i) }, + onClosed = { onClosedReceiveOnNoWaiterSuspend(cont) }, + ) + } + + private fun CancellableContinuation<*>.prepareReceiverForSuspension(segment: ChannelSegment, index: Int) { + onReceiveEnqueued() + invokeOnCancellation(SenderOrReceiverCancellationHandler(segment, index).asHandler) + } + + private fun onClosedReceiveOnNoWaiterSuspend(cont: CancellableContinuation) { + cont.resumeWithException(receiveException) + } + + /* + The implementation is exactly the same as of `receive()`, + with the only difference that this function returns a `ChannelResult` + instance and does not throw exception explicitly in case the channel + is already closed for receiving. Please refer the plain `receive()` + implementation for the comments. + */ + override suspend fun receiveCatching(): ChannelResult = + receiveImpl( // <-- this is an inline function + waiter = null, + onElementRetrieved = { element -> + success(element) + }, + onSuspend = { _, _, _ -> error("unexpected") }, + onClosed = { closed(closeCause) }, + onNoWaiterSuspend = { segm, i, r -> receiveCatchingOnNoWaiterSuspend(segm, i, r) } + ) + + private suspend fun receiveCatchingOnNoWaiterSuspend( + segment: ChannelSegment, + index: Int, + r: Long + ) = suspendCancellableCoroutineReusable> { cont -> + val waiter = ReceiveCatching(cont) + receiveImplOnNoWaiter( + segment, index, r, + waiter = waiter, + onElementRetrieved = { element -> + cont.resume(success(element), onUndeliveredElement?.bindCancellationFun(element, cont.context)) + }, + onSuspend = { segm, i, _ -> cont.prepareReceiverForSuspension(segm, i) }, + onClosed = { onClosedReceiveCatchingOnNoWaiterSuspend(cont) } + ) + } + + private fun onClosedReceiveCatchingOnNoWaiterSuspend(cont: CancellableContinuation>) { + cont.resume(closed(closeCause)) + } + + override fun tryReceive(): ChannelResult { + // Read the `receivers` counter first. + val r = receivers.value + val sendersAndCloseStatusCur = sendersAndCloseStatus.value + // Is this channel closed for receive? + if (sendersAndCloseStatusCur.isClosedForReceive0) { + return closed(closeCause) + } + // Do not try to receive an element if the plain `receive()` operation would suspend. + val s = sendersAndCloseStatusCur.sendersCounter + if (r >= s) return failure() + // Let's try to retrieve an element! + // The logic is similar to the plain `receive()` operation, with + // the only difference that we store `INTERRUPTED_RCV` in case + // the operation decides to suspend. This way, we can leverage + // the unconditional `Fetch-and-Add` instruction. + // One may consider storing `INTERRUPTED_RCV` instead of an actual waiter + // on suspension (a.k.a. "no elements to retrieve") as a short-cut of + // "suspending and cancelling immediately". + return receiveImpl( // <-- this is an inline function + // Store an already interrupted receiver in case of suspension. + waiter = INTERRUPTED_RCV, + // Finish when an element is successfully retrieved. + onElementRetrieved = { element -> success(element) }, + // On suspension, the `INTERRUPTED_RCV` token has been + // installed, and this `tryReceive()` fails. + onSuspend = { segm, _, globalIndex -> + // Emulate "cancelled" receive, thus invoking 'waitExpandBufferCompletion' manually, + // because effectively there were no cancellation + waitExpandBufferCompletion(globalIndex) + segm.onSlotCleaned() + failure() + }, + // If the channel is closed, return the corresponding result. + onClosed = { closed(closeCause) } + ) + } + + /** + * Extracts the first element from this channel until the cell with the specified + * index is moved to the logical buffer. This is a key procedure for the _conflated_ + * channel implementation, see [ConflatedBufferedChannel] with the [BufferOverflow.DROP_OLDEST] + * strategy on buffer overflowing. + */ + protected fun dropFirstElementUntilTheSpecifiedCellIsInTheBuffer(globalCellIndex: Long) { + assert { isConflatedDropOldest } + // Read the segment reference before the counter increment; + // it is crucial to be able to find the required segment later. + var segment = receiveSegment.value + while (true) { + // Read the receivers counter to check whether the specified cell is already in the buffer + // or should be moved to the buffer in a short time, due to the already started `receive()`. + val r = this.receivers.value + if (globalCellIndex < max(r + capacity, bufferEndCounter)) return + // The cell is outside the buffer. Try to extract the first element + // if the `receivers` counter has not been changed. + if (!this.receivers.compareAndSet(r, r + 1)) continue + // Count the required segment id and the cell index in it. + val id = r / SEGMENT_SIZE + val i = (r % SEGMENT_SIZE).toInt() + // Try to find the required segment if the initially obtained + // segment (in the beginning of this function) has lower id. + if (segment.id != id) { + // Find the required segment, restarting the operation if it has not been found. + segment = findSegmentReceive(id, segment) ?: + // The required segment has not been found. It is possible that the channel is already + // closed for receiving, so the linked list of segments is closed as well. + // In the latter case, the operation will finish eventually after incrementing + // the `receivers` counter sufficient times. Note that it is impossible to check + // whether this channel is closed for receiving (we do this in `receive`), + // as it may call this function when helping to complete closing the channel. + continue + } + // Update the cell according to the cell life-cycle. + val updCellResult = updateCellReceive(segment, i, r, null) + when { + updCellResult === FAILED -> { + // The cell is poisoned; restart from the beginning. + // To avoid memory leaks, we also need to reset + // the `prev` pointer of the working segment. + if (r < sendersCounter) segment.cleanPrev() + } + else -> { // element + // A buffered element was retrieved from the cell. + // Clean the reference to the previous segment. + segment.cleanPrev() + @Suppress("UNCHECKED_CAST") + onUndeliveredElement?.callUndeliveredElementCatchingException(updCellResult as E)?.let { throw it } + } + } + } + } + + /** + * Abstract receive implementation. + */ + private inline fun receiveImpl( + /* The waiter to be stored in case of suspension, + or `null` if the waiter is not created yet. + In the latter case, if the algorithm decides + to suspend, [onNoWaiterSuspend] is called. */ + waiter: Any?, + /* This lambda is invoked when an element has been + successfully retrieved, either from the buffer or + by making a rendezvous with a suspended sender. */ + onElementRetrieved: (element: E) -> R, + /* This lambda is called when the operation suspends in the cell + specified by the segment and its global and in-segment indices. */ + onSuspend: (segm: ChannelSegment, i: Int, r: Long) -> R, + /* This lambda is called when the channel is observed + in the closed state and no waiting sender is found, + which means that it is closed for receiving. */ + onClosed: () -> R, + /* This lambda is called when the operation decides + to suspend, but the waiter is not provided (equals `null`). + It should create a waiter and delegate to `sendImplOnNoWaiter`. */ + onNoWaiterSuspend: ( + segm: ChannelSegment, + i: Int, + r: Long + ) -> R = { _, _, _ -> error("unexpected") } + ): R { + // Read the segment reference before the counter increment; + // it is crucial to be able to find the required segment later. + var segment = receiveSegment.value + while (true) { + // Similar to the `send(e)` operation, `receive()` first checks + // whether the channel is already closed for receiving. + if (isClosedForReceive) return onClosed() + // Atomically increments the `receivers` counter + // and obtain the value right before the increment. + val r = this.receivers.getAndIncrement() + // Count the required segment id and the cell index in it. + val id = r / SEGMENT_SIZE + val i = (r % SEGMENT_SIZE).toInt() + // Try to find the required segment if the initially obtained + // segment (in the beginning of this function) has lower id. + if (segment.id != id) { + // Find the required segment, restarting the operation if it has not been found. + segment = findSegmentReceive(id, segment) ?: + // The required segment is not found. It is possible that the channel is already + // closed for receiving, so the linked list of segments is closed as well. + // In the latter case, the operation fails with the corresponding check at the beginning. + continue + } + // Update the cell according to the cell life-cycle. + val updCellResult = updateCellReceive(segment, i, r, waiter) + return when { + updCellResult === SUSPEND -> { + // The operation has decided to suspend and + // stored the specified waiter in the cell. + onSuspend(segment, i, r) + } + updCellResult === FAILED -> { + // The operation has tried to make a rendezvous + // but failed: either the opposite request has + // already been cancelled or the cell is poisoned. + // Restart from the beginning in this case. + // To avoid memory leaks, we also need to reset + // the `prev` pointer of the working segment. + if (r < sendersCounter) segment.cleanPrev() + continue + } + updCellResult === SUSPEND_NO_WAITER -> { + // The operation has decided to suspend, + // but no waiter has been provided. + onNoWaiterSuspend(segment, i, r) + } + else -> { // element + // Either a buffered element was retrieved from the cell + // or a rendezvous with a waiting sender has happened. + // Clean the reference to the previous segment before finishing. + segment.cleanPrev() + @Suppress("UNCHECKED_CAST") + onElementRetrieved(updCellResult as E) + } + } + } + } + + private inline fun receiveImplOnNoWaiter( + /* The working cell is specified by + the segment and the index in it. */ + segment: ChannelSegment, + index: Int, + /* The global index of the cell. */ + r: Long, + /* The waiter to be stored in case of suspension. */ + waiter: W, + /* This lambda is invoked when an element has been + successfully retrieved, either from the buffer or + by making a rendezvous with a suspended sender. */ + onElementRetrieved: (element: E) -> R, + /* This lambda is called when the operation suspends in the cell + specified by the segment and its global and in-segment indices. */ + onSuspend: (segm: ChannelSegment, i: Int, r: Long) -> R, + /* This lambda is called when the channel is observed + in the closed state and no waiting senders is found, + which means that it is closed for receiving. */ + onClosed: () -> R + ): R { + // Update the cell with the non-null waiter, + // restarting from the beginning on failure. + // Check the `receiveImpl(..)` function for the comments. + val updCellResult = updateCellReceive(segment, index, r, waiter) + when { + updCellResult === SUSPEND -> { + return onSuspend(segment, index, r) + } + updCellResult === FAILED -> { + if (r < sendersCounter) segment.cleanPrev() + return receiveImpl( + waiter = waiter, + onElementRetrieved = onElementRetrieved, + onSuspend = onSuspend, + onClosed = onClosed + ) + } + else -> { + segment.cleanPrev() + @Suppress("UNCHECKED_CAST") + return onElementRetrieved(updCellResult as E) + } + } + } + + private fun updateCellReceive( + /* The working cell is specified by + the segment and the index in it. */ + segment: ChannelSegment, + index: Int, + /* The global index of the cell. */ + r: Long, + /* The waiter to be stored in case of suspension. */ + waiter: Any?, + ): Any? { + // This is a fast-path of `updateCellReceiveSlow(..)`. + // + // Read the current cell state. + val state = segment.getState(index) + when { + // The cell is empty. + state === null -> { + // If a rendezvous must happen, the operation does not wait + // until the cell stores a buffered element or a suspended + // sender, poisoning the cell and restarting instead. + // Otherwise, try to store the specified waiter in the cell. + val senders = sendersAndCloseStatus.value.sendersCounter + if (r >= senders) { + // This `receive()` operation should suspend. + if (waiter === null) { + // The waiter is not specified; + // return the corresponding result. + return SUSPEND_NO_WAITER + } + // Try to install the waiter. + if (segment.casState(index, state, waiter)) { + // The waiter has been successfully installed. + // Invoke the `expandBuffer()` procedure and finish. + expandBuffer() + return SUSPEND + } + } + } + // The cell stores a buffered element. + state === BUFFERED -> if (segment.casState(index, state, DONE_RCV)) { + // Retrieve the element and expand the buffer. + expandBuffer() + return segment.retrieveElement(index) + } + } + return updateCellReceiveSlow(segment, index, r, waiter) + } + + private fun updateCellReceiveSlow( + /* The working cell is specified by + the segment and the index in it. */ + segment: ChannelSegment, + index: Int, + /* The global index of the cell. */ + r: Long, + /* The waiter to be stored in case of suspension. */ + waiter: Any?, + ): Any? { + // The cell state should be updated according to its state machine; + // see the paper mentioned in the very beginning for the algorithm details. + while (true) { + // Read the current cell state. + val state = segment.getState(index) + when { + // The cell is empty. + state === null || state === IN_BUFFER -> { + // If a rendezvous must happen, the operation does not wait + // until the cell stores a buffered element or a suspended + // sender, poisoning the cell and restarting instead. + // Otherwise, try to store the specified waiter in the cell. + val senders = sendersAndCloseStatus.value.sendersCounter + if (r < senders) { + // The cell is already covered by sender, + // so a rendezvous must happen. Unfortunately, + // the cell is empty, so the operation poisons it. + if (segment.casState(index, state, POISONED)) { + // When the cell becomes poisoned, it is essentially + // the same as storing an already cancelled receiver. + // Thus, the `expandBuffer()` procedure should be invoked. + expandBuffer() + return FAILED + } + } else { + // This `receive()` operation should suspend. + if (waiter === null) { + // The waiter is not specified; + // return the corresponding result. + return SUSPEND_NO_WAITER + } + // Try to install the waiter. + if (segment.casState(index, state, waiter)) { + // The waiter has been successfully installed. + // Invoke the `expandBuffer()` procedure and finish. + expandBuffer() + return SUSPEND + } + } + } + // The cell stores a buffered element. + state === BUFFERED -> if (segment.casState(index, state, DONE_RCV)) { + // Retrieve the element and expand the buffer. + expandBuffer() + return segment.retrieveElement(index) + } + // The cell stores an interrupted sender. + state === INTERRUPTED_SEND -> return FAILED + // The cell is already poisoned by a concurrent + // `hasElements` call. Restart in this case. + state === POISONED -> return FAILED + // This channel is already closed. + state === CHANNEL_CLOSED -> { + // Although the channel is closed, it is still required + // to call the `expandBuffer()` procedure to keep + // `waitForExpandBufferCompletion()` correct. + expandBuffer() + return FAILED + } + // A concurrent `expandBuffer()` is resuming a + // suspended sender. Wait in a spin-loop until + // the resumption attempt completes: the cell + // state must change to either `BUFFERED` or + // `INTERRUPTED_SEND`. + state === RESUMING_BY_EB -> continue + // The cell stores a suspended sender; try to resume it. + else -> { + // To synchronize with expandBuffer(), the algorithm + // first moves the cell to an intermediate `S_RESUMING_BY_RCV` + // state, updating it to either `BUFFERED` (on success) or + // `INTERRUPTED_SEND` (on failure). + if (segment.casState(index, state, RESUMING_BY_RCV)) { + // Has a concurrent `expandBuffer()` delegated its completion? + val helpExpandBuffer = state is WaiterEB + // Extract the sender if needed and try to resume it. + val sender = if (state is WaiterEB) state.waiter else state + return if (sender.tryResumeSender(segment, index)) { + // The sender has been resumed successfully! + // Update the cell state correspondingly, + // expand the buffer, and return the element + // stored in the cell. + // In case a concurrent `expandBuffer()` has delegated + // its completion, the procedure should finish, as the + // sender is resumed. Thus, no further action is required. + segment.setState(index, DONE_RCV) + expandBuffer() + segment.retrieveElement(index) + } else { + // The resumption has failed. Update the cell correspondingly. + // In case a concurrent `expandBuffer()` has delegated + // its completion, the procedure should skip this cell, so + // `expandBuffer()` should be called once again. + segment.setState(index, INTERRUPTED_SEND) + segment.onCancelledRequest(index, false) + if (helpExpandBuffer) expandBuffer() + FAILED + } + } + } + } + } + } + + private fun Any.tryResumeSender(segment: ChannelSegment, index: Int): Boolean = when (this) { + is CancellableContinuation<*> -> { // suspended `send(e)` operation + @Suppress("UNCHECKED_CAST") + this as CancellableContinuation + tryResume0(Unit) + } + is SelectInstance<*> -> { + this as SelectImplementation<*> + val trySelectResult = trySelectDetailed(clauseObject = this@BufferedChannel, result = Unit) + // Clean the element slot to avoid memory leaks + // if this `select` clause should be re-registered. + if (trySelectResult === REREGISTER) segment.cleanElement(index) + // Was the resumption successful? + trySelectResult === SUCCESSFUL + } + is SendBroadcast -> cont.tryResume0(true) // // suspended `sendBroadcast(e)` operation + else -> error("Unexpected waiter: $this") + } + + // ################################ + // # The expandBuffer() procedure # + // ################################ + + private fun expandBuffer() { + // Do not need to take any action if + // this channel is rendezvous or unlimited. + if (isRendezvousOrUnlimited) return + // Read the current segment of + // the `expandBuffer()` procedure. + var segment = bufferEndSegment.value + // Try to expand the buffer until succeed. + try_again@ while (true) { + // Increment the logical end of the buffer. + // The `b`-th cell is going to be added to the buffer. + val b = bufferEnd.getAndIncrement() + val id = b / SEGMENT_SIZE + // After that, read the current `senders` counter. + // In case its value is lower than `b`, the `send(e)` + // invocation that will work with this `b`-th cell + // will detect that the cell is already a part of the + // buffer when comparing with the `bufferEnd` counter. + // However, `bufferEndSegment` may reference an outdated + // segment, which should be updated to avoid memory leaks. + val s = sendersCounter + if (s <= b) { + // Should `bufferEndSegment` be moved forward to avoid memory leaks? + if (segment.id < id && segment.next != null) + moveSegmentBufferEndToSpecifiedOrLast(id, segment) + // Increment the number of completed `expandBuffer()`-s and finish. + incCompletedExpandBufferAttempts() + return + } + // Is `bufferEndSegment` outdated? + // Find the required segment, creating new ones if needed. + if (segment.id < id) { + segment = findSegmentBufferEnd(id, segment, b) + // Restart if the required segment is removed, or + // the linked list of segments is already closed, + // and the required one will never be created. + // Please note that `findSegmentBuffer(..)` updates + // the number of completed `expandBuffer()` attempt + // in this case. + ?: continue@try_again + } + // Try to add the cell to the logical buffer, + // updating the cell state according to the state-machine. + val i = (b % SEGMENT_SIZE).toInt() + if (updateCellExpandBuffer(segment, i, b)) { + // The cell has been added to the logical buffer! + // Increment the number of completed `expandBuffer()`-s and finish. + // + // Note that it is possible to increment the number of + // completed `expandBuffer()` attempts earlier, right + // after the segment is obtained. We find this change + // counter-intuitive and prefer to avoid it. + incCompletedExpandBufferAttempts() + return + } else { + // The cell has not been added to the buffer. + // Increment the number of completed `expandBuffer()` + // attempts and restart. + incCompletedExpandBufferAttempts() + continue@try_again + } + } + } + + private fun updateCellExpandBuffer( + /* The working cell is specified by + the segment and the index in it. */ + segment: ChannelSegment, + index: Int, + /* The global index of the cell. */ + b: Long + ): Boolean { + // This is a fast-path of `updateCellExpandBufferSlow(..)`. + // + // Read the current cell state. + val state = segment.getState(index) + if (state is Waiter) { + // Usually, a sender is stored in the cell. + // However, it is possible for a concurrent + // receiver to be already suspended there. + // Try to distinguish whether the waiter is a + // sender by comparing the global cell index with + // the `receivers` counter. In case the cell is not + // covered by a receiver, a sender is stored in the cell. + if (b >= receivers.value) { + // The cell stores a suspended sender. Try to resume it. + // To synchronize with a concurrent `receive()`, the algorithm + // first moves the cell state to an intermediate `RESUMING_BY_EB` + // state, updating it to either `BUFFERED` (on successful resumption) + // or `INTERRUPTED_SEND` (on failure). + if (segment.casState(index, state, RESUMING_BY_EB)) { + return if (state.tryResumeSender(segment, index)) { + // The sender has been resumed successfully! + // Move the cell to the logical buffer and finish. + segment.setState(index, BUFFERED) + true + } else { + // The resumption has failed. + segment.setState(index, INTERRUPTED_SEND) + segment.onCancelledRequest(index, false) + false + } + } + } + } + return updateCellExpandBufferSlow(segment, index, b) + } + + private fun updateCellExpandBufferSlow( + /* The working cell is specified by + the segment and the index in it. */ + segment: ChannelSegment, + index: Int, + /* The global index of the cell. */ + b: Long + ): Boolean { + // Update the cell state according to its state machine. + // See the paper mentioned in the very beginning for + // the cell life-cycle and the algorithm details. + while (true) { + // Read the current cell state. + val state = segment.getState(index) + when { + // A suspended waiter, sender or receiver. + state is Waiter -> { + // Usually, a sender is stored in the cell. + // However, it is possible for a concurrent + // receiver to be already suspended there. + // Try to distinguish whether the waiter is a + // sender by comparing the global cell index with + // the `receivers` counter. In case the cell is not + // covered by a receiver, a sender is stored in the cell. + if (b < receivers.value) { + // The algorithm cannot distinguish whether the + // suspended in the cell operation is sender or receiver. + // To make progress, `expandBuffer()` delegates its completion + // to an upcoming pairwise request, atomically wrapping + // the waiter in `WaiterEB`. In case a sender is stored + // in the cell, the upcoming receiver will call `expandBuffer()` + // if the sender resumption fails; thus, effectively, skipping + // this cell. Otherwise, if a receiver is stored in the cell, + // this `expandBuffer()` procedure must finish; therefore, + // sender ignore the `WaiterEB` wrapper. + if (segment.casState(index, state, WaiterEB(waiter = state))) + return true + } else { + // The cell stores a suspended sender. Try to resume it. + // To synchronize with a concurrent `receive()`, the algorithm + // first moves the cell state to an intermediate `RESUMING_BY_EB` + // state, updating it to either `BUFFERED` (on successful resumption) + // or `INTERRUPTED_SEND` (on failure). + if (segment.casState(index, state, RESUMING_BY_EB)) { + return if (state.tryResumeSender(segment, index)) { + // The sender has been resumed successfully! + // Move the cell to the logical buffer and finish. + segment.setState(index, BUFFERED) + true + } else { + // The resumption has failed. + segment.setState(index, INTERRUPTED_SEND) + segment.onCancelledRequest(index, false) + false + } + } + } + } + // The cell stores an interrupted sender, skip it. + state === INTERRUPTED_SEND -> return false + // The cell is empty, a concurrent sender is coming. + state === null -> { + // To inform a concurrent sender that this cell is + // already a part of the buffer, the algorithm moves + // it to a special `IN_BUFFER` state. + if (segment.casState(index, state, IN_BUFFER)) return true + } + // The cell is already a part of the buffer, finish. + state === BUFFERED -> return true + // The cell is already processed by a receiver, no further action is required. + state === POISONED || state === DONE_RCV || state === INTERRUPTED_RCV -> return true + // The channel is closed, all the following + // cells are already in the same state, finish. + state === CHANNEL_CLOSED -> return true + // A concurrent receiver is resuming the suspended sender. + // Wait in a spin-loop until it changes the cell state + // to either `DONE_RCV` or `INTERRUPTED_SEND`. + state === RESUMING_BY_RCV -> continue // spin wait + else -> error("Unexpected cell state: $state") + } + } + } + + /** + * Increments the counter of completed [expandBuffer] invocations. + * To guarantee starvation-freedom for [waitExpandBufferCompletion], + * which waits until the counters of started and completed [expandBuffer] calls + * coincide and become greater or equal to the specified value, + * [waitExpandBufferCompletion] may set a flag that pauses further progress. + */ + private fun incCompletedExpandBufferAttempts(nAttempts: Long = 1) { + // Increment the number of completed `expandBuffer()` calls. + completedExpandBuffersAndPauseFlag.addAndGet(nAttempts).also { + // Should further `expandBuffer()`-s be paused? + // If so, this thread should wait in a spin-loop + // until the flag is unset. + if (it.ebPauseExpandBuffers) { + @Suppress("ControlFlowWithEmptyBody") + while (completedExpandBuffersAndPauseFlag.value.ebPauseExpandBuffers) {} + } + } + } + + /** + * Waits in a spin-loop until the [expandBuffer] call that + * should process the [globalIndex]-th cell is completed. + * Essentially, it waits until the numbers of started ([bufferEnd]) + * and completed ([completedExpandBuffersAndPauseFlag]) [expandBuffer] + * attempts coincide and become equal or greater than [globalIndex]. + * To avoid starvation, this function may set a flag + * that pauses further progress. + */ + internal fun waitExpandBufferCompletion(globalIndex: Long) { + // Do nothing if this channel is rendezvous or unlimited; + // `expandBuffer()` is not used in these cases. + if (isRendezvousOrUnlimited) return + // Wait in an infinite loop until the number of started + // buffer expansion calls become not lower than the cell index. + @Suppress("ControlFlowWithEmptyBody") + while (bufferEndCounter <= globalIndex) {} + // Now it is guaranteed that the `expandBuffer()` call that + // should process the required cell has been started. + // Wait in a fixed-size spin-loop until the numbers of + // started and completed buffer expansion calls coincide. + repeat(EXPAND_BUFFER_COMPLETION_WAIT_ITERATIONS) { + // Read the number of started buffer expansion calls. + val b = bufferEndCounter + // Read the number of completed buffer expansion calls. + val ebCompleted = completedExpandBuffersAndPauseFlag.value.ebCompletedCounter + // Do the numbers of started and completed calls coincide? + // Note that we need to re-read the number of started `expandBuffer()` + // calls to obtain a correct snapshot. + // Here we wait to a precise match in order to ensure that **our matching expandBuffer()** + // completed. The only way to ensure that is to check that number of started expands == number of finished expands + if (b == ebCompleted && b == bufferEndCounter) return + } + // To avoid starvation, pause further `expandBuffer()` calls. + completedExpandBuffersAndPauseFlag.update { + constructEBCompletedAndPauseFlag(it.ebCompletedCounter, true) + } + // Now wait in an infinite spin-loop until the counters coincide. + while (true) { + // Read the number of started buffer expansion calls. + val b = bufferEndCounter + // Read the number of completed buffer expansion calls + // along with the flag that pauses further progress. + val ebCompletedAndBit = completedExpandBuffersAndPauseFlag.value + val ebCompleted = ebCompletedAndBit.ebCompletedCounter + val pauseExpandBuffers = ebCompletedAndBit.ebPauseExpandBuffers + // Do the numbers of started and completed calls coincide? + // Note that we need to re-read the number of started `expandBuffer()` + // calls to obtain a correct snapshot. + if (b == ebCompleted && b == bufferEndCounter) { + // Unset the flag, which pauses progress, and finish. + completedExpandBuffersAndPauseFlag.update { + constructEBCompletedAndPauseFlag(it.ebCompletedCounter, false) + } + return + } + // It is possible that a concurrent caller of this function + // has unset the flag, which pauses further progress to avoid + // starvation. In this case, set the flag back. + if (!pauseExpandBuffers) { + completedExpandBuffersAndPauseFlag.compareAndSet( + ebCompletedAndBit, + constructEBCompletedAndPauseFlag(ebCompleted, true) + ) + } + } + } + + + // ####################### + // ## Select Expression ## + // ####################### + + @Suppress("UNCHECKED_CAST") + override val onSend: SelectClause2> + get() = SelectClause2Impl( + clauseObject = this@BufferedChannel, + regFunc = BufferedChannel<*>::registerSelectForSend as RegistrationFunction, + processResFunc = BufferedChannel<*>::processResultSelectSend as ProcessResultFunction + ) + + @Suppress("UNCHECKED_CAST") + protected open fun registerSelectForSend(select: SelectInstance<*>, element: Any?) = + sendImpl( // <-- this is an inline function + element = element as E, + waiter = select, + onRendezvousOrBuffered = { select.selectInRegistrationPhase(Unit) }, + onSuspend = { segm, i -> select.prepareSenderForSuspension(segm, i) }, + onClosed = { onClosedSelectOnSend(element, select) } + ) + + private fun SelectInstance<*>.prepareSenderForSuspension( + // The working cell is specified by + // the segment and the index in it. + segment: ChannelSegment, + index: Int + ) { + if (onUndeliveredElement == null) { + disposeOnCompletion(SenderOrReceiverCancellationHandler(segment, index)) + } else { + disposeOnCompletion(SenderWithOnUndeliveredElementCancellationHandler(segment, index, context)) + } + } + + private fun onClosedSelectOnSend(element: E, select: SelectInstance<*>) { + onUndeliveredElement?.callUndeliveredElement(element, select.context) + select.selectInRegistrationPhase(CHANNEL_CLOSED) + } + + @Suppress("UNUSED_PARAMETER", "RedundantNullableReturnType") + private fun processResultSelectSend(ignoredParam: Any?, selectResult: Any?): Any? = + if (selectResult === CHANNEL_CLOSED) throw sendException + else this + + @Suppress("UNCHECKED_CAST") + override val onReceive: SelectClause1 + get() = SelectClause1Impl( + clauseObject = this@BufferedChannel, + regFunc = BufferedChannel<*>::registerSelectForReceive as RegistrationFunction, + processResFunc = BufferedChannel<*>::processResultSelectReceive as ProcessResultFunction, + onCancellationConstructor = onUndeliveredElementReceiveCancellationConstructor + ) + + @Suppress("UNCHECKED_CAST") + override val onReceiveCatching: SelectClause1> + get() = SelectClause1Impl( + clauseObject = this@BufferedChannel, + regFunc = BufferedChannel<*>::registerSelectForReceive as RegistrationFunction, + processResFunc = BufferedChannel<*>::processResultSelectReceiveCatching as ProcessResultFunction, + onCancellationConstructor = onUndeliveredElementReceiveCancellationConstructor + ) + + @Suppress("OVERRIDE_DEPRECATION", "UNCHECKED_CAST") + override val onReceiveOrNull: SelectClause1 + get() = SelectClause1Impl( + clauseObject = this@BufferedChannel, + regFunc = BufferedChannel<*>::registerSelectForReceive as RegistrationFunction, + processResFunc = BufferedChannel<*>::processResultSelectReceiveOrNull as ProcessResultFunction, + onCancellationConstructor = onUndeliveredElementReceiveCancellationConstructor + ) + + @Suppress("UNUSED_PARAMETER") + private fun registerSelectForReceive(select: SelectInstance<*>, ignoredParam: Any?) = + receiveImpl( // <-- this is an inline function + waiter = select, + onElementRetrieved = { elem -> select.selectInRegistrationPhase(elem) }, + onSuspend = { segm, i, _ -> select.prepareReceiverForSuspension(segm, i) }, + onClosed = { onClosedSelectOnReceive(select) } + ) + + private fun SelectInstance<*>.prepareReceiverForSuspension( + /* The working cell is specified by + the segment and the index in it. */ + segment: ChannelSegment, + index: Int + ) { + onReceiveEnqueued() + disposeOnCompletion(SenderOrReceiverCancellationHandler(segment, index)) + } + + private fun onClosedSelectOnReceive(select: SelectInstance<*>) { + select.selectInRegistrationPhase(CHANNEL_CLOSED) + } + + @Suppress("UNUSED_PARAMETER") + private fun processResultSelectReceive(ignoredParam: Any?, selectResult: Any?): Any? = + if (selectResult === CHANNEL_CLOSED) throw receiveException + else selectResult + + @Suppress("UNUSED_PARAMETER") + private fun processResultSelectReceiveOrNull(ignoredParam: Any?, selectResult: Any?): Any? = + if (selectResult === CHANNEL_CLOSED) { + if (closeCause == null) null + else throw receiveException + } else selectResult + + @Suppress("UNCHECKED_CAST", "UNUSED_PARAMETER", "RedundantNullableReturnType") + private fun processResultSelectReceiveCatching(ignoredParam: Any?, selectResult: Any?): Any? = + if (selectResult === CHANNEL_CLOSED) closed(closeCause) + else success(selectResult as E) + + @Suppress("UNCHECKED_CAST") + private val onUndeliveredElementReceiveCancellationConstructor: OnCancellationConstructor? = onUndeliveredElement?.let { + { select: SelectInstance<*>, _: Any?, element: Any? -> + { if (element !== CHANNEL_CLOSED) onUndeliveredElement.callUndeliveredElement(element as E, select.context) } + } + } + + // ###################### + // ## Iterator Support ## + // ###################### + + override fun iterator(): ChannelIterator = BufferedChannelIterator() + + /** + * The key idea is that an iterator is a special receiver type, + * which should be resumed differently to [receive] and [onReceive] + * operations, but can be served as a waiter in a way similar to + * [CancellableContinuation] and [SelectInstance]. + * + * Roughly, [hasNext] is a [receive] sibling, while [next] simply + * returns the already retrieved element. From the implementation + * side, [receiveResult] stores the element retrieved by [hasNext] + * (or a special [CHANNEL_CLOSED] token if the channel is closed). + * + * The [invoke] function is a [CancelHandler] implementation, + * which requires knowing the [segment] and the [index] in it + * that specify the location of the stored iterator. + * + * To resume the suspended [hasNext] call, a special [tryResumeHasNext] + * function should be used in a way similar to [CancellableContinuation.tryResume] + * and [SelectInstance.trySelect]. When the channel becomes closed, + * [tryResumeHasNextOnClosedChannel] should be used instead. + */ + private inner class BufferedChannelIterator : ChannelIterator, BeforeResumeCancelHandler(), Waiter { + /** + * Stores the element retrieved by [hasNext] or + * a special [CHANNEL_CLOSED] token if this channel is closed. + * If [hasNext] has not been invoked yet, [NO_RECEIVE_RESULT] is stored. + */ + private var receiveResult: Any? = NO_RECEIVE_RESULT + + /** + * When [hasNext] suspends, this field stores the corresponding + * continuation. The [tryResumeHasNext] and [tryResumeHasNextOnClosedChannel] + * function resume this continuation when the [hasNext] invocation should complete. + */ + private var continuation: CancellableContinuation? = null + + // When `hasNext()` suspends, the location where the continuation + // is stored is specified via the segment and the index in it. + // We need this information in the cancellation handler below. + private var segment: ChannelSegment? = null + private var index = -1 + + /** + * Invoked on cancellation, [BeforeResumeCancelHandler] implementation. + */ + override fun invoke(cause: Throwable?) { + segment?.onCancellation(index) + } + + // `hasNext()` is just a special receive operation. + override suspend fun hasNext(): Boolean = + receiveImpl( // <-- this is an inline function + // Do not create a continuation until it is required; + // it is created later via [onNoWaiterSuspend], if needed. + waiter = null, + // Store the received element in `receiveResult` on successful + // retrieval from the buffer or rendezvous with a suspended sender. + // Also, inform the `BufferedChannel` extensions that + // the synchronization of this receive operation is completed. + onElementRetrieved = { element -> + this.receiveResult = element + true + }, + // As no waiter is provided, suspension is impossible. + onSuspend = { _, _, _ -> error("unreachable") }, + // Return `false` or throw an exception if the channel is already closed. + onClosed = { onClosedHasNext() }, + // If `hasNext()` decides to suspend, the corresponding + // `suspend` function that creates a continuation is called. + // The tail-call optimization is applied here. + onNoWaiterSuspend = { segm, i, r -> return hasNextOnNoWaiterSuspend(segm, i, r) } + ) + + private fun onClosedHasNext(): Boolean { + this.receiveResult = CHANNEL_CLOSED + val cause = closeCause ?: return false + throw recoverStackTrace(cause) + } + + private suspend fun hasNextOnNoWaiterSuspend( + /* The working cell is specified by + the segment and the index in it. */ + segment: ChannelSegment, + index: Int, + /* The global index of the cell. */ + r: Long + ): Boolean = suspendCancellableCoroutineReusable { cont -> + this.continuation = cont + receiveImplOnNoWaiter( // <-- this is an inline function + segment = segment, index = index, r = r, + waiter = this, // store this iterator as a waiter + // In case of successful element retrieval, store + // it in `receiveResult` and resume the continuation. + // Importantly, the receiver coroutine may be cancelled + // after it is successfully resumed but not dispatched yet. + // In case `onUndeliveredElement` is present, we must + // invoke it in the latter case. + onElementRetrieved = { element -> + this.receiveResult = element + this.continuation = null + cont.resume(true, onUndeliveredElement?.bindCancellationFun(element, cont.context)) + }, + onSuspend = { segm, i, _ -> prepareForSuspension(segm, i) }, + onClosed = { onClosedHasNextNoWaiterSuspend() } + ) + } + + private fun prepareForSuspension(segment: ChannelSegment, index: Int) { + this.segment = segment + this.index = index + // It is possible that this `hasNext()` invocation is already + // resumed, and the `continuation` field is already updated to `null`. + this.continuation?.invokeOnCancellation(this.asHandler) + onReceiveEnqueued() + } + + private fun onClosedHasNextNoWaiterSuspend() { + // Read the current continuation and clean + // the corresponding field to avoid memory leaks. + val cont = this.continuation!! + this.continuation = null + // Update the `hasNext()` internal result. + this.receiveResult = CHANNEL_CLOSED + // If this channel was closed without exception, + // `hasNext()` should return `false`; otherwise, + // it throws the closing exception. + val cause = closeCause + if (cause == null) { + cont.resume(false) + } else { + cont.resumeWithException(recoverStackTrace(cause, cont)) + } + } + + @Suppress("UNCHECKED_CAST") + override fun next(): E { + // Read the already received result, or [NO_RECEIVE_RESULT] if [hasNext] has not been invoked yet. + val result = receiveResult + check(result !== NO_RECEIVE_RESULT) { "`hasNext()` has not been invoked" } + receiveResult = NO_RECEIVE_RESULT + // Is this channel closed? + if (result === CHANNEL_CLOSED) throw recoverStackTrace(receiveException) + // Return the element. + return result as E + } + + fun tryResumeHasNext(element: E): Boolean { + // Read the current continuation and clean + // the corresponding field to avoid memory leaks. + val cont = this.continuation!! + this.continuation = null + // Store the retrieved element in `receiveResult`. + this.receiveResult = element + // Try to resume this `hasNext()`. Importantly, the receiver coroutine + // may be cancelled after it is successfully resumed but not dispatched yet. + // In case `onUndeliveredElement` is specified, we need to invoke it in the latter case. + return cont.tryResume0(true, onUndeliveredElement?.bindCancellationFun(element, cont.context)) + } + + fun tryResumeHasNextOnClosedChannel() { + // Read the current continuation and clean + // the corresponding field to avoid memory leaks. + val cont = this.continuation!! + this.continuation = null + // Update the `hasNext()` internal result and inform + // `BufferedChannel` extensions that synchronization + // of this receive operation is completed. + this.receiveResult = CHANNEL_CLOSED + // If this channel was closed without exception, + // `hasNext()` should return `false`; otherwise, + // it throws the closing exception. + val cause = closeCause + if (cause == null) { + cont.resume(false) + } else { + cont.resumeWithException(recoverStackTrace(cause, cont)) + } + } + } + + // ############################## + // ## Closing and Cancellation ## + // ############################## + + /** + * Store the cause of closing this channel, either via [close] or [cancel] call. + * The closing cause can be set only once. + */ + private val _closeCause = atomic(NO_CLOSE_CAUSE) + // Should be called only if this channel is closed or cancelled. + protected val closeCause get() = _closeCause.value as Throwable? + + /** Returns the closing cause if it is non-null, or [ClosedSendChannelException] otherwise. */ + protected val sendException get() = closeCause ?: ClosedSendChannelException(DEFAULT_CLOSE_MESSAGE) + + /** Returns the closing cause if it is non-null, or [ClosedReceiveChannelException] otherwise. */ + private val receiveException get() = closeCause ?: ClosedReceiveChannelException(DEFAULT_CLOSE_MESSAGE) + + /** + Stores the closed handler installed by [invokeOnClose]. + To synchronize [invokeOnClose] and [close], two additional + marker states, [CLOSE_HANDLER_INVOKED] and [CLOSE_HANDLER_CLOSED] + are used. The resulting state diagram is presented below. + + +------+ install handler +---------+ close(..) +---------+ + | null |------------------>| handler |------------>| INVOKED | + +------+ +---------+ +---------+ + | + | close(..) +--------+ + +----------->| CLOSED | + +--------+ + */ + private val closeHandler = atomic(null) + + /** + * Invoked when channel is closed as the last action of [close] invocation. + * This method should be idempotent and can be called multiple times. + */ + protected open fun onClosedIdempotent() {} + + override fun close(cause: Throwable?): Boolean = + closeOrCancelImpl(cause, cancel = false) + + @Suppress("OVERRIDE_DEPRECATION") + final override fun cancel(cause: Throwable?): Boolean = cancelImpl(cause) + + @Suppress("OVERRIDE_DEPRECATION") + final override fun cancel() { cancelImpl(null) } + + final override fun cancel(cause: CancellationException?) { cancelImpl(cause) } + + internal open fun cancelImpl(cause: Throwable?): Boolean = + closeOrCancelImpl(cause ?: CancellationException("Channel was cancelled"), cancel = true) + + /** + * This is a common implementation for [close] and [cancel]. It first tries + * to install the specified cause; the invocation that successfully installs + * the cause returns `true` as a results of this function, while all further + * [close] and [cancel] calls return `false`. + * + * After the closing/cancellation cause is installed, the channel should be marked + * as closed or cancelled, which bounds further `send(e)`-s to fails. + * + * Then, [completeCloseOrCancel] is called, which cancels waiting `receive()` + * requests ([cancelSuspendedReceiveRequests]) and removes unprocessed elements + * ([removeUnprocessedElements]) in case this channel is cancelled. + * + * Finally, if this [closeOrCancelImpl] has installed the cause, therefore, + * has closed the channel, [closeHandler] and [onClosedIdempotent] should be invoked. + */ + protected open fun closeOrCancelImpl(cause: Throwable?, cancel: Boolean): Boolean { + // If this is a `cancel(..)` invocation, set a bit that the cancellation + // has been started. This is crucial for ensuring linearizability, + // when concurrent `close(..)` and `isClosedFor[Send,Receive]` operations + // help this `cancel(..)`. + if (cancel) markCancellationStarted() + // Try to install the specified cause. On success, this invocation will + // return `true` as a result; otherwise, it will complete with `false`. + val closedByThisOperation = _closeCause.compareAndSet(NO_CLOSE_CAUSE, cause) + // Mark this channel as closed or cancelled, depending on this operation type. + if (cancel) markCancelled() else markClosed() + // Complete the closing or cancellation procedure. + completeCloseOrCancel() + // Finally, if this operation has installed the cause, + // it should invoke the close handlers. + return closedByThisOperation.also { + onClosedIdempotent() + if (it) invokeCloseHandler() + } + } + + /** + * Invokes the installed close handler, + * updating the [closeHandler] state correspondingly. + */ + private fun invokeCloseHandler() { + val closeHandler = closeHandler.getAndUpdate { + if (it === null) { + // Inform concurrent `invokeOnClose` + // that this channel is already closed. + CLOSE_HANDLER_CLOSED + } else { + // Replace the handler with a special + // `INVOKED` marker to avoid memory leaks. + CLOSE_HANDLER_INVOKED + } + } ?: return // no handler was installed, finish. + // Invoke the handler. + @Suppress("UNCHECKED_CAST") + closeHandler as (cause: Throwable?) -> Unit + closeHandler(closeCause) + } + + override fun invokeOnClose(handler: (cause: Throwable?) -> Unit) { + // Try to install the handler, finishing on success. + if (closeHandler.compareAndSet(null, handler)) { + // Handler has been successfully set, finish the operation. + return + } + // Either another handler is already set, or this channel is closed. + // In the latter case, the current handler should be invoked. + // However, the implementation must ensure that at most one + // handler is called, throwing an `IllegalStateException` + // if another close handler has been invoked. + closeHandler.loop { cur -> + when { + cur === CLOSE_HANDLER_CLOSED -> { + // Try to update the state from `CLOSED` to `INVOKED`. + // This is crucial to guarantee that at most one handler can be called. + // On success, invoke the handler and finish. + if (closeHandler.compareAndSet(CLOSE_HANDLER_CLOSED, CLOSE_HANDLER_INVOKED)) { + handler(closeCause) + return + } + } + cur === CLOSE_HANDLER_INVOKED -> error("Another handler was already registered and successfully invoked") + else -> error("Another handler is already registered: $cur") + } + } + } + + /** + * Marks this channel as closed. + * In case [cancelImpl] has already been invoked, + * and this channel is marked with [CLOSE_STATUS_CANCELLATION_STARTED], + * this function marks the channel as cancelled. + * + * All operation that notice this channel in the closed state, + * must help to complete the closing via [completeCloseOrCancel]. + */ + private fun markClosed(): Unit = + sendersAndCloseStatus.update { cur -> + when (cur.sendersCloseStatus) { + CLOSE_STATUS_ACTIVE -> // the channel is neither closed nor cancelled + constructSendersAndCloseStatus(cur.sendersCounter, CLOSE_STATUS_CLOSED) + CLOSE_STATUS_CANCELLATION_STARTED -> // the channel is going to be cancelled + constructSendersAndCloseStatus(cur.sendersCounter, CLOSE_STATUS_CANCELLED) + else -> return // the channel is already marked as closed or cancelled. + } + } + + /** + * Marks this channel as cancelled. + * + * All operation that notice this channel in the cancelled state, + * must help to complete the cancellation via [completeCloseOrCancel]. + */ + private fun markCancelled(): Unit = + sendersAndCloseStatus.update { cur -> + constructSendersAndCloseStatus(cur.sendersCounter, CLOSE_STATUS_CANCELLED) + } + + /** + * When the cancellation procedure starts, it is critical + * to mark the closing status correspondingly. Thus, other + * operations, which may help to complete the cancellation, + * always correctly update the status to `CANCELLED`. + */ + private fun markCancellationStarted(): Unit = + sendersAndCloseStatus.update { cur -> + if (cur.sendersCloseStatus == CLOSE_STATUS_ACTIVE) + constructSendersAndCloseStatus(cur.sendersCounter, CLOSE_STATUS_CANCELLATION_STARTED) + else return // this channel is already closed or cancelled + } + + /** + * Completes the started [close] or [cancel] procedure. + */ + private fun completeCloseOrCancel() { + isClosedForSend // must finish the started close/cancel if one is detected. + } + + protected open val isConflatedDropOldest get() = false + + /** + * Completes the channel closing procedure. + */ + private fun completeClose(sendersCur: Long): ChannelSegment { + // Close the linked list for further segment addition, + // obtaining the last segment in the data structure. + val lastSegment = closeLinkedList() + // In the conflated channel implementation (with the DROP_OLDEST + // elements conflation strategy), it is critical to mark all empty + // cells as closed to prevent in-progress `send(e)`-s, which have not + // put their elements yet, completions after this channel is closed. + // Otherwise, it is possible for a `send(e)` to put an element when + // the buffer is already full, while a concurrent receiver may extract + // the oldest element. When the channel is not closed, we can linearize + // this `receive()` before the `send(e)`, but after the channel is closed, + // `send(e)` must fails. Marking all unprocessed cells as `CLOSED` solves the issue. + if (isConflatedDropOldest) { + val lastBufferedCellGlobalIndex = markAllEmptyCellsAsClosed(lastSegment) + if (lastBufferedCellGlobalIndex != -1L) + dropFirstElementUntilTheSpecifiedCellIsInTheBuffer(lastBufferedCellGlobalIndex) + } + // Resume waiting `receive()` requests, + // informing them that the channel is closed. + cancelSuspendedReceiveRequests(lastSegment, sendersCur) + // Return the last segment in the linked list as a result + // of this function; we need it in `completeCancel(..)`. + return lastSegment + } + + /** + * Completes the channel cancellation procedure. + */ + private fun completeCancel(sendersCur: Long) { + // First, ensure that this channel is closed, + // obtaining the last segment in the linked list. + val lastSegment = completeClose(sendersCur) + // Cancel suspended `send(e)` requests and + // remove buffered elements in the reverse order. + removeUnprocessedElements(lastSegment) + } + + /** + * Closes the underlying linked list of segments for further segment addition. + */ + private fun closeLinkedList(): ChannelSegment { + // Choose the last segment. + var lastSegment = bufferEndSegment.value + sendSegment.value.let { if (it.id > lastSegment.id) lastSegment = it } + receiveSegment.value.let { if (it.id > lastSegment.id) lastSegment = it } + // Close the linked list of segment for new segment addition + // and return the last segment in the linked list. + return lastSegment.close() + } + + /** + * This function marks all empty cells, in the `null` and [IN_BUFFER] state, + * as closed. Notably, it processes the cells from right to left, and finishes + * immediately when the processing cell is already covered by `receive()` or + * contains a buffered elements ([BUFFERED] state). + * + * This function returns the global index of the last buffered element, + * or `-1` if this channel does not contain buffered elements. + */ + private fun markAllEmptyCellsAsClosed(lastSegment: ChannelSegment): Long { + // Process the cells in reverse order, from right to left. + var segment = lastSegment + while (true) { + for (index in SEGMENT_SIZE - 1 downTo 0) { + // Is this cell already covered by `receive()`? + val globalIndex = segment.id * SEGMENT_SIZE + index + if (globalIndex < receiversCounter) return -1 + // Process the cell `segment[index]`. + cell_update@ while (true) { + val state = segment.getState(index) + when { + // The cell is empty. + state === null || state === IN_BUFFER -> { + // Inform a possibly upcoming sender that this channel is already closed. + if (segment.casState(index, state, CHANNEL_CLOSED)) { + segment.onSlotCleaned() + break@cell_update + } + } + // The cell stores a buffered element. + state === BUFFERED -> return globalIndex + // Skip this cell if it is not empty and does not store a buffered element. + else -> break@cell_update + } + } + } + // Process the next segment, finishing if the linked list ends. + segment = segment.prev ?: return -1 + } + } + + /** + * Cancels suspended `send(e)` requests and removes buffered elements + * starting from the last cell in the specified [lastSegment] (it must + * be the physical tail of the underlying linked list) and updating + * the cells in reverse order. + */ + private fun removeUnprocessedElements(lastSegment: ChannelSegment) { + // Read the `onUndeliveredElement` lambda at once. In case it + // throws an exception, this exception is handled and stored in + // the variable below. If multiple exceptions are thrown, the first + // one is stored in the variable, while the others are suppressed. + val onUndeliveredElement = onUndeliveredElement + var undeliveredElementException: UndeliveredElementException? = null // first cancel exception, others suppressed + // To perform synchronization correctly, it is critical to + // process the cells in reverse order, from right to left. + // However, according to the API, suspended senders should + // be cancelled in the order of their suspension. Therefore, + // we need to collect all of them and cancel in the reverse + // order after that. + var suspendedSenders = InlineList() + var segment = lastSegment + process_segments@ while (true) { + for (index in SEGMENT_SIZE - 1 downTo 0) { + // Process the cell `segment[index]`. + val globalIndex = segment.id * SEGMENT_SIZE + index + // Update the cell state. + update_cell@ while (true) { + // Read the current state of the cell. + val state = segment.getState(index) + when { + // The cell is already processed by a receiver. + state === DONE_RCV -> break@process_segments + // The cell stores a buffered element. + state === BUFFERED -> { + // Is the cell already covered by a receiver? + if (globalIndex < receiversCounter) break@process_segments + // Update the cell state to `CHANNEL_CLOSED`. + if (segment.casState(index, state, CHANNEL_CLOSED)) { + // If `onUndeliveredElement` lambda is non-null, call it. + if (onUndeliveredElement != null) { + val element = segment.getElement(index) + undeliveredElementException = onUndeliveredElement.callUndeliveredElementCatchingException(element, undeliveredElementException) + } + // Clean the element field and inform the segment + // that the slot is cleaned to avoid memory leaks. + segment.cleanElement(index) + segment.onSlotCleaned() + break@update_cell + } + } + // The cell is empty. + state === IN_BUFFER || state === null -> { + // Update the cell state to `CHANNEL_CLOSED`. + if (segment.casState(index, state, CHANNEL_CLOSED)) { + // Inform the segment that the slot is cleaned to avoid memory leaks. + segment.onSlotCleaned() + break@update_cell + } + } + // The cell stores a suspended waiter. + state is Waiter || state is WaiterEB -> { + // Is the cell already covered by a receiver? + if (globalIndex < receiversCounter) break@process_segments + // Obtain the sender. + val sender: Waiter = if (state is WaiterEB) state.waiter + else state as Waiter + // Update the cell state to `CHANNEL_CLOSED`. + if (segment.casState(index, state, CHANNEL_CLOSED)) { + // If `onUndeliveredElement` lambda is non-null, call it. + if (onUndeliveredElement != null) { + val element = segment.getElement(index) + undeliveredElementException = onUndeliveredElement.callUndeliveredElementCatchingException(element, undeliveredElementException) + } + // Save the sender for further cancellation. + suspendedSenders += sender + // Clean the element field and inform the segment + // that the slot is cleaned to avoid memory leaks. + segment.cleanElement(index) + segment.onSlotCleaned() + break@update_cell + } + } + // A concurrent receiver is resuming a suspended sender. + // As the cell is covered by a receiver, finish immediately. + state === RESUMING_BY_EB || state === RESUMING_BY_RCV -> break@process_segments + // A concurrent `expandBuffer()` is resuming a suspended sender. + // Wait in a spin-loop until the cell state changes. + state === RESUMING_BY_EB -> continue@update_cell + else -> break@update_cell + } + } + } + // Process the previous segment. + segment = segment.prev ?: break + } + // Cancel suspended senders in their order of addition to this channel. + suspendedSenders.forEachReversed { it.resumeSenderOnCancelledChannel() } + // Throw `UndeliveredElementException` at the end if there was one. + undeliveredElementException?.let { throw it } + } + + /** + * Cancels suspended `receive` requests from the end to the beginning, + * also moving empty cells to the `CHANNEL_CLOSED` state. + */ + private fun cancelSuspendedReceiveRequests(lastSegment: ChannelSegment, sendersCounter: Long) { + // To perform synchronization correctly, it is critical to + // extract suspended requests in the reverse order, + // from the end to the beginning. + // However, according to the API, they should be cancelled + // in the order of their suspension. Therefore, we need to + // collect the suspended requests first, cancelling them + // in the reverse order after that. + var suspendedReceivers = InlineList() + var segment: ChannelSegment? = lastSegment + process_segments@ while (segment != null) { + for (index in SEGMENT_SIZE - 1 downTo 0) { + // Is the cell already covered by a sender? Finish immediately in this case. + if (segment.id * SEGMENT_SIZE + index < sendersCounter) break@process_segments + // Try to move the cell state to `CHANNEL_CLOSED`. + cell_update@ while (true) { + val state = segment.getState(index) + when { + state === null || state === IN_BUFFER -> { + if (segment.casState(index, state, CHANNEL_CLOSED)) { + segment.onSlotCleaned() + break@cell_update + } + } + state is WaiterEB -> { + if (segment.casState(index, state, CHANNEL_CLOSED)) { + suspendedReceivers += state.waiter // save for cancellation. + segment.onCancelledRequest(index = index, receiver = true) + break@cell_update + } + } + state is Waiter -> { + if (segment.casState(index, state, CHANNEL_CLOSED)) { + suspendedReceivers += state // save for cancellation. + segment.onCancelledRequest(index = index, receiver = true) + break@cell_update + } + } + else -> break@cell_update // nothing to cancel. + } + } + } + // Process the previous segment. + segment = segment.prev + } + // Cancel the suspended requests in their order of addition to this channel. + suspendedReceivers.forEachReversed { it.resumeReceiverOnClosedChannel() } + } + + /** + * Resumes this receiver because this channel is closed. + * This function does not take any effect if the operation has already been resumed or cancelled. + */ + private fun Waiter.resumeReceiverOnClosedChannel() = resumeWaiterOnClosedChannel(receiver = true) + + /** + * Resumes this sender because this channel is cancelled. + * This function does not take any effect if the operation has already been resumed or cancelled. + */ + private fun Waiter.resumeSenderOnCancelledChannel() = resumeWaiterOnClosedChannel(receiver = false) + + private fun Waiter.resumeWaiterOnClosedChannel(receiver: Boolean) { + when (this) { + is SendBroadcast -> cont.resume(false) + is CancellableContinuation<*> -> resumeWithException(if (receiver) receiveException else sendException) + is ReceiveCatching<*> -> cont.resume(closed(closeCause)) + is BufferedChannel<*>.BufferedChannelIterator -> tryResumeHasNextOnClosedChannel() + is SelectInstance<*> -> trySelect(this@BufferedChannel, CHANNEL_CLOSED) + else -> error("Unexpected waiter: $this") + } + } + + @ExperimentalCoroutinesApi + override val isClosedForSend: Boolean + get() = sendersAndCloseStatus.value.isClosedForSend0 + + private val Long.isClosedForSend0 get() = + isClosed(this, isClosedForReceive = false) + + @ExperimentalCoroutinesApi + override val isClosedForReceive: Boolean + get() = sendersAndCloseStatus.value.isClosedForReceive0 + + private val Long.isClosedForReceive0 get() = + isClosed(this, isClosedForReceive = true) + + private fun isClosed( + sendersAndCloseStatusCur: Long, + isClosedForReceive: Boolean + ) = when (sendersAndCloseStatusCur.sendersCloseStatus) { + // This channel is active and has not been closed. + CLOSE_STATUS_ACTIVE -> false + // The cancellation procedure has been started but + // not linearized yet, so this channel should be + // considered as active. + CLOSE_STATUS_CANCELLATION_STARTED -> false + // This channel has been successfully closed. + // Help to complete the closing procedure to + // guarantee linearizability, and return `true` + // for senders or the flag whether there still + // exist elements to retrieve for receivers. + CLOSE_STATUS_CLOSED -> { + completeClose(sendersAndCloseStatusCur.sendersCounter) + // When `isClosedForReceive` is `false`, always return `true`. + // Otherwise, it is possible that the channel is closed but + // still has elements to retrieve. + if (isClosedForReceive) !hasElements() else true + } + // This channel has been successfully cancelled. + // Help to complete the cancellation procedure to + // guarantee linearizability and return `true`. + CLOSE_STATUS_CANCELLED -> { + completeCancel(sendersAndCloseStatusCur.sendersCounter) + true + } + else -> error("unexpected close status: ${sendersAndCloseStatusCur.sendersCloseStatus}") + } + + @ExperimentalCoroutinesApi + override val isEmpty: Boolean get() { + // This function should return `false` if + // this channel is closed for `receive`. + if (isClosedForReceive) return false + // Does this channel has elements to retrieve? + if (hasElements()) return false + // This channel does not have elements to retrieve; + // Check that it is still not closed for `receive`. + return !isClosedForReceive + } + + /** + * Checks whether this channel contains elements to retrieve. + * Unfortunately, simply comparing the counters is insufficient, + * as some cells can be in the `INTERRUPTED` state due to cancellation. + * This function tries to find the first "alive" element, + * updating the `receivers` counter to skip empty cells. + * + * The implementation is similar to `receive()`. + */ + internal fun hasElements(): Boolean { + while (true) { + // Read the segment before obtaining the `receivers` counter value. + var segment = receiveSegment.value + // Obtains the `receivers` and `senders` counter values. + val r = receiversCounter + val s = sendersCounter + // Is there a chance that this channel has elements? + if (s <= r) return false // no elements + // The `r`-th cell is covered by a sender; check whether it contains an element. + // First, try to find the required segment if the initially + // obtained segment (in the beginning of this function) has lower id. + val id = r / SEGMENT_SIZE + if (segment.id != id) { + // Try to find the required segment. + segment = findSegmentReceive(id, segment) ?: + // The required segment has not been found. Either it has already + // been removed, or the underlying linked list is already closed + // for segment additions. In the latter case, the channel is closed + // and does not contain elements, so this operation returns `false`. + // Otherwise, if the required segment is removed, the operation restarts. + if (receiveSegment.value.id < id) return false else continue + } + segment.cleanPrev() // all the previous segments are no longer needed. + // Does the `r`-th cell contain waiting sender or buffered element? + val i = (r % SEGMENT_SIZE).toInt() + if (isCellNonEmpty(segment, i, r)) return true + // The cell is empty. Update `receivers` counter and try again. + receivers.compareAndSet(r, r + 1) // if this CAS fails, the counter has already been updated. + } + } + + /** + * Checks whether this cell contains a buffered element or a waiting sender, + * returning `true` in this case. Otherwise, if this cell is empty + * (due to waiter cancellation, cell poisoning, or channel closing), + * this function returns `false`. + * + * Notably, this function must be called only if the cell is covered by a sender. + */ + private fun isCellNonEmpty( + segment: ChannelSegment, + index: Int, + globalIndex: Long + ): Boolean { + // The logic is similar to `updateCellReceive` with the only difference + // that this function neither changes the cell state nor retrieves the element. + while (true) { + // Read the current cell state. + val state = segment.getState(index) + when { + // The cell is empty but a sender is coming. + state === null || state === IN_BUFFER -> { + // Poison the cell to ensure correctness. + if (segment.casState(index, state, POISONED)) { + // When the cell becomes poisoned, it is essentially + // the same as storing an already cancelled receiver. + // Thus, the `expandBuffer()` procedure should be invoked. + expandBuffer() + return false + } + } + // The cell stores a buffered element. + state === BUFFERED -> return true + // The cell stores an interrupted sender. + state === INTERRUPTED_SEND -> return false + // This channel is already closed. + state === CHANNEL_CLOSED -> return false + // The cell is already processed + // by a concurrent receiver. + state === DONE_RCV -> return false + // The cell is already poisoned + // by a concurrent receiver. + state === POISONED -> return false + // A concurrent `expandBuffer()` is resuming + // a suspended sender. This function is eligible + // to linearize before the buffer expansion procedure. + state === RESUMING_BY_EB -> return true + // A concurrent receiver is resuming + // a suspended sender. The element + // is no longer available for retrieval. + state === RESUMING_BY_RCV -> return false + // The cell stores a suspended request. + // However, it is possible that this request + // is receiver if the cell is covered by both + // send and receive operations. + // In case the cell is already covered by + // a receiver, the element is no longer + // available for retrieval, and this function + // return `false`. Otherwise, it is guaranteed + // that the suspended request is sender, so + // this function returns `true`. + else -> return globalIndex == receiversCounter + } + } + } + + // ####################### + // # Segments Management # + // ####################### + + /** + * Finds the segment with the specified [id] starting by the [startFrom] + * segment and following the [ChannelSegment.next] references. In case + * the required segment has not been created yet, this function attempts + * to add it to the underlying linked list. Finally, it updates [sendSegment] + * to the found segment if its [ChannelSegment.id] is greater than the one + * of the already stored segment. + * + * In case the requested segment is already removed, or if it should be allocated + * but the linked list structure is closed for new segments addition, this function + * returns `null`. The implementation also efficiently skips a sequence of removed + * segments, updating the counter value in [sendersAndCloseStatus] correspondingly. + */ + private fun findSegmentSend(id: Long, startFrom: ChannelSegment): ChannelSegment? { + return sendSegment.findSegmentAndMoveForward(id, startFrom, ::createSegment).let { + if (it.isClosed) { + // The required segment has not been found and new segments + // cannot be added, as the linked listed in already added. + // This channel is already closed or cancelled; help to complete + // the closing or cancellation procedure. + completeCloseOrCancel() + // Clean the `prev` reference of the provided segment + // if all the previous cells are already covered by senders. + // It is important to clean the `prev` reference only in + // this case, as the closing/cancellation procedure may + // need correct value to traverse the linked list from right to left. + if (startFrom.id * SEGMENT_SIZE < receiversCounter) startFrom.cleanPrev() + // As the required segment is not found and cannot be allocated, return `null`. + null + } else { + // Get the found segment. + val segment = it.segment + // Is the required segment removed? + if (segment.id > id) { + // The required segment has been removed; `segment` is the first + // segment with `id` not lower than the required one. + // Skip the sequence of removed cells in O(1). + updateSendersCounterIfLower(segment.id * SEGMENT_SIZE) + // Clean the `prev` reference of the provided segment + // if all the previous cells are already covered by senders. + // It is important to clean the `prev` reference only in + // this case, as the closing/cancellation procedure may + // need correct value to traverse the linked list from right to left. + if (segment.id * SEGMENT_SIZE < receiversCounter) segment.cleanPrev() + // As the required segment is not found and cannot be allocated, return `null`. + null + } else { + assert { segment.id == id } + // The required segment has been found; return it! + segment + } + } + } + } + + /** + * Finds the segment with the specified [id] starting by the [startFrom] + * segment and following the [ChannelSegment.next] references. In case + * the required segment has not been created yet, this function attempts + * to add it to the underlying linked list. Finally, it updates [receiveSegment] + * to the found segment if its [ChannelSegment.id] is greater than the one + * of the already stored segment. + * + * In case the requested segment is already removed, or if it should be allocated + * but the linked list structure is closed for new segments addition, this function + * returns `null`. The implementation also efficiently skips a sequence of removed + * segments, updating the [receivers] counter correspondingly. + */ + private fun findSegmentReceive(id: Long, startFrom: ChannelSegment): ChannelSegment? = + receiveSegment.findSegmentAndMoveForward(id, startFrom, ::createSegment).let { + if (it.isClosed) { + // The required segment has not been found and new segments + // cannot be added, as the linked listed in already added. + // This channel is already closed or cancelled; help to complete + // the closing or cancellation procedure. + completeCloseOrCancel() + // Clean the `prev` reference of the provided segment + // if all the previous cells are already covered by senders. + // It is important to clean the `prev` reference only in + // this case, as the closing/cancellation procedure may + // need correct value to traverse the linked list from right to left. + if (startFrom.id * SEGMENT_SIZE < sendersCounter) startFrom.cleanPrev() + // As the required segment is not found and cannot be allocated, return `null`. + null + } else { + // Get the found segment. + val segment = it.segment + // Advance the `bufferEnd` segment if required. + if (!isRendezvousOrUnlimited && id <= bufferEndCounter / SEGMENT_SIZE) { + bufferEndSegment.moveForward(segment) + } + // Is the required segment removed? + if (segment.id > id) { + // The required segment has been removed; `segment` is the first + // segment with `id` not lower than the required one. + // Skip the sequence of removed cells in O(1). + updateReceiversCounterIfLower(segment.id * SEGMENT_SIZE) + // Clean the `prev` reference of the provided segment + // if all the previous cells are already covered by senders. + // It is important to clean the `prev` reference only in + // this case, as the closing/cancellation procedure may + // need correct value to traverse the linked list from right to left. + if (segment.id * SEGMENT_SIZE < sendersCounter) segment.cleanPrev() + // As the required segment is already removed, return `null`. + null + } else { + assert { segment.id == id } + // The required segment has been found; return it! + segment + } + } + } + + /** + * Importantly, when this function does not find the requested segment, + * it always updates the number of completed `expandBuffer()` attempts. + */ + private fun findSegmentBufferEnd(id: Long, startFrom: ChannelSegment, currentBufferEndCounter: Long): ChannelSegment? = + bufferEndSegment.findSegmentAndMoveForward(id, startFrom, ::createSegment).let { + if (it.isClosed) { + // The required segment has not been found and new segments + // cannot be added, as the linked listed in already added. + // This channel is already closed or cancelled; help to complete + // the closing or cancellation procedure. + completeCloseOrCancel() + // Update `bufferEndSegment` to the last segment + // in the linked list to avoid memory leaks. + moveSegmentBufferEndToSpecifiedOrLast(id, startFrom) + // When this function does not find the requested segment, + // it should update the number of completed `expandBuffer()` attempts. + incCompletedExpandBufferAttempts() + null + } else { + // Get the found segment. + val segment = it.segment + // Is the required segment removed? + if (segment.id > id) { + // The required segment has been removed; `segment` is the first segment + // with `id` not lower than the required one. + // Try to skip the sequence of removed cells in O(1) by increasing the `bufferEnd` counter. + // Importantly, when this function does not find the requested segment, + // it should update the number of completed `expandBuffer()` attempts. + if (bufferEnd.compareAndSet(currentBufferEndCounter + 1, segment.id * SEGMENT_SIZE)) { + incCompletedExpandBufferAttempts(segment.id * SEGMENT_SIZE - currentBufferEndCounter) + } else { + incCompletedExpandBufferAttempts() + } + // As the required segment is already removed, return `null`. + null + } else { + assert { segment.id == id } + // The required segment has been found; return it! + segment + } + } + } + + /** + * Updates [bufferEndSegment] to the one with the specified [id] or + * to the last existing segment, if the required segment is not yet created. + * + * Unlike [findSegmentBufferEnd], this function does not allocate new segments. + */ + private fun moveSegmentBufferEndToSpecifiedOrLast(id: Long, startFrom: ChannelSegment) { + // Start searching the required segment from the specified one. + var segment: ChannelSegment = startFrom + while (segment.id < id) { + segment = segment.next ?: break + } + // Skip all removed segments and try to update `bufferEndSegment` + // to the first non-removed one. This part should succeed eventually, + // as the tail segment is never removed. + while (true) { + while (segment.isRemoved) { + segment = segment.next ?: break + } + // Try to update `bufferEndSegment`. On failure, + // the found segment is already removed, so it + // should be skipped. + if (bufferEndSegment.moveForward(segment)) return + } + } + + /** + * Updates the `senders` counter if its value + * is lower that the specified one. + * + * Senders use this function to efficiently skip + * a sequence of cancelled receivers. + */ + private fun updateSendersCounterIfLower(value: Long): Unit = + sendersAndCloseStatus.loop { cur -> + val curCounter = cur.sendersCounter + if (curCounter >= value) return + val update = constructSendersAndCloseStatus(curCounter, cur.sendersCloseStatus) + if (sendersAndCloseStatus.compareAndSet(cur, update)) return + } + + /** + * Updates the `receivers` counter if its value + * is lower that the specified one. + * + * Receivers use this function to efficiently skip + * a sequence of cancelled senders. + */ + private fun updateReceiversCounterIfLower(value: Long): Unit = + receivers.loop { cur -> + if (cur >= value) return + if (receivers.compareAndSet(cur, value)) return + } + + // ################### + // # Debug Functions # + // ################### + + @Suppress("ConvertTwoComparisonsToRangeCheck") + override fun toString(): String { + val sb = StringBuilder() + // Append the close status + when (sendersAndCloseStatus.value.sendersCloseStatus) { + CLOSE_STATUS_CLOSED -> sb.append("closed,") + CLOSE_STATUS_CANCELLED -> sb.append("cancelled,") + } + // Append the buffer capacity + sb.append("capacity=$capacity,") + // Append the data + sb.append("data=[") + val firstSegment = listOf(receiveSegment.value, sendSegment.value, bufferEndSegment.value) + .filter { it !== NULL_SEGMENT } + .minBy { it.id } + val r = receiversCounter + val s = sendersCounter + var segment = firstSegment + append_elements@ while (true) { + process_cell@ for (i in 0 until SEGMENT_SIZE) { + val globalCellIndex = segment.id * SEGMENT_SIZE + i + if (globalCellIndex >= s && globalCellIndex >= r) break@append_elements + val cellState = segment.getState(i) + val element = segment.getElement(i) + val cellStateString = when (cellState) { + is CancellableContinuation<*> -> { + when { + globalCellIndex < r && globalCellIndex >= s -> "receive" + globalCellIndex < s && globalCellIndex >= r -> "send" + else -> "cont" + } + } + is SelectInstance<*> -> { + when { + globalCellIndex < r && globalCellIndex >= s -> "onReceive" + globalCellIndex < s && globalCellIndex >= r -> "onSend" + else -> "select" + } + } + is ReceiveCatching<*> -> "receiveCatching" + is SendBroadcast -> "sendBroadcast" + is WaiterEB -> "EB($cellState)" + RESUMING_BY_RCV, RESUMING_BY_EB -> "resuming_sender" + null, IN_BUFFER, DONE_RCV, POISONED, INTERRUPTED_RCV, INTERRUPTED_SEND, CHANNEL_CLOSED -> continue@process_cell + else -> cellState.toString() // leave it just in case something is missed. + } + if (element != null) { + sb.append("($cellStateString,$element),") + } else { + sb.append("$cellStateString,") + } + } + // Process the next segment if exists. + segment = segment.next ?: break + } + if (sb.last() == ',') sb.deleteAt(sb.length - 1) + sb.append("]") + // The string representation is constructed. + return sb.toString() + } + + // Returns a debug representation of this channel, + // which is actively used in Lincheck tests. + internal fun toStringDebug(): String { + val sb = StringBuilder() + // Append the counter values and the close status + sb.append("S=${sendersCounter},R=${receiversCounter},B=${bufferEndCounter},B'=${completedExpandBuffersAndPauseFlag.value},C=${sendersAndCloseStatus.value.sendersCloseStatus},") + when (sendersAndCloseStatus.value.sendersCloseStatus) { + CLOSE_STATUS_CANCELLATION_STARTED -> sb.append("CANCELLATION_STARTED,") + CLOSE_STATUS_CLOSED -> sb.append("CLOSED,") + CLOSE_STATUS_CANCELLED -> sb.append("CANCELLED,") + } + // Append the segment references + sb.append("SEND_SEGM=${sendSegment.value.hexAddress},RCV_SEGM=${receiveSegment.value.hexAddress}") + if (!isRendezvousOrUnlimited) sb.append(",EB_SEGM=${bufferEndSegment.value.hexAddress}") + sb.append(" ") // add some space + // Append the linked list of segments. + val firstSegment = listOf(receiveSegment.value, sendSegment.value, bufferEndSegment.value) + .filter { it !== NULL_SEGMENT } + .minBy { it.id } + var segment = firstSegment + while (true) { + sb.append("${segment.hexAddress}=[${if (segment.isRemoved) "*" else ""}${segment.id},prev=${segment.prev?.hexAddress},") + repeat(SEGMENT_SIZE) { i -> + val cellState = segment.getState(i) + val element = segment.getElement(i) + val cellStateString = when (cellState) { + is CancellableContinuation<*> -> "cont" + is SelectInstance<*> -> "select" + is ReceiveCatching<*> -> "receiveCatching" + is SendBroadcast -> "send(broadcast)" + is WaiterEB -> "EB($cellState)" + else -> cellState.toString() + } + sb.append("[$i]=($cellStateString,$element),") + } + sb.append("next=${segment.next?.hexAddress}] ") + // Process the next segment if exists. + segment = segment.next ?: break + } + // The string representation of this channel is now constructed! + return sb.toString() + } + + + // This is an internal methods for tests. + fun checkSegmentStructureInvariants() { + if (isRendezvousOrUnlimited) { + check(bufferEndSegment.value === NULL_SEGMENT) { + "bufferEndSegment must be NULL_SEGMENT for rendezvous and unlimited channels; they do not manipulate it.\n" + + "Channel state: $this" + } + } else { + check(receiveSegment.value.id <= bufferEndSegment.value.id) { + "bufferEndSegment should not have lower id than receiveSegment.\n" + + "Channel state: $this" + } + } + val firstSegment = listOf(receiveSegment.value, sendSegment.value, bufferEndSegment.value) + .filter { it !== NULL_SEGMENT } + .minBy { it.id } + check(firstSegment.prev == null) { + "All processed segments should be unreachable from the data structure, but the `prev` link of the leftmost segment is non-null.\n" + + "Channel state: $this" + } + // Check that the doubly-linked list of segments does not + // contain full-of-cancelled-cells segments. + var segment = firstSegment + while (segment.next != null) { + // Note that the `prev` reference can be `null` if this channel is closed. + check(segment.next!!.prev == null || segment.next!!.prev === segment) { + "The `segment.next.prev === segment` invariant is violated.\n" + + "Channel state: $this" + } + // Count the number of closed/interrupted cells + // and check that all cells are in expected states. + var interruptedOrClosedCells = 0 + for (i in 0 until SEGMENT_SIZE) { + when (val state = segment.getState(i)) { + BUFFERED -> {} // The cell stores a buffered element. + is Waiter -> {} // The cell stores a suspended request. + INTERRUPTED_RCV, INTERRUPTED_SEND, CHANNEL_CLOSED -> { + // The cell stored an interrupted request or indicates + // that this channel is already closed. + // Check that the element slot is cleaned and increment + // the number of cells in closed/interrupted state. + check(segment.getElement(i) == null) + interruptedOrClosedCells++ + } + POISONED, DONE_RCV -> { + // The cell is successfully processed or poisoned. + // Check that the element slot is cleaned. + check(segment.getElement(i) == null) + } + // Other states are illegal after all running operations finish. + else -> error("Unexpected segment cell state: $state.\nChannel state: $this") + } + } + // Is this segment full of cancelled/closed cells? + // If so, this segment should be removed from the + // linked list if nether `receiveSegment`, nor + // `sendSegment`, nor `bufferEndSegment` reference it. + if (interruptedOrClosedCells == SEGMENT_SIZE) { + check(segment === receiveSegment.value || segment === sendSegment.value || segment === bufferEndSegment.value) { + "Logically removed segment is reachable.\nChannel state: $this" + } + } + // Process the next segment. + segment = segment.next!! + } + } +} + +/** + * The channel is represented as a list of segments, which simulates an infinite array. + * Each segment has its own [id], which increase from the beginning. These [id]s help + * to update [BufferedChannel.sendSegment], [BufferedChannel.receiveSegment], + * and [BufferedChannel.bufferEndSegment] correctly. + */ +internal class ChannelSegment(id: Long, prev: ChannelSegment?, channel: BufferedChannel?, pointers: Int) : Segment>(id, prev, pointers) { + private val _channel: BufferedChannel? = channel + val channel get() = _channel!! // always non-null except for `NULL_SEGMENT` + + private val data = atomicArrayOfNulls(SEGMENT_SIZE * 2) // 2 registers per slot: state + element + override val numberOfSlots: Int get() = SEGMENT_SIZE + + // ######################################## + // # Manipulation with the Element Fields # + // ######################################## + + internal fun storeElement(index: Int, element: E) { + setElementLazy(index, element) + } + + @Suppress("UNCHECKED_CAST") + internal fun getElement(index: Int) = data[index * 2].value as E + + internal fun retrieveElement(index: Int): E = getElement(index).also { cleanElement(index) } + + internal fun cleanElement(index: Int) { + setElementLazy(index, null) + } + + private fun setElementLazy(index: Int, value: Any?) { + data[index * 2].lazySet(value) + } + + // ###################################### + // # Manipulation with the State Fields # + // ###################################### + + internal fun getState(index: Int): Any? = data[index * 2 + 1].value + + internal fun setState(index: Int, value: Any?) { + data[index * 2 + 1].value = value + } + + internal fun casState(index: Int, from: Any?, to: Any?) = data[index * 2 + 1].compareAndSet(from, to) + + internal fun getAndSetState(index: Int, update: Any?) = data[index * 2 + 1].getAndSet(update) + + + // ######################## + // # Cancellation Support # + // ######################## + + fun onSenderCancellationWithOnUndeliveredElement(index: Int, context: CoroutineContext) { + // Read the element first. If the operation has not been successfully resumed + // (this cancellation may be caused by prompt cancellation during dispatching), + // it is guaranteed that the element is presented. + val element = getElement(index) + // Perform the cancellation; `onCancellationImpl(..)` return `true` if the + // cancelled operation had not been resumed. In this case, the `onUndeliveredElement` + // lambda should be called. + if (onCancellation(index)) { + channel.onUndeliveredElement!!.callUndeliveredElement(element, context) + } + } + + /** + * Returns `true` if the request is successfully cancelled, + * and no rendezvous has happened. We need this knowledge + * to keep [BufferedChannel.onUndeliveredElement] correct. + */ + @Suppress("ConvertTwoComparisonsToRangeCheck") + fun onCancellation(index: Int): Boolean { + // Count the global index of this cell and read + // the current counters of send and receive operations. + val globalIndex = id * SEGMENT_SIZE + index + val s = channel.sendersCounter + val r = channel.receiversCounter + // Update the cell state trying to distinguish whether + // the cancelled coroutine is sender or receiver. + var isSender: Boolean + var isReceiver: Boolean + while (true) { // CAS-loop + // Read the current state of the cell. + val cur = data[index * 2 + 1].value + when { + // The cell stores a waiter. + cur is Waiter || cur is WaiterEB -> { + // Is the cancelled request send for sure? + isSender = globalIndex < s && globalIndex >= r + // Is the cancelled request receiver for sure? + isReceiver = globalIndex < r && globalIndex >= s + // If the cancelled coroutine neither sender + // nor receiver, clean the element slot and finish. + // An opposite operation will resume this request + // and update the cell state eventually. + if (!isSender && !isReceiver) { + cleanElement(index) + return true + } + // The cancelled request is either send or receive. + // Update the cell state correspondingly. + val update = if (isSender) INTERRUPTED_SEND else INTERRUPTED_RCV + if (data[index * 2 + 1].compareAndSet(cur, update)) break + } + // The cell already indicates that the operation is cancelled. + cur === INTERRUPTED_SEND || cur === INTERRUPTED_RCV -> { + // Clean the element slot to avoid memory leaks and finish. + cleanElement(index) + return true + } + // An opposite operation is resuming this request; + // wait until the cell state updates. + // It is possible that an opposite operation has already + // resumed this request, which will result in updating + // the cell state to `DONE_RCV` or `BUFFERED`, while the + // current cancellation is caused by prompt cancellation. + cur === RESUMING_BY_EB || cur === RESUMING_BY_RCV -> continue + // This request was successfully resumed, so this cancellation + // is caused by the prompt cancellation feature and should be ignored. + cur === DONE_RCV || cur === BUFFERED -> return false + // The cell state indicates that the channel is closed; + // this cancellation should be ignored. + cur === CHANNEL_CLOSED -> { + return false + } + else -> error("unexpected state: $cur") + } + } + // Clean the element slot and invoke `onSlotCleaned()`, + // which may cause deleting the whole segment from the linked list. + // In case the cancelled request is receiver, it is critical to ensure + // that the `expandBuffer()` attempt that processes this cell is completed, + // so `onCancelledRequest(..)` waits for its completion before invoking `onSlotCleaned()`. + cleanElement(index) + onCancelledRequest(index, isReceiver) + return true + } + + /** + * Invokes `onSlotCleaned()` preceded by a `waitExpandBufferCompletion(..)` call + * in case the cancelled request is receiver. + */ + fun onCancelledRequest(index: Int, receiver: Boolean) { + if (receiver) channel.waitExpandBufferCompletion(id * SEGMENT_SIZE + index) + onSlotCleaned() + } +} +private fun createSegment(id: Long, prev: ChannelSegment) = ChannelSegment( + id = id, + prev = prev, + channel = prev.channel, + pointers = 0 +) +private val NULL_SEGMENT = ChannelSegment(id = -1, prev = null, channel = null, pointers = 0) + +/** + * Number of cells in each segment. + */ +@JvmField +internal val SEGMENT_SIZE = systemProp("kotlinx.coroutines.bufferedChannel.segmentSize", 32) + +/** + * Number of iterations to wait in [BufferedChannel.waitExpandBufferCompletion] until the numbers of started and completed + * [BufferedChannel.expandBuffer] calls coincide. When the limit is reached, [BufferedChannel.waitExpandBufferCompletion] + * blocks further [BufferedChannel.expandBuffer]-s to avoid starvation. + */ +private val EXPAND_BUFFER_COMPLETION_WAIT_ITERATIONS = systemProp("kotlinx.coroutines.bufferedChannel.expandBufferCompletionWaitIterations", 10_000) + +/** + * Tries to resume this continuation with the specified + * value. Returns `true` on success and `false` on failure. + */ +private fun CancellableContinuation.tryResume0( + value: T, + onCancellation: ((cause: Throwable) -> Unit)? = null +): Boolean = + tryResume(value, null, onCancellation).let { token -> + if (token != null) { + completeResume(token) + true + } else false + } + +/* + If the channel is rendezvous or unlimited, the `bufferEnd` counter + should be initialized with the corresponding value below and never change. + In this case, the `expandBuffer(..)` operation does nothing. + */ +private const val BUFFER_END_RENDEZVOUS = 0L // no buffer +private const val BUFFER_END_UNLIMITED = Long.MAX_VALUE // infinite buffer +private fun initialBufferEnd(capacity: Int): Long = when (capacity) { + Channel.RENDEZVOUS -> BUFFER_END_RENDEZVOUS + Channel.UNLIMITED -> BUFFER_END_UNLIMITED + else -> capacity.toLong() +} + +/* + Cell states. The initial "empty" state is represented with `null`, + and suspended operations are represented with [Waiter] instances. + */ + +// The cell stores a buffered element. +@JvmField +internal val BUFFERED = Symbol("BUFFERED") +// Concurrent `expandBuffer(..)` can inform the +// upcoming sender that it should buffer the element. +private val IN_BUFFER = Symbol("SHOULD_BUFFER") +// Indicates that a receiver (RCV suffix) is resuming +// the suspended sender; after that, it should update +// the state to either `DONE_RCV` (on success) or +// `INTERRUPTED_SEND` (on failure). +private val RESUMING_BY_RCV = Symbol("S_RESUMING_BY_RCV") +// Indicates that `expandBuffer(..)` (RCV suffix) is resuming +// the suspended sender; after that, it should update +// the state to either `BUFFERED` (on success) or +// `INTERRUPTED_SEND` (on failure). +private val RESUMING_BY_EB = Symbol("RESUMING_BY_EB") +// When a receiver comes to the cell already covered by +// a sender (according to the counters), but the cell +// is still in `EMPTY` or `IN_BUFFER` state, it breaks +// the cell by changing its state to `POISONED`. +private val POISONED = Symbol("POISONED") +// When the element is successfully transferred +// to a receiver, the cell changes to `DONE_RCV`. +private val DONE_RCV = Symbol("DONE_RCV") +// Cancelled sender. +private val INTERRUPTED_SEND = Symbol("INTERRUPTED_SEND") +// Cancelled receiver. +private val INTERRUPTED_RCV = Symbol("INTERRUPTED_RCV") +// Indicates that the channel is closed. +internal val CHANNEL_CLOSED = Symbol("CHANNEL_CLOSED") +// When the cell is already covered by both sender and +// receiver (`sender` and `receivers` counters are greater +// than the cell number), the `expandBuffer(..)` procedure +// cannot distinguish which kind of operation is stored +// in the cell. Thus, it wraps the waiter with this descriptor, +// informing the possibly upcoming receiver that it should +// complete the `expandBuffer(..)` procedure if the waiter stored +// in the cell is sender. In turn, senders ignore this information. +private class WaiterEB(@JvmField val waiter: Waiter) { + override fun toString() = "WaiterEB($waiter)" +} + + + +/** + * To distinguish suspended [BufferedChannel.receive] and + * [BufferedChannel.receiveCatching] operations, the latter + * uses this wrapper for its continuation. + */ +private class ReceiveCatching( + @JvmField val cont: CancellableContinuation> +) : Waiter + +/* + Internal results for [BufferedChannel.updateCellReceive]. + On successful rendezvous with waiting sender or + buffered element retrieval, the corresponding element + is returned as result of [BufferedChannel.updateCellReceive]. + */ +private val SUSPEND = Symbol("SUSPEND") +private val SUSPEND_NO_WAITER = Symbol("SUSPEND_NO_WAITER") +private val FAILED = Symbol("FAILED") + +/* + Internal results for [BufferedChannel.updateCellSend] + */ +private const val RESULT_RENDEZVOUS = 0 +private const val RESULT_BUFFERED = 1 +private const val RESULT_SUSPEND = 2 +private const val RESULT_SUSPEND_NO_WAITER = 3 +private const val RESULT_CLOSED = 4 +private const val RESULT_FAILED = 5 + +/** + * Special value for [BufferedChannel.BufferedChannelIterator.receiveResult] + * that indicates the absence of pre-received result. + */ +private val NO_RECEIVE_RESULT = Symbol("NO_RECEIVE_RESULT") + +/* + As [BufferedChannel.invokeOnClose] can be invoked concurrently + with channel closing, we have to synchronize them. These two + markers help with the synchronization. + */ +private val CLOSE_HANDLER_CLOSED = Symbol("CLOSE_HANDLER_CLOSED") +private val CLOSE_HANDLER_INVOKED = Symbol("CLOSE_HANDLER_INVOKED") + +/** + * Specifies the absence of closing cause, stored in [BufferedChannel._closeCause]. + * When the channel is closed or cancelled without exception, this [NO_CLOSE_CAUSE] + * marker should be replaced with `null`. + */ +private val NO_CLOSE_CAUSE = Symbol("NO_CLOSE_CAUSE") + +/* + The channel close statuses. The transition scheme is the following: + +--------+ +----------------------+ +-----------+ + | ACTIVE |-->| CANCELLATION_STARTED |-->| CANCELLED | + +--------+ +----------------------+ +-----------+ + | ^ + | +--------+ | + +------------>| CLOSED |------------------+ + +--------+ + We need `CANCELLATION_STARTED` to synchronize + concurrent closing and cancellation. + */ +private const val CLOSE_STATUS_ACTIVE = 0 +private const val CLOSE_STATUS_CANCELLATION_STARTED = 1 +private const val CLOSE_STATUS_CLOSED = 2 +private const val CLOSE_STATUS_CANCELLED = 3 + +/* + The `senders` counter and the channel close status + are stored in a single 64-bit register to save the space + and reduce the number of reads in sending operations. + The code below encapsulates the required bit arithmetics. + */ +private const val SENDERS_CLOSE_STATUS_SHIFT = 60 +private const val SENDERS_COUNTER_MASK = (1L shl SENDERS_CLOSE_STATUS_SHIFT) - 1 +private inline val Long.sendersCounter get() = this and SENDERS_COUNTER_MASK +private inline val Long.sendersCloseStatus: Int get() = (this shr SENDERS_CLOSE_STATUS_SHIFT).toInt() +private fun constructSendersAndCloseStatus(counter: Long, closeStatus: Int): Long = + (closeStatus.toLong() shl SENDERS_CLOSE_STATUS_SHIFT) + counter + +/* + The `completedExpandBuffersAndPauseFlag` 64-bit counter contains + the number of completed `expandBuffer()` attempts along with a special + flag that pauses progress to avoid starvation in `waitExpandBufferCompletion(..)`. + The code below encapsulates the required bit arithmetics. + */ +private const val EB_COMPLETED_PAUSE_EXPAND_BUFFERS_BIT = 1L shl 62 +private const val EB_COMPLETED_COUNTER_MASK = EB_COMPLETED_PAUSE_EXPAND_BUFFERS_BIT - 1 +private inline val Long.ebCompletedCounter get() = this and EB_COMPLETED_COUNTER_MASK +private inline val Long.ebPauseExpandBuffers: Boolean get() = (this and EB_COMPLETED_PAUSE_EXPAND_BUFFERS_BIT) != 0L +private fun constructEBCompletedAndPauseFlag(counter: Long, pauseEB: Boolean): Long = + (if (pauseEB) EB_COMPLETED_PAUSE_EXPAND_BUFFERS_BIT else 0) + counter + +/** + * All waiters, such as [CancellableContinuationImpl], [SelectInstance], and + * [BufferedChannel.BufferedChannelIterator], should be marked with this interface + * to make the code faster and easier to read. + */ +internal interface Waiter diff --git a/kotlinx-coroutines-core/common/src/channels/Channel.kt b/kotlinx-coroutines-core/common/src/channels/Channel.kt index a9d4d6fb30..052bddb4d9 100644 --- a/kotlinx-coroutines-core/common/src/channels/Channel.kt +++ b/kotlinx-coroutines-core/common/src/channels/Channel.kt @@ -377,7 +377,7 @@ public interface ReceiveChannel { level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("onReceiveCatching") ) // Warning since 1.3.0, error in 1.5.0, will be hidden or removed in 1.7.0 - public val onReceiveOrNull: SelectClause1 get() = (this as AbstractChannel).onReceiveOrNull + public val onReceiveOrNull: SelectClause1 get() = (this as BufferedChannel).onReceiveOrNull } /** @@ -771,26 +771,24 @@ public fun Channel( when (capacity) { RENDEZVOUS -> { if (onBufferOverflow == BufferOverflow.SUSPEND) - RendezvousChannel(onUndeliveredElement) // an efficient implementation of rendezvous channel + BufferedChannel(RENDEZVOUS, onUndeliveredElement) // an efficient implementation of rendezvous channel else - ArrayChannel(1, onBufferOverflow, onUndeliveredElement) // support buffer overflow with buffered channel + ConflatedBufferedChannel(1, onBufferOverflow, onUndeliveredElement) // support buffer overflow with buffered channel } CONFLATED -> { require(onBufferOverflow == BufferOverflow.SUSPEND) { "CONFLATED capacity cannot be used with non-default onBufferOverflow" } - ConflatedChannel(onUndeliveredElement) + ConflatedBufferedChannel(1, BufferOverflow.DROP_OLDEST, onUndeliveredElement) + } + UNLIMITED -> BufferedChannel(UNLIMITED, onUndeliveredElement) // ignores onBufferOverflow: it has buffer, but it never overflows + BUFFERED -> { // uses default capacity with SUSPEND + if (onBufferOverflow == BufferOverflow.SUSPEND) BufferedChannel(CHANNEL_DEFAULT_CAPACITY, onUndeliveredElement) + else ConflatedBufferedChannel(1, onBufferOverflow, onUndeliveredElement) } - UNLIMITED -> LinkedListChannel(onUndeliveredElement) // ignores onBufferOverflow: it has buffer, but it never overflows - BUFFERED -> ArrayChannel( // uses default capacity with SUSPEND - if (onBufferOverflow == BufferOverflow.SUSPEND) CHANNEL_DEFAULT_CAPACITY else 1, - onBufferOverflow, onUndeliveredElement - ) else -> { - if (capacity == 1 && onBufferOverflow == BufferOverflow.DROP_OLDEST) - ConflatedChannel(onUndeliveredElement) // conflated implementation is more efficient but appears to work in the same way - else - ArrayChannel(capacity, onBufferOverflow, onUndeliveredElement) + if (onBufferOverflow === BufferOverflow.SUSPEND) BufferedChannel(capacity, onUndeliveredElement) + else ConflatedBufferedChannel(capacity, onBufferOverflow, onUndeliveredElement) } } diff --git a/kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt deleted file mode 100644 index ff9f769ec9..0000000000 --- a/kotlinx-coroutines-core/common/src/channels/ConflatedBroadcastChannel.kt +++ /dev/null @@ -1,296 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines.channels - -import kotlinx.atomicfu.* -import kotlinx.coroutines.* -import kotlinx.coroutines.internal.* -import kotlinx.coroutines.intrinsics.* -import kotlinx.coroutines.selects.* -import kotlin.jvm.* - -/** - * Broadcasts the most recently sent element (aka [value]) to all [openSubscription] subscribers. - * - * Back-to-send sent elements are _conflated_ -- only the most recently sent value is received, - * while previously sent elements **are lost**. - * Every subscriber immediately receives the most recently sent element. - * Sender to this broadcast channel never suspends and [trySend] always succeeds. - * - * A secondary constructor can be used to create an instance of this class that already holds a value. - * This channel is also created by `BroadcastChannel(Channel.CONFLATED)` factory function invocation. - * - * This implementation is fully lock-free. In this implementation - * [opening][openSubscription] and [closing][ReceiveChannel.cancel] subscription takes O(N) time, where N is the - * number of subscribers. - * - * **Note: This API is obsolete since 1.5.0.** It will be deprecated with warning in 1.6.0 - * and with error in 1.7.0. It is replaced with [StateFlow][kotlinx.coroutines.flow.StateFlow]. - */ -@ObsoleteCoroutinesApi -public class ConflatedBroadcastChannel() : BroadcastChannel { - /** - * Creates an instance of this class that already holds a value. - * - * It is as a shortcut to creating an instance with a default constructor and - * immediately sending an element: `ConflatedBroadcastChannel().apply { offer(value) }`. - */ - public constructor(value: E) : this() { - _state.lazySet(State(value, null)) - } - - private val _state = atomic(INITIAL_STATE) // State | Closed - private val _updating = atomic(0) - // State transitions: null -> handler -> HANDLER_INVOKED - private val onCloseHandler = atomic(null) - - private companion object { - private val CLOSED = Closed(null) - private val UNDEFINED = Symbol("UNDEFINED") - private val INITIAL_STATE = State(UNDEFINED, null) - } - - private class State( - @JvmField val value: Any?, // UNDEFINED | E - @JvmField val subscribers: Array>? - ) - - private class Closed(@JvmField val closeCause: Throwable?) { - val sendException: Throwable get() = closeCause ?: ClosedSendChannelException(DEFAULT_CLOSE_MESSAGE) - val valueException: Throwable get() = closeCause ?: IllegalStateException(DEFAULT_CLOSE_MESSAGE) - } - - /** - * The most recently sent element to this channel. - * - * Access to this property throws [IllegalStateException] when this class is constructed without - * initial value and no value was sent yet or if it was [closed][close] without a cause. - * It throws the original [close][SendChannel.close] cause exception if the channel has _failed_. - */ - @Suppress("UNCHECKED_CAST") - public val value: E get() { - _state.loop { state -> - when (state) { - is Closed -> throw state.valueException - is State<*> -> { - if (state.value === UNDEFINED) throw IllegalStateException("No value") - return state.value as E - } - else -> error("Invalid state $state") - } - } - } - - /** - * The most recently sent element to this channel or `null` when this class is constructed without - * initial value and no value was sent yet or if it was [closed][close]. - */ - public val valueOrNull: E? get() = when (val state = _state.value) { - is Closed -> null - is State<*> -> UNDEFINED.unbox(state.value) - else -> error("Invalid state $state") - } - - public override val isClosedForSend: Boolean get() = _state.value is Closed - - @Suppress("UNCHECKED_CAST") - public override fun openSubscription(): ReceiveChannel { - val subscriber = Subscriber(this) - _state.loop { state -> - when (state) { - is Closed -> { - subscriber.close(state.closeCause) - return subscriber - } - is State<*> -> { - if (state.value !== UNDEFINED) - subscriber.offerInternal(state.value as E) - val update = State(state.value, addSubscriber((state as State).subscribers, subscriber)) - if (_state.compareAndSet(state, update)) - return subscriber - } - else -> error("Invalid state $state") - } - } - } - - @Suppress("UNCHECKED_CAST") - private fun closeSubscriber(subscriber: Subscriber) { - _state.loop { state -> - when (state) { - is Closed -> return - is State<*> -> { - val update = State(state.value, removeSubscriber((state as State).subscribers!!, subscriber)) - if (_state.compareAndSet(state, update)) - return - } - else -> error("Invalid state $state") - } - } - } - - private fun addSubscriber(list: Array>?, subscriber: Subscriber): Array> { - if (list == null) return Array(1) { subscriber } - return list + subscriber - } - - @Suppress("UNCHECKED_CAST") - private fun removeSubscriber(list: Array>, subscriber: Subscriber): Array>? { - val n = list.size - val i = list.indexOf(subscriber) - assert { i >= 0 } - if (n == 1) return null - val update = arrayOfNulls>(n - 1) - list.copyInto( - destination = update, - endIndex = i - ) - list.copyInto( - destination = update, - destinationOffset = i, - startIndex = i + 1 - ) - return update as Array> - } - - @Suppress("UNCHECKED_CAST") - public override fun close(cause: Throwable?): Boolean { - _state.loop { state -> - when (state) { - is Closed -> return false - is State<*> -> { - val update = if (cause == null) CLOSED else Closed(cause) - if (_state.compareAndSet(state, update)) { - (state as State).subscribers?.forEach { it.close(cause) } - invokeOnCloseHandler(cause) - return true - } - } - else -> error("Invalid state $state") - } - } - } - - private fun invokeOnCloseHandler(cause: Throwable?) { - val handler = onCloseHandler.value - if (handler !== null && handler !== HANDLER_INVOKED - && onCloseHandler.compareAndSet(handler, HANDLER_INVOKED)) { - @Suppress("UNCHECKED_CAST") - (handler as Handler)(cause) - } - } - - override fun invokeOnClose(handler: Handler) { - // Intricate dance for concurrent invokeOnClose and close - if (!onCloseHandler.compareAndSet(null, handler)) { - val value = onCloseHandler.value - if (value === HANDLER_INVOKED) { - throw IllegalStateException("Another handler was already registered and successfully invoked") - } else { - throw IllegalStateException("Another handler was already registered: $value") - } - } else { - val state = _state.value - if (state is Closed && onCloseHandler.compareAndSet(handler, HANDLER_INVOKED)) { - (handler)(state.closeCause) - } - } - } - - /** - * @suppress This method has bad semantics when cause is not a [CancellationException]. Use [cancel]. - */ - @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") - public override fun cancel(cause: Throwable?): Boolean = close(cause) - - /** - * Cancels this conflated broadcast channel with an optional cause, same as [close]. - * This function closes the channel with - * the specified cause (unless it was already closed), - * and [cancels][ReceiveChannel.cancel] all open subscriptions. - * A cause can be used to specify an error message or to provide other details on - * a cancellation reason for debugging purposes. - */ - public override fun cancel(cause: CancellationException?) { - close(cause) - } - - /** - * Sends the value to all subscribed receives and stores this value as the most recent state for - * future subscribers. This implementation never suspends. - * It throws exception if the channel [isClosedForSend] (see [close] for details). - */ - public override suspend fun send(element: E) { - offerInternal(element)?.let { throw it.sendException } - } - - /** - * Sends the value to all subscribed receives and stores this value as the most recent state for - * future subscribers. This implementation always returns either successful result - * or closed with an exception. - */ - public override fun trySend(element: E): ChannelResult { - offerInternal(element)?.let { return ChannelResult.closed(it.sendException) } - return ChannelResult.success(Unit) - } - - @Suppress("UNCHECKED_CAST") - private fun offerInternal(element: E): Closed? { - // If some other thread is updating the state in its offer operation we assume that our offer had linearized - // before that offer (we lost) and that offer overwrote us and conflated our offer. - if (!_updating.compareAndSet(0, 1)) return null - try { - _state.loop { state -> - when (state) { - is Closed -> return state - is State<*> -> { - val update = State(element, (state as State).subscribers) - if (_state.compareAndSet(state, update)) { - // Note: Using offerInternal here to ignore the case when this subscriber was - // already concurrently closed (assume the close had conflated our offer for this - // particular subscriber). - state.subscribers?.forEach { it.offerInternal(element) } - return null - } - } - else -> error("Invalid state $state") - } - } - } finally { - _updating.value = 0 // reset the updating flag to zero even when something goes wrong - } - } - - @Suppress("UNCHECKED_CAST") - public override val onSend: SelectClause2> - get() = SelectClause2Impl( - clauseObject = this, - regFunc = ConflatedBroadcastChannel<*>::registerSelectForSend as RegistrationFunction, - processResFunc = ConflatedBroadcastChannel<*>::processResultSelectSend as ProcessResultFunction - ) - - @Suppress("UNCHECKED_CAST") - private fun registerSelectForSend(select: SelectInstance<*>, element: Any?) { - select.selectInRegistrationPhase(offerInternal(element as E)) - } - - @Suppress("RedundantNullableReturnType", "UNUSED_PARAMETER") - private fun processResultSelectSend(ignoredParam: Any?, selectResult: Any?): Any? = - if (selectResult is Closed) throw selectResult.sendException - else this - - private class Subscriber( - private val broadcastChannel: ConflatedBroadcastChannel - ) : ConflatedChannel(null), ReceiveChannel { - - override fun onCancelIdempotent(wasClosed: Boolean) { - if (wasClosed) { - broadcastChannel.closeSubscriber(this) - } - } - - public override fun offerInternal(element: E): Any = super.offerInternal(element) - } -} diff --git a/kotlinx-coroutines-core/common/src/channels/ConflatedBufferedChannel.kt b/kotlinx-coroutines-core/common/src/channels/ConflatedBufferedChannel.kt new file mode 100644 index 0000000000..6a9f23e958 --- /dev/null +++ b/kotlinx-coroutines-core/common/src/channels/ConflatedBufferedChannel.kt @@ -0,0 +1,118 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.channels + +import kotlinx.atomicfu.* +import kotlinx.coroutines.channels.BufferOverflow.* +import kotlinx.coroutines.channels.ChannelResult.Companion.closed +import kotlinx.coroutines.channels.ChannelResult.Companion.success +import kotlinx.coroutines.internal.* +import kotlinx.coroutines.internal.OnUndeliveredElement +import kotlinx.coroutines.selects.* +import kotlin.coroutines.* + +/** + * This is a special [BufferedChannel] extension that supports [DROP_OLDEST] and [DROP_LATEST] + * strategies for buffer overflowing. This implementation ensures that `send(e)` never suspends, + * either extracting the first element ([DROP_OLDEST]) or dropping the sending one ([DROP_LATEST]) + * when the channel capacity exceeds. + */ +internal open class ConflatedBufferedChannel( + private val capacity: Int, + private val onBufferOverflow: BufferOverflow, + onUndeliveredElement: OnUndeliveredElement? = null +) : BufferedChannel(capacity = capacity, onUndeliveredElement = onUndeliveredElement) { + init { + require(onBufferOverflow !== SUSPEND) { + "This implementation does not support suspension for senders, use ${BufferedChannel::class.simpleName} instead" + } + require(capacity >= 1) { + "Buffered channel capacity must be at least 1, but $capacity was specified" + } + } + + override val isConflatedDropOldest: Boolean + get() = onBufferOverflow == DROP_OLDEST + + override suspend fun send(element: E) { + // Should never suspend, implement via `trySend(..)`. + trySendImpl(element, isSendOp = true).onClosed { // fails only when this channel is closed. + onUndeliveredElement?.callUndeliveredElementCatchingException(element)?.let { + it.addSuppressed(sendException) + throw it + } + throw sendException + } + } + + override suspend fun sendBroadcast(element: E): Boolean { + // Should never suspend, implement via `trySend(..)`. + trySendImpl(element, isSendOp = true) // fails only when this channel is closed. + .onSuccess { return true } + return false + } + + override fun trySend(element: E): ChannelResult = trySendImpl(element, isSendOp = false) + + private fun trySendImpl(element: E, isSendOp: Boolean) = + if (onBufferOverflow === DROP_LATEST) trySendDropLatest(element, isSendOp) + else trySendDropOldest(element) + + private fun trySendDropLatest(element: E, isSendOp: Boolean): ChannelResult { + // Try to send the element without suspension. + val result = super.trySend(element) + // Complete on success or if this channel is closed. + if (result.isSuccess || result.isClosed) return result + // This channel is full. Drop the sending element. + // Call the `onUndeliveredElement` lambda ONLY for 'send()' invocations, + // for 'trySend()' it is responsibility of the caller + if (isSendOp) { + onUndeliveredElement?.callUndeliveredElementCatchingException(element)?.let { + throw it + } + } + return success(Unit) + } + + private fun trySendDropOldest(element: E): ChannelResult = + sendImpl( // <-- this is an inline function + element = element, + // Put the element into the logical buffer even + // if this channel is already full, the `onSuspend` + // callback below extract the first (oldest) element. + waiter = BUFFERED, + // Finish successfully when a rendezvous has happened + // or the element has been buffered. + onRendezvousOrBuffered = { success(Unit) }, + // In case the algorithm decided to suspend, the element + // was added to the buffer. However, as the buffer is now + // overflowed, the first (oldest) element has to be extracted. + onSuspend = { segm, i -> + dropFirstElementUntilTheSpecifiedCellIsInTheBuffer(segm.id * SEGMENT_SIZE + i) + success(Unit) + }, + // If the channel is closed, return the corresponding result. + onClosed = { closed(sendException) } + ) + + @Suppress("UNCHECKED_CAST") + override fun registerSelectForSend(select: SelectInstance<*>, element: Any?) { + // The plain `send(..)` operation never suspends. Thus, either this + // attempt to send the element succeeds or the channel is closed. + // In any case, complete this `select` in the registration phase. + trySend(element as E).let { + it.onSuccess { + select.selectInRegistrationPhase(Unit) + return + }.onClosed { + select.selectInRegistrationPhase(CHANNEL_CLOSED) + return + } + } + error("unreachable") + } + + override fun shouldSendSuspend() = false // never suspends. +} diff --git a/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt b/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt deleted file mode 100644 index 54033a8764..0000000000 --- a/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines.channels - -import kotlinx.coroutines.* -import kotlinx.coroutines.internal.* - -/** - * Channel that buffers at most one element and conflates all subsequent `send` and `trySend` invocations, - * so that the receiver always gets the most recently sent element. - * Back-to-send sent elements are _conflated_ -- only the most recently sent element is received, - * while previously sent elements **are lost**. - * Sender to this channel never suspends and [trySend] always succeeds. - * - * This channel is created by `Channel(Channel.CONFLATED)` factory function invocation. - */ -internal open class ConflatedChannel(onUndeliveredElement: OnUndeliveredElement?) : AbstractChannel(onUndeliveredElement) { - protected final override val isBufferAlwaysEmpty: Boolean get() = false - protected final override val isBufferEmpty: Boolean get() = lock.withLock { value === EMPTY } - protected final override val isBufferAlwaysFull: Boolean get() = false - protected final override val isBufferFull: Boolean get() = false - - override val isEmpty: Boolean get() = lock.withLock { isEmptyImpl } - - private val lock = ReentrantLock() - - private var value: Any? = EMPTY - - // result is `OFFER_SUCCESS | Closed` - protected override fun offerInternal(element: E): Any { - var receive: ReceiveOrClosed? = null - lock.withLock { - closedForSend?.let { return it } - // if there is no element written in buffer - if (value === EMPTY) { - // check for receivers that were waiting on the empty buffer - loop@ while(true) { - receive = takeFirstReceiveOrPeekClosed() ?: break@loop // break when no receivers queued - if (receive is Closed) { - return receive!! - } - val token = receive!!.tryResumeReceive(element) - if (token != null) { - assert { token === RESUME_TOKEN } - return@withLock - } - } - } - updateValueLocked(element)?.let { throw it } - return OFFER_SUCCESS - } - // breaks here if offer meets receiver - receive!!.completeResumeReceive(element) - return receive!!.offerResult - } - - // result is `E | POLL_FAILED | Closed` - protected override fun pollInternal(): Any? { - var result: Any? = null - lock.withLock { - if (value === EMPTY) return closedForSend ?: POLL_FAILED - result = value - value = EMPTY - } - return result - } - - protected override fun onCancelIdempotent(wasClosed: Boolean) { - var undeliveredElementException: UndeliveredElementException? = null // resource cancel exception - lock.withLock { - undeliveredElementException = updateValueLocked(EMPTY) - } - super.onCancelIdempotent(wasClosed) - undeliveredElementException?.let { throw it } // throw UndeliveredElementException at the end if there was one - } - - @Suppress("UNCHECKED_CAST") - private fun updateValueLocked(element: Any?): UndeliveredElementException? { - val old = value - val undeliveredElementException = if (old === EMPTY) null else - onUndeliveredElement?.callUndeliveredElementCatchingException(old as E) - value = element - return undeliveredElementException - } - - override fun enqueueReceiveInternal(receive: Receive): Boolean = lock.withLock { - super.enqueueReceiveInternal(receive) - } - - // ------ debug ------ - - override val bufferDebugString: String - get() = lock.withLock { "(value=$value)" } -} diff --git a/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt b/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt deleted file mode 100644 index 18bdcc6465..0000000000 --- a/kotlinx-coroutines-core/common/src/channels/LinkedListChannel.kt +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines.channels - -import kotlinx.coroutines.internal.* - -/** - * Channel with linked-list buffer of a unlimited capacity (limited only by available memory). - * Sender to this channel never suspends and [trySend] always succeeds. - * - * This channel is created by `Channel(Channel.UNLIMITED)` factory function invocation. - * - * This implementation is fully lock-free. - * - * @suppress **This an internal API and should not be used from general code.** - */ -internal open class LinkedListChannel(onUndeliveredElement: OnUndeliveredElement?) : AbstractChannel(onUndeliveredElement) { - protected final override val isBufferAlwaysEmpty: Boolean get() = true - protected final override val isBufferEmpty: Boolean get() = true - protected final override val isBufferAlwaysFull: Boolean get() = false - protected final override val isBufferFull: Boolean get() = false - - // result is always `OFFER_SUCCESS | Closed` - protected override fun offerInternal(element: E): Any { - while (true) { - val result = super.offerInternal(element) - when { - result === OFFER_SUCCESS -> return OFFER_SUCCESS - result === OFFER_FAILED -> { // try to buffer - when (val sendResult = sendBuffered(element)) { - null -> return OFFER_SUCCESS - is Closed<*> -> return sendResult - } - // otherwise there was receiver in queue, retry super.offerInternal - } - result is Closed<*> -> return result - else -> error("Invalid offerInternal result $result") - } - } - } - - override fun onCancelIdempotentList(list: InlineList, closed: Closed<*>) { - var undeliveredElementException: UndeliveredElementException? = null - list.forEachReversed { - when (it) { - is SendBuffered<*> -> { - @Suppress("UNCHECKED_CAST") - undeliveredElementException = onUndeliveredElement?.callUndeliveredElementCatchingException(it.element as E, undeliveredElementException) - } - else -> it.resumeSendClosed(closed) - } - } - undeliveredElementException?.let { throw it } // throw UndeliveredElementException at the end if there was one - } -} - diff --git a/kotlinx-coroutines-core/common/src/channels/RendezvousChannel.kt b/kotlinx-coroutines-core/common/src/channels/RendezvousChannel.kt deleted file mode 100644 index e8ade513f5..0000000000 --- a/kotlinx-coroutines-core/common/src/channels/RendezvousChannel.kt +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines.channels - -import kotlinx.coroutines.internal.* - -/** - * Rendezvous channel. This channel does not have any buffer at all. An element is transferred from sender - * to receiver only when [send] and [receive] invocations meet in time (rendezvous), so [send] suspends - * until another coroutine invokes [receive] and [receive] suspends until another coroutine invokes [send]. - * - * Use `Channel()` factory function to conveniently create an instance of rendezvous channel. - * - * This implementation is fully lock-free. - **/ -internal open class RendezvousChannel(onUndeliveredElement: OnUndeliveredElement?) : AbstractChannel(onUndeliveredElement) { - protected final override val isBufferAlwaysEmpty: Boolean get() = true - protected final override val isBufferEmpty: Boolean get() = true - protected final override val isBufferAlwaysFull: Boolean get() = true - protected final override val isBufferFull: Boolean get() = true -} diff --git a/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt b/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt index 6b42dd15db..848a42c867 100644 --- a/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt +++ b/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt @@ -4,18 +4,6 @@ package kotlinx.coroutines.internal -/** - * Special kind of list intended to be used as collection of subscribers in `ArrayBroadcastChannel` - * On JVM it's CopyOnWriteList and on JS it's MutableList. - * - * Note that this alias is intentionally not named as CopyOnWriteList to avoid accidental misusage outside of the ArrayBroadcastChannel - */ -internal typealias SubscribersList = MutableList - -@Deprecated(message = "Implementation of this primitive is tailored to specific ArrayBroadcastChannel usages on K/N " + - "and K/JS platforms and it is unsafe to use it anywhere else") -internal expect fun subscriberList(): SubscribersList - internal expect class ReentrantLock() { fun tryLock(): Boolean fun unlock() diff --git a/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt b/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt index 5eee2bd59c..2bcf97b7ad 100644 --- a/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt +++ b/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt @@ -14,7 +14,7 @@ import kotlin.jvm.* */ private inline fun > S.findSegmentInternal( id: Long, - createNewSegment: (id: Long, prev: S?) -> S + createNewSegment: (id: Long, prev: S) -> S ): SegmentOrClosed { /* Go through `next` references and add new segments if needed, similarly to the `push` in the Michael-Scott @@ -22,7 +22,7 @@ private inline fun > S.findSegmentInternal( added, so the algorithm just uses it. This way, only one segment with each id can be added. */ var cur: S = this - while (cur.id < id || cur.removed) { + while (cur.id < id || cur.isRemoved) { val next = cur.nextOrIfClosed { return SegmentOrClosed(CLOSED) } if (next != null) { // there is a next node -- move there cur = next @@ -30,7 +30,7 @@ private inline fun > S.findSegmentInternal( } val newTail = createNewSegment(cur.id + 1, cur) if (cur.trySetNext(newTail)) { // successfully added new node -- move there - if (cur.removed) cur.remove() + if (cur.isRemoved) cur.remove() cur = newTail } } @@ -40,8 +40,8 @@ private inline fun > S.findSegmentInternal( /** * Returns `false` if the segment `to` is logically removed, `true` on a successful update. */ -@Suppress("NOTHING_TO_INLINE") // Must be inline because it is an AtomicRef extension -private inline fun > AtomicRef.moveForward(to: S): Boolean = loop { cur -> +@Suppress("NOTHING_TO_INLINE", "RedundantNullableReturnType") // Must be inline because it is an AtomicRef extension +internal inline fun > AtomicRef.moveForward(to: S): Boolean = loop { cur -> if (cur.id >= to.id) return true if (!to.tryIncPointers()) return false if (compareAndSet(cur, to)) { // the segment is moved @@ -65,7 +65,7 @@ private inline fun > AtomicRef.moveForward(to: S): Boolean = l internal inline fun > AtomicRef.findSegmentAndMoveForward( id: Long, startFrom: S, - createNewSegment: (id: Long, prev: S?) -> S + createNewSegment: (id: Long, prev: S) -> S ): SegmentOrClosed { while (true) { val s = startFrom.findSegmentInternal(id, createNewSegment) @@ -136,47 +136,49 @@ internal abstract class ConcurrentLinkedListNode /** * This property indicates whether the current node is logically removed. - * The expected use-case is removing the node logically (so that [removed] becomes true), + * The expected use-case is removing the node logically (so that [isRemoved] becomes true), * and invoking [remove] after that. Note that this implementation relies on the contract * that the physical tail cannot be logically removed. Please, do not break this contract; * otherwise, memory leaks and unexpected behavior can occur. */ - abstract val removed: Boolean + abstract val isRemoved: Boolean /** * Removes this node physically from this linked list. The node should be - * logically removed (so [removed] returns `true`) at the point of invocation. + * logically removed (so [isRemoved] returns `true`) at the point of invocation. */ fun remove() { - assert { removed } // The node should be logically removed at first. - assert { !isTail } // The physical tail cannot be removed. + assert { isRemoved || isTail } // The node should be logically removed at first. + // The physical tail cannot be removed. Instead, we remove it when + // a new segment is added and this segment is not the tail one anymore. + if (isTail) return while (true) { // Read `next` and `prev` pointers ignoring logically removed nodes. - val prev = leftmostAliveNode - val next = rightmostAliveNode + val prev = aliveSegmentLeft + val next = aliveSegmentRight // Link `next` and `prev`. - next._prev.value = prev + next._prev.update { if (it === null) null else prev } if (prev !== null) prev._next.value = next // Checks that prev and next are still alive. - if (next.removed) continue - if (prev !== null && prev.removed) continue + if (next.isRemoved && !next.isTail) continue + if (prev !== null && prev.isRemoved) continue // This node is removed. return } } - private val leftmostAliveNode: N? get() { + private val aliveSegmentLeft: N? get() { var cur = prev - while (cur !== null && cur.removed) + while (cur !== null && cur.isRemoved) cur = cur._prev.value return cur } - private val rightmostAliveNode: N get() { + private val aliveSegmentRight: N get() { assert { !isTail } // Should not be invoked on the tail node var cur = next!! - while (cur.removed) - cur = cur.next!! + while (cur.isRemoved) + cur = cur.next ?: return cur return cur } } @@ -188,10 +190,10 @@ internal abstract class ConcurrentLinkedListNode */ internal abstract class Segment>(val id: Long, prev: S?, pointers: Int): ConcurrentLinkedListNode(prev) { /** - * This property should return the maximal number of slots in this segment, + * This property should return the number of slots in this segment, * it is used to define whether the segment is logically removed. */ - abstract val maxSlots: Int + abstract val numberOfSlots: Int /** * Numbers of cleaned slots (the lowest bits) and AtomicRef pointers to this segment (the highest bits) @@ -199,23 +201,22 @@ internal abstract class Segment>(val id: Long, prev: S?, pointers private val cleanedAndPointers = atomic(pointers shl POINTERS_SHIFT) /** - * The segment is considered as removed if all the slots are cleaned. - * There are no pointers to this segment from outside, and - * it is not a physical tail in the linked list of segments. + * The segment is considered as removed if all the slots are cleaned + * and there are no pointers to this segment from outside. */ - override val removed get() = cleanedAndPointers.value == maxSlots && !isTail + override val isRemoved get() = cleanedAndPointers.value == numberOfSlots && !isTail // increments the number of pointers if this segment is not logically removed. - internal fun tryIncPointers() = cleanedAndPointers.addConditionally(1 shl POINTERS_SHIFT) { it != maxSlots || isTail } + internal fun tryIncPointers() = cleanedAndPointers.addConditionally(1 shl POINTERS_SHIFT) { it != numberOfSlots || isTail } // returns `true` if this segment is logically removed after the decrement. - internal fun decPointers() = cleanedAndPointers.addAndGet(-(1 shl POINTERS_SHIFT)) == maxSlots && !isTail + internal fun decPointers() = cleanedAndPointers.addAndGet(-(1 shl POINTERS_SHIFT)) == numberOfSlots && !isTail /** * Invoked on each slot clean-up; should not be invoked twice for the same slot. */ fun onSlotCleaned() { - if (cleanedAndPointers.incrementAndGet() == maxSlots && !isTail) remove() + if (cleanedAndPointers.incrementAndGet() == numberOfSlots) remove() } } diff --git a/kotlinx-coroutines-core/common/src/selects/Select.kt b/kotlinx-coroutines-core/common/src/selects/Select.kt index 5da5e95702..b9d128b7f8 100644 --- a/kotlinx-coroutines-core/common/src/selects/Select.kt +++ b/kotlinx-coroutines-core/common/src/selects/Select.kt @@ -238,7 +238,7 @@ public sealed interface SelectInstance { */ public fun selectInRegistrationPhase(internalResult: Any?) } -internal interface SelectInstanceInternal: SelectInstance +internal interface SelectInstanceInternal: SelectInstance, Waiter @PublishedApi internal open class SelectImplementation constructor( @@ -705,7 +705,11 @@ internal open class SelectImplementation constructor( // Update the state. state.update { cur -> // Finish immediately when this `select` is already completed. - if (cur is ClauseData<*> || cur == STATE_COMPLETED) return + // Notably, this select might be logically completed + // (the `state` field stores the selected `ClauseData`), + // while the continuation is already cancelled. + // We need to invoke the cancellation handler in this case. + if (cur === STATE_COMPLETED) return STATE_CANCELLED } // Read the list of clauses. If the `clauses` field is already `null`, diff --git a/kotlinx-coroutines-core/common/src/sync/Mutex.kt b/kotlinx-coroutines-core/common/src/sync/Mutex.kt index 40e4308f4e..fbd1fe55f4 100644 --- a/kotlinx-coroutines-core/common/src/sync/Mutex.kt +++ b/kotlinx-coroutines-core/common/src/sync/Mutex.kt @@ -247,9 +247,8 @@ internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1 ) : SelectInstanceInternal by select as SelectInstanceInternal { override fun trySelect(clauseObject: Any, result: Any?): Boolean { assert { this@MutexImpl.owner.value === NO_OWNER } - this@MutexImpl.owner.value = owner return select.trySelect(clauseObject, result).also { success -> - if (!success) this@MutexImpl.owner.value = NO_OWNER + if (success) this@MutexImpl.owner.value = owner } } diff --git a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt index 9ba6eaf36b..4db8ae3ca6 100644 --- a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt +++ b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt @@ -375,11 +375,11 @@ private class CancelSemaphoreAcquisitionHandler( override fun toString() = "CancelSemaphoreAcquisitionHandler[$segment, $index]" } -private fun createSegment(id: Long, prev: SemaphoreSegment?) = SemaphoreSegment(id, prev, 0) +private fun createSegment(id: Long, prev: SemaphoreSegment) = SemaphoreSegment(id, prev, 0) private class SemaphoreSegment(id: Long, prev: SemaphoreSegment?, pointers: Int) : Segment(id, prev, pointers) { val acquirers = atomicArrayOfNulls(SEGMENT_SIZE) - override val maxSlots: Int get() = SEGMENT_SIZE + override val numberOfSlots: Int get() = SEGMENT_SIZE @Suppress("NOTHING_TO_INLINE") inline fun get(index: Int): Any? = acquirers[index].value diff --git a/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt b/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt index 4538f6c680..aeb6199134 100644 --- a/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt @@ -21,7 +21,7 @@ class BasicOperationsTest : TestBase() { @Test fun testTrySendAfterClose() = runTest { - TestChannelKind.values().forEach { kind -> testTrySend(kind) } + TestChannelKind.values().forEach { kind -> testTrySendAfterClose(kind) } } @Test @@ -114,7 +114,7 @@ class BasicOperationsTest : TestBase() { finish(6) } - private suspend fun testTrySend(kind: TestChannelKind) = coroutineScope { + private suspend fun testTrySendAfterClose(kind: TestChannelKind) = coroutineScope { val channel = kind.create() val d = async { channel.send(42) } yield() diff --git a/kotlinx-coroutines-core/common/test/channels/BroadcastChannelFactoryTest.kt b/kotlinx-coroutines-core/common/test/channels/BroadcastChannelFactoryTest.kt index 61e93fa8ea..e27edcf6f7 100644 --- a/kotlinx-coroutines-core/common/test/channels/BroadcastChannelFactoryTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/BroadcastChannelFactoryTest.kt @@ -16,7 +16,7 @@ class BroadcastChannelFactoryTest : TestBase() { } @Test - fun testLinkedListChannelNotSupported() { + fun testUnlimitedChannelNotSupported() { assertFailsWith { BroadcastChannel(Channel.UNLIMITED) } } @@ -26,9 +26,9 @@ class BroadcastChannelFactoryTest : TestBase() { } @Test - fun testArrayBroadcastChannel() { - assertTrue { BroadcastChannel(1) is ArrayBroadcastChannel } - assertTrue { BroadcastChannel(10) is ArrayBroadcastChannel } + fun testBufferedBroadcastChannel() { + assertTrue { BroadcastChannel(1) is BroadcastChannelImpl } + assertTrue { BroadcastChannel(10) is BroadcastChannelImpl } } @Test diff --git a/kotlinx-coroutines-core/common/test/channels/BroadcastTest.kt b/kotlinx-coroutines-core/common/test/channels/BroadcastTest.kt index ab1a85d697..34b1395564 100644 --- a/kotlinx-coroutines-core/common/test/channels/BroadcastTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/BroadcastTest.kt @@ -7,6 +7,7 @@ package kotlinx.coroutines.channels import kotlinx.coroutines.* +import kotlinx.coroutines.selects.* import kotlin.test.* class BroadcastTest : TestBase() { @@ -17,7 +18,7 @@ class BroadcastTest : TestBase() { expect(4) send(1) // goes to receiver expect(5) - send(2) // goes to buffer + select { onSend(2) {} } // goes to buffer expect(6) send(3) // suspends, will not be consumes, but will not be cancelled either expect(10) diff --git a/kotlinx-coroutines-core/common/test/channels/ArrayBroadcastChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/BufferedBroadcastChannelTest.kt similarity index 99% rename from kotlinx-coroutines-core/common/test/channels/ArrayBroadcastChannelTest.kt rename to kotlinx-coroutines-core/common/test/channels/BufferedBroadcastChannelTest.kt index 2d71cc94ed..fad6500805 100644 --- a/kotlinx-coroutines-core/common/test/channels/ArrayBroadcastChannelTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/BufferedBroadcastChannelTest.kt @@ -7,7 +7,7 @@ package kotlinx.coroutines.channels import kotlinx.coroutines.* import kotlin.test.* -class ArrayBroadcastChannelTest : TestBase() { +class BufferedBroadcastChannelTest : TestBase() { @Test fun testConcurrentModification() = runTest { diff --git a/kotlinx-coroutines-core/common/test/channels/ArrayChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/BufferedChannelTest.kt similarity index 89% rename from kotlinx-coroutines-core/common/test/channels/ArrayChannelTest.kt rename to kotlinx-coroutines-core/common/test/channels/BufferedChannelTest.kt index 632fd2928b..0f7035214b 100644 --- a/kotlinx-coroutines-core/common/test/channels/ArrayChannelTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/BufferedChannelTest.kt @@ -7,7 +7,7 @@ package kotlinx.coroutines.channels import kotlinx.coroutines.* import kotlin.test.* -class ArrayChannelTest : TestBase() { +class BufferedChannelTest : TestBase() { @Test fun testSimple() = runTest { val q = Channel(1) @@ -34,6 +34,7 @@ class ArrayChannelTest : TestBase() { sender.join() receiver.join() check(q.isEmpty) + (q as BufferedChannel<*>).checkSegmentStructureInvariants() finish(10) } @@ -59,6 +60,7 @@ class ArrayChannelTest : TestBase() { check(!q.isEmpty && q.isClosedForSend && !q.isClosedForReceive) yield() check(!q.isEmpty && q.isClosedForSend && q.isClosedForReceive) + (q as BufferedChannel<*>).checkSegmentStructureInvariants() finish(8) } @@ -81,6 +83,7 @@ class ArrayChannelTest : TestBase() { expect(6) try { q.send(42) } catch (e: ClosedSendChannelException) { + (q as BufferedChannel<*>).checkSegmentStructureInvariants() finish(7) } } @@ -112,6 +115,7 @@ class ArrayChannelTest : TestBase() { expect(8) assertFalse(q.trySend(4).isSuccess) yield() + (q as BufferedChannel<*>).checkSegmentStructureInvariants() finish(12) } @@ -135,6 +139,7 @@ class ArrayChannelTest : TestBase() { check(q.isClosedForSend) check(q.isClosedForReceive) assertFailsWith { q.receiveCatching().getOrThrow() } + (q as BufferedChannel<*>).checkSegmentStructureInvariants() finish(12) } @@ -165,6 +170,11 @@ class ArrayChannelTest : TestBase() { checkBufferChannel(channel, capacity) } + @Test + fun testBufferIsNotPreallocated() { + (0..100_000).map { Channel(Int.MAX_VALUE / 2) } + } + private suspend fun CoroutineScope.checkBufferChannel( channel: Channel, capacity: Int @@ -189,6 +199,7 @@ class ArrayChannelTest : TestBase() { result.add(it) } assertEquals((0..capacity).toList(), result) + (channel as BufferedChannel<*>).checkSegmentStructureInvariants() finish(6) } } diff --git a/kotlinx-coroutines-core/common/test/channels/ChannelFactoryTest.kt b/kotlinx-coroutines-core/common/test/channels/ChannelFactoryTest.kt index 413c91f5a7..706a2fdd0a 100644 --- a/kotlinx-coroutines-core/common/test/channels/ChannelFactoryTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/ChannelFactoryTest.kt @@ -11,28 +11,28 @@ import kotlin.test.* class ChannelFactoryTest : TestBase() { @Test fun testRendezvousChannel() { - assertTrue(Channel() is RendezvousChannel) - assertTrue(Channel(0) is RendezvousChannel) + assertTrue(Channel() is BufferedChannel) + assertTrue(Channel(0) is BufferedChannel) } @Test - fun testLinkedListChannel() { - assertTrue(Channel(Channel.UNLIMITED) is LinkedListChannel) - assertTrue(Channel(Channel.UNLIMITED, BufferOverflow.DROP_OLDEST) is LinkedListChannel) - assertTrue(Channel(Channel.UNLIMITED, BufferOverflow.DROP_LATEST) is LinkedListChannel) + fun testUnlimitedChannel() { + assertTrue(Channel(Channel.UNLIMITED) is BufferedChannel) + assertTrue(Channel(Channel.UNLIMITED, BufferOverflow.DROP_OLDEST) is BufferedChannel) + assertTrue(Channel(Channel.UNLIMITED, BufferOverflow.DROP_LATEST) is BufferedChannel) } @Test fun testConflatedChannel() { - assertTrue(Channel(Channel.CONFLATED) is ConflatedChannel) - assertTrue(Channel(1, BufferOverflow.DROP_OLDEST) is ConflatedChannel) + assertTrue(Channel(Channel.CONFLATED) is ConflatedBufferedChannel) + assertTrue(Channel(1, BufferOverflow.DROP_OLDEST) is ConflatedBufferedChannel) } @Test - fun testArrayChannel() { - assertTrue(Channel(1) is ArrayChannel) - assertTrue(Channel(1, BufferOverflow.DROP_LATEST) is ArrayChannel) - assertTrue(Channel(10) is ArrayChannel) + fun testBufferedChannel() { + assertTrue(Channel(1) is BufferedChannel) + assertTrue(Channel(1, BufferOverflow.DROP_LATEST) is ConflatedBufferedChannel) + assertTrue(Channel(10) is BufferedChannel) } @Test diff --git a/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementFailureTest.kt b/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementFailureTest.kt index ae05fb8d74..f02bd09a66 100644 --- a/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementFailureTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementFailureTest.kt @@ -107,11 +107,71 @@ class ChannelUndeliveredElementFailureTest : TestBase() { } @Test - fun testChannelCancelledFail() = runTest(expected = { it.isElementCancelException()}) { + fun testChannelCancelledFail() = runTest(expected = { it.isElementCancelException() }) { val channel = Channel(1, onUndeliveredElement = onCancelFail) channel.send(item) channel.cancel() expectUnreached() } + @Test + fun testFailedHandlerInClosedConflatedChannel() = runTest(expected = { it is UndeliveredElementException }) { + val conflated = Channel(Channel.CONFLATED, onUndeliveredElement = { + finish(2) + throw TestException() + }) + expect(1) + conflated.close(IndexOutOfBoundsException()) + conflated.send(3) + } + + @Test + fun testFailedHandlerInClosedBufferedChannel() = runTest(expected = { it is UndeliveredElementException }) { + val conflated = Channel(3, onUndeliveredElement = { + finish(2) + throw TestException() + }) + expect(1) + conflated.close(IndexOutOfBoundsException()) + conflated.send(3) + } + + @Test + fun testSendDropOldestInvokeHandlerBuffered() = runTest(expected = { it is UndeliveredElementException }) { + val channel = Channel(1, BufferOverflow.DROP_OLDEST, onUndeliveredElement = { + finish(2) + throw TestException() + }) + + channel.send(42) + expect(1) + channel.send(12) + } + + @Test + fun testSendDropLatestInvokeHandlerBuffered() = runTest(expected = { it is UndeliveredElementException }) { + val channel = Channel(2, BufferOverflow.DROP_LATEST, onUndeliveredElement = { + finish(2) + throw TestException() + }) + + channel.send(42) + channel.send(12) + expect(1) + channel.send(12) + expectUnreached() + } + + @Test + fun testSendDropOldestInvokeHandlerConflated() = runTest(expected = { it is UndeliveredElementException }) { + val channel = Channel(Channel.CONFLATED, onUndeliveredElement = { + finish(2) + println(TestException().stackTraceToString()) + throw TestException() + }) + channel.send(42) + expect(1) + channel.send(42) + expectUnreached() + } } diff --git a/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementTest.kt b/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementTest.kt index f26361f2f8..3a99484630 100644 --- a/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementTest.kt @@ -138,4 +138,63 @@ class ChannelUndeliveredElementTest : TestBase() { channel.send(Unit) finish(3) } + + @Test + fun testChannelBufferOverflow() = runTest { + testBufferOverflowStrategy(listOf(1, 2), BufferOverflow.DROP_OLDEST) + testBufferOverflowStrategy(listOf(3), BufferOverflow.DROP_LATEST) + } + + private suspend fun testBufferOverflowStrategy(expectedDroppedElements: List, strategy: BufferOverflow) { + val list = ArrayList() + val channel = Channel( + capacity = 2, + onBufferOverflow = strategy, + onUndeliveredElement = { value -> list.add(value) } + ) + + channel.send(1) + channel.send(2) + + channel.send(3) + channel.trySend(4).onFailure { expectUnreached() } + assertEquals(expectedDroppedElements, list) + } + + + @Test + fun testTrySendDoesNotInvokeHandlerOnClosedConflatedChannel() = runTest { + val conflated = Channel(Channel.CONFLATED, onUndeliveredElement = { + expectUnreached() + }) + conflated.close(IndexOutOfBoundsException()) + conflated.trySend(3) + } + + @Test + fun testTrySendDoesNotInvokeHandlerOnClosedChannel() = runTest { + val conflated = Channel(3, onUndeliveredElement = { + expectUnreached() + }) + conflated.close(IndexOutOfBoundsException()) + repeat(10) { + conflated.trySend(3) + } + } + + @Test + fun testTrySendDoesNotInvokeHandler() { + for (capacity in 0..2) { + testTrySendDoesNotInvokeHandler(capacity) + } + } + + private fun testTrySendDoesNotInvokeHandler(capacity: Int) { + val channel = Channel(capacity, BufferOverflow.DROP_LATEST, onUndeliveredElement = { + expectUnreached() + }) + repeat(10) { + channel.trySend(3) + } + } } diff --git a/kotlinx-coroutines-core/common/test/channels/ChannelsTest.kt b/kotlinx-coroutines-core/common/test/channels/ChannelsTest.kt index fb704c5b86..e40071b91e 100644 --- a/kotlinx-coroutines-core/common/test/channels/ChannelsTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/ChannelsTest.kt @@ -20,7 +20,10 @@ class ChannelsTest: TestBase() { } @Test - fun testCloseWithMultipleWaiters() = runTest { + fun testCloseWithMultipleSuspendedReceivers() = runTest { + // Once the channel is closed, the waiting + // requests should be cancelled in the order + // they were suspended in the channel. val channel = Channel() launch { try { @@ -50,6 +53,40 @@ class ChannelsTest: TestBase() { finish(7) } + @Test + fun testCloseWithMultipleSuspendedSenders() = runTest { + // Once the channel is closed, the waiting + // requests should be cancelled in the order + // they were suspended in the channel. + val channel = Channel() + launch { + try { + expect(2) + channel.send(42) + expectUnreached() + } catch (e: CancellationException) { + expect(5) + } + } + + launch { + try { + expect(3) + channel.send(42) + expectUnreached() + } catch (e: CancellationException) { + expect(6) + } + } + + expect(1) + yield() + expect(4) + channel.cancel() + yield() + finish(7) + } + @Test fun testEmptyList() = runTest { assertTrue(emptyList().asReceiveChannel().toList().isEmpty()) diff --git a/kotlinx-coroutines-core/common/test/channels/ConflatedChannelArrayModelTest.kt b/kotlinx-coroutines-core/common/test/channels/ConflatedChannelArrayModelTest.kt deleted file mode 100644 index e80309be89..0000000000 --- a/kotlinx-coroutines-core/common/test/channels/ConflatedChannelArrayModelTest.kt +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines.channels - -// Test that ArrayChannel(1, DROP_OLDEST) works just like ConflatedChannel() -class ConflatedChannelArrayModelTest : ConflatedChannelTest() { - override fun createConflatedChannel(): Channel = - ArrayChannel(1, BufferOverflow.DROP_OLDEST, null) -} diff --git a/kotlinx-coroutines-core/common/test/channels/SendReceiveStressTest.kt b/kotlinx-coroutines-core/common/test/channels/SendReceiveStressTest.kt index b9aa9990b0..dcbb2d2f15 100644 --- a/kotlinx-coroutines-core/common/test/channels/SendReceiveStressTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/SendReceiveStressTest.kt @@ -5,7 +5,6 @@ package kotlinx.coroutines.channels import kotlinx.coroutines.* -import kotlin.coroutines.* import kotlin.test.* class SendReceiveStressTest : TestBase() { @@ -13,12 +12,12 @@ class SendReceiveStressTest : TestBase() { // Emulate parametrized by hand :( @Test - fun testArrayChannel() = runTest { + fun testBufferedChannel() = runTest { testStress(Channel(2)) } @Test - fun testLinkedListChannel() = runTest { + fun testUnlimitedChannel() = runTest { testStress(Channel(Channel.UNLIMITED)) } diff --git a/kotlinx-coroutines-core/common/test/channels/TestBroadcastChannelKind.kt b/kotlinx-coroutines-core/common/test/channels/TestBroadcastChannelKind.kt index d58c05da46..94a488763f 100644 --- a/kotlinx-coroutines-core/common/test/channels/TestBroadcastChannelKind.kt +++ b/kotlinx-coroutines-core/common/test/channels/TestBroadcastChannelKind.kt @@ -7,11 +7,11 @@ package kotlinx.coroutines.channels enum class TestBroadcastChannelKind { ARRAY_1 { override fun create(): BroadcastChannel = BroadcastChannel(1) - override fun toString(): String = "ArrayBroadcastChannel(1)" + override fun toString(): String = "BufferedBroadcastChannel(1)" }, ARRAY_10 { override fun create(): BroadcastChannel = BroadcastChannel(10) - override fun toString(): String = "ArrayBroadcastChannel(10)" + override fun toString(): String = "BufferedBroadcastChannel(10)" }, CONFLATED { override fun create(): BroadcastChannel = ConflatedBroadcastChannel() diff --git a/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt b/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt index 227e690ce8..305c0eea7f 100644 --- a/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt +++ b/kotlinx-coroutines-core/common/test/channels/TestChannelKind.kt @@ -13,13 +13,13 @@ enum class TestChannelKind( val viaBroadcast: Boolean = false ) { RENDEZVOUS(0, "RendezvousChannel"), - ARRAY_1(1, "ArrayChannel(1)"), - ARRAY_2(2, "ArrayChannel(2)"), - ARRAY_10(10, "ArrayChannel(10)"), - LINKED_LIST(Channel.UNLIMITED, "LinkedListChannel"), + BUFFERED_1(1, "BufferedChannel(1)"), + BUFFERED_2(2, "BufferedChannel(2)"), + BUFFERED_10(10, "BufferedChannel(10)"), + UNLIMITED(Channel.UNLIMITED, "UnlimitedChannel"), CONFLATED(Channel.CONFLATED, "ConflatedChannel"), - ARRAY_1_BROADCAST(1, "ArrayBroadcastChannel(1)", viaBroadcast = true), - ARRAY_10_BROADCAST(10, "ArrayBroadcastChannel(10)", viaBroadcast = true), + BUFFERED_1_BROADCAST(1, "BufferedBroadcastChannel(1)", viaBroadcast = true), + BUFFERED_10_BROADCAST(10, "BufferedBroadcastChannel(10)", viaBroadcast = true), CONFLATED_BROADCAST(Channel.CONFLATED, "ConflatedBroadcastChannel", viaBroadcast = true) ; @@ -33,7 +33,7 @@ enum class TestChannelKind( override fun toString(): String = description } -private class ChannelViaBroadcast( +internal class ChannelViaBroadcast( private val broadcast: BroadcastChannel ): Channel, SendChannel by broadcast { val sub = broadcast.openSubscription() @@ -46,16 +46,14 @@ private class ChannelViaBroadcast( override fun iterator(): ChannelIterator = sub.iterator() override fun tryReceive(): ChannelResult = sub.tryReceive() - override fun cancel(cause: CancellationException?) = sub.cancel(cause) + override fun cancel(cause: CancellationException?) = broadcast.cancel(cause) // implementing hidden method anyway, so can cast to an internal class @Deprecated(level = DeprecationLevel.HIDDEN, message = "Since 1.2.0, binary compatibility with versions <= 1.1.x") - override fun cancel(cause: Throwable?): Boolean = (sub as AbstractChannel).cancelInternal(cause) + override fun cancel(cause: Throwable?): Boolean = error("unsupported") override val onReceive: SelectClause1 get() = sub.onReceive override val onReceiveCatching: SelectClause1> get() = sub.onReceiveCatching - override val onReceiveOrNull: SelectClause1 - get() = error("unsupported") } diff --git a/kotlinx-coroutines-core/common/test/channels/LinkedListChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/UnlimitedChannelTest.kt similarity index 96% rename from kotlinx-coroutines-core/common/test/channels/LinkedListChannelTest.kt rename to kotlinx-coroutines-core/common/test/channels/UnlimitedChannelTest.kt index 501affb4d9..24b9d3d058 100644 --- a/kotlinx-coroutines-core/common/test/channels/LinkedListChannelTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/UnlimitedChannelTest.kt @@ -7,7 +7,7 @@ package kotlinx.coroutines.channels import kotlinx.coroutines.* import kotlin.test.* -class LinkedListChannelTest : TestBase() { +class UnlimitedChannelTest : TestBase() { @Test fun testBasic() = runTest { val c = Channel(Channel.UNLIMITED) diff --git a/kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectBufferedChannelTest.kt similarity index 99% rename from kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt rename to kotlinx-coroutines-core/common/test/selects/SelectBufferedChannelTest.kt index 88c21160ea..6bb8049e54 100644 --- a/kotlinx-coroutines-core/common/test/selects/SelectArrayChannelTest.kt +++ b/kotlinx-coroutines-core/common/test/selects/SelectBufferedChannelTest.kt @@ -6,10 +6,9 @@ package kotlinx.coroutines.selects import kotlinx.coroutines.* import kotlinx.coroutines.channels.* -import kotlinx.coroutines.intrinsics.* import kotlin.test.* -class SelectArrayChannelTest : TestBase() { +class SelectBufferedChannelTest : TestBase() { @Test fun testSelectSendSuccess() = runTest { diff --git a/kotlinx-coroutines-core/common/test/selects/SelectLinkedListChannelTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectUnlimitedChannelTest.kt similarity index 93% rename from kotlinx-coroutines-core/common/test/selects/SelectLinkedListChannelTest.kt rename to kotlinx-coroutines-core/common/test/selects/SelectUnlimitedChannelTest.kt index a066f6b3a9..081c9183aa 100644 --- a/kotlinx-coroutines-core/common/test/selects/SelectLinkedListChannelTest.kt +++ b/kotlinx-coroutines-core/common/test/selects/SelectUnlimitedChannelTest.kt @@ -8,7 +8,7 @@ import kotlinx.coroutines.* import kotlinx.coroutines.channels.* import kotlin.test.* -class SelectLinkedListChannelTest : TestBase() { +class SelectUnlimitedChannelTest : TestBase() { @Test fun testSelectSendWhenClosed() = runTest { expect(1) diff --git a/kotlinx-coroutines-core/concurrent/test/channels/ChannelCancelUndeliveredElementStressTest.kt b/kotlinx-coroutines-core/concurrent/test/channels/ChannelCancelUndeliveredElementStressTest.kt index 76918850e5..1cf7d8a17d 100644 --- a/kotlinx-coroutines-core/concurrent/test/channels/ChannelCancelUndeliveredElementStressTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/channels/ChannelCancelUndeliveredElementStressTest.kt @@ -51,6 +51,7 @@ class ChannelCancelUndeliveredElementStressTest : TestBase() { println(" Undelivered: ${dUndeliveredCnt.value}") error("Failed") } + (channel as? BufferedChannel<*>)?.checkSegmentStructureInvariants() trySendFailedCnt += dTrySendFailedCnt receivedCnt += dReceivedCnt undeliveredCnt += dUndeliveredCnt.value diff --git a/kotlinx-coroutines-core/concurrent/test/selects/SelectChannelStressTest.kt b/kotlinx-coroutines-core/concurrent/test/selects/SelectChannelStressTest.kt index 0b687a49c6..a5bf90ac40 100644 --- a/kotlinx-coroutines-core/concurrent/test/selects/SelectChannelStressTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/selects/SelectChannelStressTest.kt @@ -6,7 +6,6 @@ package kotlinx.coroutines.selects import kotlinx.coroutines.* import kotlinx.coroutines.channels.* -import kotlinx.coroutines.intrinsics.* import kotlin.test.* class SelectChannelStressTest: TestBase() { @@ -15,7 +14,7 @@ class SelectChannelStressTest: TestBase() { private val iterations = (if (isNative) 1_000 else 1_000_000) * stressTestMultiplier @Test - fun testSelectSendResourceCleanupArrayChannel() = runTest { + fun testSelectSendResourceCleanupBufferedChannel() = runTest { val channel = Channel(1) expect(1) channel.send(-1) // fill the buffer, so all subsequent sends cannot proceed @@ -29,7 +28,7 @@ class SelectChannelStressTest: TestBase() { } @Test - fun testSelectReceiveResourceCleanupArrayChannel() = runTest { + fun testSelectReceiveResourceCleanupBufferedChannel() = runTest { val channel = Channel(1) expect(1) repeat(iterations) { i -> diff --git a/kotlinx-coroutines-core/js/src/internal/Concurrent.kt b/kotlinx-coroutines-core/js/src/internal/Concurrent.kt index 71f652271a..6272679e3f 100644 --- a/kotlinx-coroutines-core/js/src/internal/Concurrent.kt +++ b/kotlinx-coroutines-core/js/src/internal/Concurrent.kt @@ -13,7 +13,5 @@ internal class NoOpLock { fun unlock(): Unit {} } -internal actual fun subscriberList(): SubscribersList = CopyOnWriteList() - internal actual fun identitySet(expectedSize: Int): MutableSet = HashSet(expectedSize) diff --git a/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt b/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt index 050b974755..5df79b8d75 100644 --- a/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt +++ b/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt @@ -9,8 +9,6 @@ import java.util.* import java.util.concurrent.* import kotlin.concurrent.withLock as withLockJvm -internal actual fun subscriberList(): SubscribersList = CopyOnWriteArrayList() - @Suppress("ACTUAL_WITHOUT_EXPECT") internal actual typealias ReentrantLock = java.util.concurrent.locks.ReentrantLock diff --git a/kotlinx-coroutines-core/jvm/src/internal/SegmentBasedQueue.kt b/kotlinx-coroutines-core/jvm/src/internal/SegmentBasedQueue.kt deleted file mode 100644 index f3447b7164..0000000000 --- a/kotlinx-coroutines-core/jvm/src/internal/SegmentBasedQueue.kt +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines.internal - -import kotlinx.atomicfu.* - -/** - * This queue implementation is based on [SegmentList] for testing purposes and is organized as follows. Essentially, - * the [SegmentBasedQueue] is represented as an infinite array of segments, each stores one element (see [OneElementSegment]). - * Both [enqueue] and [dequeue] operations increment the corresponding global index ([enqIdx] for [enqueue] and - * [deqIdx] for [dequeue]) and work with the indexed by this counter cell. Since both operations increment the indices - * at first, there could be a race: [enqueue] increments [enqIdx], then [dequeue] checks that the queue is not empty - * (that's true) and increments [deqIdx], looking into the corresponding cell after that; however, the cell is empty - * because the [enqIdx] operation has not been put its element yet. To make the queue non-blocking, [dequeue] can mark - * the cell with [BROKEN] token and retry the operation, [enqueue] at the same time should restart as well; this way, - * the queue is obstruction-free. - */ -internal class SegmentBasedQueue { - private val head: AtomicRef> - private val tail: AtomicRef> - - private val enqIdx = atomic(0L) - private val deqIdx = atomic(0L) - - init { - val s = OneElementSegment(0, null, 2) - head = atomic(s) - tail = atomic(s) - } - - // Returns the segments associated with the enqueued element, or `null` if the queue is closed. - fun enqueue(element: T): OneElementSegment? { - while (true) { - val curTail = this.tail.value - val enqIdx = this.enqIdx.getAndIncrement() - @Suppress("INFERRED_TYPE_VARIABLE_INTO_POSSIBLE_EMPTY_INTERSECTION") // KT-54411 - val segmentOrClosed = this.tail.findSegmentAndMoveForward(id = enqIdx, startFrom = curTail, createNewSegment = ::createSegment) - if (segmentOrClosed.isClosed) return null - val s = segmentOrClosed.segment - if (s.element.value === BROKEN) continue - if (s.element.compareAndSet(null, element)) return s - } - } - - fun dequeue(): T? { - while (true) { - if (this.deqIdx.value >= this.enqIdx.value) return null - val curHead = this.head.value - val deqIdx = this.deqIdx.getAndIncrement() - @Suppress("INFERRED_TYPE_VARIABLE_INTO_POSSIBLE_EMPTY_INTERSECTION") // KT-54411 - val segmentOrClosed = this.head.findSegmentAndMoveForward(id = deqIdx, startFrom = curHead, createNewSegment = ::createSegment) - if (segmentOrClosed.isClosed) return null - val s = segmentOrClosed.segment - if (s.id > deqIdx) continue - var el = s.element.value - if (el === null) { - if (s.element.compareAndSet(null, BROKEN)) continue - else el = s.element.value - } - // The link to the previous segment should be cleaned after retrieving the element; - // otherwise, `close()` cannot clean the slot. - s.cleanPrev() - if (el === BROKEN) continue - @Suppress("UNCHECKED_CAST") - return el as T - } - } - - // `enqueue` should return `null` after the queue is closed - fun close(): OneElementSegment { - val s = this.tail.value.close() - var cur = s - while (true) { - cur.element.compareAndSet(null, BROKEN) - cur = cur.prev ?: break - } - return s - } - - val numberOfSegments: Int get() { - var cur = head.value - var i = 1 - while (true) { - cur = cur.next ?: return i - i++ - } - } - - fun checkHeadPrevIsCleaned() { - check(head.value.prev === null) { "head.prev is not null"} - } - - fun checkAllSegmentsAreNotLogicallyRemoved() { - var prev: OneElementSegment? = null - var cur = head.value - while (true) { - check(!cur.logicallyRemoved || cur.isTail) { - "This queue contains removed segments, memory leak detected" - } - check(cur.prev === prev) { - "Two neighbour segments are incorrectly linked: S.next.prev != S" - } - prev = cur - cur = cur.next ?: return - } - } - -} - -private fun createSegment(id: Long, prev: OneElementSegment?) = OneElementSegment(id, prev, 0) - -internal class OneElementSegment(id: Long, prev: OneElementSegment?, pointers: Int) : Segment>(id, prev, pointers) { - val element = atomic(null) - - override val maxSlots get() = 1 - - val logicallyRemoved get() = element.value === BROKEN - - fun removeSegment() { - val old = element.getAndSet(BROKEN) - if (old !== BROKEN) onSlotCleaned() - } -} - -private val BROKEN = Symbol("BROKEN") diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveFromChannel.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveFromChannel.txt index af8c1fd389..64085ad329 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveFromChannel.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testReceiveFromChannel.txt @@ -1,8 +1,10 @@ kotlinx.coroutines.RecoverableTestException - at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveFromChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:97) - at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) - at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.channelReceive(StackTraceRecoveryChannelsTest.kt:116) - at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveFromChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:101) + at kotlinx.coroutines.internal.StackTraceRecoveryKt.recoverStackTrace(StackTraceRecovery.kt) + at kotlinx.coroutines.channels.BufferedChannel.receive$suspendImpl(BufferedChannel.kt) + at kotlinx.coroutines.channels.BufferedChannel.receive(BufferedChannel.kt) + at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.channelReceive(StackTraceRecoveryChannelsTest.kt) + at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.access$channelReceive(StackTraceRecoveryChannelsTest.kt) + at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$channelReceive$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt) Caused by: kotlinx.coroutines.RecoverableTestException - at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveFromChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:97) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) + at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveFromChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt) + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt) \ No newline at end of file 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 4a8e320e2d..e40cc741d8 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/channels/testSendToChannel.txt @@ -1,12 +1,20 @@ -java.util.concurrent.CancellationException: RendezvousChannel was cancelled - at kotlinx.coroutines.channels.AbstractChannel.cancel(AbstractChannel.kt:630) - at kotlinx.coroutines.channels.ReceiveChannel$DefaultImpls.cancel$default(Channel.kt:311) - at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:52) +java.util.concurrent.CancellationException: Channel was cancelled + at kotlinx.coroutines.channels.BufferedChannel.cancelImpl$(BufferedChannel.kt) + at kotlinx.coroutines.channels.BufferedChannel.cancel(BufferedChannel.kt) + at kotlinx.coroutines.channels.ReceiveChannel$DefaultImpls.cancel$default(Channel.kt) + at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt) at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) - at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.channelSend(StackTraceRecoveryChannelsTest.kt:73) - at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:56) -Caused by: java.util.concurrent.CancellationException: RendezvousChannel was cancelled - at kotlinx.coroutines.channels.AbstractChannel.cancel(AbstractChannel.kt:630) - at kotlinx.coroutines.channels.ReceiveChannel$DefaultImpls.cancel$default(Channel.kt:311) - at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:52) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) + at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.channelSend(StackTraceRecoveryChannelsTest.kt) + at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt) +Caused by: java.util.concurrent.CancellationException: Channel was cancelled + at kotlinx.coroutines.channels.BufferedChannel.cancelImpl$(BufferedChannel.kt) + at kotlinx.coroutines.channels.BufferedChannel.cancel(BufferedChannel.kt) + at kotlinx.coroutines.channels.ReceiveChannel$DefaultImpls.cancel$default(Channel.kt) + at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt) + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt) + 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.runBlocking(Unknown Source) + at kotlinx.coroutines.TestBase.runTest(TestBase.kt) diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testUnconfined.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testUnconfined.txt index 4f0103ecc8..a8461556d1 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testUnconfined.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/resume-mode/testUnconfined.txt @@ -1,12 +1,10 @@ kotlinx.coroutines.RecoverableTestException - at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:61) - at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testUnconfined$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:40) - at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt) - at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$withContext$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:76) - at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.doFastPath(StackTraceRecoveryResumeModeTest.kt:71) - at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:62) - at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testUnconfined$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:40) + at kotlinx.coroutines.internal.StackTraceRecoveryKt.recoverStackTrace(StackTraceRecovery.kt) + at kotlinx.coroutines.channels.BufferedChannel.receive$suspendImpl(BufferedChannel.kt) + at kotlinx.coroutines.channels.BufferedChannel.receive(BufferedChannel.kt) + at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$withContext$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt) Caused by: kotlinx.coroutines.RecoverableTestException - at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:61) - at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testUnconfined$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:40) - at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) + at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt) + at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.access$testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt) + at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testUnconfined$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt) + at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt) \ No newline at end of file diff --git a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectOnReceive.txt b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectOnReceive.txt index ae40259914..8b958d2058 100644 --- a/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectOnReceive.txt +++ b/kotlinx-coroutines-core/jvm/test-resources/stacktraces/select/testSelectOnReceive.txt @@ -1,9 +1,9 @@ kotlinx.coroutines.channels.ClosedReceiveChannelException: Channel was closed - at kotlinx.coroutines.channels.Closed.getReceiveException(AbstractChannel.kt) - at kotlinx.coroutines.channels.AbstractChannel.processResultSelectReceive(AbstractChannel.kt) - at kotlinx.coroutines.channels.AbstractChannel.access$processResultSelectReceive(AbstractChannel.kt) - at kotlinx.coroutines.channels.AbstractChannel$onReceive$2.invoke(AbstractChannel.kt) - at kotlinx.coroutines.channels.AbstractChannel$onReceive$2.invoke(AbstractChannel.kt) + at kotlinx.coroutines.channels.BufferedChannel.getReceiveException(BufferedChannel.kt) + at kotlinx.coroutines.channels.BufferedChannel.processResultSelectReceive(BufferedChannel.kt) + at kotlinx.coroutines.channels.BufferedChannel.access$processResultSelectReceive(BufferedChannel.kt) + at kotlinx.coroutines.channels.BufferedChannel$onReceive$2.invoke(BufferedChannel.kt) + at kotlinx.coroutines.channels.BufferedChannel$onReceive$2.invoke(BufferedChannel.kt) at kotlinx.coroutines.selects.SelectImplementation$ClauseData.processResult(Select.kt) at kotlinx.coroutines.selects.SelectImplementation.processResultAndInvokeBlockRecoveringException(Select.kt) at kotlinx.coroutines.selects.SelectImplementation.complete(Select.kt) @@ -16,11 +16,11 @@ kotlinx.coroutines.channels.ClosedReceiveChannelException: Channel was closed at kotlinx.coroutines.selects.SelectImplementation.processResultAndInvokeBlockRecoveringException(Select.kt) at kotlinx.coroutines.exceptions.StackTraceRecoverySelectTest$testSelectOnReceive$1.invokeSuspend(StackTraceRecoverySelectTest.kt) Caused by: kotlinx.coroutines.channels.ClosedReceiveChannelException: Channel was closed - at kotlinx.coroutines.channels.Closed.getReceiveException(AbstractChannel.kt) - at kotlinx.coroutines.channels.AbstractChannel.processResultSelectReceive(AbstractChannel.kt) - at kotlinx.coroutines.channels.AbstractChannel.access$processResultSelectReceive(AbstractChannel.kt) - at kotlinx.coroutines.channels.AbstractChannel$onReceive$2.invoke(AbstractChannel.kt) - at kotlinx.coroutines.channels.AbstractChannel$onReceive$2.invoke(AbstractChannel.kt) + at kotlinx.coroutines.channels.BufferedChannel.getReceiveException(BufferedChannel.kt) + at kotlinx.coroutines.channels.BufferedChannel.processResultSelectReceive(BufferedChannel.kt) + at kotlinx.coroutines.channels.BufferedChannel.access$processResultSelectReceive(BufferedChannel.kt) + at kotlinx.coroutines.channels.BufferedChannel$onReceive$2.invoke(BufferedChannel.kt) + at kotlinx.coroutines.channels.BufferedChannel$onReceive$2.invoke(BufferedChannel.kt) at kotlinx.coroutines.selects.SelectImplementation$ClauseData.processResult(Select.kt) at kotlinx.coroutines.selects.SelectImplementation.processResultAndInvokeBlockRecoveringException(Select.kt) at kotlinx.coroutines.selects.SelectImplementation.complete(Select.kt) diff --git a/kotlinx-coroutines-core/jvm/test/AbstractLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/AbstractLincheckTest.kt index 89bbbfd7ee..bdb615d83c 100644 --- a/kotlinx-coroutines-core/jvm/test/AbstractLincheckTest.kt +++ b/kotlinx-coroutines-core/jvm/test/AbstractLincheckTest.kt @@ -16,7 +16,7 @@ abstract class AbstractLincheckTest : VerifierState() { @Test fun modelCheckingTest() = ModelCheckingOptions() - .iterations(if (isStressTest) 100 else 20) + .iterations(if (isStressTest) 200 else 20) .invocationsPerIteration(if (isStressTest) 10_000 else 1_000) .commonConfiguration() .customize(isStressTest) @@ -24,7 +24,7 @@ abstract class AbstractLincheckTest : VerifierState() { @Test fun stressTest() = StressOptions() - .iterations(if (isStressTest) 100 else 20) + .iterations(if (isStressTest) 200 else 20) .invocationsPerIteration(if (isStressTest) 10_000 else 1_000) .commonConfiguration() .customize(isStressTest) @@ -32,8 +32,13 @@ abstract class AbstractLincheckTest : VerifierState() { private fun > O.commonConfiguration(): O = this .actorsBefore(if (isStressTest) 3 else 1) + // All the bugs we have discovered so far + // were reproducible on at most 3 threads .threads(3) - .actorsPerThread(if (isStressTest) 4 else 2) + // 3 operations per thread is sufficient, + // while increasing this number declines + // the model checking coverage. + .actorsPerThread(if (isStressTest) 3 else 2) .actorsAfter(if (isStressTest) 3 else 0) .customize(isStressTest) diff --git a/kotlinx-coroutines-core/jvm/test/MutexCancellationStressTest.kt b/kotlinx-coroutines-core/jvm/test/MutexCancellationStressTest.kt index 9e676e9a60..eb6360dac0 100644 --- a/kotlinx-coroutines-core/jvm/test/MutexCancellationStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/MutexCancellationStressTest.kt @@ -48,7 +48,7 @@ class MutexCancellationStressTest : TestBase() { val checkProgressJob = launch(dispatcher + CoroutineName("checkProgressJob")) { var lastCounterLocalSnapshot = (0 until mutexJobNumber).map { 0 } while (completed.value == 0) { - delay(500) + delay(1000) val c = counterLocal.map { it.value } for (i in 0 until mutexJobNumber) { assert(c[i] > lastCounterLocalSnapshot[i]) { "No progress in MutexJob-$i" } diff --git a/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelLeakTest.kt b/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelLeakTest.kt index 66b08c74e4..df944654b6 100644 --- a/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelLeakTest.kt +++ b/kotlinx-coroutines-core/jvm/test/channels/BroadcastChannelLeakTest.kt @@ -6,8 +6,8 @@ import kotlin.test.* class BroadcastChannelLeakTest : TestBase() { @Test - fun testArrayBroadcastChannelSubscriptionLeak() { - checkLeak { ArrayBroadcastChannel(1) } + fun testBufferedBroadcastChannelSubscriptionLeak() { + checkLeak { BroadcastChannelImpl(1) } } @Test diff --git a/kotlinx-coroutines-core/jvm/test/channels/ArrayChannelStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/BufferedChannelStressTest.kt similarity index 95% rename from kotlinx-coroutines-core/jvm/test/channels/ArrayChannelStressTest.kt rename to kotlinx-coroutines-core/jvm/test/channels/BufferedChannelStressTest.kt index 74dc24c7f6..a6464263cc 100644 --- a/kotlinx-coroutines-core/jvm/test/channels/ArrayChannelStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/channels/BufferedChannelStressTest.kt @@ -10,7 +10,7 @@ import org.junit.runner.* import org.junit.runners.* @RunWith(Parameterized::class) -class ArrayChannelStressTest(private val capacity: Int) : TestBase() { +class BufferedChannelStressTest(private val capacity: Int) : TestBase() { companion object { @Parameterized.Parameters(name = "{0}, nSenders={1}, nReceivers={2}") diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelMemoryLeakStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelMemoryLeakStressTest.kt new file mode 100644 index 0000000000..ebc2bee89a --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelMemoryLeakStressTest.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package channels + +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.* +import org.junit.Test + +class ChannelMemoryLeakStressTest : TestBase() { + private val nRepeat = 1_000_000 * stressTestMultiplier + + @Test + fun test() = runTest { + val c = Channel(1) + repeat(nRepeat) { + c.send(bigValue()) + c.receive() + } + } + + // capture big value for fast OOM in case of a bug + private fun bigValue(): ByteArray = ByteArray(4096) +} diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelSendReceiveStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelSendReceiveStressTest.kt index 7e55f2e602..8a60ce5051 100644 --- a/kotlinx-coroutines-core/jvm/test/channels/ChannelSendReceiveStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelSendReceiveStressTest.kt @@ -7,12 +7,14 @@ package kotlinx.coroutines.channels import kotlinx.coroutines.* import kotlinx.coroutines.selects.* import org.junit.* +import org.junit.Ignore import org.junit.Test import org.junit.runner.* import org.junit.runners.* import java.util.concurrent.atomic.* import kotlin.test.* +@Ignore @RunWith(Parameterized::class) class ChannelSendReceiveStressTest( private val kind: TestChannelKind, @@ -25,10 +27,7 @@ class ChannelSendReceiveStressTest( fun params(): Collection> = listOf(1, 2, 10).flatMap { nSenders -> listOf(1, 10).flatMap { nReceivers -> - TestChannelKind.values() - // Workaround for bug that won't be fixed unless new channel implementation, see #2443 - .filter { it != TestChannelKind.LINKED_LIST } - .map { arrayOf(it, nSenders, nReceivers) } + TestChannelKind.values().map { arrayOf(it, nSenders, nReceivers) } } } } @@ -36,7 +35,7 @@ class ChannelSendReceiveStressTest( private val timeLimit = 30_000L * stressTestMultiplier // 30 sec private val nEvents = 200_000 * stressTestMultiplier - private val maxBuffer = 10_000 // artificial limit for LinkedListChannel + private val maxBuffer = 10_000 // artificial limit for unlimited channel val channel = kind.create() private val sendersCompleted = AtomicInteger() @@ -107,6 +106,7 @@ class ChannelSendReceiveStressTest( repeat(nReceivers) { receiveIndex -> println(" Received by #$receiveIndex ${receivedBy[receiveIndex]}") } + (channel as? BufferedChannel<*>)?.checkSegmentStructureInvariants() assertEquals(nSenders, sendersCompleted.get()) assertEquals(nReceivers, receiversCompleted.get()) assertEquals(0, dupes.get()) @@ -121,7 +121,7 @@ class ChannelSendReceiveStressTest( sentTotal.incrementAndGet() if (!kind.isConflated) { while (sentTotal.get() > receivedTotal.get() + maxBuffer) - yield() // throttle fast senders to prevent OOM with LinkedListChannel + yield() // throttle fast senders to prevent OOM with an unlimited channel } } diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementSelectOldStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementSelectOldStressTest.kt index 1b4b83eb53..25cccf948a 100644 --- a/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementSelectOldStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementSelectOldStressTest.kt @@ -160,7 +160,7 @@ class ChannelUndeliveredElementSelectOldStressTest(private val kind: TestChannel sentStatus[trySendData.x] = 3 when { // must artificially slow down LINKED_LIST sender to avoid overwhelming receiver and going OOM - kind == TestChannelKind.LINKED_LIST -> while (sentCnt > lastReceived + 100) yield() + kind == TestChannelKind.UNLIMITED -> while (sentCnt > lastReceived + 100) yield() // yield periodically to check cancellation on conflated channels kind.isConflated -> if (counter++ % 100 == 0) yield() } diff --git a/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt index 335980daaf..f8a5644769 100644 --- a/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/channels/ChannelUndeliveredElementStressTest.kt @@ -116,6 +116,7 @@ class ChannelUndeliveredElementStressTest(private val kind: TestChannelKind) : T printErrorDetails() throw e } + (channel as? BufferedChannel<*>)?.checkSegmentStructureInvariants() sentStatus.clear() receivedStatus.clear() failedStatus.clear() @@ -165,7 +166,7 @@ class ChannelUndeliveredElementStressTest(private val kind: TestChannelKind) : T sentStatus[trySendData.x] = sendMode + 2 when { // must artificially slow down LINKED_LIST sender to avoid overwhelming receiver and going OOM - kind == TestChannelKind.LINKED_LIST -> while (sentCnt > lastReceived + 100) yield() + kind == TestChannelKind.UNLIMITED -> while (sentCnt > lastReceived + 100) yield() // yield periodically to check cancellation on conflated channels kind.isConflated -> if (counter++ % 100 == 0) yield() } diff --git a/kotlinx-coroutines-core/jvm/test/channels/InvokeOnCloseStressTest.kt b/kotlinx-coroutines-core/jvm/test/channels/InvokeOnCloseStressTest.kt index 888522c63c..8ac859137e 100644 --- a/kotlinx-coroutines-core/jvm/test/channels/InvokeOnCloseStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/channels/InvokeOnCloseStressTest.kt @@ -5,7 +5,6 @@ package kotlinx.coroutines.channels import kotlinx.coroutines.* -import kotlinx.coroutines.channels.* import org.junit.* import org.junit.Test import java.util.concurrent.* @@ -28,7 +27,7 @@ class InvokeOnCloseStressTest : TestBase(), CoroutineScope { @Test fun testInvokedExactlyOnce() = runBlocking { - runStressTest(TestChannelKind.ARRAY_1) + runStressTest(TestChannelKind.BUFFERED_1) } @Test diff --git a/kotlinx-coroutines-core/jvm/test/internal/SegmentQueueTest.kt b/kotlinx-coroutines-core/jvm/test/internal/SegmentQueueTest.kt deleted file mode 100644 index 1390403a3b..0000000000 --- a/kotlinx-coroutines-core/jvm/test/internal/SegmentQueueTest.kt +++ /dev/null @@ -1,110 +0,0 @@ -package kotlinx.coroutines.internal - -import kotlinx.coroutines.* -import kotlinx.coroutines.internal.* -import org.junit.Test -import java.util.* -import java.util.concurrent.CyclicBarrier -import java.util.concurrent.atomic.AtomicInteger -import kotlin.concurrent.thread -import kotlin.random.Random -import kotlin.test.* - -class SegmentQueueTest : TestBase() { - @Test - fun testSimpleTest() { - val q = SegmentBasedQueue() - assertEquals(1, q.numberOfSegments) - assertNull(q.dequeue()) - q.enqueue(1) - assertEquals(1, q.numberOfSegments) - q.enqueue(2) - assertEquals(2, q.numberOfSegments) - assertEquals(1, q.dequeue()) - assertEquals(2, q.numberOfSegments) - assertEquals(2, q.dequeue()) - assertEquals(1, q.numberOfSegments) - assertNull(q.dequeue()) - } - - @Test - fun testSegmentRemoving() { - val q = SegmentBasedQueue() - q.enqueue(1) - val s = q.enqueue(2) - q.enqueue(3) - assertEquals(3, q.numberOfSegments) - s!!.removeSegment() - assertEquals(2, q.numberOfSegments) - assertEquals(1, q.dequeue()) - assertEquals(3, q.dequeue()) - assertNull(q.dequeue()) - } - - @Test - fun testRemoveHeadSegment() { - val q = SegmentBasedQueue() - q.enqueue(1) - val s = q.enqueue(2) - assertEquals(1, q.dequeue()) - q.enqueue(3) - s!!.removeSegment() - assertEquals(3, q.dequeue()) - assertNull(q.dequeue()) - } - - @Test - fun testClose() { - val q = SegmentBasedQueue() - q.enqueue(1) - assertEquals(0, q.close().id) - assertEquals(null, q.enqueue(2)) - assertEquals(1, q.dequeue()) - assertEquals(null, q.dequeue()) - } - - @Test - fun stressTest() { - val q = SegmentBasedQueue() - val expectedQueue = ArrayDeque() - val r = Random(0) - repeat(1_000_000 * stressTestMultiplier) { - if (r.nextBoolean()) { // add - val el = r.nextInt() - q.enqueue(el) - expectedQueue.add(el) - } else { // remove - assertEquals(expectedQueue.poll(), q.dequeue()) - q.checkHeadPrevIsCleaned() - } - } - } - - @Test - fun testRemoveSegmentsSerial() = stressTestRemoveSegments(false) - - @Test - fun testRemoveSegmentsRandom() = stressTestRemoveSegments(true) - - private fun stressTestRemoveSegments(random: Boolean) { - val N = 100_000 * stressTestMultiplier - val T = 10 - val q = SegmentBasedQueue() - val segments = (1..N).map { q.enqueue(it)!! }.toMutableList() - if (random) segments.shuffle() - assertEquals(N, q.numberOfSegments) - val nextSegmentIndex = AtomicInteger() - val barrier = CyclicBarrier(T) - (1..T).map { - thread { - barrier.await() - while (true) { - val i = nextSegmentIndex.getAndIncrement() - if (i >= N) break - segments[i].removeSegment() - } - } - }.forEach { it.join() } - assertEquals(2, q.numberOfSegments) - } -} diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/ChannelsLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/ChannelsLincheckTest.kt index 46611b792b..87ed74b715 100644 --- a/kotlinx-coroutines-core/jvm/test/lincheck/ChannelsLincheckTest.kt +++ b/kotlinx-coroutines-core/jvm/test/lincheck/ChannelsLincheckTest.kt @@ -1,7 +1,7 @@ /* * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("unused") +@file:Suppress("unused", "MemberVisibilityCanBePrivate") package kotlinx.coroutines.lincheck @@ -15,107 +15,160 @@ import org.jetbrains.kotlinx.lincheck.* import org.jetbrains.kotlinx.lincheck.annotations.* import org.jetbrains.kotlinx.lincheck.annotations.Operation import org.jetbrains.kotlinx.lincheck.paramgen.* +import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.* import org.jetbrains.kotlinx.lincheck.verifier.* -class RendezvousChannelLincheckTest : ChannelLincheckTestBase( +class RendezvousChannelLincheckTest : ChannelLincheckTestBaseWithOnSend( c = Channel(RENDEZVOUS), sequentialSpecification = SequentialRendezvousChannel::class.java ) class SequentialRendezvousChannel : SequentialIntChannelBase(RENDEZVOUS) -class Array1ChannelLincheckTest : ChannelLincheckTestBase( +class Buffered1ChannelLincheckTest : ChannelLincheckTestBaseWithOnSend( c = Channel(1), - sequentialSpecification = SequentialArray1RendezvousChannel::class.java + sequentialSpecification = SequentialBuffered1Channel::class.java ) -class SequentialArray1RendezvousChannel : SequentialIntChannelBase(1) +class Buffered1BroadcastChannelLincheckTest : ChannelLincheckTestBase( + c = ChannelViaBroadcast(BroadcastChannelImpl(1)), + sequentialSpecification = SequentialBuffered1Channel::class.java, + obstructionFree = false +) +class SequentialBuffered1Channel : SequentialIntChannelBase(1) -class Array2ChannelLincheckTest : ChannelLincheckTestBase( +class Buffered2ChannelLincheckTest : ChannelLincheckTestBaseWithOnSend( c = Channel(2), - sequentialSpecification = SequentialArray2RendezvousChannel::class.java + sequentialSpecification = SequentialBuffered2Channel::class.java +) +class Buffered2BroadcastChannelLincheckTest : ChannelLincheckTestBase( + c = ChannelViaBroadcast(BroadcastChannelImpl(2)), + sequentialSpecification = SequentialBuffered2Channel::class.java, + obstructionFree = false ) -class SequentialArray2RendezvousChannel : SequentialIntChannelBase(2) +class SequentialBuffered2Channel : SequentialIntChannelBase(2) -class UnlimitedChannelLincheckTest : ChannelLincheckTestBase( +class UnlimitedChannelLincheckTest : ChannelLincheckTestBaseAll( c = Channel(UNLIMITED), sequentialSpecification = SequentialUnlimitedChannel::class.java ) class SequentialUnlimitedChannel : SequentialIntChannelBase(UNLIMITED) -class ConflatedChannelLincheckTest : ChannelLincheckTestBase( +class ConflatedChannelLincheckTest : ChannelLincheckTestBaseAll( c = Channel(CONFLATED), - sequentialSpecification = SequentialConflatedChannel::class.java + sequentialSpecification = SequentialConflatedChannel::class.java, + obstructionFree = false +) +class ConflatedBroadcastChannelLincheckTest : ChannelLincheckTestBaseAll( + c = ChannelViaBroadcast(ConflatedBroadcastChannel()), + sequentialSpecification = SequentialConflatedChannel::class.java, + obstructionFree = false ) class SequentialConflatedChannel : SequentialIntChannelBase(CONFLATED) +abstract class ChannelLincheckTestBaseAll( + c: Channel, + sequentialSpecification: Class<*>, + obstructionFree: Boolean = true +) : ChannelLincheckTestBaseWithOnSend(c, sequentialSpecification, obstructionFree) { + @Operation + override fun trySend(value: Int) = super.trySend(value) + @Operation + override fun isClosedForReceive() = super.isClosedForReceive() + @Operation + override fun isEmpty() = super.isEmpty() +} + +abstract class ChannelLincheckTestBaseWithOnSend( + c: Channel, + sequentialSpecification: Class<*>, + obstructionFree: Boolean = true +) : ChannelLincheckTestBase(c, sequentialSpecification, obstructionFree) { + @Operation(allowExtraSuspension = true, blocking = true) + suspend fun sendViaSelect(@Param(name = "value") value: Int): Any = try { + select { c.onSend(value) {} } + } catch (e: NumberedCancellationException) { + e.testResult + } +} + @Param.Params( - Param(name = "value", gen = IntGen::class, conf = "1:5"), - Param(name = "closeToken", gen = IntGen::class, conf = "1:3") + Param(name = "value", gen = IntGen::class, conf = "1:9"), + Param(name = "closeToken", gen = IntGen::class, conf = "1:9") ) abstract class ChannelLincheckTestBase( - private val c: Channel, - private val sequentialSpecification: Class<*> + protected val c: Channel, + private val sequentialSpecification: Class<*>, + private val obstructionFree: Boolean = true ) : AbstractLincheckTest() { - @Operation(promptCancellation = true) + + @Operation(allowExtraSuspension = true, blocking = true) suspend fun send(@Param(name = "value") value: Int): Any = try { c.send(value) } catch (e: NumberedCancellationException) { e.testResult } - @Operation - fun trySend(@Param(name = "value") value: Int): Any = c.trySend(value) - .onSuccess { return true } - .onFailure { - return if (it is NumberedCancellationException) it.testResult - else false - } - - @Operation(promptCancellation = true) - suspend fun sendViaSelect(@Param(name = "value") value: Int): Any = try { - select { c.onSend(value) {} } - } catch (e: NumberedCancellationException) { - e.testResult - } + // @Operation TODO: `trySend()` is not linearizable as it can fail due to postponed buffer expansion + // TODO: or make a rendezvous with `tryReceive`, which violates the sequential specification. + open fun trySend(@Param(name = "value") value: Int): Any = c.trySend(value) + .onSuccess { return true } + .onFailure { + return if (it is NumberedCancellationException) it.testResult + else false + } - @Operation(promptCancellation = true) + @Operation(allowExtraSuspension = true, blocking = true) suspend fun receive(): Any = try { c.receive() } catch (e: NumberedCancellationException) { e.testResult } - @Operation + @Operation(allowExtraSuspension = true, blocking = true) + suspend fun receiveCatching(): Any = c.receiveCatching() + .onSuccess { return it } + .onClosed { e -> return (e as NumberedCancellationException).testResult } + + @Operation(blocking = true) fun tryReceive(): Any? = c.tryReceive() .onSuccess { return it } .onFailure { return if (it is NumberedCancellationException) it.testResult else null } - @Operation(promptCancellation = true) + @Operation(allowExtraSuspension = true, blocking = true) suspend fun receiveViaSelect(): Any = try { select { c.onReceive { it } } } catch (e: NumberedCancellationException) { e.testResult } - @Operation(causesBlocking = true) + @Operation(causesBlocking = true, blocking = true) fun close(@Param(name = "closeToken") token: Int): Boolean = c.close(NumberedCancellationException(token)) - // TODO: this operation should be (and can be!) linearizable, but is not - // @Operation + @Operation(causesBlocking = true, blocking = true) fun cancel(@Param(name = "closeToken") token: Int) = c.cancel(NumberedCancellationException(token)) - // @Operation - fun isClosedForReceive() = c.isClosedForReceive + // @Operation TODO non-linearizable in BufferedChannel + open fun isClosedForReceive() = c.isClosedForReceive - // @Operation + @Operation(blocking = true) fun isClosedForSend() = c.isClosedForSend - // TODO: this operation should be (and can be!) linearizable, but is not - // @Operation - fun isEmpty() = c.isEmpty + // @Operation TODO non-linearizable in BufferedChannel + open fun isEmpty() = c.isEmpty + + @StateRepresentation + fun state() = (c as? BufferedChannel<*>)?.toStringDebug() ?: c.toString() - override fun > O.customize(isStressTest: Boolean): O = + @Validate + fun validate() { + (c as? BufferedChannel<*>)?.checkSegmentStructureInvariants() + } + + override fun > O.customize(isStressTest: Boolean) = actorsBefore(0).sequentialSpecification(sequentialSpecification) + + override fun ModelCheckingOptions.customize(isStressTest: Boolean) = + checkObstructionFreedom(obstructionFree) } private class NumberedCancellationException(number: Int) : CancellationException() { @@ -165,6 +218,8 @@ abstract class SequentialIntChannelBase(private val capacity: Int) : VerifierSta receivers.add(cont) } + suspend fun receiveCatching() = receive() + fun tryReceive(): Any? { if (buffer.isNotEmpty()) { val el = buffer.removeAt(0) @@ -198,7 +253,7 @@ abstract class SequentialIntChannelBase(private val capacity: Int) : VerifierSta } fun cancel(token: Int) { - if (!close(token)) return + close(token) for ((s, _) in senders) s.resume(closedMessage!!) senders.clear() buffer.clear() diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt index 6e6609d6f6..02964f9793 100644 --- a/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt +++ b/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt @@ -22,7 +22,8 @@ class MutexLincheckTest : AbstractLincheckTest() { @Operation(promptCancellation = true) suspend fun lock(@Param(name = "owner") owner: Int) = mutex.lock(owner.asOwnerOrNull) - @Operation(promptCancellation = true) + // onLock may suspend in case of clause re-registration. + @Operation(allowExtraSuspension = true, promptCancellation = true) suspend fun onLock(@Param(name = "owner") owner: Int) = select { mutex.onLock(owner.asOwnerOrNull) {} } @Operation(handleExceptionsAsResult = [IllegalStateException::class]) diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/SegmentListRemoveLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/SegmentListRemoveLincheckTest.kt deleted file mode 100644 index 5a8d7b475d..0000000000 --- a/kotlinx-coroutines-core/jvm/test/lincheck/SegmentListRemoveLincheckTest.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -@file:Suppress("unused") - -package kotlinx.coroutines.lincheck - -import kotlinx.coroutines.* -import kotlinx.coroutines.internal.* -import org.jetbrains.kotlinx.lincheck.* -import org.jetbrains.kotlinx.lincheck.annotations.* -import org.jetbrains.kotlinx.lincheck.paramgen.* -import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.* - -class SegmentListRemoveLincheckTest : AbstractLincheckTest() { - private val q = SegmentBasedQueue() - private val segments: Array> - - init { - segments = (0..5).map { q.enqueue(it)!! }.toTypedArray() - q.enqueue(6) - } - - @Operation - fun removeSegment(@Param(gen = IntGen::class, conf = "1:5") index: Int) { - segments[index].removeSegment() - } - - override fun > O.customize(isStressTest: Boolean): O = this - .actorsBefore(0).actorsAfter(0) - - override fun extractState() = segments.map { it.logicallyRemoved } - - @Validate - fun checkAllRemoved() { - q.checkHeadPrevIsCleaned() - q.checkAllSegmentsAreNotLogicallyRemoved() - } - - override fun ModelCheckingOptions.customize(isStressTest: Boolean) = - checkObstructionFreedom() -} \ No newline at end of file diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/SegmentQueueLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/SegmentQueueLincheckTest.kt deleted file mode 100644 index 76a59e39e7..0000000000 --- a/kotlinx-coroutines-core/jvm/test/lincheck/SegmentQueueLincheckTest.kt +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ -@file:Suppress("unused") - -package kotlinx.coroutines.lincheck - -import kotlinx.coroutines.* -import kotlinx.coroutines.internal.SegmentBasedQueue -import org.jetbrains.kotlinx.lincheck.annotations.* -import org.jetbrains.kotlinx.lincheck.annotations.Operation -import org.jetbrains.kotlinx.lincheck.paramgen.* -import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.* - -@Param(name = "value", gen = IntGen::class, conf = "1:5") -class SegmentQueueLincheckTest : AbstractLincheckTest() { - private val q = SegmentBasedQueue() - - @Operation - fun enqueue(@Param(name = "value") x: Int): Boolean { - return q.enqueue(x) !== null - } - - @Operation - fun dequeue(): Int? = q.dequeue() - - @Operation - fun close() { - q.close() - } - - override fun extractState(): Any { - val elements = ArrayList() - while (true) { - val x = q.dequeue() ?: break - elements.add(x) - } - val closed = q.enqueue(0) === null - return elements to closed - } - - override fun ModelCheckingOptions.customize(isStressTest: Boolean) = - checkObstructionFreedom() -} \ No newline at end of file diff --git a/kotlinx-coroutines-core/jvm/test/selects/SelectMemoryLeakStressTest.kt b/kotlinx-coroutines-core/jvm/test/selects/SelectMemoryLeakStressTest.kt index 7f924dba09..2ddf133f00 100644 --- a/kotlinx-coroutines-core/jvm/test/selects/SelectMemoryLeakStressTest.kt +++ b/kotlinx-coroutines-core/jvm/test/selects/SelectMemoryLeakStressTest.kt @@ -40,7 +40,7 @@ class SelectMemoryLeakStressTest : TestBase() { val data = Channel(1) repeat(nRepeat) { value -> val bigValue = bigValue() // new instance - select { + select { leak.onReceive { println("Capture big value into this lambda: $bigValue") expectUnreached() diff --git a/kotlinx-coroutines-core/native/src/internal/Concurrent.kt b/kotlinx-coroutines-core/native/src/internal/Concurrent.kt index 55c3efe360..17975e2e7f 100644 --- a/kotlinx-coroutines-core/native/src/internal/Concurrent.kt +++ b/kotlinx-coroutines-core/native/src/internal/Concurrent.kt @@ -12,8 +12,6 @@ internal actual typealias ReentrantLock = kotlinx.atomicfu.locks.SynchronizedObj internal actual inline fun ReentrantLock.withLock(action: () -> T): T = this.withLock2(action) -internal actual fun subscriberList(): MutableList = CopyOnWriteList() - internal actual fun identitySet(expectedSize: Int): MutableSet = HashSet() diff --git a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt index a26c1928c1..06f702861b 100644 --- a/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt +++ b/kotlinx-coroutines-debug/src/CoroutinesBlockHoundIntegration.kt @@ -106,43 +106,39 @@ public class CoroutinesBlockHoundIntegration : BlockHoundIntegration { } private fun BlockHound.Builder.allowBlockingCallsInChannels() { - allowBlockingCallsInArrayChannel() - allowBlockingCallsInBroadcastChannel() - allowBlockingCallsInConflatedChannel() + allowBlockingCallsInBroadcastChannels() + allowBlockingCallsInConflatedChannels() } /** - * Allows blocking inside [kotlinx.coroutines.channels.ArrayChannel]. + * Allows blocking inside [kotlinx.coroutines.channels.BroadcastChannel]. */ - private fun BlockHound.Builder.allowBlockingCallsInArrayChannel() { - for (method in listOf( - "pollInternal", "isEmpty", "isFull", "isClosedForReceive", "offerInternal", "offerSelectInternal", - "enqueueSend", "pollSelectInternal", "enqueueReceiveInternal", "onCancelIdempotent")) + private fun BlockHound.Builder.allowBlockingCallsInBroadcastChannels() { + for (method in listOf("openSubscription", "removeSubscriber", "send", "trySend", "registerSelectForSend", + "close", "cancelImpl", "isClosedForSend", "value", "valueOrNull")) { - allowBlockingCallsInside("kotlinx.coroutines.channels.ArrayChannel", method) + allowBlockingCallsInside("kotlinx.coroutines.channels.BroadcastChannelImpl", method) } - } - - /** - * Allows blocking inside [kotlinx.coroutines.channels.ArrayBroadcastChannel]. - */ - private fun BlockHound.Builder.allowBlockingCallsInBroadcastChannel() { - for (method in listOf("offerInternal", "offerSelectInternal", "updateHead")) { - allowBlockingCallsInside("kotlinx.coroutines.channels.ArrayBroadcastChannel", method) + for (method in listOf("cancelImpl")) { + allowBlockingCallsInside("kotlinx.coroutines.channels.BroadcastChannelImpl\$SubscriberConflated", method) } - for (method in listOf("checkOffer", "pollInternal", "pollSelectInternal")) { - allowBlockingCallsInside("kotlinx.coroutines.channels.ArrayBroadcastChannel\$Subscriber", method) + for (method in listOf("cancelImpl")) { + allowBlockingCallsInside("kotlinx.coroutines.channels.BroadcastChannelImpl\$SubscriberBuffered", method) } } /** - * Allows blocking inside [kotlinx.coroutines.channels.ConflatedChannel]. + * Allows blocking inside [kotlinx.coroutines.channels.ConflatedBufferedChannel]. */ - private fun BlockHound.Builder.allowBlockingCallsInConflatedChannel() { - for (method in listOf("offerInternal", "offerSelectInternal", "pollInternal", "pollSelectInternal", - "onCancelIdempotent", "isEmpty", "enqueueReceiveInternal")) + private fun BlockHound.Builder.allowBlockingCallsInConflatedChannels() { + for (method in listOf("receive", "receiveCatching", "tryReceive", "registerSelectForReceive", + "send", "trySend", "sendBroadcast", "registerSelectForSend", + "close", "cancelImpl", "isClosedForSend", "isClosedForReceive", "isEmpty")) { - allowBlockingCallsInside("kotlinx.coroutines.channels.ConflatedChannel", method) + allowBlockingCallsInside("kotlinx.coroutines.channels.ConflatedBufferedChannel", method) + } + for (method in listOf("hasNext")) { + allowBlockingCallsInside("kotlinx.coroutines.channels.ConflatedBufferedChannel\$ConflatedChannelIterator", method) } } diff --git a/kotlinx-coroutines-debug/test/BlockHoundTest.kt b/kotlinx-coroutines-debug/test/BlockHoundTest.kt index 3f58878525..5ec767c3d9 100644 --- a/kotlinx-coroutines-debug/test/BlockHoundTest.kt +++ b/kotlinx-coroutines-debug/test/BlockHoundTest.kt @@ -1,4 +1,5 @@ package kotlinx.coroutines.debug + import kotlinx.coroutines.* import kotlinx.coroutines.channels.* import org.junit.* @@ -55,20 +56,22 @@ class BlockHoundTest : TestBase() { } @Test - fun testChannelNotBeingConsideredBlocking() = runTest { + fun testBroadcastChannelNotBeingConsideredBlocking() = runTest { withContext(Dispatchers.Default) { - // Copy of kotlinx.coroutines.channels.ArrayChannelTest.testSimple - val q = Channel(1) - check(q.isEmpty) - check(!q.isClosedForReceive) + // Copy of kotlinx.coroutines.channels.BufferedChannelTest.testSimple + val q = BroadcastChannel(1) + val s = q.openSubscription() check(!q.isClosedForSend) + check(s.isEmpty) + check(!s.isClosedForReceive) val sender = launch { q.send(1) q.send(2) } val receiver = launch { - q.receive() == 1 - q.receive() == 2 + s.receive() == 1 + s.receive() == 2 + s.cancel() } sender.join() receiver.join() @@ -76,7 +79,7 @@ class BlockHoundTest : TestBase() { } @Test - fun testConflatedChannelsNotBeingConsideredBlocking() = runTest { + fun testConflatedChannelNotBeingConsideredBlocking() = runTest { withContext(Dispatchers.Default) { val q = Channel(Channel.CONFLATED) check(q.isEmpty) @@ -110,5 +113,4 @@ class BlockHoundTest : TestBase() { } } } - } diff --git a/reactive/kotlinx-coroutines-reactive/src/Channel.kt b/reactive/kotlinx-coroutines-reactive/src/Channel.kt index a8db21711d..7836ed7d0d 100644 --- a/reactive/kotlinx-coroutines-reactive/src/Channel.kt +++ b/reactive/kotlinx-coroutines-reactive/src/Channel.kt @@ -7,7 +7,6 @@ package kotlinx.coroutines.reactive import kotlinx.atomicfu.* import kotlinx.coroutines.channels.* import kotlinx.coroutines.flow.* -import kotlinx.coroutines.internal.* import org.reactivestreams.* /** @@ -29,7 +28,7 @@ internal fun Publisher.toChannel(request: Int = 1): ReceiveChannel { @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER", "SubscriberImplementation") private class SubscriptionChannel( private val request: Int -) : LinkedListChannel(null), Subscriber { +) : BufferedChannel(capacity = Channel.UNLIMITED), Subscriber { init { require(request >= 0) { "Invalid request size: $request" } } @@ -40,7 +39,7 @@ private class SubscriptionChannel( // can be negative if we have receivers, but no subscription yet private val _requested = atomic(0) - // --------------------- AbstractChannel overrides ------------------------------- + // --------------------- BufferedChannel overrides ------------------------------- @Suppress("CANNOT_OVERRIDE_INVISIBLE_MEMBER") override fun onReceiveEnqueued() { _requested.loop { wasRequested -> @@ -64,7 +63,7 @@ private class SubscriptionChannel( } @Suppress("CANNOT_OVERRIDE_INVISIBLE_MEMBER") - override fun onClosedIdempotent(closed: LockFreeLinkedListNode) { + override fun onClosedIdempotent() { _subscription.getAndSet(null)?.cancel() // cancel exactly once } diff --git a/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt b/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt index 8db22799c0..94c9c2224c 100644 --- a/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt +++ b/reactive/kotlinx-coroutines-rx2/src/RxChannel.kt @@ -8,7 +8,6 @@ import io.reactivex.* import io.reactivex.disposables.* import kotlinx.atomicfu.* import kotlinx.coroutines.channels.* -import kotlinx.coroutines.internal.* import kotlinx.coroutines.flow.* import kotlinx.coroutines.reactive.* @@ -46,12 +45,12 @@ internal fun ObservableSource.toChannel(): ReceiveChannel { @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") private class SubscriptionChannel : - LinkedListChannel(null), Observer, MaybeObserver + BufferedChannel(capacity = Channel.UNLIMITED), Observer, MaybeObserver { private val _subscription = atomic(null) @Suppress("CANNOT_OVERRIDE_INVISIBLE_MEMBER") - override fun onClosedIdempotent(closed: LockFreeLinkedListNode) { + override fun onClosedIdempotent() { _subscription.getAndSet(null)?.dispose() // dispose exactly once } diff --git a/reactive/kotlinx-coroutines-rx3/src/RxChannel.kt b/reactive/kotlinx-coroutines-rx3/src/RxChannel.kt index 5d7624a17a..614494438e 100644 --- a/reactive/kotlinx-coroutines-rx3/src/RxChannel.kt +++ b/reactive/kotlinx-coroutines-rx3/src/RxChannel.kt @@ -8,7 +8,6 @@ import io.reactivex.rxjava3.core.* import io.reactivex.rxjava3.disposables.* import kotlinx.atomicfu.* import kotlinx.coroutines.channels.* -import kotlinx.coroutines.internal.* import kotlinx.coroutines.flow.* /** @@ -58,12 +57,12 @@ public suspend inline fun ObservableSource.collect(action: (T) -> U @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") private class SubscriptionChannel : - LinkedListChannel(null), Observer, MaybeObserver + BufferedChannel(capacity = Channel.UNLIMITED), Observer, MaybeObserver { private val _subscription = atomic(null) @Suppress("CANNOT_OVERRIDE_INVISIBLE_MEMBER") - override fun onClosedIdempotent(closed: LockFreeLinkedListNode) { + override fun onClosedIdempotent() { _subscription.getAndSet(null)?.dispose() // dispose exactly once } diff --git a/ui/coroutines-guide-ui.md b/ui/coroutines-guide-ui.md index 4ee898e2a4..6cd791cf6f 100644 --- a/ui/coroutines-guide-ui.md +++ b/ui/coroutines-guide-ui.md @@ -335,10 +335,12 @@ processed. This is also a desired behaviour for UI applications that have to react to incoming high-frequency event streams by updating their UI based on the most recently received update. A coroutine that is using -`ConflatedChannel` avoids delays that are usually introduced by buffering of events. +a conflated channel (`capacity = Channel.CONFLATED`, or a buffered channel with +`onBufferOverflow = DROP_OLDEST` or `onBufferOverflow = DROP_LATEST`) avoids delays +that are usually introduced by buffering of events. You can experiment with `capacity` parameter in the above line to see how it affects the behaviour of the code. -Setting `capacity = Channel.UNLIMITED` creates a coroutine with `LinkedListChannel` mailbox that buffers all +Setting `capacity = Channel.UNLIMITED` creates a coroutine with an unbounded mailbox that buffers all events. In this case, the animation runs as many times as the circle is clicked. ## Blocking operations From 54c3108768f4c1a11cd4edc183e8994f84cdfd62 Mon Sep 17 00:00:00 2001 From: Mike Cumings Date: Mon, 13 Feb 2023 06:53:05 -0800 Subject: [PATCH 078/106] NonConcurrentlyModifiable concurrency failures should identify both parties (#3396) Fixes #3395 by capturing the stack frames for both the reader+writer and the writer+writer cases --- .../common/src/internal/TestMainDispatcher.kt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt b/kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt index 24e093be21..411699b9d8 100644 --- a/kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt +++ b/kotlinx-coroutines-test/common/src/internal/TestMainDispatcher.kt @@ -61,29 +61,32 @@ internal class TestMainDispatcher(delegate: CoroutineDispatcher): * next modification. */ private class NonConcurrentlyModifiable(initialValue: T, private val name: String) { + private val reader: AtomicRef = atomic(null) // last reader to attempt access private val readers = atomic(0) // number of concurrent readers - private val isWriting = atomic(false) // a modification is happening currently + private val writer: AtomicRef = atomic(null) // writer currently performing value modification private val exceptionWhenReading: AtomicRef = atomic(null) // exception from reading private val _value = atomic(initialValue) // the backing field for the value - private fun concurrentWW() = IllegalStateException("$name is modified concurrently") - private fun concurrentRW() = IllegalStateException("$name is used concurrently with setting it") + private fun concurrentWW(location: Throwable) = IllegalStateException("$name is modified concurrently", location) + private fun concurrentRW(location: Throwable) = IllegalStateException("$name is used concurrently with setting it", location) var value: T get() { + reader.value = Throwable("reader location") readers.incrementAndGet() - if (isWriting.value) exceptionWhenReading.value = concurrentRW() + writer.value?.let { exceptionWhenReading.value = concurrentRW(it) } val result = _value.value readers.decrementAndGet() return result } set(value) { exceptionWhenReading.getAndSet(null)?.let { throw it } - if (readers.value != 0) throw concurrentRW() - if (!isWriting.compareAndSet(expect = false, update = true)) throw concurrentWW() + if (readers.value != 0) reader.value?.let { throw concurrentRW(it) } + val writerLocation = Throwable("other writer location") + writer.getAndSet(writerLocation)?.let { throw concurrentWW(it) } _value.value = value - isWriting.value = false - if (readers.value != 0) throw concurrentRW() + writer.compareAndSet(writerLocation, null) + if (readers.value != 0) reader.value?.let { throw concurrentRW(it) } } } } From f538af68324e445c6e2c2b600187cd6155ed131f Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 14 Feb 2023 12:03:24 +0300 Subject: [PATCH 079/106] Workaround for debugger+compiler crash (#3625) * 'inline' modifier is removed from 'findSegmentInternal'. It previously lead to KT-55983: incorrect SMAP was produced that crashed the debugger consistently * Non-inline functions are non-capturing, thus no additional performance overhead * Method-reference is extracted to a variable in few places as it were crashing JVM compiler with AIOOB consistently --- .../common/src/channels/BufferedChannel.kt | 10 +++++++--- .../common/src/internal/ConcurrentLinkedList.kt | 5 +++-- kotlinx-coroutines-core/common/src/sync/Semaphore.kt | 6 ++++-- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt b/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt index 01b5a16b9c..6be3da8714 100644 --- a/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt @@ -2431,7 +2431,7 @@ internal open class BufferedChannel( * segments, updating the counter value in [sendersAndCloseStatus] correspondingly. */ private fun findSegmentSend(id: Long, startFrom: ChannelSegment): ChannelSegment? { - return sendSegment.findSegmentAndMoveForward(id, startFrom, ::createSegment).let { + return sendSegment.findSegmentAndMoveForward(id, startFrom, createSegmentFunction()).let { if (it.isClosed) { // The required segment has not been found and new segments // cannot be added, as the linked listed in already added. @@ -2486,7 +2486,7 @@ internal open class BufferedChannel( * segments, updating the [receivers] counter correspondingly. */ private fun findSegmentReceive(id: Long, startFrom: ChannelSegment): ChannelSegment? = - receiveSegment.findSegmentAndMoveForward(id, startFrom, ::createSegment).let { + receiveSegment.findSegmentAndMoveForward(id, startFrom, createSegmentFunction()).let { if (it.isClosed) { // The required segment has not been found and new segments // cannot be added, as the linked listed in already added. @@ -2535,7 +2535,7 @@ internal open class BufferedChannel( * it always updates the number of completed `expandBuffer()` attempts. */ private fun findSegmentBufferEnd(id: Long, startFrom: ChannelSegment, currentBufferEndCounter: Long): ChannelSegment? = - bufferEndSegment.findSegmentAndMoveForward(id, startFrom, ::createSegment).let { + bufferEndSegment.findSegmentAndMoveForward(id, startFrom, createSegmentFunction()).let { if (it.isClosed) { // The required segment has not been found and new segments // cannot be added, as the linked listed in already added. @@ -2953,6 +2953,10 @@ internal class ChannelSegment(id: Long, prev: ChannelSegment?, channel: Bu onSlotCleaned() } } + +// WA for atomicfu + JVM_IR compiler bug that lead to SMAP-related compiler crashes: KT-55983 +internal fun createSegmentFunction(): KFunction2, ChannelSegment> = ::createSegment + private fun createSegment(id: Long, prev: ChannelSegment) = ChannelSegment( id = id, prev = prev, diff --git a/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt b/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt index 2bcf97b7ad..4b27d8491a 100644 --- a/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt +++ b/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt @@ -12,7 +12,7 @@ import kotlin.jvm.* * Returns the first segment `s` with `s.id >= id` or `CLOSED` * if all the segments in this linked list have lower `id`, and the list is closed for further segment additions. */ -private inline fun > S.findSegmentInternal( +internal fun > S.findSegmentInternal( id: Long, createNewSegment: (id: Long, prev: S) -> S ): SegmentOrClosed { @@ -62,10 +62,11 @@ internal inline fun > AtomicRef.moveForward(to: S): Boolean = * Returns the segment `s` with `s.id >= id` or `CLOSED` if all the segments in this linked list have lower `id`, * and the list is closed. */ +@Suppress("NOTHING_TO_INLINE") internal inline fun > AtomicRef.findSegmentAndMoveForward( id: Long, startFrom: S, - createNewSegment: (id: Long, prev: S) -> S + noinline createNewSegment: (id: Long, prev: S) -> S ): SegmentOrClosed { while (true) { val s = startFrom.findSegmentInternal(id, createNewSegment) diff --git a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt index 4db8ae3ca6..82c1ed63f6 100644 --- a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt +++ b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt @@ -284,8 +284,9 @@ internal open class SemaphoreImpl(private val permits: Int, acquiredPermits: Int private fun addAcquireToQueue(waiter: Any): Boolean { val curTail = this.tail.value val enqIdx = enqIdx.getAndIncrement() + val createNewSegment = ::createSegment val segment = this.tail.findSegmentAndMoveForward(id = enqIdx / SEGMENT_SIZE, startFrom = curTail, - createNewSegment = ::createSegment).segment // cannot be closed + createNewSegment = createNewSegment).segment // cannot be closed val i = (enqIdx % SEGMENT_SIZE).toInt() // the regular (fast) path -- if the cell is empty, try to install continuation if (segment.cas(i, null, waiter)) { // installed continuation successfully @@ -325,8 +326,9 @@ internal open class SemaphoreImpl(private val permits: Int, acquiredPermits: Int val curHead = this.head.value val deqIdx = deqIdx.getAndIncrement() val id = deqIdx / SEGMENT_SIZE + val createNewSegment = ::createSegment val segment = this.head.findSegmentAndMoveForward(id, startFrom = curHead, - createNewSegment = ::createSegment).segment // cannot be closed + createNewSegment = createNewSegment).segment // cannot be closed segment.cleanPrev() if (segment.id > id) return false val i = (deqIdx % SEGMENT_SIZE).toInt() From b6e18395b0cf6ec83efd9102660c992c0159a868 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Tue, 14 Feb 2023 14:07:39 +0300 Subject: [PATCH 080/106] Explain the test framework behavior in the withTimeout message (#3623) Fixes #3588 --- kotlinx-coroutines-core/common/src/Delay.kt | 13 +++++++++++++ kotlinx-coroutines-core/common/src/Timeout.kt | 17 +++++++++++------ .../common/test/flow/operators/TimeoutTest.kt | 3 ++- .../api/kotlinx-coroutines-test.api | 3 ++- .../common/src/TestDispatcher.kt | 11 ++++++++++- .../common/test/TestScopeTest.kt | 14 ++++++++++++++ 6 files changed, 52 insertions(+), 9 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/Delay.kt b/kotlinx-coroutines-core/common/src/Delay.kt index 1742c9625c..ba06d9778d 100644 --- a/kotlinx-coroutines-core/common/src/Delay.kt +++ b/kotlinx-coroutines-core/common/src/Delay.kt @@ -56,6 +56,19 @@ public interface Delay { DefaultDelay.invokeOnTimeout(timeMillis, block, context) } +/** + * Enhanced [Delay] interface that provides additional diagnostics for [withTimeout]. + * Is going to be removed once there is proper JVM-default support. + * Then we'll be able put this function into [Delay] without breaking binary compatibility. + */ +@InternalCoroutinesApi +internal interface DelayWithTimeoutDiagnostics : Delay { + /** + * Returns a string that explains that the timeout has occurred, and explains what can be done about it. + */ + fun timeoutMessage(timeout: Duration): String +} + /** * Suspends until cancellation, in which case it will throw a [CancellationException]. * diff --git a/kotlinx-coroutines-core/common/src/Timeout.kt b/kotlinx-coroutines-core/common/src/Timeout.kt index aea57546a1..3ce74c00d0 100644 --- a/kotlinx-coroutines-core/common/src/Timeout.kt +++ b/kotlinx-coroutines-core/common/src/Timeout.kt @@ -13,6 +13,7 @@ import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* import kotlin.jvm.* import kotlin.time.* +import kotlin.time.Duration.Companion.milliseconds /** * Runs a given suspending [block] of code inside a coroutine with a specified [timeout][timeMillis] and throws @@ -135,9 +136,9 @@ public suspend fun withTimeoutOrNull(timeMillis: Long, block: suspend Corout * > Implementation note: how the time is tracked exactly is an implementation detail of the context's [CoroutineDispatcher]. */ public suspend fun withTimeoutOrNull(timeout: Duration, block: suspend CoroutineScope.() -> T): T? = - withTimeoutOrNull(timeout.toDelayMillis(), block) + withTimeoutOrNull(timeout.toDelayMillis(), block) -private fun setupTimeout( +private fun setupTimeout( coroutine: TimeoutCoroutine, block: suspend CoroutineScope.() -> T ): Any? { @@ -150,12 +151,12 @@ private fun setupTimeout( return coroutine.startUndispatchedOrReturnIgnoreTimeout(coroutine, block) } -private class TimeoutCoroutine( +private class TimeoutCoroutine( @JvmField val time: Long, uCont: Continuation // unintercepted continuation ) : ScopeCoroutine(uCont.context, uCont), Runnable { override fun run() { - cancelCoroutine(TimeoutCancellationException(time, this)) + cancelCoroutine(TimeoutCancellationException(time, context.delay, this)) } override fun nameString(): String = @@ -173,7 +174,6 @@ public class TimeoutCancellationException internal constructor( * Creates a timeout exception with the given message. * This constructor is needed for exception stack-traces recovery. */ - @Suppress("UNUSED") internal constructor(message: String) : this(message, null) // message is never null in fact @@ -183,5 +183,10 @@ public class TimeoutCancellationException internal constructor( internal fun TimeoutCancellationException( time: Long, + delay: Delay, coroutine: Job -) : TimeoutCancellationException = TimeoutCancellationException("Timed out waiting for $time ms", coroutine) +) : TimeoutCancellationException { + val message = (delay as? DelayWithTimeoutDiagnostics)?.timeoutMessage(time.milliseconds) + ?: "Timed out waiting for $time ms" + return TimeoutCancellationException(message, coroutine) +} diff --git a/kotlinx-coroutines-core/common/test/flow/operators/TimeoutTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/TimeoutTest.kt index 854e331f1a..c09882f2b5 100644 --- a/kotlinx-coroutines-core/common/test/flow/operators/TimeoutTest.kt +++ b/kotlinx-coroutines-core/common/test/flow/operators/TimeoutTest.kt @@ -97,7 +97,8 @@ class TimeoutTest : TestBase() { fun testUpstreamError() = testUpstreamError(TestException()) @Test - fun testUpstreamErrorTimeoutException() = testUpstreamError(TimeoutCancellationException(0, Job())) + fun testUpstreamErrorTimeoutException() = + testUpstreamError(TimeoutCancellationException("Timed out waiting for ${0} ms", Job())) @Test fun testUpstreamErrorCancellationException() = testUpstreamError(CancellationException("")) diff --git a/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api b/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api index 53aa355c5b..3f63364489 100644 --- a/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api +++ b/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api @@ -95,11 +95,12 @@ public final class kotlinx/coroutines/test/TestCoroutineScopeKt { public static final fun runCurrent (Lkotlinx/coroutines/test/TestCoroutineScope;)V } -public abstract class kotlinx/coroutines/test/TestDispatcher : kotlinx/coroutines/CoroutineDispatcher, kotlinx/coroutines/Delay { +public abstract class kotlinx/coroutines/test/TestDispatcher : kotlinx/coroutines/CoroutineDispatcher, kotlinx/coroutines/Delay, kotlinx/coroutines/DelayWithTimeoutDiagnostics { public fun delay (JLkotlin/coroutines/Continuation;)Ljava/lang/Object; public abstract fun getScheduler ()Lkotlinx/coroutines/test/TestCoroutineScheduler; public fun invokeOnTimeout (JLjava/lang/Runnable;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/DisposableHandle; public fun scheduleResumeAfterDelay (JLkotlinx/coroutines/CancellableContinuation;)V + public synthetic fun timeoutMessage-LRDsOJo (J)Ljava/lang/String; } public final class kotlinx/coroutines/test/TestDispatchers { diff --git a/kotlinx-coroutines-test/common/src/TestDispatcher.kt b/kotlinx-coroutines-test/common/src/TestDispatcher.kt index 8ed8192b9e..9f8d6477f9 100644 --- a/kotlinx-coroutines-test/common/src/TestDispatcher.kt +++ b/kotlinx-coroutines-test/common/src/TestDispatcher.kt @@ -7,6 +7,7 @@ package kotlinx.coroutines.test import kotlinx.coroutines.* import kotlin.coroutines.* import kotlin.jvm.* +import kotlin.time.* /** * A test dispatcher that can interface with a [TestCoroutineScheduler]. @@ -17,7 +18,8 @@ import kotlin.jvm.* * the virtual time. */ @ExperimentalCoroutinesApi -public abstract class TestDispatcher internal constructor() : CoroutineDispatcher(), Delay { +@Suppress("INVISIBLE_REFERENCE") +public abstract class TestDispatcher internal constructor() : CoroutineDispatcher(), Delay, DelayWithTimeoutDiagnostics { /** The scheduler that this dispatcher is linked to. */ @ExperimentalCoroutinesApi public abstract val scheduler: TestCoroutineScheduler @@ -44,6 +46,13 @@ public abstract class TestDispatcher internal constructor() : CoroutineDispatche /** @suppress */ override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle = scheduler.registerEvent(this, timeMillis, block, context) { false } + + /** @suppress */ + @Suppress("CANNOT_OVERRIDE_INVISIBLE_MEMBER") + @Deprecated("Is only needed internally", level = DeprecationLevel.HIDDEN) + public override fun timeoutMessage(timeout: Duration): String = + "Timed out after $timeout of _virtual_ (kotlinx.coroutines.test) time. " + + "To use the real time, wrap 'withTimeout' in 'withContext(Dispatchers.Default.limitedParallelism(1))'" } /** diff --git a/kotlinx-coroutines-test/common/test/TestScopeTest.kt b/kotlinx-coroutines-test/common/test/TestScopeTest.kt index 45f7f3ef80..d46e5a24bc 100644 --- a/kotlinx-coroutines-test/common/test/TestScopeTest.kt +++ b/kotlinx-coroutines-test/common/test/TestScopeTest.kt @@ -476,6 +476,20 @@ class TestScopeTest { } } + /** + * Tests that [TestScope.withTimeout] notifies the programmer about using the virtual time. + */ + @Test + fun testTimingOutWithVirtualTimeMessage() = runTest { + try { + withTimeout(1_000_000) { + Channel().receive() + } + } catch (e: TimeoutCancellationException) { + assertContains(e.message!!, "virtual") + } + } + companion object { internal val invalidContexts = listOf( Dispatchers.Default, // not a [TestDispatcher] From 8c4ff51f8f8a62a7587a227017854ad315d539e5 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 14 Feb 2023 14:30:42 +0300 Subject: [PATCH 081/106] Preserve mutex invariant: throw ISE when incorrect access with owner is detected (#3620) * Behaviour compatible with the previous implementation is restored * For everything else, there is #3626 Signed-off-by: Nikita Koval Co-authored-by: Nikita Koval --- .../common/src/sync/Mutex.kt | 50 ++++++++++++++++--- .../common/test/sync/MutexTest.kt | 12 ++++- .../jvm/test/lincheck/MutexLincheckTest.kt | 8 +-- 3 files changed, 58 insertions(+), 12 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/sync/Mutex.kt b/kotlinx-coroutines-core/common/src/sync/Mutex.kt index fbd1fe55f4..b32c67c0ec 100644 --- a/kotlinx-coroutines-core/common/src/sync/Mutex.kt +++ b/kotlinx-coroutines-core/common/src/sync/Mutex.kt @@ -170,14 +170,36 @@ internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1 acquire(contWithOwner) } - override fun tryLock(owner: Any?): Boolean = - if (tryAcquire()) { - assert { this.owner.value === NO_OWNER } - this.owner.value = owner - true - } else { - false + override fun tryLock(owner: Any?): Boolean = when (tryLockImpl(owner)) { + TRY_LOCK_SUCCESS -> true + TRY_LOCK_FAILED -> false + TRY_LOCK_ALREADY_LOCKED_BY_OWNER -> error("This mutex is already locked by the specified owner: $owner") + else -> error("unexpected") + } + + private fun tryLockImpl(owner: Any?): Int { + while (true) { + if (tryAcquire()) { + assert { this.owner.value === NO_OWNER } + this.owner.value = owner + return TRY_LOCK_SUCCESS + } else { + // The semaphore permit acquisition has failed. + // However, we need to check that this mutex is not + // locked by our owner. + if (owner != null) { + // Is this mutex locked by our owner? + if (holdsLock(owner)) return TRY_LOCK_ALREADY_LOCKED_BY_OWNER + // This mutex is either locked by another owner or unlocked. + // In the latter case, it is possible that it WAS locked by + // our owner when the semaphore permit acquisition has failed. + // To preserve linearizability, the operation restarts in this case. + if (!isLocked) continue + } + return TRY_LOCK_FAILED + } } + } override fun unlock(owner: Any?) { while (true) { @@ -205,10 +227,17 @@ internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1 ) protected open fun onLockRegFunction(select: SelectInstance<*>, owner: Any?) { - onAcquireRegFunction(SelectInstanceWithOwner(select, owner), owner) + if (owner != null && holdsLock(owner)) { + select.selectInRegistrationPhase(ON_LOCK_ALREADY_LOCKED_BY_OWNER) + } else { + onAcquireRegFunction(SelectInstanceWithOwner(select, owner), owner) + } } protected open fun onLockProcessResult(owner: Any?, result: Any?): Any? { + if (result == ON_LOCK_ALREADY_LOCKED_BY_OWNER) { + error("This mutex is already locked by the specified owner: $owner") + } return this } @@ -263,3 +292,8 @@ internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1 } private val NO_OWNER = Symbol("NO_OWNER") +private val ON_LOCK_ALREADY_LOCKED_BY_OWNER = Symbol("ALREADY_LOCKED_BY_OWNER") + +private const val TRY_LOCK_SUCCESS = 0 +private const val TRY_LOCK_FAILED = 1 +private const val TRY_LOCK_ALREADY_LOCKED_BY_OWNER = 2 diff --git a/kotlinx-coroutines-core/common/test/sync/MutexTest.kt b/kotlinx-coroutines-core/common/test/sync/MutexTest.kt index 6a60387672..b4acd94e9c 100644 --- a/kotlinx-coroutines-core/common/test/sync/MutexTest.kt +++ b/kotlinx-coroutines-core/common/test/sync/MutexTest.kt @@ -4,8 +4,8 @@ package kotlinx.coroutines.sync -import kotlinx.atomicfu.* import kotlinx.coroutines.* +import kotlinx.coroutines.selects.* import kotlin.test.* class MutexTest : TestBase() { @@ -138,4 +138,14 @@ class MutexTest : TestBase() { assertTrue(mutex.holdsLock(owner2)) finish(4) } + + @Test + fun testIllegalStateInvariant() = runTest { + val mutex = Mutex() + val owner = Any() + assertTrue(mutex.tryLock(owner)) + assertFailsWith { mutex.tryLock(owner) } + assertFailsWith { mutex.lock(owner) } + assertFailsWith { select { mutex.onLock(owner) {} } } + } } diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt index 02964f9793..6fd28e424e 100644 --- a/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt +++ b/kotlinx-coroutines-core/jvm/test/lincheck/MutexLincheckTest.kt @@ -16,15 +16,17 @@ import org.jetbrains.kotlinx.lincheck.paramgen.* class MutexLincheckTest : AbstractLincheckTest() { private val mutex = Mutex() - @Operation + @Operation(handleExceptionsAsResult = [IllegalStateException::class]) fun tryLock(@Param(name = "owner") owner: Int) = mutex.tryLock(owner.asOwnerOrNull) + // TODO: `lock()` with non-null owner is non-linearizable @Operation(promptCancellation = true) - suspend fun lock(@Param(name = "owner") owner: Int) = mutex.lock(owner.asOwnerOrNull) + suspend fun lock() = mutex.lock(null) + // TODO: `onLock` with non-null owner is non-linearizable // onLock may suspend in case of clause re-registration. @Operation(allowExtraSuspension = true, promptCancellation = true) - suspend fun onLock(@Param(name = "owner") owner: Int) = select { mutex.onLock(owner.asOwnerOrNull) {} } + suspend fun onLock() = select { mutex.onLock(null) {} } @Operation(handleExceptionsAsResult = [IllegalStateException::class]) fun unlock(@Param(name = "owner") owner: Int) = mutex.unlock(owner.asOwnerOrNull) From fc6c811f4e6554f3771be48f4c9dd6622e5b8532 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 14 Feb 2023 16:29:26 +0300 Subject: [PATCH 082/106] =?UTF-8?q?Remove=20dead-code=20in=20the=20LockFre?= =?UTF-8?q?eLinkedList=20implementation=20after=20new=20c=E2=80=A6=20(#362?= =?UTF-8?q?4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove dead-code in the LockFreeLinkedList implementation after new channels implementation * Slightly speedup the build without unnecessary stress-tests * Get rid of dead-code that was used only by channels, so it's easier to reason about JobSupport implementation * Slightly cut dex size in the face of new channels regression as LFLL is no longer shared between JobSupport and Channel --- .../common/src/internal/Atomic.kt | 42 +-- .../src/internal/LockFreeLinkedList.common.kt | 61 ---- .../ChannelUndeliveredElementFailureTest.kt | 1 - .../src/internal/LockFreeLinkedList.kt | 301 ------------------ .../test/internal/LockFreeLinkedListTest.kt | 85 ----- .../js/src/internal/LinkedList.kt | 69 ---- .../LockFreeLinkedListAddRemoveStressTest.kt | 56 ---- .../LockFreeLinkedListShortStressTest.kt | 89 ------ .../test/lincheck/LockFreeListLincheckTest.kt | 53 --- .../native/test/internal/LinkedListTest.kt | 47 --- 10 files changed, 1 insertion(+), 803 deletions(-) delete mode 100644 kotlinx-coroutines-core/concurrent/test/internal/LockFreeLinkedListTest.kt delete mode 100644 kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListAddRemoveStressTest.kt delete mode 100644 kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListShortStressTest.kt delete mode 100644 kotlinx-coroutines-core/jvm/test/lincheck/LockFreeListLincheckTest.kt delete mode 100644 kotlinx-coroutines-core/native/test/internal/LinkedListTest.kt diff --git a/kotlinx-coroutines-core/common/src/internal/Atomic.kt b/kotlinx-coroutines-core/common/src/internal/Atomic.kt index 127b70f7e8..ff4320e0b3 100644 --- a/kotlinx-coroutines-core/common/src/internal/Atomic.kt +++ b/kotlinx-coroutines-core/common/src/internal/Atomic.kt @@ -29,12 +29,6 @@ public abstract class OpDescriptor { abstract val atomicOp: AtomicOp<*>? override fun toString(): String = "$classSimpleName@$hexAddress" // debug - - fun isEarlierThan(that: OpDescriptor): Boolean { - val thisOp = atomicOp ?: return false - val thatOp = that.atomicOp ?: return false - return thisOp.opSequence < thatOp.opSequence - } } @JvmField @@ -55,25 +49,9 @@ internal val NO_DECISION: Any = Symbol("NO_DECISION") public abstract class AtomicOp : OpDescriptor() { private val _consensus = atomic(NO_DECISION) - // Returns NO_DECISION when there is not decision yet - val consensus: Any? get() = _consensus.value - - val isDecided: Boolean get() = _consensus.value !== NO_DECISION - - /** - * Sequence number of this multi-word operation for deadlock resolution. - * An operation with lower number aborts itself with (using [RETRY_ATOMIC] error symbol) if it encounters - * the need to help the operation with higher sequence number and then restarts - * (using higher `opSequence` to ensure progress). - * Simple operations that cannot get into the deadlock always return zero here. - * - * See https://github.com/Kotlin/kotlinx.coroutines/issues/504 - */ - open val opSequence: Long get() = 0L - override val atomicOp: AtomicOp<*> get() = this - fun decide(decision: Any?): Any? { + private fun decide(decision: Any?): Any? { assert { decision !== NO_DECISION } val current = _consensus.value if (current !== NO_DECISION) return current @@ -98,21 +76,3 @@ public abstract class AtomicOp : OpDescriptor() { return decision } } - -/** - * A part of multi-step atomic operation [AtomicOp]. - * - * @suppress **This is unstable API and it is subject to change.** - */ -public abstract class AtomicDesc { - lateinit var atomicOp: AtomicOp<*> // the reference to parent atomicOp, init when AtomicOp is created - abstract fun prepare(op: AtomicOp<*>): Any? // returns `null` if prepared successfully - abstract fun complete(op: AtomicOp<*>, failure: Any?) // decision == null if success -} - -/** - * It is returned as an error by [AtomicOp] implementations when they detect potential deadlock - * using [AtomicOp.opSequence] numbers. - */ -@JvmField -internal val RETRY_ATOMIC: Any = Symbol("RETRY_ATOMIC") diff --git a/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt b/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt index 26fef8c012..121cdedc9c 100644 --- a/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt +++ b/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt @@ -16,27 +16,8 @@ public expect open class LockFreeLinkedListNode() { public fun addLast(node: LockFreeLinkedListNode) public fun addOneIfEmpty(node: LockFreeLinkedListNode): Boolean public inline fun addLastIf(node: LockFreeLinkedListNode, crossinline condition: () -> Boolean): Boolean - public inline fun addLastIfPrev( - node: LockFreeLinkedListNode, - predicate: (LockFreeLinkedListNode) -> Boolean - ): Boolean - - public inline fun addLastIfPrevAndIf( - node: LockFreeLinkedListNode, - predicate: (LockFreeLinkedListNode) -> Boolean, // prev node predicate - crossinline condition: () -> Boolean // atomically checked condition - ): Boolean - public open fun remove(): Boolean - /** - * Helps fully finish [remove] operation, must be invoked after [remove] if needed. - * Ensures that traversing the list via prev pointers sees this node as removed. - * No-op on JS - */ - public fun helpRemove() - public fun removeFirstOrNull(): LockFreeLinkedListNode? - public inline fun removeFirstIfIsInstanceOfOrPeekIf(predicate: (T) -> Boolean): T? } /** @suppress **This is unstable API and it is subject to change.** */ @@ -45,45 +26,3 @@ public expect open class LockFreeLinkedListHead() : LockFreeLinkedListNode { public inline fun forEach(block: (T) -> Unit) public final override fun remove(): Nothing } - -/** @suppress **This is unstable API and it is subject to change.** */ -public expect open class AddLastDesc( - queue: LockFreeLinkedListNode, - node: T -) : AbstractAtomicDesc { - val queue: LockFreeLinkedListNode - val node: T - override fun finishPrepare(prepareOp: PrepareOp) - override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) -} - -/** @suppress **This is unstable API and it is subject to change.** */ -public expect open class RemoveFirstDesc(queue: LockFreeLinkedListNode): AbstractAtomicDesc { - val queue: LockFreeLinkedListNode - public val result: T - override fun finishPrepare(prepareOp: PrepareOp) - final override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) -} - -/** @suppress **This is unstable API and it is subject to change.** */ -public expect abstract class AbstractAtomicDesc : AtomicDesc { - final override fun prepare(op: AtomicOp<*>): Any? - final override fun complete(op: AtomicOp<*>, failure: Any?) - protected open fun failure(affected: LockFreeLinkedListNode): Any? - protected open fun retry(affected: LockFreeLinkedListNode, next: Any): Boolean - public abstract fun finishPrepare(prepareOp: PrepareOp) // non-null on failure - public open fun onPrepare(prepareOp: PrepareOp): Any? // non-null on failure - public open fun onRemoved(affected: LockFreeLinkedListNode) // non-null on failure - protected abstract fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) -} - -/** @suppress **This is unstable API and it is subject to change.** */ -public expect class PrepareOp: OpDescriptor { - val affected: LockFreeLinkedListNode - override val atomicOp: AtomicOp<*> - val desc: AbstractAtomicDesc - fun finishPrepare() -} - -@JvmField -internal val REMOVE_PREPARED: Any = Symbol("REMOVE_PREPARED") diff --git a/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementFailureTest.kt b/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementFailureTest.kt index f02bd09a66..0faa79b3ae 100644 --- a/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementFailureTest.kt +++ b/kotlinx-coroutines-core/common/test/channels/ChannelUndeliveredElementFailureTest.kt @@ -166,7 +166,6 @@ class ChannelUndeliveredElementFailureTest : TestBase() { fun testSendDropOldestInvokeHandlerConflated() = runTest(expected = { it is UndeliveredElementException }) { val channel = Channel(Channel.CONFLATED, onUndeliveredElement = { finish(2) - println(TestException().stackTraceToString()) throw TestException() }) channel.send(42) diff --git a/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt b/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt index c9a1b61261..00888499c6 100644 --- a/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt +++ b/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt @@ -23,21 +23,6 @@ internal const val FAILURE: Int = 2 @PublishedApi internal val CONDITION_FALSE: Any = Symbol("CONDITION_FALSE") -@PublishedApi -internal val LIST_EMPTY: Any = Symbol("LIST_EMPTY") - -/** @suppress **This is unstable API and it is subject to change.** */ -public actual typealias RemoveFirstDesc = LockFreeLinkedListNode.RemoveFirstDesc - -/** @suppress **This is unstable API and it is subject to change.** */ -public actual typealias AddLastDesc = LockFreeLinkedListNode.AddLastDesc - -/** @suppress **This is unstable API and it is subject to change.** */ -public actual typealias AbstractAtomicDesc = LockFreeLinkedListNode.AbstractAtomicDesc - -/** @suppress **This is unstable API and it is subject to change.** */ -public actual typealias PrepareOp = LockFreeLinkedListNode.PrepareOp - /** * Doubly-linked concurrent list node with remove support. * Based on paper @@ -142,8 +127,6 @@ public actual open class LockFreeLinkedListNode { } } - public fun describeAddLast(node: T): AddLastDesc = AddLastDesc(this, node) - /** * Adds last item to this list atomically if the [condition] is true. */ @@ -158,30 +141,6 @@ public actual open class LockFreeLinkedListNode { } } - public actual inline fun addLastIfPrev(node: Node, predicate: (Node) -> Boolean): Boolean { - while (true) { // lock-free loop on prev.next - val prev = prevNode // sentinel node is never removed, so prev is always defined - if (!predicate(prev)) return false - if (prev.addNext(node, this)) return true - } - } - - public actual inline fun addLastIfPrevAndIf( - node: Node, - predicate: (Node) -> Boolean, // prev node predicate - crossinline condition: () -> Boolean // atomically checked condition - ): Boolean { - val condAdd = makeCondAddOp(node, condition) - while (true) { // lock-free loop on prev.next - val prev = prevNode // sentinel node is never removed, so prev is always defined - if (!predicate(prev)) return false - when (prev.tryCondAddNext(node, this, condAdd)) { - SUCCESS -> return true - FAILURE -> return false - } - } - } - // ------ addXXX util ------ /** @@ -236,7 +195,6 @@ public actual open class LockFreeLinkedListNode { * * **Note**: Invocation of this operation does not guarantee that remove was actually complete if result was `false`. * In particular, invoking [nextNode].[prevNode] might still return this node even though it is "already removed". - * Invoke [helpRemove] to make sure that remove was completed. */ public actual open fun remove(): Boolean = removeOrNext() == null @@ -257,263 +215,9 @@ public actual open class LockFreeLinkedListNode { } } - // Helps with removal of this node - public actual fun helpRemove() { - // Note: this node must be already removed - (next as Removed).ref.helpRemovePrev() - } - - // Helps with removal of nodes that are previous to this - @PublishedApi - internal fun helpRemovePrev() { - // We need to call correctPrev on a non-removed node to ensure progress, since correctPrev bails out when - // called on a removed node. There's always at least one non-removed node (list head). - var node = this - while (true) { - val next = node.next - if (next !is Removed) break - node = next.ref - } - // Found a non-removed node - node.correctPrev(null) - } - - public actual fun removeFirstOrNull(): Node? { - while (true) { // try to linearize - val first = next as Node - if (first === this) return null - if (first.remove()) return first - first.helpRemove() // must help remove to ensure lock-freedom - } - } - - public fun describeRemoveFirst(): RemoveFirstDesc = RemoveFirstDesc(this) - - // just peek at item when predicate is true - public actual inline fun removeFirstIfIsInstanceOfOrPeekIf(predicate: (T) -> Boolean): T? { - while (true) { - val first = this.next as Node - if (first === this) return null // got list head -- nothing to remove - if (first !is T) return null - if (predicate(first)) { - // check for removal of the current node to make sure "peek" operation is linearizable - if (!first.isRemoved) return first - } - val next = first.removeOrNext() - if (next === null) return first // removed successfully -- return it - // help and start from the beginning - next.helpRemovePrev() - } - } - - // ------ multi-word atomic operations helpers ------ - - public open class AddLastDesc constructor( - @JvmField val queue: Node, - @JvmField val node: T - ) : AbstractAtomicDesc() { - init { - // require freshly allocated node here - assert { node._next.value === node && node._prev.value === node } - } - - // Returns null when atomic op got into deadlock trying to help operation that started later (RETRY_ATOMIC) - final override fun takeAffectedNode(op: OpDescriptor): Node? = - queue.correctPrev(op) // queue head is never removed, so null result can only mean RETRY_ATOMIC - - private val _affectedNode = atomic(null) - final override val affectedNode: Node? get() = _affectedNode.value - final override val originalNext: Node get() = queue - - override fun retry(affected: Node, next: Any): Boolean = next !== queue - - override fun finishPrepare(prepareOp: PrepareOp) { - // Note: onPrepare must use CAS to make sure the stale invocation is not - // going to overwrite the previous decision on successful preparation. - // Result of CAS is irrelevant, but we must ensure that it is set when invoker completes - _affectedNode.compareAndSet(null, prepareOp.affected) - } - - override fun updatedNext(affected: Node, next: Node): Any { - // it is invoked only on successfully completion of operation, but this invocation can be stale, - // so we must use CAS to set both prev & next pointers - node._prev.compareAndSet(node, affected) - node._next.compareAndSet(node, queue) - return node - } - - override fun finishOnSuccess(affected: Node, next: Node) { - node.finishAdd(queue) - } - } - - public open class RemoveFirstDesc( - @JvmField val queue: Node - ) : AbstractAtomicDesc() { - private val _affectedNode = atomic(null) - private val _originalNext = atomic(null) - - @Suppress("UNCHECKED_CAST") - public val result: T get() = affectedNode!! as T - - final override fun takeAffectedNode(op: OpDescriptor): Node? { - queue._next.loop { next -> - if (next is OpDescriptor) { - if (op.isEarlierThan(next)) - return null // RETRY_ATOMIC - next.perform(queue) - } else { - return next as Node - } - } - } - - final override val affectedNode: Node? get() = _affectedNode.value - final override val originalNext: Node? get() = _originalNext.value - - // check node predicates here, must signal failure if affect is not of type T - protected override fun failure(affected: Node): Any? = - if (affected === queue) LIST_EMPTY else null - - final override fun retry(affected: Node, next: Any): Boolean { - if (next !is Removed) return false - next.ref.helpRemovePrev() // must help delete to ensure lock-freedom - return true - } - - override fun finishPrepare(prepareOp: PrepareOp) { - // Note: finishPrepare must use CAS to make sure the stale invocation is not - // going to overwrite the previous decision on successful preparation. - // Result of CAS is irrelevant, but we must ensure that it is set when invoker completes - _affectedNode.compareAndSet(null, prepareOp.affected) - _originalNext.compareAndSet(null, prepareOp.next) - } - - final override fun updatedNext(affected: Node, next: Node): Any = next.removed() - - final override fun finishOnSuccess(affected: Node, next: Node) { - // Complete removal operation here. It bails out if next node is also removed. It becomes - // responsibility of the next's removes to call correctPrev which would help fix all the links. - next.correctPrev(null) - } - } - // This is Harris's RDCSS (Restricted Double-Compare Single Swap) operation // It inserts "op" descriptor of when "op" status is still undecided (rolls back otherwise) - public class PrepareOp( - @JvmField val affected: Node, - @JvmField val next: Node, - @JvmField val desc: AbstractAtomicDesc - ) : OpDescriptor() { - override val atomicOp: AtomicOp<*> get() = desc.atomicOp - - // Returns REMOVE_PREPARED or null (it makes decision on any failure) - override fun perform(affected: Any?): Any? { - assert { affected === this.affected } - affected as Node // type assertion - val decision = desc.onPrepare(this) - if (decision === REMOVE_PREPARED) { - // remove element on failure -- do not mark as decided, will try another one - val next = this.next - val removed = next.removed() - if (affected._next.compareAndSet(this, removed)) { - // The element was actually removed - desc.onRemoved(affected) - // Complete removal operation here. It bails out if next node is also removed and it becomes - // responsibility of the next's removes to call correctPrev which would help fix all the links. - next.correctPrev(null) - } - return REMOVE_PREPARED - } - // We need to ensure progress even if it operation result consensus was already decided - val consensus = if (decision != null) { - // some other logic failure, including RETRY_ATOMIC -- reach consensus on decision fail reason ASAP - atomicOp.decide(decision) - } else { - atomicOp.consensus // consult with current decision status like in Harris DCSS - } - val update: Any = when { - consensus === NO_DECISION -> atomicOp // desc.onPrepare returned null -> start doing atomic op - consensus == null -> desc.updatedNext(affected, next) // move forward if consensus on success - else -> next // roll back if consensus if failure - } - affected._next.compareAndSet(this, update) - return null - } - - public fun finishPrepare(): Unit = desc.finishPrepare(this) - - override fun toString(): String = "PrepareOp(op=$atomicOp)" - } - - public abstract class AbstractAtomicDesc : AtomicDesc() { - protected abstract val affectedNode: Node? - protected abstract val originalNext: Node? - protected open fun takeAffectedNode(op: OpDescriptor): Node? = affectedNode!! // null for RETRY_ATOMIC - protected open fun failure(affected: Node): Any? = null // next: Node | Removed - protected open fun retry(affected: Node, next: Any): Boolean = false // next: Node | Removed - protected abstract fun finishOnSuccess(affected: Node, next: Node) - - public abstract fun updatedNext(affected: Node, next: Node): Any - - public abstract fun finishPrepare(prepareOp: PrepareOp) - - // non-null on failure - public open fun onPrepare(prepareOp: PrepareOp): Any? { - finishPrepare(prepareOp) - return null - } - public open fun onRemoved(affected: Node) {} // called once when node was prepared & later removed - - @Suppress("UNCHECKED_CAST") - final override fun prepare(op: AtomicOp<*>): Any? { - while (true) { // lock free loop on next - val affected = takeAffectedNode(op) ?: return RETRY_ATOMIC - // read its original next pointer first - val next = affected._next.value - // then see if already reached consensus on overall operation - if (next === op) return null // already in process of operation -- all is good - if (op.isDecided) return null // already decided this operation -- go to next desc - if (next is OpDescriptor) { - // some other operation is in process - // if operation in progress (preparing or prepared) has higher sequence number -- abort our preparations - if (op.isEarlierThan(next)) - return RETRY_ATOMIC - next.perform(affected) - continue // and retry - } - // next: Node | Removed - val failure = failure(affected) - if (failure != null) return failure // signal failure - if (retry(affected, next)) continue // retry operation - val prepareOp = PrepareOp(affected, next as Node, this) - if (affected._next.compareAndSet(next, prepareOp)) { - // prepared -- complete preparations - try { - val prepFail = prepareOp.perform(affected) - if (prepFail === REMOVE_PREPARED) continue // retry - assert { prepFail == null } - return null - } catch (e: Throwable) { - // Crashed during preparation (for example IllegalStateExpception) -- undo & rethrow - affected._next.compareAndSet(prepareOp, next) - throw e - } - } - } - } - - final override fun complete(op: AtomicOp<*>, failure: Any?) { - val success = failure == null - val affectedNode = affectedNode ?: run { assert { !success }; return } - val originalNext = originalNext ?: run { assert { !success }; return } - val update = if (success) updatedNext(affectedNode, originalNext) else originalNext - if (affectedNode._next.compareAndSet(op, update)) { - if (success) finishOnSuccess(affectedNode, originalNext) - } - } - } // ------ other helpers ------ @@ -562,9 +266,6 @@ public actual open class LockFreeLinkedListNode { * * When this node is removed. In this case there is no need to waste time on corrections, because * remover of this node will ultimately call [correctPrev] on the next node and that will fix all * the links from this node, too. - * * When [op] descriptor is not `null` and operation descriptor that is [OpDescriptor.isEarlierThan] - * that current [op] is found while traversing the list. This `null` result will be translated - * by callers to [RETRY_ATOMIC]. */ private tailrec fun correctPrev(op: OpDescriptor?): Node? { val oldPrev = _prev.value @@ -587,8 +288,6 @@ public actual open class LockFreeLinkedListNode { this.isRemoved -> return null // nothing to do, this node was removed, bail out asap to save time prevNext === op -> return prev // part of the same op -- don't recurse, didn't correct prev prevNext is OpDescriptor -> { // help & retry - if (op != null && op.isEarlierThan(prevNext)) - return null // RETRY_ATOMIC prevNext.perform(prev) return correctPrev(op) // retry from scratch } diff --git a/kotlinx-coroutines-core/concurrent/test/internal/LockFreeLinkedListTest.kt b/kotlinx-coroutines-core/concurrent/test/internal/LockFreeLinkedListTest.kt deleted file mode 100644 index 1e08c35a8e..0000000000 --- a/kotlinx-coroutines-core/concurrent/test/internal/LockFreeLinkedListTest.kt +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines.internal - -import kotlin.test.* - -class LockFreeLinkedListTest { - data class IntNode(val i: Int) : LockFreeLinkedListNode() - - @Test - fun testSimpleAddLast() { - val list = LockFreeLinkedListHead() - assertContents(list) - val n1 = IntNode(1).apply { list.addLast(this) } - assertContents(list, 1) - val n2 = IntNode(2).apply { list.addLast(this) } - assertContents(list, 1, 2) - val n3 = IntNode(3).apply { list.addLast(this) } - assertContents(list, 1, 2, 3) - val n4 = IntNode(4).apply { list.addLast(this) } - assertContents(list, 1, 2, 3, 4) - assertTrue(n1.remove()) - assertContents(list, 2, 3, 4) - assertTrue(n3.remove()) - assertContents(list, 2, 4) - assertTrue(n4.remove()) - assertContents(list, 2) - assertTrue(n2.remove()) - assertFalse(n2.remove()) - assertContents(list) - } - - @Test - fun testCondOps() { - val list = LockFreeLinkedListHead() - assertContents(list) - assertTrue(list.addLastIf(IntNode(1)) { true }) - assertContents(list, 1) - assertFalse(list.addLastIf(IntNode(2)) { false }) - assertContents(list, 1) - assertTrue(list.addLastIf(IntNode(3)) { true }) - assertContents(list, 1, 3) - assertFalse(list.addLastIf(IntNode(4)) { false }) - assertContents(list, 1, 3) - } - - @Suppress("UNUSED_VARIABLE") - @Test - fun testAtomicOpsSingle() { - val list = LockFreeLinkedListHead() - assertContents(list) - val n1 = IntNode(1).also { single(list.describeAddLast(it)) } - assertContents(list, 1) - val n2 = IntNode(2).also { single(list.describeAddLast(it)) } - assertContents(list, 1, 2) - val n3 = IntNode(3).also { single(list.describeAddLast(it)) } - assertContents(list, 1, 2, 3) - val n4 = IntNode(4).also { single(list.describeAddLast(it)) } - assertContents(list, 1, 2, 3, 4) - } - - private fun single(part: AtomicDesc) { - val operation = object : AtomicOp() { - init { - part.atomicOp = this - } - override fun prepare(affected: Any?): Any? = part.prepare(this) - override fun complete(affected: Any?, failure: Any?) = part.complete(this, failure) - } - assertTrue(operation.perform(null) == null) - } - - private fun assertContents(list: LockFreeLinkedListHead, vararg expected: Int) { - list.validate() - val n = expected.size - val actual = IntArray(n) - var index = 0 - list.forEach { actual[index++] = it.i } - assertEquals(n, index) - for (i in 0 until n) assertEquals(expected[i], actual[i], "item $i") - assertEquals(expected.isEmpty(), list.isEmpty) - } -} diff --git a/kotlinx-coroutines-core/js/src/internal/LinkedList.kt b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt index 7286496b4b..de5d491121 100644 --- a/kotlinx-coroutines-core/js/src/internal/LinkedList.kt +++ b/kotlinx-coroutines-core/js/src/internal/LinkedList.kt @@ -96,75 +96,6 @@ public open class LinkedListNode : DisposableHandle { check(next.removeImpl()) { "Should remove" } return next } - - public inline fun removeFirstIfIsInstanceOfOrPeekIf(predicate: (T) -> Boolean): T? { - val next = _next - if (next === this) return null - if (next !is T) return null - if (predicate(next)) return next - check(next.removeImpl()) { "Should remove" } - return next - } -} - -/** @suppress **This is unstable API and it is subject to change.** */ -public actual open class AddLastDesc actual constructor( - actual val queue: Node, - actual val node: T -) : AbstractAtomicDesc() { - override val affectedNode: Node get() = queue._prev - actual override fun finishPrepare(prepareOp: PrepareOp) {} - override fun onComplete() = queue.addLast(node) - actual override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) = Unit -} - -/** @suppress **This is unstable API and it is subject to change.** */ -public actual open class RemoveFirstDesc actual constructor( - actual val queue: LockFreeLinkedListNode -) : AbstractAtomicDesc() { - @Suppress("UNCHECKED_CAST") - actual val result: T get() = affectedNode as T - override val affectedNode: Node = queue.nextNode - actual override fun finishPrepare(prepareOp: PrepareOp) {} - override fun onComplete() { queue.removeFirstOrNull() } - actual override fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) = Unit -} - -/** @suppress **This is unstable API and it is subject to change.** */ -public actual abstract class AbstractAtomicDesc : AtomicDesc() { - protected abstract val affectedNode: Node - actual abstract fun finishPrepare(prepareOp: PrepareOp) - protected abstract fun onComplete() - - actual open fun onPrepare(prepareOp: PrepareOp): Any? { - finishPrepare(prepareOp) - return null - } - - actual open fun onRemoved(affected: Node) {} - - actual final override fun prepare(op: AtomicOp<*>): Any? { - val affected = affectedNode - val failure = failure(affected) - if (failure != null) return failure - @Suppress("UNCHECKED_CAST") - return onPrepare(PrepareOp(affected, this, op)) - } - - actual final override fun complete(op: AtomicOp<*>, failure: Any?) = onComplete() - protected actual open fun failure(affected: LockFreeLinkedListNode): Any? = null // Never fails by default - protected actual open fun retry(affected: LockFreeLinkedListNode, next: Any): Boolean = false // Always succeeds - protected actual abstract fun finishOnSuccess(affected: LockFreeLinkedListNode, next: LockFreeLinkedListNode) -} - -/** @suppress **This is unstable API and it is subject to change.** */ -public actual class PrepareOp( - actual val affected: LockFreeLinkedListNode, - actual val desc: AbstractAtomicDesc, - actual override val atomicOp: AtomicOp<*> -): OpDescriptor() { - override fun perform(affected: Any?): Any? = null - actual fun finishPrepare() {} } /** @suppress **This is unstable API and it is subject to change.** */ diff --git a/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListAddRemoveStressTest.kt b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListAddRemoveStressTest.kt deleted file mode 100644 index 3229e664c1..0000000000 --- a/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListAddRemoveStressTest.kt +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines.internal - -import kotlinx.atomicfu.* -import kotlinx.coroutines.* -import java.util.concurrent.* -import kotlin.concurrent.* -import kotlin.test.* - -class LockFreeLinkedListAddRemoveStressTest : TestBase() { - private class Node : LockFreeLinkedListNode() - - private val nRepeat = 100_000 * stressTestMultiplier - private val list = LockFreeLinkedListHead() - private val barrier = CyclicBarrier(3) - private val done = atomic(false) - private val removed = atomic(0) - - @Test - fun testStressAddRemove() { - val threads = ArrayList() - threads += testThread("adder") { - val node = Node() - list.addLast(node) - if (node.remove()) removed.incrementAndGet() - } - threads += testThread("remover") { - val node = list.removeFirstOrNull() - if (node != null) removed.incrementAndGet() - } - try { - for (i in 1..nRepeat) { - barrier.await() - barrier.await() - assertEquals(i, removed.value) - list.validate() - } - } finally { - done.value = true - barrier.await() - threads.forEach { it.join() } - } - } - - private fun testThread(name: String, op: () -> Unit) = thread(name = name) { - while (true) { - barrier.await() - if (done.value) break - op() - barrier.await() - } - } -} \ No newline at end of file diff --git a/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListShortStressTest.kt b/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListShortStressTest.kt deleted file mode 100644 index 2ac51b9b1d..0000000000 --- a/kotlinx-coroutines-core/jvm/test/internal/LockFreeLinkedListShortStressTest.kt +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines.internal - -import kotlinx.coroutines.* -import org.junit.Test -import java.util.* -import java.util.concurrent.atomic.* -import kotlin.concurrent.* -import kotlin.test.* - -/** - * This stress test has 6 threads adding randomly first to the list and them immediately undoing - * this addition by remove, and 4 threads removing first node. The resulting list that is being - * stressed is very short. - */ -class LockFreeLinkedListShortStressTest : TestBase() { - data class IntNode(val i: Int) : LockFreeLinkedListNode() - val list = LockFreeLinkedListHead() - - private val TEST_DURATION = 5000L * stressTestMultiplier - - val threads = mutableListOf() - private val nAdderThreads = 6 - private val nRemoverThreads = 4 - private val completedAdder = AtomicInteger() - private val completedRemover = AtomicInteger() - - private val undone = AtomicInteger() - private val missed = AtomicInteger() - private val removed = AtomicInteger() - - @Test - fun testStress() { - println("--- LockFreeLinkedListShortStressTest") - val deadline = System.currentTimeMillis() + TEST_DURATION - repeat(nAdderThreads) { threadId -> - threads += thread(start = false, name = "adder-$threadId") { - val rnd = Random() - while (System.currentTimeMillis() < deadline) { - var node: IntNode? = IntNode(threadId) - when (rnd.nextInt(3)) { - 0 -> list.addLast(node!!) - 1 -> assertTrue(list.addLastIf(node!!, { true })) // just to test conditional add - 2 -> { // just to test failed conditional add - assertFalse(list.addLastIf(node!!, { false })) - node = null - } - } - if (node != null) { - if (node.remove()) { - undone.incrementAndGet() - } else { - // randomly help other removal's completion - if (rnd.nextBoolean()) node.helpRemove() - missed.incrementAndGet() - } - } - } - completedAdder.incrementAndGet() - } - } - repeat(nRemoverThreads) { threadId -> - threads += thread(start = false, name = "remover-$threadId") { - while (System.currentTimeMillis() < deadline) { - val node = list.removeFirstOrNull() - if (node != null) removed.incrementAndGet() - - } - completedRemover.incrementAndGet() - } - } - threads.forEach { it.start() } - threads.forEach { it.join() } - println("Completed successfully ${completedAdder.get()} adder threads") - println("Completed successfully ${completedRemover.get()} remover threads") - println(" Adders undone ${undone.get()} node additions") - println(" Adders missed ${missed.get()} nodes") - println("Remover removed ${removed.get()} nodes") - assertEquals(nAdderThreads, completedAdder.get()) - assertEquals(nRemoverThreads, completedRemover.get()) - assertEquals(missed.get(), removed.get()) - assertTrue(undone.get() > 0) - assertTrue(missed.get() > 0) - list.validate() - } -} \ No newline at end of file diff --git a/kotlinx-coroutines-core/jvm/test/lincheck/LockFreeListLincheckTest.kt b/kotlinx-coroutines-core/jvm/test/lincheck/LockFreeListLincheckTest.kt deleted file mode 100644 index 4f1bb6ad02..0000000000 --- a/kotlinx-coroutines-core/jvm/test/lincheck/LockFreeListLincheckTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ -@file:Suppress("unused") - -package kotlinx.coroutines.lincheck - -import kotlinx.coroutines.* -import kotlinx.coroutines.internal.* -import org.jetbrains.kotlinx.lincheck.annotations.* -import org.jetbrains.kotlinx.lincheck.annotations.Operation -import org.jetbrains.kotlinx.lincheck.paramgen.* -import org.jetbrains.kotlinx.lincheck.strategy.managed.modelchecking.* - -@Param(name = "value", gen = IntGen::class, conf = "1:5") -class LockFreeListLincheckTest : AbstractLincheckTest() { - class Node(val value: Int): LockFreeLinkedListNode() - - private val q: LockFreeLinkedListHead = LockFreeLinkedListHead() - - @Operation - fun addLast(@Param(name = "value") value: Int) { - q.addLast(Node(value)) - } - - @Operation - fun addLastIfNotSame(@Param(name = "value") value: Int) { - q.addLastIfPrev(Node(value)) { !it.isSame(value) } - } - - @Operation - fun removeFirst(): Int? { - val node = q.removeFirstOrNull() ?: return null - return (node as Node).value - } - - @Operation - fun removeFirstOrPeekIfNotSame(@Param(name = "value") value: Int): Int? { - val node = q.removeFirstIfIsInstanceOfOrPeekIf { !it.isSame(value) } ?: return null - return node.value - } - - private fun Any.isSame(value: Int) = this is Node && this.value == value - - override fun extractState(): Any { - val elements = ArrayList() - q.forEach { elements.add(it.value) } - return elements - } - - override fun ModelCheckingOptions.customize(isStressTest: Boolean) = - checkObstructionFreedom() -} \ No newline at end of file diff --git a/kotlinx-coroutines-core/native/test/internal/LinkedListTest.kt b/kotlinx-coroutines-core/native/test/internal/LinkedListTest.kt deleted file mode 100644 index 44ddf471d7..0000000000 --- a/kotlinx-coroutines-core/native/test/internal/LinkedListTest.kt +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines.internal - -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertFalse -import kotlin.test.assertTrue - -class LinkedListTest { - data class IntNode(val i: Int) : LockFreeLinkedListNode() - - @Test - fun testSimpleAddLastRemove() { - val list = LockFreeLinkedListHead() - assertContents(list) - val n1 = IntNode(1).apply { list.addLast(this) } - assertContents(list, 1) - val n2 = IntNode(2).apply { list.addLast(this) } - assertContents(list, 1, 2) - val n3 = IntNode(3).apply { list.addLast(this) } - assertContents(list, 1, 2, 3) - val n4 = IntNode(4).apply { list.addLast(this) } - assertContents(list, 1, 2, 3, 4) - assertTrue(n1.remove()) - assertContents(list, 2, 3, 4) - assertTrue(n3.remove()) - assertContents(list, 2, 4) - assertTrue(n4.remove()) - assertContents(list, 2) - assertTrue(n2.remove()) - assertFalse(n2.remove()) - assertContents(list) - } - - private fun assertContents(list: LockFreeLinkedListHead, vararg expected: Int) { - val n = expected.size - val actual = IntArray(n) - var index = 0 - list.forEach { actual[index++] = it.i } - assertEquals(n, index) - for (i in 0 until n) assertEquals(expected[i], actual[i], "item i") - assertEquals(expected.isEmpty(), list.isEmpty) - } -} From 36065e66d08d9024cbc9e4a0cd8a1e89b9ccde4f Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Tue, 14 Feb 2023 18:02:44 +0300 Subject: [PATCH 083/106] Restore TCO in BufferedChannel (#3627) --- kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt b/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt index 6be3da8714..e30486fa8c 100644 --- a/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt @@ -130,7 +130,8 @@ internal open class BufferedChannel( onNoWaiterSuspend = { segm, i, elem, s -> sendOnNoWaiterSuspend(segm, i, elem, s) } ) - private suspend fun onClosedSend(element: E): Nothing = suspendCancellableCoroutine { continuation -> + // NB: return type could've been Nothing, but it breaks TCO + private suspend fun onClosedSend(element: E): Unit = suspendCancellableCoroutine { continuation -> onUndeliveredElement?.callUndeliveredElementCatchingException(element)?.let { // If it crashes, add send exception as suppressed for better diagnostics it.addSuppressed(sendException) From dc19e1fd7728bf2d912593363e870f1462d0599f Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 15 Feb 2023 19:17:18 +0300 Subject: [PATCH 084/106] Update Kotlin to 1.8.10 (#3628) --- README.md | 10 +++++----- gradle.properties | 2 +- integration-testing/gradle.properties | 2 +- .../jvm/resources/DebugProbesKt.bin | Bin 1733 -> 1738 bytes 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index c11d97258a..820693d23a 100644 --- a/README.md +++ b/README.md @@ -4,11 +4,11 @@ [![JetBrains official project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) [![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0) [![Download](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.6.4)](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.6.4/pom) -[![Kotlin](https://img.shields.io/badge/kotlin-1.7.20-blue.svg?logo=kotlin)](http://kotlinlang.org) +[![Kotlin](https://img.shields.io/badge/kotlin-1.8.10-blue.svg?logo=kotlin)](http://kotlinlang.org) [![Slack channel](https://img.shields.io/badge/chat-slack-green.svg?logo=slack)](https://kotlinlang.slack.com/messages/coroutines/) Library support for Kotlin coroutines with [multiplatform](#multiplatform) support. -This is a companion version for the Kotlin `1.7.20` release. +This is a companion version for the Kotlin `1.8.10` release. ```kotlin suspend fun main() = coroutineScope { @@ -93,7 +93,7 @@ And make sure that you use the latest Kotlin version: ```xml - 1.7.20 + 1.8.10 ``` @@ -112,10 +112,10 @@ And make sure that you use the latest Kotlin version: ```kotlin plugins { // For build.gradle.kts (Kotlin DSL) - kotlin("jvm") version "1.7.20" + kotlin("jvm") version "1.8.10" // For build.gradle (Groovy DSL) - id "org.jetbrains.kotlin.jvm" version "1.7.20" + id "org.jetbrains.kotlin.jvm" version "1.8.10" } ``` diff --git a/gradle.properties b/gradle.properties index 229904ead4..a9c85f5542 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ # Kotlin version=1.6.4-SNAPSHOT group=org.jetbrains.kotlinx -kotlin_version=1.8.0-Beta +kotlin_version=1.8.10 # Dependencies junit_version=4.12 diff --git a/integration-testing/gradle.properties b/integration-testing/gradle.properties index 1e33bbe1ad..7d3f05c799 100644 --- a/integration-testing/gradle.properties +++ b/integration-testing/gradle.properties @@ -1,4 +1,4 @@ -kotlin_version=1.7.20 +kotlin_version=1.8.10 coroutines_version=1.6.4-SNAPSHOT asm_version=9.3 diff --git a/kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin b/kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin index 4c3babb1db15a06d31f0457dababc98c1fe836fc..9d171f3a7a171bc5b861db55ab3a0011d91e155c 100644 GIT binary patch delta 324 zcma)$Ic@?$6a}kk%>W}H#ta@L2v`g@J7y1%5PgQ>7JUF8g4CBl5D}P&ghN?;kj3JN z;Hln|zN-6>JSM&4@9qa^GhKUI?Z&hWHhcZ8RByF(a*I#dE=uCjm!6OgDdJ>=14)D^ zL*e-TkT@lSR{81#WtlUtjKT&cuT=xO2Y#_Y|(4+}BNzdR7o&e0jUeL^zl4 r6>%tqXhYdhF}zVV)Oa_1P!Ca-2F;MT#j#{8Eqm_1r7Z|71xx1yy!;;6 delta 320 zcmZ{dO=`k$5XFE0gqS9%h>I3uYZiiFtlHMkey-~oTzQ5$KzoO5y+pK#xNxOVJXGnZ z1*_oV@!l{qzqiS(Gydjhu>{(@CH|Lf-08|58`F(PPWax_MGB+x>1$4FhAbX|`lHIi zeb)o*Oa?RgRpvW~g-T2!HKMt_;5v=qNc5@0S?*U5fYuBzl%7=4u6E33}l|(ld z2Xyu Date: Tue, 21 Feb 2023 13:50:08 +0300 Subject: [PATCH 085/106] Add explicit module-info.java for JPMS compatibility (#3629) * Compiles hand-written module-info.java into Multi-Release JAR for all modules Contributed by @lion7 Fixes #2237 Co-authored-by: Gerard de Leeuw --- build.gradle | 1 + buildSrc/src/main/kotlin/Java9Modularity.kt | 147 ++++++++++++++++++ .../java-modularity-conventions.gradle.kts | 17 ++ gradle/compile-jvm-multiplatform.gradle | 2 + .../kotlin/PrecompiledDebugProbesTest.kt | 21 ++- .../src/module-info.java | 7 + .../src/module-info.java | 3 + .../src/module-info.java | 7 + .../jvm/src/module-info.java | 29 ++++ kotlinx-coroutines-debug/src/module-info.java | 14 ++ .../jvm/src/module-info.java | 11 ++ .../src/module-info.java | 9 ++ .../src/module-info.java | 10 ++ .../src/module-info.java | 14 ++ .../src/module-info.java | 10 ++ .../src/module-info.java | 10 ++ .../src/module-info.java | 11 ++ ui/kotlinx-coroutines-javafx/build.gradle.kts | 27 +++- .../src/module-info.java | 13 ++ .../src/module-info.java | 12 ++ 20 files changed, 369 insertions(+), 6 deletions(-) create mode 100644 buildSrc/src/main/kotlin/Java9Modularity.kt create mode 100644 buildSrc/src/main/kotlin/java-modularity-conventions.gradle.kts create mode 100644 integration/kotlinx-coroutines-guava/src/module-info.java create mode 100644 integration/kotlinx-coroutines-jdk8/src/module-info.java create mode 100644 integration/kotlinx-coroutines-slf4j/src/module-info.java create mode 100644 kotlinx-coroutines-core/jvm/src/module-info.java create mode 100644 kotlinx-coroutines-debug/src/module-info.java create mode 100644 kotlinx-coroutines-test/jvm/src/module-info.java create mode 100644 reactive/kotlinx-coroutines-jdk9/src/module-info.java create mode 100644 reactive/kotlinx-coroutines-reactive/src/module-info.java create mode 100644 reactive/kotlinx-coroutines-reactor/src/module-info.java create mode 100644 reactive/kotlinx-coroutines-rx2/src/module-info.java create mode 100644 reactive/kotlinx-coroutines-rx3/src/module-info.java create mode 100644 ui/kotlinx-coroutines-android/src/module-info.java create mode 100644 ui/kotlinx-coroutines-javafx/src/module-info.java create mode 100644 ui/kotlinx-coroutines-swing/src/module-info.java diff --git a/build.gradle b/build.gradle index 3e824a2eff..e65558b151 100644 --- a/build.gradle +++ b/build.gradle @@ -181,6 +181,7 @@ configure(subprojects.findAll { !sourceless.contains(it.name) && it.name != core } apply plugin: "bom-conventions" +apply plugin: "java-modularity-conventions" if (build_snapshot_train) { println "Hacking test tasks, removing stress and flaky tests" diff --git a/buildSrc/src/main/kotlin/Java9Modularity.kt b/buildSrc/src/main/kotlin/Java9Modularity.kt new file mode 100644 index 0000000000..9aeaa67f74 --- /dev/null +++ b/buildSrc/src/main/kotlin/Java9Modularity.kt @@ -0,0 +1,147 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +import org.gradle.api.* +import org.gradle.api.attributes.* +import org.gradle.api.file.* +import org.gradle.api.provider.* +import org.gradle.api.specs.* +import org.gradle.api.tasks.bundling.* +import org.gradle.api.tasks.compile.* +import org.gradle.jvm.toolchain.* +import org.gradle.kotlin.dsl.* +import org.gradle.api.logging.Logger +import org.jetbrains.kotlin.gradle.dsl.* +import java.io.* + +/** + * This object configures the Java compilation of a JPMS (aka Jigsaw) module descriptor. + * The source file for the module descriptor is expected at /src/module-info.java. + * + * To maintain backwards compatibility with Java 8, the jvm JAR is marked as a multi-release JAR + * with the module-info.class being moved to META-INF/versions/9/module-info.class. + * + * The Java toolchains feature of Gradle is used to detect or provision a JDK 11, + * which is used to compile the module descriptor. + */ +object Java9Modularity { + + private class ModuleInfoFilter( + private val compileKotlinTaskPath: String, + private val javaVersionProvider: Provider, + private val moduleInfoFile: File, + private val logger: Logger + ) : Spec { + private val isJava9Compatible + get() = javaVersionProvider.orNull?.isJava9Compatible == true + private var logged = false + + private fun logStatusOnce() { + if (logged) return + if (isJava9Compatible) { + logger.info("Module-info checking is enabled; $compileKotlinTaskPath is compiled using Java ${javaVersionProvider.get()}") + } else { + logger.info("Module-info checking is disabled") + } + logged = true + } + + override fun isSatisfiedBy(element: FileTreeElement): Boolean { + logStatusOnce() + if (isJava9Compatible) return false + return element.file == moduleInfoFile + } + } + + @JvmStatic + fun configure(project: Project) = with(project) { + val javaToolchains = extensions.findByType(JavaToolchainService::class.java) + ?: error("Gradle JavaToolchainService is not available") + val target = when (val kotlin = extensions.getByName("kotlin")) { + is KotlinJvmProjectExtension -> kotlin.target + is KotlinMultiplatformExtension -> kotlin.targets.getByName("jvm") + else -> throw IllegalStateException("Unknown Kotlin project extension in $project") + } + val compilation = target.compilations.getByName("main") + + // Force the use of JARs for compile dependencies, so any JPMS descriptors are picked up. + // For more details, see https://github.com/gradle/gradle/issues/890#issuecomment-623392772 + configurations.getByName(compilation.compileDependencyConfigurationName).attributes { + attribute( + LibraryElements.LIBRARY_ELEMENTS_ATTRIBUTE, + objects.named(LibraryElements::class, LibraryElements.JAR) + ) + } + + val compileJavaModuleInfo = tasks.register("compileModuleInfoJava", JavaCompile::class.java) { + val moduleName = project.name.replace('-', '.') // this module's name + val sourceFile = file("${target.name.ifEmpty { "." }}/src/module-info.java") + if (!sourceFile.exists()) { + throw IllegalStateException("$sourceFile not found in $project") + } + val compileKotlinTask = + compilation.compileTaskProvider.get() as? org.jetbrains.kotlin.gradle.tasks.KotlinCompile + ?: error("Cannot access Kotlin compile task ${compilation.compileKotlinTaskName}") + val targetDir = compileKotlinTask.destinationDirectory.dir("../java9") + + // Use a Java 11 compiler for the module-info. + javaCompiler.set(javaToolchains.compilerFor { + languageVersion.set(JavaLanguageVersion.of(11)) + }) + + // Always compile kotlin classes before the module descriptor. + dependsOn(compileKotlinTask) + + // Add the module-info source file. + // Note that we use the parent dir and an include filter, + // this is needed for Gradle's module detection to work in + // org.gradle.api.tasks.compile.JavaCompile.createSpec + source(sourceFile.parentFile) + include { it.file == sourceFile } + + // The Kotlin compiler will parse and check module dependencies, + // but it currently won't compile to a module-info.class file. + // Note that module checking only works on JDK 9+, + // because the JDK built-in base modules are not available in earlier versions. + val javaVersionProvider = compileKotlinTask.kotlinJavaToolchain.javaVersion + compileKotlinTask.exclude(ModuleInfoFilter(compileKotlinTask.path, javaVersionProvider, sourceFile, logger)) + + // Set the task outputs and destination directory + outputs.dir(targetDir) + destinationDirectory.set(targetDir) + + // Configure JVM compatibility + sourceCompatibility = JavaVersion.VERSION_1_9.toString() + targetCompatibility = JavaVersion.VERSION_1_9.toString() + + // Set the Java release version. + options.release.set(9) + + // Ignore warnings about using 'requires transitive' on automatic modules. + // not needed when compiling with recent JDKs, e.g. 17 + options.compilerArgs.add("-Xlint:-requires-transitive-automatic") + + // Patch the compileKotlinJvm output classes into the compilation so exporting packages works correctly. + val destinationDirProperty = compileKotlinTask.destinationDirectory.asFile + options.compilerArgumentProviders.add { + val kotlinCompileDestinationDir = destinationDirProperty.get() + listOf("--patch-module", "$moduleName=$kotlinCompileDestinationDir") + } + + // Use the classpath of the compileKotlinJvm task. + // Also ensure that the module path is used instead of classpath. + classpath = compileKotlinTask.libraries + modularity.inferModulePath.set(true) + } + + tasks.named(target.artifactsTaskName) { + manifest { + attributes("Multi-Release" to true) + } + from(compileJavaModuleInfo) { + into("META-INF/versions/9/") + } + } + } +} diff --git a/buildSrc/src/main/kotlin/java-modularity-conventions.gradle.kts b/buildSrc/src/main/kotlin/java-modularity-conventions.gradle.kts new file mode 100644 index 0000000000..a5f72aa800 --- /dev/null +++ b/buildSrc/src/main/kotlin/java-modularity-conventions.gradle.kts @@ -0,0 +1,17 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +// Currently the compilation of the module-info fails for +// kotlinx-coroutines-play-services because it depends on Android JAR's +// which do not have an explicit module-info descriptor. +// Because the JAR's are all named `classes.jar`, +// the automatic module name also becomes `classes`. +// This conflicts since there are multiple JAR's with identical names. +val invalidModules = listOf("kotlinx-coroutines-play-services") + +configure(subprojects.filter { + !unpublished.contains(it.name) && !invalidModules.contains(it.name) && it.extensions.findByName("kotlin") != null +}) { + Java9Modularity.configure(project) +} diff --git a/gradle/compile-jvm-multiplatform.gradle b/gradle/compile-jvm-multiplatform.gradle index 88b717976d..cb8325ca92 100644 --- a/gradle/compile-jvm-multiplatform.gradle +++ b/gradle/compile-jvm-multiplatform.gradle @@ -10,6 +10,8 @@ kotlin { sourceSets { jvmMain.dependencies { compileOnly "org.codehaus.mojo:animal-sniffer-annotations:1.20" + // Workaround until https://github.com/JetBrains/kotlin/pull/4999 is picked up + api "org.jetbrains:annotations:23.0.0" } jvmTest.dependencies { diff --git a/integration-testing/src/debugAgentTest/kotlin/PrecompiledDebugProbesTest.kt b/integration-testing/src/debugAgentTest/kotlin/PrecompiledDebugProbesTest.kt index 84886a18ab..ab207e092b 100644 --- a/integration-testing/src/debugAgentTest/kotlin/PrecompiledDebugProbesTest.kt +++ b/integration-testing/src/debugAgentTest/kotlin/PrecompiledDebugProbesTest.kt @@ -16,10 +16,9 @@ class PrecompiledDebugProbesTest { @Test fun testClassFileContent() { val clz = Class.forName("kotlin.coroutines.jvm.internal.DebugProbesKt") - val className: String = clz.getName() - val classFileResourcePath = className.replace(".", "/") + ".class" - val stream = clz.classLoader.getResourceAsStream(classFileResourcePath)!! - val array = stream.readBytes() + val classFileResourcePath = clz.name.replace(".", "/") + ".class" + val array = clz.classLoader.getResourceAsStream(classFileResourcePath).use { it.readBytes() } + assertJava8Compliance(array) // we expect the integration testing project to be in a subdirectory of the main kotlinx.coroutines project val base = File("").absoluteFile.parentFile val probes = File(base, "kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin") @@ -31,8 +30,20 @@ class PrecompiledDebugProbesTest { assertTrue( array.contentEquals(binContent), "Compiled DebugProbesKt.class does not match the file shipped as a resource in kotlinx-coroutines-core. " + - "Typically it happens because of the Kotlin version update (-> binary metadata). In that case, run the same test with -Poverwrite.probes=true." + "Typically it happens because of the Kotlin version update (-> binary metadata). " + + "In that case, run the same test with -Poverwrite.probes=true." ) } } + + private fun assertJava8Compliance(classBytes: ByteArray) { + DataInputStream(classBytes.inputStream()).use { + val magic: Int = it.readInt() + if (magic != -0x35014542) throw IllegalArgumentException("Not a valid class!") + val minor: Int = it.readUnsignedShort() + val major: Int = it.readUnsignedShort() + assertEquals(52, major) + assertEquals(0, minor) + } + } } diff --git a/integration/kotlinx-coroutines-guava/src/module-info.java b/integration/kotlinx-coroutines-guava/src/module-info.java new file mode 100644 index 0000000000..0b8ccafd68 --- /dev/null +++ b/integration/kotlinx-coroutines-guava/src/module-info.java @@ -0,0 +1,7 @@ +module kotlinx.coroutines.guava { + requires kotlin.stdlib; + requires kotlinx.coroutines.core; + requires com.google.common; + + exports kotlinx.coroutines.guava; +} diff --git a/integration/kotlinx-coroutines-jdk8/src/module-info.java b/integration/kotlinx-coroutines-jdk8/src/module-info.java new file mode 100644 index 0000000000..d83596c2fa --- /dev/null +++ b/integration/kotlinx-coroutines-jdk8/src/module-info.java @@ -0,0 +1,3 @@ +@SuppressWarnings("JavaModuleNaming") +module kotlinx.coroutines.jdk8 { +} diff --git a/integration/kotlinx-coroutines-slf4j/src/module-info.java b/integration/kotlinx-coroutines-slf4j/src/module-info.java new file mode 100644 index 0000000000..57e5aae4d0 --- /dev/null +++ b/integration/kotlinx-coroutines-slf4j/src/module-info.java @@ -0,0 +1,7 @@ +module kotlinx.coroutines.slf4j { + requires kotlin.stdlib; + requires kotlinx.coroutines.core; + requires org.slf4j; + + exports kotlinx.coroutines.slf4j; +} diff --git a/kotlinx-coroutines-core/jvm/src/module-info.java b/kotlinx-coroutines-core/jvm/src/module-info.java new file mode 100644 index 0000000000..2759a34296 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/src/module-info.java @@ -0,0 +1,29 @@ +import kotlinx.coroutines.CoroutineExceptionHandler; +import kotlinx.coroutines.internal.MainDispatcherFactory; + +module kotlinx.coroutines.core { + requires transitive kotlin.stdlib; + requires kotlinx.atomicfu; + + // these are used by kotlinx.coroutines.debug.AgentPremain + requires static java.instrument; // contains java.lang.instrument.* + requires static jdk.unsupported; // contains sun.misc.Signal + + exports kotlinx.coroutines; + exports kotlinx.coroutines.channels; + exports kotlinx.coroutines.debug; + exports kotlinx.coroutines.debug.internal; + exports kotlinx.coroutines.flow; + exports kotlinx.coroutines.flow.internal; + exports kotlinx.coroutines.future; + exports kotlinx.coroutines.internal; + exports kotlinx.coroutines.intrinsics; + exports kotlinx.coroutines.scheduling; + exports kotlinx.coroutines.selects; + exports kotlinx.coroutines.stream; + exports kotlinx.coroutines.sync; + exports kotlinx.coroutines.time; + + uses CoroutineExceptionHandler; + uses MainDispatcherFactory; +} diff --git a/kotlinx-coroutines-debug/src/module-info.java b/kotlinx-coroutines-debug/src/module-info.java new file mode 100644 index 0000000000..2c7571ec0d --- /dev/null +++ b/kotlinx-coroutines-debug/src/module-info.java @@ -0,0 +1,14 @@ +module kotlinx.coroutines.debug { + requires java.management; + requires java.instrument; + requires kotlin.stdlib; + requires kotlinx.coroutines.core; + requires net.bytebuddy; + requires net.bytebuddy.agent; + requires org.junit.jupiter.api; + requires org.junit.platform.commons; + +// exports kotlinx.coroutines.debug; // already exported by kotlinx.coroutines.core + exports kotlinx.coroutines.debug.junit4; + exports kotlinx.coroutines.debug.junit5; +} diff --git a/kotlinx-coroutines-test/jvm/src/module-info.java b/kotlinx-coroutines-test/jvm/src/module-info.java new file mode 100644 index 0000000000..f67ce6a161 --- /dev/null +++ b/kotlinx-coroutines-test/jvm/src/module-info.java @@ -0,0 +1,11 @@ +import kotlinx.coroutines.internal.MainDispatcherFactory; +import kotlinx.coroutines.test.internal.TestMainDispatcherFactory; + +module kotlinx.coroutines.test { + requires kotlin.stdlib; + requires kotlinx.coroutines.core; + + exports kotlinx.coroutines.test; + + provides MainDispatcherFactory with TestMainDispatcherFactory; +} diff --git a/reactive/kotlinx-coroutines-jdk9/src/module-info.java b/reactive/kotlinx-coroutines-jdk9/src/module-info.java new file mode 100644 index 0000000000..ce31d81015 --- /dev/null +++ b/reactive/kotlinx-coroutines-jdk9/src/module-info.java @@ -0,0 +1,9 @@ +@SuppressWarnings("JavaModuleNaming") +module kotlinx.coroutines.jdk9 { + requires kotlin.stdlib; + requires kotlinx.coroutines.core; + requires kotlinx.coroutines.reactive; + requires org.reactivestreams; + + exports kotlinx.coroutines.jdk9; +} diff --git a/reactive/kotlinx-coroutines-reactive/src/module-info.java b/reactive/kotlinx-coroutines-reactive/src/module-info.java new file mode 100644 index 0000000000..67fcc26b40 --- /dev/null +++ b/reactive/kotlinx-coroutines-reactive/src/module-info.java @@ -0,0 +1,10 @@ +module kotlinx.coroutines.reactive { + requires kotlin.stdlib; + requires kotlinx.coroutines.core; + requires kotlinx.atomicfu; + requires org.reactivestreams; + + exports kotlinx.coroutines.reactive; + + uses kotlinx.coroutines.reactive.ContextInjector; +} diff --git a/reactive/kotlinx-coroutines-reactor/src/module-info.java b/reactive/kotlinx-coroutines-reactor/src/module-info.java new file mode 100644 index 0000000000..b75308b521 --- /dev/null +++ b/reactive/kotlinx-coroutines-reactor/src/module-info.java @@ -0,0 +1,14 @@ +import kotlinx.coroutines.reactive.ContextInjector; +import kotlinx.coroutines.reactor.ReactorContextInjector; + +module kotlinx.coroutines.reactor { + requires kotlin.stdlib; + requires kotlinx.coroutines.core; + requires kotlinx.coroutines.reactive; + requires org.reactivestreams; + requires reactor.core; + + exports kotlinx.coroutines.reactor; + + provides ContextInjector with ReactorContextInjector; +} diff --git a/reactive/kotlinx-coroutines-rx2/src/module-info.java b/reactive/kotlinx-coroutines-rx2/src/module-info.java new file mode 100644 index 0000000000..539ea3ee05 --- /dev/null +++ b/reactive/kotlinx-coroutines-rx2/src/module-info.java @@ -0,0 +1,10 @@ +@SuppressWarnings("JavaModuleNaming") +module kotlinx.coroutines.rx2 { + requires kotlin.stdlib; + requires kotlinx.coroutines.core; + requires kotlinx.coroutines.reactive; + requires kotlinx.atomicfu; + requires io.reactivex.rxjava2; + + exports kotlinx.coroutines.rx2; +} diff --git a/reactive/kotlinx-coroutines-rx3/src/module-info.java b/reactive/kotlinx-coroutines-rx3/src/module-info.java new file mode 100644 index 0000000000..d57d5279d8 --- /dev/null +++ b/reactive/kotlinx-coroutines-rx3/src/module-info.java @@ -0,0 +1,10 @@ +@SuppressWarnings("JavaModuleNaming") +module kotlinx.coroutines.rx3 { + requires kotlin.stdlib; + requires kotlinx.coroutines.core; + requires kotlinx.coroutines.reactive; + requires kotlinx.atomicfu; + requires io.reactivex.rxjava3; + + exports kotlinx.coroutines.rx3; +} diff --git a/ui/kotlinx-coroutines-android/src/module-info.java b/ui/kotlinx-coroutines-android/src/module-info.java new file mode 100644 index 0000000000..719844004b --- /dev/null +++ b/ui/kotlinx-coroutines-android/src/module-info.java @@ -0,0 +1,11 @@ +import kotlinx.coroutines.android.AndroidDispatcherFactory; +import kotlinx.coroutines.internal.MainDispatcherFactory; + +module kotlinx.coroutines.android { + requires kotlin.stdlib; + requires kotlinx.coroutines.core; + + exports kotlinx.coroutines.android; + + provides MainDispatcherFactory with AndroidDispatcherFactory; +} diff --git a/ui/kotlinx-coroutines-javafx/build.gradle.kts b/ui/kotlinx-coroutines-javafx/build.gradle.kts index 456ced137c..634423a517 100644 --- a/ui/kotlinx-coroutines-javafx/build.gradle.kts +++ b/ui/kotlinx-coroutines-javafx/build.gradle.kts @@ -2,8 +2,15 @@ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +buildscript { + dependencies { + // this line can be removed when https://github.com/openjfx/javafx-gradle-plugin/pull/135 is released + classpath("org.javamodularity:moduleplugin:1.8.12") + } +} + plugins { - id("org.openjfx.javafxplugin") version "0.0.9" + id("org.openjfx.javafxplugin") version "0.0.13" } configurations { @@ -21,3 +28,21 @@ javafx { modules = listOf("javafx.controls") configuration = "javafx" } + +// Fixup moduleplugin in order to properly run with classpath +tasks { + test { + extensions.configure(org.javamodularity.moduleplugin.extensions.TestModuleOptions::class) { + addReads["kotlinx.coroutines.core"] = "junit" + addReads["kotlinx.coroutines.javafx"] = "kotlin.test" + } + jvmArgs = listOf( + "--patch-module", + "kotlinx.coroutines.core=${ + project(":kotlinx-coroutines-core").tasks.named( + "compileTestKotlinJvm" + ).get().destinationDirectory.get() + }" + ) + } +} diff --git a/ui/kotlinx-coroutines-javafx/src/module-info.java b/ui/kotlinx-coroutines-javafx/src/module-info.java new file mode 100644 index 0000000000..d9b47e5ec4 --- /dev/null +++ b/ui/kotlinx-coroutines-javafx/src/module-info.java @@ -0,0 +1,13 @@ +import kotlinx.coroutines.internal.MainDispatcherFactory; +import kotlinx.coroutines.javafx.JavaFxDispatcherFactory; + +module kotlinx.coroutines.javafx { + requires kotlin.stdlib; + requires kotlinx.coroutines.core; + requires javafx.base; + requires javafx.graphics; + + exports kotlinx.coroutines.javafx; + + provides MainDispatcherFactory with JavaFxDispatcherFactory; +} diff --git a/ui/kotlinx-coroutines-swing/src/module-info.java b/ui/kotlinx-coroutines-swing/src/module-info.java new file mode 100644 index 0000000000..62744873d4 --- /dev/null +++ b/ui/kotlinx-coroutines-swing/src/module-info.java @@ -0,0 +1,12 @@ +import kotlinx.coroutines.internal.MainDispatcherFactory; +import kotlinx.coroutines.swing.SwingDispatcherFactory; + +module kotlinx.coroutines.swing { + requires kotlin.stdlib; + requires kotlinx.coroutines.core; + requires java.desktop; + + exports kotlinx.coroutines.swing; + + provides MainDispatcherFactory with SwingDispatcherFactory; +} From 3b22c2783708e6c869db187a913b89e4af4f7613 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Tue, 21 Feb 2023 17:35:53 +0300 Subject: [PATCH 086/106] Allow specifying the timeout for `runTest` (#3603) Deprecate `dispatchTimeoutMs`, as this is a confusing implementation detail that made it to the final API. We use the fact that the `runTest(Duration)` overload was never published, so we can reuse it to have the `Duration` mean the whole-test timeout in a backward-compatible manner. --- .../api/kotlinx-coroutines-test.api | 4 +- kotlinx-coroutines-test/build.gradle.kts | 12 +- .../common/src/TestBuilders.kt | 217 +++++++++++++----- .../common/src/TestCoroutineScheduler.kt | 38 ++- .../common/src/TestScope.kt | 25 +- .../common/test/RunTestTest.kt | 84 +++++-- .../common/test/StandardTestDispatcherTest.kt | 2 +- .../common/test/TestCoroutineSchedulerTest.kt | 21 +- .../common/test/TestScopeTest.kt | 15 +- .../js/src/TestBuilders.kt | 2 + .../jvm/src/TestBuildersJvm.kt | 14 ++ .../src/migration/TestBuildersDeprecated.kt | 45 ++-- .../jvm/test/DumpOnTimeoutTest.kt | 48 ++++ .../jvm/test/MultithreadingTest.kt | 22 +- .../native/src/TestBuilders.kt | 3 + 15 files changed, 431 insertions(+), 121 deletions(-) create mode 100644 kotlinx-coroutines-test/jvm/test/DumpOnTimeoutTest.kt diff --git a/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api b/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api index 3f63364489..ac9edb90d4 100644 --- a/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api +++ b/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api @@ -22,10 +22,10 @@ public final class kotlinx/coroutines/test/TestBuildersKt { public static final fun runTest (Lkotlinx/coroutines/test/TestScope;JLkotlin/jvm/functions/Function2;)V public static synthetic fun runTest$default (Lkotlin/coroutines/CoroutineContext;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public static synthetic fun runTest$default (Lkotlinx/coroutines/test/TestCoroutineScope;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V - public static synthetic fun runTest$default (Lkotlinx/coroutines/test/TestScope;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public static final fun runTest-8Mi8wO0 (Lkotlin/coroutines/CoroutineContext;JLkotlin/jvm/functions/Function2;)V public static final fun runTest-8Mi8wO0 (Lkotlinx/coroutines/test/TestScope;JLkotlin/jvm/functions/Function2;)V public static synthetic fun runTest-8Mi8wO0$default (Lkotlin/coroutines/CoroutineContext;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static synthetic fun runTest-8Mi8wO0$default (Lkotlinx/coroutines/test/TestScope;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V public static final fun runTestWithLegacyScope (Lkotlin/coroutines/CoroutineContext;JLkotlin/jvm/functions/Function2;)V public static synthetic fun runTestWithLegacyScope$default (Lkotlin/coroutines/CoroutineContext;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V } @@ -66,6 +66,7 @@ public final class kotlinx/coroutines/test/TestCoroutineScheduler : kotlin/corou public static final field Key Lkotlinx/coroutines/test/TestCoroutineScheduler$Key; public fun ()V public final fun advanceTimeBy (J)V + public final fun advanceTimeBy-LRDsOJo (J)V public final fun advanceUntilIdle ()V public final fun getCurrentTime ()J public final fun getTimeSource ()Lkotlin/time/TimeSource; @@ -117,6 +118,7 @@ public final class kotlinx/coroutines/test/TestScopeKt { public static final fun TestScope (Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/test/TestScope; public static synthetic fun TestScope$default (Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lkotlinx/coroutines/test/TestScope; public static final fun advanceTimeBy (Lkotlinx/coroutines/test/TestScope;J)V + public static final fun advanceTimeBy-HG0u8IE (Lkotlinx/coroutines/test/TestScope;J)V public static final fun advanceUntilIdle (Lkotlinx/coroutines/test/TestScope;)V public static final fun getCurrentTime (Lkotlinx/coroutines/test/TestScope;)J public static final fun getTestTimeSource (Lkotlinx/coroutines/test/TestScope;)Lkotlin/time/TimeSource; diff --git a/kotlinx-coroutines-test/build.gradle.kts b/kotlinx-coroutines-test/build.gradle.kts index 98d3e9fa74..c968fc4991 100644 --- a/kotlinx-coroutines-test/build.gradle.kts +++ b/kotlinx-coroutines-test/build.gradle.kts @@ -1,9 +1,9 @@ -import org.jetbrains.kotlin.gradle.plugin.mpp.* - /* * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +import org.jetbrains.kotlin.gradle.plugin.mpp.* + val experimentalAnnotations = listOf( "kotlin.Experimental", "kotlinx.coroutines.ExperimentalCoroutinesApi", @@ -19,4 +19,12 @@ kotlin { binaryOptions["memoryModel"] = "experimental" } } + + sourceSets { + jvmTest { + dependencies { + implementation(project(":kotlinx-coroutines-debug")) + } + } + } } diff --git a/kotlinx-coroutines-test/common/src/TestBuilders.kt b/kotlinx-coroutines-test/common/src/TestBuilders.kt index e9d8fec0d3..de16967a41 100644 --- a/kotlinx-coroutines-test/common/src/TestBuilders.kt +++ b/kotlinx-coroutines-test/common/src/TestBuilders.kt @@ -7,11 +7,14 @@ package kotlinx.coroutines.test import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* import kotlinx.coroutines.selects.* import kotlin.coroutines.* import kotlin.jvm.* import kotlin.time.* import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.internal.* /** * A test result. @@ -118,6 +121,18 @@ public expect class TestResult * * If the created coroutine completes with an exception, then this exception will be thrown at the end of the test. * + * #### Timing out + * + * There's a built-in timeout of 10 seconds for the test body. If the test body doesn't complete within this time, + * then the test fails with an [AssertionError]. The timeout can be changed by setting the [timeout] parameter. + * + * The test finishes by the timeout procedure cancelling the test body. If the code inside the test body does not + * respond to cancellation, we will not be able to make the test execution stop, in which case, the test will hang + * despite our best efforts to terminate it. + * + * On the JVM, if `DebugProbes` from the `kotlinx-coroutines-debug` module are installed, the current dump of the + * coroutines' stack is printed to the console on timeout. + * * #### Reported exceptions * * Unhandled exceptions will be thrown at the end of the test. @@ -131,12 +146,6 @@ public expect class TestResult * Otherwise, the test will be failed (which, on JVM and Native, means that [runTest] itself will throw * [AssertionError], whereas on JS, the `Promise` will fail with it). * - * In the general case, if there are active jobs, it's impossible to detect if they are going to complete eventually due - * to the asynchronous nature of coroutines. In order to prevent tests hanging in this scenario, [runTest] will wait - * for [dispatchTimeout] (by default, 60 seconds) from the moment when [TestCoroutineScheduler] becomes - * idle before throwing [AssertionError]. If some dispatcher linked to [TestCoroutineScheduler] receives a - * task during that time, the timer gets reset. - * * ### Configuration * * [context] can be used to affect the environment of the code under test. Beside just being passed to the coroutine @@ -148,12 +157,13 @@ public expect class TestResult @ExperimentalCoroutinesApi public fun runTest( context: CoroutineContext = EmptyCoroutineContext, - dispatchTimeout: Duration = DEFAULT_DISPATCH_TIMEOUT, + timeout: Duration = DEFAULT_TIMEOUT, testBody: suspend TestScope.() -> Unit ): TestResult { - if (context[RunningInRunTest] != null) - throw IllegalStateException("Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details.") - return TestScope(context + RunningInRunTest).runTest(dispatchTimeout, testBody) + check(context[RunningInRunTest] == null) { + "Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details." + } + return TestScope(context + RunningInRunTest).runTest(timeout, testBody) } /** @@ -269,46 +279,128 @@ public fun runTest( * * @throws IllegalArgumentException if the [context] is invalid. See the [TestScope] constructor docs for details. */ -@ExperimentalCoroutinesApi +@Deprecated( + "Define a total timeout for the whole test instead of using dispatchTimeoutMs. " + + "Warning: the proposed replacement is not identical as it uses 'dispatchTimeoutMs' as the timeout for the whole test!", + ReplaceWith("runTest(context, timeout = dispatchTimeoutMs.milliseconds, testBody)", + "kotlin.time.Duration.Companion.milliseconds"), + DeprecationLevel.WARNING +) public fun runTest( context: CoroutineContext = EmptyCoroutineContext, dispatchTimeoutMs: Long, testBody: suspend TestScope.() -> Unit -): TestResult = runTest( - context = context, - dispatchTimeout = dispatchTimeoutMs.milliseconds, - testBody = testBody -) +): TestResult { + if (context[RunningInRunTest] != null) + throw IllegalStateException("Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details.") + @Suppress("DEPRECATION") + return TestScope(context + RunningInRunTest).runTest(dispatchTimeoutMs = dispatchTimeoutMs, testBody) +} /** - * Performs [runTest] on an existing [TestScope]. + * Performs [runTest] on an existing [TestScope]. See the documentation for [runTest] for details. */ @ExperimentalCoroutinesApi public fun TestScope.runTest( - dispatchTimeout: Duration, + timeout: Duration = DEFAULT_TIMEOUT, testBody: suspend TestScope.() -> Unit -): TestResult = asSpecificImplementation().let { - it.enter() +): TestResult = asSpecificImplementation().let { scope -> + scope.enter() createTestResult { - runTestCoroutine(it, dispatchTimeout, TestScopeImpl::tryGetCompletionCause, testBody) { + /** TODO: moving this [AbstractCoroutine.start] call outside [createTestResult] fails on JS. */ + scope.start(CoroutineStart.UNDISPATCHED, scope) { + /* we're using `UNDISPATCHED` to avoid the event loop, but we do want to set up the timeout machinery + before any code executes, so we have to park here. */ + yield() + testBody() + } + var timeoutError: Throwable? = null + var cancellationException: CancellationException? = null + val workRunner = launch(CoroutineName("kotlinx.coroutines.test runner")) { + while (true) { + val executedSomething = testScheduler.tryRunNextTaskUnless { !isActive } + if (executedSomething) { + /** yield to check for cancellation. On JS, we can't use [ensureActive] here, as the cancellation + * procedure needs a chance to run concurrently. */ + yield() + } else { + // waiting for the next task to be scheduled, or for the test runner to be cancelled + testScheduler.receiveDispatchEvent() + } + } + } + try { + withTimeout(timeout) { + coroutineContext.job.invokeOnCompletion(onCancelling = true) { exception -> + if (exception is TimeoutCancellationException) { + dumpCoroutines() + val activeChildren = scope.children.filter(Job::isActive).toList() + val completionCause = if (scope.isCancelled) scope.tryGetCompletionCause() else null + var message = "After waiting for $timeout" + if (completionCause == null) + message += ", the test coroutine is not completing" + if (activeChildren.isNotEmpty()) + message += ", there were active child jobs: $activeChildren" + if (completionCause != null && activeChildren.isEmpty()) { + message += if (scope.isCompleted) + ", the test coroutine completed" + else + ", the test coroutine was not completed" + } + timeoutError = UncompletedCoroutinesError(message) + cancellationException = CancellationException("The test timed out") + (scope as Job).cancel(cancellationException!!) + } + } + scope.join() + workRunner.cancelAndJoin() + } + } catch (_: TimeoutCancellationException) { + scope.join() + val completion = scope.getCompletionExceptionOrNull() + if (completion != null && completion !== cancellationException) { + timeoutError!!.addSuppressed(completion) + } + workRunner.cancelAndJoin() + } finally { backgroundScope.cancel() testScheduler.advanceUntilIdleOr { false } - it.leave() + val uncaughtExceptions = scope.leave() + throwAll(timeoutError ?: scope.getCompletionExceptionOrNull(), uncaughtExceptions) } } } /** * Performs [runTest] on an existing [TestScope]. + * + * In the general case, if there are active jobs, it's impossible to detect if they are going to complete eventually due + * to the asynchronous nature of coroutines. In order to prevent tests hanging in this scenario, [runTest] will wait + * for [dispatchTimeoutMs] from the moment when [TestCoroutineScheduler] becomes + * idle before throwing [AssertionError]. If some dispatcher linked to [TestCoroutineScheduler] receives a + * task during that time, the timer gets reset. */ -@ExperimentalCoroutinesApi +@Deprecated( + "Define a total timeout for the whole test instead of using dispatchTimeoutMs. " + + "Warning: the proposed replacement is not identical as it uses 'dispatchTimeoutMs' as the timeout for the whole test!", + ReplaceWith("this.runTest(timeout = dispatchTimeoutMs.milliseconds, testBody)", + "kotlin.time.Duration.Companion.milliseconds"), + DeprecationLevel.WARNING +) public fun TestScope.runTest( - dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS, + dispatchTimeoutMs: Long, testBody: suspend TestScope.() -> Unit -): TestResult = runTest( - dispatchTimeout = dispatchTimeoutMs.milliseconds, - testBody = testBody -) +): TestResult = asSpecificImplementation().let { + it.enter() + @Suppress("DEPRECATION") + createTestResult { + runTestCoroutineLegacy(it, dispatchTimeoutMs.milliseconds, TestScopeImpl::tryGetCompletionCause, testBody) { + backgroundScope.cancel() + testScheduler.advanceUntilIdleOr { false } + it.legacyLeave() + } + } +} /** * Runs [testProcedure], creating a [TestResult]. @@ -327,18 +419,23 @@ internal object RunningInRunTest : CoroutineContext.Key, Corou /** The default timeout to use when waiting for asynchronous completions of the coroutines managed by * a [TestCoroutineScheduler]. */ internal const val DEFAULT_DISPATCH_TIMEOUT_MS = 60_000L -internal val DEFAULT_DISPATCH_TIMEOUT = DEFAULT_DISPATCH_TIMEOUT_MS.milliseconds + +/** + * The default timeout to use when running a test. + */ +internal val DEFAULT_TIMEOUT = 10.seconds /** * Run the [body][testBody] of the [test coroutine][coroutine], waiting for asynchronous completions for at most - * [dispatchTimeout], and performing the [cleanup] procedure at the end. + * [dispatchTimeout] and performing the [cleanup] procedure at the end. * * [tryGetCompletionCause] is the [JobSupport.completionCause], which is passed explicitly because it is protected. * * The [cleanup] procedure may either throw [UncompletedCoroutinesError] to denote that child coroutines were leaked, or * return a list of uncaught exceptions that should be reported at the end of the test. */ -internal suspend fun > CoroutineScope.runTestCoroutine( +@Deprecated("Used for support of legacy behavior") +internal suspend fun > CoroutineScope.runTestCoroutineLegacy( coroutine: T, dispatchTimeout: Duration, tryGetCompletionCause: T.() -> Throwable?, @@ -351,6 +448,8 @@ internal suspend fun > CoroutineScope.runTestCoroutin testBody() } /** + * This is the legacy behavior, kept for now for compatibility only. + * * The general procedure here is as follows: * 1. Try running the work that the scheduler knows about, both background and foreground. * @@ -376,16 +475,22 @@ internal suspend fun > CoroutineScope.runTestCoroutin scheduler.advanceUntilIdle() if (coroutine.isCompleted) { /* don't even enter `withTimeout`; this allows to use a timeout of zero to check that there are no - non-trivial dispatches. */ + non-trivial dispatches. */ completed = true continue } // in case progress depends on some background work, we need to keep spinning it. val backgroundWorkRunner = launch(CoroutineName("background work runner")) { while (true) { - scheduler.tryRunNextTaskUnless { !isActive } - // yield so that the `select` below has a chance to check if its conditions are fulfilled - yield() + val executedSomething = scheduler.tryRunNextTaskUnless { !isActive } + if (executedSomething) { + // yield so that the `select` below has a chance to finish successfully or time out + yield() + } else { + // no more tasks, we should suspend until there are some more. + // this doesn't interfere with the `select` below, because different channels are used. + scheduler.receiveDispatchEvent() + } } } try { @@ -394,11 +499,11 @@ internal suspend fun > CoroutineScope.runTestCoroutin // observe that someone completed the test coroutine and leave without waiting for the timeout completed = true } - scheduler.onDispatchEvent { + scheduler.onDispatchEventForeground { // we received knowledge that `scheduler` observed a dispatch event, so we reset the timeout } onTimeout(dispatchTimeout) { - handleTimeout(coroutine, dispatchTimeout, tryGetCompletionCause, cleanup) + throw handleTimeout(coroutine, dispatchTimeout, tryGetCompletionCause, cleanup) } } } finally { @@ -412,21 +517,20 @@ internal suspend fun > CoroutineScope.runTestCoroutin // it's normal that some jobs are not completed if the test body has failed, won't clutter the output emptyList() } - (listOf(exception) + exceptions).throwAll() + throwAll(exception, exceptions) } - cleanup().throwAll() + throwAll(null, cleanup()) } /** - * Invoked on timeout in [runTest]. Almost always just builds a nice [UncompletedCoroutinesError] and throws it. - * However, sometimes it detects that the coroutine completed, in which case it returns normally. + * Invoked on timeout in [runTest]. Just builds a nice [UncompletedCoroutinesError] and returns it. */ -private inline fun> handleTimeout( +private inline fun > handleTimeout( coroutine: T, dispatchTimeout: Duration, tryGetCompletionCause: T.() -> Throwable?, cleanup: () -> List, -) { +): AssertionError { val uncaughtExceptions = try { cleanup() } catch (e: UncompletedCoroutinesError) { @@ -441,20 +545,29 @@ private inline fun> handleTimeout( if (activeChildren.isNotEmpty()) message += ", there were active child jobs: $activeChildren" if (completionCause != null && activeChildren.isEmpty()) { - if (coroutine.isCompleted) - return - // TODO: can this really ever happen? - message += ", the test coroutine was not completed" + message += if (coroutine.isCompleted) + ", the test coroutine completed" + else + ", the test coroutine was not completed" } val error = UncompletedCoroutinesError(message) completionCause?.let { cause -> error.addSuppressed(cause) } uncaughtExceptions.forEach { error.addSuppressed(it) } - throw error + return error } -internal fun List.throwAll() { - firstOrNull()?.apply { - drop(1).forEach { addSuppressed(it) } - throw this +internal fun throwAll(head: Throwable?, other: List) { + if (head != null) { + other.forEach { head.addSuppressed(it) } + throw head + } else { + with(other) { + firstOrNull()?.apply { + drop(1).forEach { addSuppressed(it) } + throw this + } + } } } + +internal expect fun dumpCoroutines() diff --git a/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt b/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt index 5f7198cfff..cdb669c0b3 100644 --- a/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt +++ b/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.selects.* import kotlin.coroutines.* import kotlin.jvm.* import kotlin.time.* +import kotlin.time.Duration.Companion.milliseconds /** * This is a scheduler for coroutines used in tests, providing the delay-skipping behavior. @@ -49,6 +50,9 @@ public class TestCoroutineScheduler : AbstractCoroutineContextElement(TestCorout get() = synchronized(lock) { field } private set + /** A channel for notifying about the fact that a foreground work dispatch recently happened. */ + private val dispatchEventsForeground: Channel = Channel(CONFLATED) + /** A channel for notifying about the fact that a dispatch recently happened. */ private val dispatchEvents: Channel = Channel(CONFLATED) @@ -73,8 +77,8 @@ public class TestCoroutineScheduler : AbstractCoroutineContextElement(TestCorout val time = addClamping(currentTime, timeDeltaMillis) val event = TestDispatchEvent(dispatcher, count, time, marker as Any, isForeground) { isCancelled(marker) } events.addLast(event) - /** can't be moved above: otherwise, [onDispatchEvent] could consume the token sent here before there's - * actually anything in the event queue. */ + /** can't be moved above: otherwise, [onDispatchEventForeground] or [onDispatchEvent] could consume the + * token sent here before there's actually anything in the event queue. */ sendDispatchEvent(context) DisposableHandle { synchronized(lock) { @@ -150,13 +154,22 @@ public class TestCoroutineScheduler : AbstractCoroutineContextElement(TestCorout * * Overflowing the target time used to lead to nothing being done, but will now run the tasks scheduled at up to * (but not including) [Long.MAX_VALUE]. * - * @throws IllegalStateException if passed a negative [delay][delayTimeMillis]. + * @throws IllegalArgumentException if passed a negative [delay][delayTimeMillis]. + */ + @ExperimentalCoroutinesApi + public fun advanceTimeBy(delayTimeMillis: Long): Unit = advanceTimeBy(delayTimeMillis.milliseconds) + + /** + * Moves the virtual clock of this dispatcher forward by [the specified amount][delayTime], running the + * scheduled tasks in the meantime. + * + * @throws IllegalArgumentException if passed a negative [delay][delayTime]. */ @ExperimentalCoroutinesApi - public fun advanceTimeBy(delayTimeMillis: Long) { - require(delayTimeMillis >= 0) { "Can not advance time by a negative delay: $delayTimeMillis" } + public fun advanceTimeBy(delayTime: Duration) { + require(!delayTime.isNegative()) { "Can not advance time by a negative delay: $delayTime" } val startingTime = currentTime - val targetTime = addClamping(startingTime, delayTimeMillis) + val targetTime = addClamping(startingTime, delayTime.inWholeMilliseconds) while (true) { val event = synchronized(lock) { val timeMark = currentTime @@ -191,15 +204,26 @@ public class TestCoroutineScheduler : AbstractCoroutineContextElement(TestCorout * [context] is the context in which the task will be dispatched. */ internal fun sendDispatchEvent(context: CoroutineContext) { + dispatchEvents.trySend(Unit) if (context[BackgroundWork] !== BackgroundWork) - dispatchEvents.trySend(Unit) + dispatchEventsForeground.trySend(Unit) } + /** + * Waits for a notification about a dispatch event. + */ + internal suspend fun receiveDispatchEvent() = dispatchEvents.receive() + /** * Consumes the knowledge that a dispatch event happened recently. */ internal val onDispatchEvent: SelectClause1 get() = dispatchEvents.onReceive + /** + * Consumes the knowledge that a foreground work dispatch event happened recently. + */ + internal val onDispatchEventForeground: SelectClause1 get() = dispatchEventsForeground.onReceive + /** * Returns the [TimeSource] representation of the virtual time of this scheduler. */ diff --git a/kotlinx-coroutines-test/common/src/TestScope.kt b/kotlinx-coroutines-test/common/src/TestScope.kt index 15d48a2ae2..a301ff966b 100644 --- a/kotlinx-coroutines-test/common/src/TestScope.kt +++ b/kotlinx-coroutines-test/common/src/TestScope.kt @@ -52,9 +52,9 @@ public sealed interface TestScope : CoroutineScope { * A scope for background work. * * This scope is automatically cancelled when the test finishes. - * Additionally, while the coroutines in this scope are run as usual when - * using [advanceTimeBy] and [runCurrent], [advanceUntilIdle] will stop advancing the virtual time - * once only the coroutines in this scope are left unprocessed. + * The coroutines in this scope are run as usual when using [advanceTimeBy] and [runCurrent]. + * [advanceUntilIdle], on the other hand, will stop advancing the virtual time once only the coroutines in this + * scope are left unprocessed. * * Failures in coroutines in this scope do not terminate the test. * Instead, they are reported at the end of the test. @@ -123,6 +123,16 @@ public fun TestScope.runCurrent(): Unit = testScheduler.runCurrent() @ExperimentalCoroutinesApi public fun TestScope.advanceTimeBy(delayTimeMillis: Long): Unit = testScheduler.advanceTimeBy(delayTimeMillis) +/** + * Moves the virtual clock of this dispatcher forward by [the specified amount][delayTime], running the + * scheduled tasks in the meantime. + * + * @throws IllegalStateException if passed a negative [delay][delayTime]. + * @see TestCoroutineScheduler.advanceTimeBy + */ +@ExperimentalCoroutinesApi +public fun TestScope.advanceTimeBy(delayTime: Duration): Unit = testScheduler.advanceTimeBy(delayTime) + /** * The [test scheduler][TestScope.testScheduler] as a [TimeSource]. * @see TestCoroutineScheduler.timeSource @@ -230,8 +240,15 @@ internal class TestScopeImpl(context: CoroutineContext) : } } + /** Called at the end of the test. May only be called once. Returns the list of caught unhandled exceptions. */ + fun leave(): List = synchronized(lock) { + check(entered && !finished) + finished = true + uncaughtExceptions + } + /** Called at the end of the test. May only be called once. */ - fun leave(): List { + fun legacyLeave(): List { val exceptions = synchronized(lock) { check(entered && !finished) finished = true diff --git a/kotlinx-coroutines-test/common/test/RunTestTest.kt b/kotlinx-coroutines-test/common/test/RunTestTest.kt index 0315543d54..183eb8cb3a 100644 --- a/kotlinx-coroutines-test/common/test/RunTestTest.kt +++ b/kotlinx-coroutines-test/common/test/RunTestTest.kt @@ -9,6 +9,8 @@ import kotlinx.coroutines.internal.* import kotlinx.coroutines.flow.* import kotlin.coroutines.* import kotlin.test.* +import kotlin.time.* +import kotlin.time.Duration.Companion.milliseconds class RunTestTest { @@ -52,7 +54,7 @@ class RunTestTest { /** Tests that even the dispatch timeout of `0` is fine if all the dispatches go through the same scheduler. */ @Test - fun testRunTestWithZeroTimeoutWithControlledDispatches() = runTest(dispatchTimeoutMs = 0) { + fun testRunTestWithZeroDispatchTimeoutWithControlledDispatches() = runTest(dispatchTimeoutMs = 0) { // below is some arbitrary concurrent code where all dispatches go through the same scheduler. launch { delay(2000) @@ -71,8 +73,13 @@ class RunTestTest { /** Tests that too low of a dispatch timeout causes crashes. */ @Test - fun testRunTestWithSmallTimeout() = testResultMap({ fn -> - assertFailsWith { fn() } + fun testRunTestWithSmallDispatchTimeout() = testResultMap({ fn -> + try { + fn() + fail("shouldn't be reached") + } catch (e: Throwable) { + assertIs(e) + } }) { runTest(dispatchTimeoutMs = 100) { withContext(Dispatchers.Default) { @@ -83,6 +90,48 @@ class RunTestTest { } } + /** + * Tests that [runTest] times out after the specified time. + */ + @Test + fun testRunTestWithSmallTimeout() = testResultMap({ fn -> + try { + fn() + fail("shouldn't be reached") + } catch (e: Throwable) { + assertIs(e) + } + }) { + runTest(timeout = 100.milliseconds) { + withContext(Dispatchers.Default) { + delay(10000) + 3 + } + fail("shouldn't be reached") + } + } + + /** Tests that [runTest] times out after the specified time, even if the test framework always knows the test is + * still doing something. */ + @Test + fun testRunTestWithSmallTimeoutAndManyDispatches() = testResultMap({ fn -> + try { + fn() + fail("shouldn't be reached") + } catch (e: Throwable) { + assertIs(e) + } + }) { + runTest(timeout = 100.milliseconds) { + while (true) { + withContext(Dispatchers.Default) { + delay(10) + 3 + } + } + } + } + /** Tests that, on timeout, the names of the active coroutines are listed, * whereas the names of the completed ones are not. */ @Test @@ -119,26 +168,33 @@ class RunTestTest { } catch (e: UncompletedCoroutinesError) { @Suppress("INVISIBLE_MEMBER") val suppressed = unwrap(e).suppressedExceptions - assertEquals(1, suppressed.size) + assertEquals(1, suppressed.size, "$suppressed") assertIs(suppressed[0]).also { assertEquals("A", it.message) } } }) { - runTest(dispatchTimeoutMs = 10) { - launch { - withContext(NonCancellable) { - awaitCancellation() + runTest(timeout = 10.milliseconds) { + launch(start = CoroutineStart.UNDISPATCHED) { + withContext(NonCancellable + Dispatchers.Default) { + delay(100.milliseconds) } } - yield() throw TestException("A") } } /** Tests that real delays can be accounted for with a large enough dispatch timeout. */ @Test - fun testRunTestWithLargeTimeout() = runTest(dispatchTimeoutMs = 5000) { + fun testRunTestWithLargeDispatchTimeout() = runTest(dispatchTimeoutMs = 5000) { + withContext(Dispatchers.Default) { + delay(50) + } + } + + /** Tests that delays can be accounted for with a large enough timeout. */ + @Test + fun testRunTestWithLargeTimeout() = runTest(timeout = 5000.milliseconds) { withContext(Dispatchers.Default) { delay(50) } @@ -153,13 +209,13 @@ class RunTestTest { } catch (e: UncompletedCoroutinesError) { @Suppress("INVISIBLE_MEMBER") val suppressed = unwrap(e).suppressedExceptions - assertEquals(1, suppressed.size) + assertEquals(1, suppressed.size, "$suppressed") assertIs(suppressed[0]).also { assertEquals("A", it.message) } } }) { - runTest(dispatchTimeoutMs = 1) { + runTest(timeout = 1.milliseconds) { coroutineContext[CoroutineExceptionHandler]!!.handleException(coroutineContext, TestException("A")) withContext(Dispatchers.Default) { delay(10000) @@ -324,7 +380,7 @@ class RunTestTest { } } - /** Tests that [TestCoroutineScope.runTest] does not inherit the exception handler and works. */ + /** Tests that [TestScope.runTest] does not inherit the exception handler and works. */ @Test fun testScopeRunTestExceptionHandler(): TestResult { val scope = TestScope() @@ -349,7 +405,7 @@ class RunTestTest { * The test will hang if this is not the case. */ @Test - fun testCoroutineCompletingWithoutDispatch() = runTest(dispatchTimeoutMs = Long.MAX_VALUE) { + fun testCoroutineCompletingWithoutDispatch() = runTest(timeout = Duration.INFINITE) { launch(Dispatchers.Default) { delay(100) } } } diff --git a/kotlinx-coroutines-test/common/test/StandardTestDispatcherTest.kt b/kotlinx-coroutines-test/common/test/StandardTestDispatcherTest.kt index 9e9c93e10e..280d668588 100644 --- a/kotlinx-coroutines-test/common/test/StandardTestDispatcherTest.kt +++ b/kotlinx-coroutines-test/common/test/StandardTestDispatcherTest.kt @@ -20,7 +20,7 @@ class StandardTestDispatcherTest: OrderedExecutionTestBase() { @AfterTest fun cleanup() { scope.runCurrent() - assertEquals(listOf(), scope.asSpecificImplementation().leave()) + assertEquals(listOf(), scope.asSpecificImplementation().legacyLeave()) } /** Tests that the [StandardTestDispatcher] follows an execution order similar to `runBlocking`. */ diff --git a/kotlinx-coroutines-test/common/test/TestCoroutineSchedulerTest.kt b/kotlinx-coroutines-test/common/test/TestCoroutineSchedulerTest.kt index d050e9c8c0..7203dbd270 100644 --- a/kotlinx-coroutines-test/common/test/TestCoroutineSchedulerTest.kt +++ b/kotlinx-coroutines-test/common/test/TestCoroutineSchedulerTest.kt @@ -8,6 +8,7 @@ import kotlinx.coroutines.* import kotlin.test.* import kotlin.time.* import kotlin.time.Duration.Companion.seconds +import kotlin.time.Duration.Companion.milliseconds class TestCoroutineSchedulerTest { /** Tests that `TestCoroutineScheduler` attempts to detect if there are several instances of it. */ @@ -28,7 +29,7 @@ class TestCoroutineSchedulerTest { delay(15) entered = true } - testScheduler.advanceTimeBy(15) + testScheduler.advanceTimeBy(15.milliseconds) assertFalse(entered) testScheduler.runCurrent() assertTrue(entered) @@ -39,7 +40,7 @@ class TestCoroutineSchedulerTest { fun testAdvanceTimeByWithNegativeDelay() { val scheduler = TestCoroutineScheduler() assertFailsWith { - scheduler.advanceTimeBy(-1) + scheduler.advanceTimeBy((-1).milliseconds) } } @@ -65,7 +66,7 @@ class TestCoroutineSchedulerTest { assertEquals(Long.MAX_VALUE - 1, currentTime) enteredNearInfinity = true } - testScheduler.advanceTimeBy(Long.MAX_VALUE) + testScheduler.advanceTimeBy(Duration.INFINITE) assertFalse(enteredInfinity) assertTrue(enteredNearInfinity) assertEquals(Long.MAX_VALUE, currentTime) @@ -95,10 +96,10 @@ class TestCoroutineSchedulerTest { } assertEquals(1, stage) assertEquals(0, currentTime) - advanceTimeBy(2_000) + advanceTimeBy(2.seconds) assertEquals(3, stage) assertEquals(2_000, currentTime) - advanceTimeBy(2) + advanceTimeBy(2.milliseconds) assertEquals(4, stage) assertEquals(2_002, currentTime) } @@ -120,11 +121,11 @@ class TestCoroutineSchedulerTest { delay(1) stage += 10 } - testScheduler.advanceTimeBy(1) + testScheduler.advanceTimeBy(1.milliseconds) assertEquals(0, stage) runCurrent() assertEquals(2, stage) - testScheduler.advanceTimeBy(1) + testScheduler.advanceTimeBy(1.milliseconds) assertEquals(2, stage) runCurrent() assertEquals(22, stage) @@ -143,10 +144,10 @@ class TestCoroutineSchedulerTest { delay(SLOW) stage = 3 } - scheduler.advanceTimeBy(SLOW) + scheduler.advanceTimeBy(SLOW.milliseconds) stage = 2 } - scheduler.advanceTimeBy(SLOW) + scheduler.advanceTimeBy(SLOW.milliseconds) assertEquals(1, stage) scheduler.runCurrent() assertEquals(2, stage) @@ -249,7 +250,7 @@ class TestCoroutineSchedulerTest { } } advanceUntilIdle() - asSpecificImplementation().leave().throwAll() + throwAll(null, asSpecificImplementation().legacyLeave()) if (timesOut) assertTrue(caughtException) else diff --git a/kotlinx-coroutines-test/common/test/TestScopeTest.kt b/kotlinx-coroutines-test/common/test/TestScopeTest.kt index d46e5a24bc..a21e9168f1 100644 --- a/kotlinx-coroutines-test/common/test/TestScopeTest.kt +++ b/kotlinx-coroutines-test/common/test/TestScopeTest.kt @@ -9,6 +9,7 @@ import kotlinx.coroutines.channels.* import kotlinx.coroutines.flow.* import kotlin.coroutines.* import kotlin.test.* +import kotlin.time.Duration.Companion.milliseconds class TestScopeTest { /** Tests failing to create a [TestScope] with incorrect contexts. */ @@ -95,7 +96,7 @@ class TestScopeTest { } assertFalse(result) scope.asSpecificImplementation().enter() - assertFailsWith { scope.asSpecificImplementation().leave() } + assertFailsWith { scope.asSpecificImplementation().legacyLeave() } assertFalse(result) } @@ -111,7 +112,7 @@ class TestScopeTest { } assertFalse(result) scope.asSpecificImplementation().enter() - assertFailsWith { scope.asSpecificImplementation().leave() } + assertFailsWith { scope.asSpecificImplementation().legacyLeave() } assertFalse(result) } @@ -128,7 +129,7 @@ class TestScopeTest { job.cancel() assertFalse(result) scope.asSpecificImplementation().enter() - assertFailsWith { scope.asSpecificImplementation().leave() } + assertFailsWith { scope.asSpecificImplementation().legacyLeave() } assertFalse(result) } @@ -162,7 +163,7 @@ class TestScopeTest { launch(SupervisorJob()) { throw TestException("y") } launch(SupervisorJob()) { throw TestException("z") } runCurrent() - val e = asSpecificImplementation().leave() + val e = asSpecificImplementation().legacyLeave() assertEquals(3, e.size) assertEquals("x", e[0].message) assertEquals("y", e[1].message) @@ -249,7 +250,7 @@ class TestScopeTest { assertEquals(1, j) } job.join() - advanceTimeBy(199) // should work the same for the background tasks + advanceTimeBy(199.milliseconds) // should work the same for the background tasks assertEquals(2, i) assertEquals(4, j) advanceUntilIdle() // once again, should do nothing @@ -377,7 +378,7 @@ class TestScopeTest { } }) { - runTest(dispatchTimeoutMs = 100) { + runTest(timeout = 100.milliseconds) { backgroundScope.launch { while (true) { yield() @@ -407,7 +408,7 @@ class TestScopeTest { } }) { - runTest(UnconfinedTestDispatcher(), dispatchTimeoutMs = 100) { + runTest(UnconfinedTestDispatcher(), timeout = 100.milliseconds) { /** * Having a coroutine like this will still cause the test to hang: backgroundScope.launch { diff --git a/kotlinx-coroutines-test/js/src/TestBuilders.kt b/kotlinx-coroutines-test/js/src/TestBuilders.kt index 9da91ffc39..97c9da0eee 100644 --- a/kotlinx-coroutines-test/js/src/TestBuilders.kt +++ b/kotlinx-coroutines-test/js/src/TestBuilders.kt @@ -13,3 +13,5 @@ internal actual fun createTestResult(testProcedure: suspend CoroutineScope.() -> GlobalScope.promise { testProcedure() } + +internal actual fun dumpCoroutines() { } diff --git a/kotlinx-coroutines-test/jvm/src/TestBuildersJvm.kt b/kotlinx-coroutines-test/jvm/src/TestBuildersJvm.kt index 06fbe81064..0521fd22ae 100644 --- a/kotlinx-coroutines-test/jvm/src/TestBuildersJvm.kt +++ b/kotlinx-coroutines-test/jvm/src/TestBuildersJvm.kt @@ -4,6 +4,7 @@ package kotlinx.coroutines.test import kotlinx.coroutines.* +import kotlinx.coroutines.debug.internal.* @Suppress("ACTUAL_WITHOUT_EXPECT") public actual typealias TestResult = Unit @@ -13,3 +14,16 @@ internal actual fun createTestResult(testProcedure: suspend CoroutineScope.() -> testProcedure() } } + +internal actual fun dumpCoroutines() { + @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + if (DebugProbesImpl.isInstalled) { + DebugProbesImpl.install() + try { + DebugProbesImpl.dumpCoroutines(System.err) + System.err.flush() + } finally { + DebugProbesImpl.uninstall() + } + } +} diff --git a/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt b/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt index c0d6c17cb6..c7ef9cc8e7 100644 --- a/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt +++ b/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt @@ -50,10 +50,12 @@ import kotlin.time.Duration.Companion.milliseconds * then they must implement [DelayController] and [TestCoroutineExceptionHandler] respectively. * @param testBody The code of the unit-test. */ -@Deprecated("Use `runTest` instead to support completing from other dispatchers. " + - "Please see the migration guide for details: " + - "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md", - level = DeprecationLevel.WARNING) +@Deprecated( + "Use `runTest` instead to support completing from other dispatchers. " + + "Please see the migration guide for details: " + + "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md", + level = DeprecationLevel.WARNING +) // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 public fun runBlockingTest( context: CoroutineContext = EmptyCoroutineContext, @@ -91,20 +93,20 @@ public fun runBlockingTestOnTestScope( val throwable = try { scope.getCompletionExceptionOrNull() } catch (e: IllegalStateException) { - null // the deferred was not completed yet; `scope.leave()` should complain then about unfinished jobs + null // the deferred was not completed yet; `scope.legacyLeave()` should complain then about unfinished jobs } scope.backgroundScope.cancel() scope.testScheduler.advanceUntilIdleOr { false } throwable?.let { val exceptions = try { - scope.leave() + scope.legacyLeave() } catch (e: UncompletedCoroutinesError) { listOf() } - (listOf(it) + exceptions).throwAll() + throwAll(it, exceptions) return } - scope.leave().throwAll() + throwAll(null, scope.legacyLeave()) val jobs = completeContext.activeJobs() - startJobs if (jobs.isNotEmpty()) throw UncompletedCoroutinesError("Some jobs were not completed at the end of the test: $jobs") @@ -118,10 +120,12 @@ public fun runBlockingTestOnTestScope( * [migration guide](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md) * for an instruction on how to update the code for the new API. */ -@Deprecated("Use `runTest` instead to support completing from other dispatchers. " + - "Please see the migration guide for details: " + - "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md", - level = DeprecationLevel.WARNING) +@Deprecated( + "Use `runTest` instead to support completing from other dispatchers. " + + "Please see the migration guide for details: " + + "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md", + level = DeprecationLevel.WARNING +) // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 public fun TestCoroutineScope.runBlockingTest(block: suspend TestCoroutineScope.() -> Unit): Unit = runBlockingTest(coroutineContext, block) @@ -142,10 +146,12 @@ public fun TestScope.runBlockingTest(block: suspend TestScope.() -> Unit): Unit * [migration guide](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md) * for an instruction on how to update the code for the new API. */ -@Deprecated("Use `runTest` instead to support completing from other dispatchers. " + - "Please see the migration guide for details: " + - "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md", - level = DeprecationLevel.WARNING) +@Deprecated( + "Use `runTest` instead to support completing from other dispatchers. " + + "Please see the migration guide for details: " + + "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md", + level = DeprecationLevel.WARNING +) // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 public fun TestCoroutineDispatcher.runBlockingTest(block: suspend TestCoroutineScope.() -> Unit): Unit = runBlockingTest(this, block) @@ -165,7 +171,12 @@ public fun runTestWithLegacyScope( throw IllegalStateException("Calls to `runTest` can't be nested. Please read the docs on `TestResult` for details.") val testScope = TestBodyCoroutine(createTestCoroutineScope(context + RunningInRunTest)) return createTestResult { - runTestCoroutine(testScope, dispatchTimeoutMs.milliseconds, TestBodyCoroutine::tryGetCompletionCause, testBody) { + runTestCoroutineLegacy( + testScope, + dispatchTimeoutMs.milliseconds, + TestBodyCoroutine::tryGetCompletionCause, + testBody + ) { try { testScope.cleanup() emptyList() diff --git a/kotlinx-coroutines-test/jvm/test/DumpOnTimeoutTest.kt b/kotlinx-coroutines-test/jvm/test/DumpOnTimeoutTest.kt new file mode 100644 index 0000000000..814e5f0667 --- /dev/null +++ b/kotlinx-coroutines-test/jvm/test/DumpOnTimeoutTest.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.test + +import kotlinx.coroutines.* +import kotlinx.coroutines.debug.* +import org.junit.Test +import java.io.* +import kotlin.test.* +import kotlin.time.Duration.Companion.milliseconds + +class DumpOnTimeoutTest { + /** + * Tests that the dump on timeout contains the correct stacktrace. + */ + @Test + fun testDumpOnTimeout() { + val oldErr = System.err + val baos = ByteArrayOutputStream() + try { + System.setErr(PrintStream(baos, true)) + DebugProbes.withDebugProbes { + try { + runTest(timeout = 100.milliseconds) { + uniquelyNamedFunction() + } + throw IllegalStateException("unreachable") + } catch (e: UncompletedCoroutinesError) { + // do nothing + } + } + baos.toString().let { + assertTrue(it.contains("uniquelyNamedFunction"), "Actual trace:\n$it") + } + } finally { + System.setErr(oldErr) + } + } + + fun CoroutineScope.uniquelyNamedFunction() { + while (true) { + ensureActive() + Thread.sleep(10) + } + } +} diff --git a/kotlinx-coroutines-test/jvm/test/MultithreadingTest.kt b/kotlinx-coroutines-test/jvm/test/MultithreadingTest.kt index 90a16d0622..2ac577c41b 100644 --- a/kotlinx-coroutines-test/jvm/test/MultithreadingTest.kt +++ b/kotlinx-coroutines-test/jvm/test/MultithreadingTest.kt @@ -99,14 +99,24 @@ class MultithreadingTest { } } - /** Tests that [StandardTestDispatcher] is confined to the thread that interacts with the scheduler. */ + /** Tests that [StandardTestDispatcher] is not executed in-place but confined to the thread in which the + * virtual time control happens. */ @Test - fun testStandardTestDispatcherIsConfined() = runTest { + fun testStandardTestDispatcherIsConfined(): Unit = runBlocking { + val scheduler = TestCoroutineScheduler() val initialThread = Thread.currentThread() - withContext(Dispatchers.IO) { - val ioThread = Thread.currentThread() - assertNotSame(initialThread, ioThread) + val job = launch(StandardTestDispatcher(scheduler)) { + assertEquals(initialThread, Thread.currentThread()) + withContext(Dispatchers.IO) { + val ioThread = Thread.currentThread() + assertNotSame(initialThread, ioThread) + } + assertEquals(initialThread, Thread.currentThread()) + } + scheduler.advanceUntilIdle() + while (job.isActive) { + scheduler.receiveDispatchEvent() + scheduler.advanceUntilIdle() } - assertEquals(initialThread, Thread.currentThread()) } } diff --git a/kotlinx-coroutines-test/native/src/TestBuilders.kt b/kotlinx-coroutines-test/native/src/TestBuilders.kt index a959901919..607dec6a73 100644 --- a/kotlinx-coroutines-test/native/src/TestBuilders.kt +++ b/kotlinx-coroutines-test/native/src/TestBuilders.kt @@ -4,6 +4,7 @@ package kotlinx.coroutines.test import kotlinx.coroutines.* +import kotlin.native.concurrent.* @Suppress("ACTUAL_WITHOUT_EXPECT") public actual typealias TestResult = Unit @@ -13,3 +14,5 @@ internal actual fun createTestResult(testProcedure: suspend CoroutineScope.() -> testProcedure() } } + +internal actual fun dumpCoroutines() { } From c5f73ba4f64bf78e521e90486edcf9f979ea3733 Mon Sep 17 00:00:00 2001 From: mvicsokolova Date: Tue, 21 Feb 2023 15:39:52 +0100 Subject: [PATCH 087/106] Update atomicfu to 0.20.0 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index a9c85f5542..cb1ff7b9e6 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,7 +10,7 @@ kotlin_version=1.8.10 # Dependencies junit_version=4.12 junit5_version=5.7.0 -atomicfu_version=0.19.0 +atomicfu_version=0.20.0 knit_version=0.4.0 html_version=0.7.2 lincheck_version=2.16 From 70149615c6ee466bda0e2e3d7c98eba8c5355425 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Wed, 22 Feb 2023 12:20:15 +0300 Subject: [PATCH 088/106] Update the documentation for runTest (#3632) --- .../common/src/CoroutineExceptionHandler.kt | 17 ++++++----- .../common/src/TestBuilders.kt | 30 ++++++++++--------- 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt b/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt index 49923a92e7..d13ef67c66 100644 --- a/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt +++ b/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt @@ -83,15 +83,16 @@ public inline fun CoroutineExceptionHandler(crossinline handler: (CoroutineConte * } * ``` * - * ### Implementation details + * ### Uncaught exceptions with no handler * - * By default, when no handler is installed, uncaught exception are handled in the following way: - * * If exception is [CancellationException] then it is ignored - * (because that is the supposed mechanism to cancel the running coroutine) - * * Otherwise: - * * if there is a [Job] in the context, then [Job.cancel] is invoked; - * * Otherwise, all instances of [CoroutineExceptionHandler] found via [ServiceLoader] - * * and current thread's [Thread.uncaughtExceptionHandler] are invoked. + * When no handler is installed, exception are handled in the following way: + * * If exception is [CancellationException], it is ignored, as these exceptions are used to cancel coroutines. + * * Otherwise, if there is a [Job] in the context, then [Job.cancel] is invoked. + * * Otherwise, as a last resort, the exception is processed in a platform-specific manner: + * - On JVM, all instances of [CoroutineExceptionHandler] found via [ServiceLoader], as well as + * the current thread's [Thread.uncaughtExceptionHandler], are invoked. + * - On Native, the whole application crashes with the exception. + * - On JS, the exception is logged via the Console API. * * [CoroutineExceptionHandler] can be invoked from an arbitrary thread. */ diff --git a/kotlinx-coroutines-test/common/src/TestBuilders.kt b/kotlinx-coroutines-test/common/src/TestBuilders.kt index de16967a41..3c45f86096 100644 --- a/kotlinx-coroutines-test/common/src/TestBuilders.kt +++ b/kotlinx-coroutines-test/common/src/TestBuilders.kt @@ -39,7 +39,7 @@ public expect class TestResult * Executes [testBody] as a test in a new coroutine, returning [TestResult]. * * On JVM and Native, this function behaves similarly to `runBlocking`, with the difference that the code that it runs - * will skip delays. This allows to use [delay] in without causing the tests to take more time than necessary. + * will skip delays. This allows to use [delay] in tests without causing them to take more time than necessary. * On JS, this function creates a `Promise` that executes the test body with the delay-skipping behavior. * * ``` @@ -60,7 +60,7 @@ public expect class TestResult * immediately return the produced [TestResult] from the test method, without doing anything else afterwards. See * [TestResult] for details on this. * - * The test is run in a single thread, unless other [CoroutineDispatcher] are used for child coroutines. + * The test is run on a single thread, unless other [CoroutineDispatcher] are used for child coroutines. * Because of this, child coroutines are not executed in parallel to the test body. * In order for the spawned-off asynchronous code to actually be executed, one must either [yield] or suspend the * test body some other way, or use commands that control scheduling (see [TestCoroutineScheduler]). @@ -84,19 +84,19 @@ public expect class TestResult * // 3 * } * // 2 - * advanceUntilIdle() // runs the tasks until their queue is empty + * testScheduler.advanceUntilIdle() // runs the tasks until their queue is empty * // 4 * } * ``` * * ### Task scheduling * - * Delay-skipping is achieved by using virtual time. + * Delay skipping is achieved by using virtual time. * If [Dispatchers.Main] is set to a [TestDispatcher] via [Dispatchers.setMain] before the test, * then its [TestCoroutineScheduler] is used; * otherwise, a new one is automatically created (or taken from [context] in some way) and can be used to control * the virtual time, advancing it, running the tasks scheduled at a specific time etc. - * Some convenience methods are available on [TestScope] to control the scheduler. + * The scheduler can be accessed via [TestScope.testScheduler]. * * Delays in code that runs inside dispatchers that don't use a [TestCoroutineScheduler] don't get skipped: * ``` @@ -126,25 +126,27 @@ public expect class TestResult * There's a built-in timeout of 10 seconds for the test body. If the test body doesn't complete within this time, * then the test fails with an [AssertionError]. The timeout can be changed by setting the [timeout] parameter. * - * The test finishes by the timeout procedure cancelling the test body. If the code inside the test body does not - * respond to cancellation, we will not be able to make the test execution stop, in which case, the test will hang - * despite our best efforts to terminate it. + * On timeout, the test body is cancelled so that the test finishes. If the code inside the test body does not + * respond to cancellation, the timeout will not be able to make the test execution stop. + * In that case, the test will hang despite the attempt to terminate it. * * On the JVM, if `DebugProbes` from the `kotlinx-coroutines-debug` module are installed, the current dump of the - * coroutines' stack is printed to the console on timeout. + * coroutines' stack is printed to the console on timeout before the test body is cancelled. * * #### Reported exceptions * * Unhandled exceptions will be thrown at the end of the test. - * If the uncaught exceptions happen after the test finishes, the error is propagated in a platform-specific manner. + * If uncaught exceptions happen after the test finishes, they are propagated in a platform-specific manner: + * see [handleCoroutineException] for details. * If the test coroutine completes with an exception, the unhandled exceptions are suppressed by it. * * #### Uncompleted coroutines * - * This method requires that, after the test coroutine has completed, all the other coroutines launched inside - * [testBody] also complete, or are cancelled. - * Otherwise, the test will be failed (which, on JVM and Native, means that [runTest] itself will throw - * [AssertionError], whereas on JS, the `Promise` will fail with it). + * Otherwise, the test will hang until all the coroutines launched inside [testBody] complete. + * This may be an issue when there are some coroutines that are not supposed to complete, like infinite loops that + * perform some background work and are supposed to outlive the test. + * In that case, [TestScope.backgroundScope] can be used to launch such coroutines. + * They will be cancelled automatically when the test finishes. * * ### Configuration * From bf03c4831d56aa580822d4f2785feade51d6bd4e Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Wed, 22 Feb 2023 15:36:22 +0300 Subject: [PATCH 089/106] Remove some ExperimentalCoroutinesApi markers in the test module (#3622) --- kotlinx-coroutines-test/common/src/TestBuilders.kt | 3 --- .../common/src/TestCoroutineDispatchers.kt | 1 - .../common/src/TestCoroutineScheduler.kt | 7 +------ kotlinx-coroutines-test/common/src/TestDispatcher.kt | 2 -- kotlinx-coroutines-test/common/src/TestScope.kt | 4 ---- 5 files changed, 1 insertion(+), 16 deletions(-) diff --git a/kotlinx-coroutines-test/common/src/TestBuilders.kt b/kotlinx-coroutines-test/common/src/TestBuilders.kt index 3c45f86096..677450ba89 100644 --- a/kotlinx-coroutines-test/common/src/TestBuilders.kt +++ b/kotlinx-coroutines-test/common/src/TestBuilders.kt @@ -32,7 +32,6 @@ import kotlinx.coroutines.internal.* * * Don't nest functions returning a [TestResult]. */ @Suppress("NO_ACTUAL_FOR_EXPECT") -@ExperimentalCoroutinesApi public expect class TestResult /** @@ -156,7 +155,6 @@ public expect class TestResult * * @throws IllegalArgumentException if the [context] is invalid. See the [TestScope] constructor docs for details. */ -@ExperimentalCoroutinesApi public fun runTest( context: CoroutineContext = EmptyCoroutineContext, timeout: Duration = DEFAULT_TIMEOUT, @@ -302,7 +300,6 @@ public fun runTest( /** * Performs [runTest] on an existing [TestScope]. See the documentation for [runTest] for details. */ -@ExperimentalCoroutinesApi public fun TestScope.runTest( timeout: Duration = DEFAULT_TIMEOUT, testBody: suspend TestScope.() -> Unit diff --git a/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt b/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt index e99fe8b124..3777cd26f8 100644 --- a/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt +++ b/kotlinx-coroutines-test/common/src/TestCoroutineDispatchers.kt @@ -137,7 +137,6 @@ private class UnconfinedTestDispatcherImpl( * * @see UnconfinedTestDispatcher for a dispatcher that is not confined to any particular thread. */ -@ExperimentalCoroutinesApi @Suppress("FunctionName") public fun StandardTestDispatcher( scheduler: TestCoroutineScheduler? = null, diff --git a/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt b/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt index cdb669c0b3..102498bad5 100644 --- a/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt +++ b/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt @@ -27,7 +27,6 @@ import kotlin.time.Duration.Companion.milliseconds * virtual time as needed (via [advanceUntilIdle]), or run the tasks that are scheduled to run as soon as possible but * haven't yet been dispatched (via [runCurrent]). */ -@ExperimentalCoroutinesApi public class TestCoroutineScheduler : AbstractCoroutineContextElement(TestCoroutineScheduler), CoroutineContext.Element { @@ -109,11 +108,10 @@ public class TestCoroutineScheduler : AbstractCoroutineContextElement(TestCorout * Runs the enqueued tasks in the specified order, advancing the virtual time as needed until there are no more * tasks associated with the dispatchers linked to this scheduler. * - * A breaking change from [TestCoroutineDispatcher.advanceTimeBy] is that it no longer returns the total number of + * A breaking change from `TestCoroutineDispatcher.advanceTimeBy` is that it no longer returns the total number of * milliseconds by which the execution of this method has advanced the virtual time. If you want to recreate that * functionality, query [currentTime] before and after the execution to achieve the same result. */ - @ExperimentalCoroutinesApi public fun advanceUntilIdle(): Unit = advanceUntilIdleOr { events.none(TestDispatchEvent<*>::isForeground) } /** @@ -129,7 +127,6 @@ public class TestCoroutineScheduler : AbstractCoroutineContextElement(TestCorout /** * Runs the tasks that are scheduled to execute at this moment of virtual time. */ - @ExperimentalCoroutinesApi public fun runCurrent() { val timeMark = synchronized(lock) { currentTime } while (true) { @@ -165,7 +162,6 @@ public class TestCoroutineScheduler : AbstractCoroutineContextElement(TestCorout * * @throws IllegalArgumentException if passed a negative [delay][delayTime]. */ - @ExperimentalCoroutinesApi public fun advanceTimeBy(delayTime: Duration) { require(!delayTime.isNegative()) { "Can not advance time by a negative delay: $delayTime" } val startingTime = currentTime @@ -227,7 +223,6 @@ public class TestCoroutineScheduler : AbstractCoroutineContextElement(TestCorout /** * Returns the [TimeSource] representation of the virtual time of this scheduler. */ - @ExperimentalCoroutinesApi @ExperimentalTime public val timeSource: TimeSource = object : AbstractLongTimeSource(DurationUnit.MILLISECONDS) { override fun read(): Long = currentTime diff --git a/kotlinx-coroutines-test/common/src/TestDispatcher.kt b/kotlinx-coroutines-test/common/src/TestDispatcher.kt index 9f8d6477f9..b027131c82 100644 --- a/kotlinx-coroutines-test/common/src/TestDispatcher.kt +++ b/kotlinx-coroutines-test/common/src/TestDispatcher.kt @@ -17,11 +17,9 @@ import kotlin.time.* * * [UnconfinedTestDispatcher] is a dispatcher that behaves like [Dispatchers.Unconfined] while allowing to control * the virtual time. */ -@ExperimentalCoroutinesApi @Suppress("INVISIBLE_REFERENCE") public abstract class TestDispatcher internal constructor() : CoroutineDispatcher(), Delay, DelayWithTimeoutDiagnostics { /** The scheduler that this dispatcher is linked to. */ - @ExperimentalCoroutinesApi public abstract val scheduler: TestCoroutineScheduler /** Notifies the dispatcher that it should process a single event marked with [marker] happening at time [time]. */ diff --git a/kotlinx-coroutines-test/common/src/TestScope.kt b/kotlinx-coroutines-test/common/src/TestScope.kt index a301ff966b..a0a29a9553 100644 --- a/kotlinx-coroutines-test/common/src/TestScope.kt +++ b/kotlinx-coroutines-test/common/src/TestScope.kt @@ -40,12 +40,10 @@ import kotlin.time.* * paused by default, like [StandardTestDispatcher]. * * No access to the list of unhandled exceptions. */ -@ExperimentalCoroutinesApi public sealed interface TestScope : CoroutineScope { /** * The delay-skipping scheduler used by the test dispatchers running the code in this scope. */ - @ExperimentalCoroutinesApi public val testScheduler: TestCoroutineScheduler /** @@ -82,7 +80,6 @@ public sealed interface TestScope : CoroutineScope { * } * ``` */ - @ExperimentalCoroutinesApi public val backgroundScope: CoroutineScope } @@ -166,7 +163,6 @@ public val TestScope.testTimeSource: TimeSource get() = testScheduler.timeSource * @throws IllegalArgumentException if [context] has an [CoroutineExceptionHandler] that is not an * [UncaughtExceptionCaptor]. */ -@ExperimentalCoroutinesApi @Suppress("FunctionName") public fun TestScope(context: CoroutineContext = EmptyCoroutineContext): TestScope { val ctxWithDispatcher = context.withDelaySkipping() From 1ed19c872e3c283354349ced921d5f039048d4e1 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 23 Feb 2023 00:38:01 +0300 Subject: [PATCH 090/106] Introduce first version of Dispatchers.IO for K/N (#3576) * Emulate expect declaration refinement via extension property as the only way to do it in a backwards-compatible manner: in the current model, it is impossible to have common 'expect' Dispatchers declaration, then refined 'concurrent' Dispatchers declaration with 'expect val IO' and then JVM declaration with JVM-specific members. Current solutions seems to be the less intrusive one Fixes #3205 --- README.md | 2 +- .../api/kotlinx-coroutines-core.api | 1 + .../common/src/Dispatchers.common.kt | 2 +- .../concurrent/src/Dispatchers.kt | 37 +++++++++++++++++++ ...t => DefaultDispatchersConcurrencyTest.kt} | 4 ++ .../jvm/src/Dispatchers.kt | 14 +++++-- .../native/src/Dispatchers.kt | 32 ++++++++++++++++ 7 files changed, 87 insertions(+), 5 deletions(-) create mode 100644 kotlinx-coroutines-core/concurrent/src/Dispatchers.kt rename kotlinx-coroutines-core/concurrent/test/{DefaultDispatcherConcurrencyTest.kt => DefaultDispatchersConcurrencyTest.kt} (66%) diff --git a/README.md b/README.md index 820693d23a..ec5eeca3e5 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ See [Contributing Guidelines](CONTRIBUTING.md). [MainScope()]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html [SupervisorJob()]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-supervisor-job.html [CoroutineExceptionHandler]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html -[Dispatchers.IO]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-i-o.html +[Dispatchers.IO]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-i-o.html [asCoroutineDispatcher]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/as-coroutine-dispatcher.html [Promise.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/await.html [promise]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/promise.html diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index def21f8130..e880916cc6 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -302,6 +302,7 @@ public final class kotlinx/coroutines/Dispatchers { public final class kotlinx/coroutines/DispatchersKt { public static final field IO_PARALLELISM_PROPERTY_NAME Ljava/lang/String; + public static final synthetic fun getIO (Lkotlinx/coroutines/Dispatchers;)Lkotlinx/coroutines/CoroutineDispatcher; } public abstract interface class kotlinx/coroutines/DisposableHandle { diff --git a/kotlinx-coroutines-core/common/src/Dispatchers.common.kt b/kotlinx-coroutines-core/common/src/Dispatchers.common.kt index f3d4bc2cc5..a5fe275b8a 100644 --- a/kotlinx-coroutines-core/common/src/Dispatchers.common.kt +++ b/kotlinx-coroutines-core/common/src/Dispatchers.common.kt @@ -12,7 +12,7 @@ import kotlin.coroutines.* public expect object Dispatchers { /** * The default [CoroutineDispatcher] that is used by all standard builders like - * [launch][CoroutineScope.launch], [async][CoroutineScope.async], etc + * [launch][CoroutineScope.launch], [async][CoroutineScope.async], etc. * if neither a dispatcher nor any other [ContinuationInterceptor] is specified in their context. * * It is backed by a shared pool of threads on JVM and Native. By default, the maximum number of threads used diff --git a/kotlinx-coroutines-core/concurrent/src/Dispatchers.kt b/kotlinx-coroutines-core/concurrent/src/Dispatchers.kt new file mode 100644 index 0000000000..8a937e38fc --- /dev/null +++ b/kotlinx-coroutines-core/concurrent/src/Dispatchers.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +/** + * The [CoroutineDispatcher] that is designed for offloading blocking IO tasks to a shared pool of threads. + * Additional threads in this pool are created on demand. + * Default IO pool size is `64`; on JVM it can be configured using JVM-specific mechanisms, + * please refer to `Dispatchers.IO` documentation on JVM platform. + * + * ### Elasticity for limited parallelism + * + * `Dispatchers.IO` has a unique property of elasticity: its views + * obtained with [CoroutineDispatcher.limitedParallelism] are + * not restricted by the `Dispatchers.IO` parallelism. Conceptually, there is + * a dispatcher backed by an unlimited pool of threads, and both `Dispatchers.IO` + * and views of `Dispatchers.IO` are actually views of that dispatcher. In practice + * this means that, despite not abiding by `Dispatchers.IO`'s parallelism + * restrictions, its views share threads and resources with it. + * + * In the following example + * ``` + * // 100 threads for MySQL connection + * val myMysqlDbDispatcher = Dispatchers.IO.limitedParallelism(100) + * // 60 threads for MongoDB connection + * val myMongoDbDispatcher = Dispatchers.IO.limitedParallelism(60) + * ``` + * the system may have up to `64 + 100 + 60` threads dedicated to blocking tasks during peak loads, + * but during its steady state there is only a small number of threads shared + * among `Dispatchers.IO`, `myMysqlDbDispatcher` and `myMongoDbDispatcher` + */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +public expect val Dispatchers.IO: CoroutineDispatcher + + diff --git a/kotlinx-coroutines-core/concurrent/test/DefaultDispatcherConcurrencyTest.kt b/kotlinx-coroutines-core/concurrent/test/DefaultDispatchersConcurrencyTest.kt similarity index 66% rename from kotlinx-coroutines-core/concurrent/test/DefaultDispatcherConcurrencyTest.kt rename to kotlinx-coroutines-core/concurrent/test/DefaultDispatchersConcurrencyTest.kt index a12930cc12..66dfd07ab0 100644 --- a/kotlinx-coroutines-core/concurrent/test/DefaultDispatcherConcurrencyTest.kt +++ b/kotlinx-coroutines-core/concurrent/test/DefaultDispatchersConcurrencyTest.kt @@ -6,3 +6,7 @@ package kotlinx.coroutines class DefaultDispatcherConcurrencyTest : AbstractDispatcherConcurrencyTest() { override val dispatcher: CoroutineDispatcher = Dispatchers.Default } + +class IoDispatcherConcurrencyTest : AbstractDispatcherConcurrencyTest() { + override val dispatcher: CoroutineDispatcher = Dispatchers.IO +} diff --git a/kotlinx-coroutines-core/jvm/src/Dispatchers.kt b/kotlinx-coroutines-core/jvm/src/Dispatchers.kt index 6a4724e325..2222f0dabb 100644 --- a/kotlinx-coroutines-core/jvm/src/Dispatchers.kt +++ b/kotlinx-coroutines-core/jvm/src/Dispatchers.kt @@ -1,9 +1,7 @@ /* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("unused") - package kotlinx.coroutines import kotlinx.coroutines.internal.* @@ -97,3 +95,13 @@ public actual object Dispatchers { DefaultScheduler.shutdown() } } + +/** + * `actual` counterpart of the corresponding `expect` declaration. + * Should never be used directly from JVM sources, all accesses + * to `Dispatchers.IO` should be resolved to the corresponding member of [Dispatchers] object. + * @suppress + */ +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Deprecated(message = "Should not be used directly", level = DeprecationLevel.HIDDEN) +public actual val Dispatchers.IO: CoroutineDispatcher get() = Dispatchers.IO diff --git a/kotlinx-coroutines-core/native/src/Dispatchers.kt b/kotlinx-coroutines-core/native/src/Dispatchers.kt index 9f990534cc..2576ba5cf0 100644 --- a/kotlinx-coroutines-core/native/src/Dispatchers.kt +++ b/kotlinx-coroutines-core/native/src/Dispatchers.kt @@ -4,6 +4,9 @@ package kotlinx.coroutines +import kotlinx.coroutines.internal.* +import kotlin.coroutines.* + public actual object Dispatchers { public actual val Default: CoroutineDispatcher = createDefaultDispatcher() @@ -19,6 +22,35 @@ public actual object Dispatchers { internal fun injectMain(dispatcher: MainCoroutineDispatcher) { injectedMainDispatcher = dispatcher } + + internal val IO: CoroutineDispatcher = DefaultIoScheduler } +internal object DefaultIoScheduler : CoroutineDispatcher() { + // 2048 is an arbitrary KMP-friendly constant + private val unlimitedPool = newFixedThreadPoolContext(2048, "Dispatchers.IO") + private val io = unlimitedPool.limitedParallelism(64) // Default JVM size + + @ExperimentalCoroutinesApi + override fun limitedParallelism(parallelism: Int): CoroutineDispatcher { + // See documentation to Dispatchers.IO for the rationale + return unlimitedPool.limitedParallelism(parallelism) + } + + override fun dispatch(context: CoroutineContext, block: Runnable) { + io.dispatch(context, block) + } + + @InternalCoroutinesApi + override fun dispatchYield(context: CoroutineContext, block: Runnable) { + io.dispatchYield(context, block) + } + + override fun toString(): String = "Dispatchers.IO" +} + + +@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +public actual val Dispatchers.IO: CoroutineDispatcher get() = IO + internal expect fun createMainDispatcher(default: CoroutineDispatcher): MainCoroutineDispatcher From 747db9e47c44eeb2de4830cde34e0fde6f40b44a Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Thu, 23 Feb 2023 19:13:43 +0300 Subject: [PATCH 091/106] Change the contract of CoroutineContext.isActive to return 'true' for contexts with no job in it. (#3639) Otherwise, the API is becoming an error prone for being called from jobless entrypoints (i.e. 'suspend fun main' or Ktor handlers), as 'if (ctx.isActive)' is a well-established pattern for busy-wait or synchronous job. It is now aligned with CoroutineScope.isActive behaviour. Fixes #3300 --- kotlinx-coroutines-core/common/src/Job.kt | 6 +++--- .../common/test/CoroutineScopeTest.kt | 11 +++++++++++ 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/Job.kt b/kotlinx-coroutines-core/common/src/Job.kt index 65646dc59f..5f40dfc194 100644 --- a/kotlinx-coroutines-core/common/src/Job.kt +++ b/kotlinx-coroutines-core/common/src/Job.kt @@ -539,7 +539,7 @@ public fun Job.cancelChildren(cause: Throwable? = null) { /** * Returns `true` when the [Job] of the coroutine in this context is still active - * (has not completed and was not cancelled yet). + * (has not completed and was not cancelled yet) or the context does not have a [Job] in it. * * Check this property in long-running computation loops to support cancellation * when [CoroutineScope.isActive] is not available: @@ -550,11 +550,11 @@ public fun Job.cancelChildren(cause: Throwable? = null) { * } * ``` * - * The `coroutineContext.isActive` expression is a shortcut for `coroutineContext[Job]?.isActive == true`. + * The `coroutineContext.isActive` expression is a shortcut for `get(Job)?.isActive ?: true`. * See [Job.isActive]. */ public val CoroutineContext.isActive: Boolean - get() = this[Job]?.isActive == true + get() = get(Job)?.isActive ?: true /** * Cancels [Job] of this context with an optional cancellation cause. diff --git a/kotlinx-coroutines-core/common/test/CoroutineScopeTest.kt b/kotlinx-coroutines-core/common/test/CoroutineScopeTest.kt index c46f41a073..b678b03c7a 100644 --- a/kotlinx-coroutines-core/common/test/CoroutineScopeTest.kt +++ b/kotlinx-coroutines-core/common/test/CoroutineScopeTest.kt @@ -277,4 +277,15 @@ class CoroutineScopeTest : TestBase() { private fun scopePlusContext(c1: CoroutineContext, c2: CoroutineContext) = (ContextScope(c1) + c2).coroutineContext + + @Test + fun testIsActiveWithoutJob() { + var invoked = false + suspend fun testIsActive() { + assertTrue(coroutineContext.isActive) + invoked = true + } + ::testIsActive.startCoroutine(Continuation(EmptyCoroutineContext){}) + assertTrue(invoked) + } } From 1245d7edb0ec61848b620691406b79c370d706d5 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Fri, 24 Feb 2023 20:45:49 +0300 Subject: [PATCH 092/106] Attempt to report uncaught exceptions in the test module (#3449) When when a `Job` doesn't have a parent, failures in it can not be reported via structured concurrency. Instead, the mechanism of unhandled exceptions is used. If there is a `CoroutineExceptionHandler` in the coroutine context or registered as a `ServiceLoader` service, this gets notified, and otherwise, something platform-specific happens: on Native, the program crashes, and on the JVM, by default, the exception is just logged, though this is configurable via `Thread.setUncaughtExceptionHandler`. With tests on the JVM, this is an issue: we want exceptions not simply *logged*, we want them to fail the test, and this extends beyond just coroutines. However, JUnit does not override the uncaught exception handler, and uncaught exceptions do just get logged: This is a problem with a widely-used `viewModelScope` on Android. This is a scope without a parent, and so the exceptions in it are uncaught. On Android, such uncaught exceptions crash the app by default, but in tests, they just get logged. Clearly, without overriding the behavior of uncaught exceptions, the tests are lacking. This can be solved on the test writers' side via `setUncaughtExceptionHandler`, but one has to remember to do that. In this commit, we attempt to solve this issue for the overwhelming majority of users. To that end, when the test framework is used, we collect the uncaught exceptions and report them at the end of a test. This approach is marginally less robust than `setUncaughtExceptionHandler`: if an exception happened after the last test using `kotlinx-coroutines-test`, it won't get reported, for example. `CoroutineExceptionHandler` is designed in such a way that, when it is used in a coroutine context, its presence means that the exceptions are safe in its care and will not be propagated further, but when used as a service, it has no such property. We, however, know that our `CoroutineExceptionHandler` reports the exceptions properly and they don't need to be further logged, and so we had to extend the behavior of mechanism for uncaught exception handling so that the handler throws a new kind of exception if the exception was processed successfully. Also, because there's no `ServiceLoader` mechanism on JS or Native, we had to refactor the whole uncaught exception handling mechanism a bit in the same vein as we had to adapt the `Main` dispatcher to `Dispatchers.setMain`: by introducing internal setter APIs that services have to call manually to register in. Fixes #1205 as thoroughly as we can, given the circumstances. --- ...ListAllCoroutineThrowableSubclassesTest.kt | 3 +- .../common/src/CoroutineExceptionHandler.kt | 7 +- .../CoroutineExceptionHandlerImpl.common.kt | 72 ++++++++++++++ .../js/src/CoroutineExceptionHandlerImpl.kt | 12 --- .../internal/CoroutineExceptionHandlerImpl.kt | 26 +++++ .../jvm/src/CoroutineExceptionHandlerImpl.kt | 62 ------------ .../internal/CoroutineExceptionHandlerImpl.kt | 49 ++++++++++ .../src/CoroutineExceptionHandlerImpl.kt | 14 --- .../internal/CoroutineExceptionHandlerImpl.kt | 31 ++++++ .../common/src/TestScope.kt | 12 +++ .../common/src/internal/ExceptionCollector.kt | 98 +++++++++++++++++++ .../common/test/Helpers.kt | 27 ++++- .../common/test/TestScopeTest.kt | 56 +++++++++++ kotlinx-coroutines-test/js/test/Helpers.kt | 13 +-- ...tlinx.coroutines.CoroutineExceptionHandler | 1 + .../TestCoroutineExceptionHandler.kt | 2 +- .../jvm/src/module-info.java | 3 + .../jvm/test/HelpersJvm.kt | 9 +- .../native/test/Helpers.kt | 9 +- 19 files changed, 396 insertions(+), 110 deletions(-) create mode 100644 kotlinx-coroutines-core/common/src/internal/CoroutineExceptionHandlerImpl.common.kt delete mode 100644 kotlinx-coroutines-core/js/src/CoroutineExceptionHandlerImpl.kt create mode 100644 kotlinx-coroutines-core/js/src/internal/CoroutineExceptionHandlerImpl.kt delete mode 100644 kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt create mode 100644 kotlinx-coroutines-core/jvm/src/internal/CoroutineExceptionHandlerImpl.kt delete mode 100644 kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt create mode 100644 kotlinx-coroutines-core/native/src/internal/CoroutineExceptionHandlerImpl.kt create mode 100644 kotlinx-coroutines-test/common/src/internal/ExceptionCollector.kt create mode 100644 kotlinx-coroutines-test/jvm/resources/META-INF/services/kotlinx.coroutines.CoroutineExceptionHandler diff --git a/integration-testing/src/jvmCoreTest/kotlin/ListAllCoroutineThrowableSubclassesTest.kt b/integration-testing/src/jvmCoreTest/kotlin/ListAllCoroutineThrowableSubclassesTest.kt index 21fe496bd3..7253658e9b 100644 --- a/integration-testing/src/jvmCoreTest/kotlin/ListAllCoroutineThrowableSubclassesTest.kt +++ b/integration-testing/src/jvmCoreTest/kotlin/ListAllCoroutineThrowableSubclassesTest.kt @@ -27,7 +27,8 @@ class ListAllCoroutineThrowableSubclassesTest { "kotlinx.coroutines.JobCancellationException", "kotlinx.coroutines.internal.UndeliveredElementException", "kotlinx.coroutines.CompletionHandlerException", - "kotlinx.coroutines.DiagnosticCoroutineContextException", + "kotlinx.coroutines.internal.DiagnosticCoroutineContextException", + "kotlinx.coroutines.internal.ExceptionSuccessfullyProcessed", "kotlinx.coroutines.CoroutinesInternalError", "kotlinx.coroutines.channels.ClosedSendChannelException", "kotlinx.coroutines.channels.ClosedReceiveChannelException", diff --git a/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt b/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt index d13ef67c66..e641447ba3 100644 --- a/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt +++ b/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt @@ -4,10 +4,9 @@ package kotlinx.coroutines +import kotlinx.coroutines.internal.* import kotlin.coroutines.* -internal expect fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) - /** * Helper function for coroutine builder implementations to handle uncaught and unexpected exceptions in coroutines, * that could not be otherwise handled in a normal way through structured concurrency, saving them to a future, and @@ -26,11 +25,11 @@ public fun handleCoroutineException(context: CoroutineContext, exception: Throwa return } } catch (t: Throwable) { - handleCoroutineExceptionImpl(context, handlerException(exception, t)) + handleUncaughtCoroutineException(context, handlerException(exception, t)) return } // If a handler is not present in the context or an exception was thrown, fallback to the global handler - handleCoroutineExceptionImpl(context, exception) + handleUncaughtCoroutineException(context, exception) } internal fun handlerException(originalException: Throwable, thrownException: Throwable): Throwable { diff --git a/kotlinx-coroutines-core/common/src/internal/CoroutineExceptionHandlerImpl.common.kt b/kotlinx-coroutines-core/common/src/internal/CoroutineExceptionHandlerImpl.common.kt new file mode 100644 index 0000000000..3f5925a3ee --- /dev/null +++ b/kotlinx-coroutines-core/common/src/internal/CoroutineExceptionHandlerImpl.common.kt @@ -0,0 +1,72 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.internal + +import kotlinx.coroutines.* +import kotlin.coroutines.* + +/** + * The list of globally installed [CoroutineExceptionHandler] instances that will be notified of any exceptions that + * were not processed in any other manner. + */ +internal expect val platformExceptionHandlers: Collection + +/** + * Ensures that the given [callback] is present in the [platformExceptionHandlers] list. + */ +internal expect fun ensurePlatformExceptionHandlerLoaded(callback: CoroutineExceptionHandler) + +/** + * The platform-dependent global exception handler, used so that the exception is logged at least *somewhere*. + */ +internal expect fun propagateExceptionFinalResort(exception: Throwable) + +/** + * Deal with exceptions that happened in coroutines and weren't programmatically dealt with. + * + * First, it notifies every [CoroutineExceptionHandler] in the [platformExceptionHandlers] list. + * If one of them throws [ExceptionSuccessfullyProcessed], it means that that handler believes that the exception was + * dealt with sufficiently well and doesn't need any further processing. + * Otherwise, the platform-dependent global exception handler is also invoked. + */ +internal fun handleUncaughtCoroutineException(context: CoroutineContext, exception: Throwable) { + // use additional extension handlers + for (handler in platformExceptionHandlers) { + try { + handler.handleException(context, exception) + } catch (_: ExceptionSuccessfullyProcessed) { + return + } catch (t: Throwable) { + propagateExceptionFinalResort(handlerException(exception, t)) + } + } + + try { + exception.addSuppressed(DiagnosticCoroutineContextException(context)) + } catch (e: Throwable) { + // addSuppressed is never user-defined and cannot normally throw with the only exception being OOM + // we do ignore that just in case to definitely deliver the exception + } + propagateExceptionFinalResort(exception) +} + +/** + * Private exception that is added to suppressed exceptions of the original exception + * when it is reported to the last-ditch current thread 'uncaughtExceptionHandler'. + * + * The purpose of this exception is to add an otherwise inaccessible diagnostic information and to + * be able to poke the context of the failing coroutine in the debugger. + */ +internal expect class DiagnosticCoroutineContextException(context: CoroutineContext) : RuntimeException + +/** + * A dummy exception that signifies that the exception was successfully processed by the handler and no further + * action is required. + * + * Would be nicer if [CoroutineExceptionHandler] could return a boolean, but that would be a breaking change. + * For now, we will take solace in knowledge that such exceptions are exceedingly rare, even rarer than globally + * uncaught exceptions in general. + */ +internal object ExceptionSuccessfullyProcessed : Exception() diff --git a/kotlinx-coroutines-core/js/src/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/js/src/CoroutineExceptionHandlerImpl.kt deleted file mode 100644 index 54a65e10a6..0000000000 --- a/kotlinx-coroutines-core/js/src/CoroutineExceptionHandlerImpl.kt +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines - -import kotlin.coroutines.* - -internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) { - // log exception - console.error(exception) -} diff --git a/kotlinx-coroutines-core/js/src/internal/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/js/src/internal/CoroutineExceptionHandlerImpl.kt new file mode 100644 index 0000000000..675cc4a67a --- /dev/null +++ b/kotlinx-coroutines-core/js/src/internal/CoroutineExceptionHandlerImpl.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.internal + +import kotlinx.coroutines.* +import kotlin.coroutines.* + +private val platformExceptionHandlers_ = mutableSetOf() + +internal actual val platformExceptionHandlers: Collection + get() = platformExceptionHandlers_ + +internal actual fun ensurePlatformExceptionHandlerLoaded(callback: CoroutineExceptionHandler) { + platformExceptionHandlers_ += callback +} + +internal actual fun propagateExceptionFinalResort(exception: Throwable) { + // log exception + console.error(exception) +} + +internal actual class DiagnosticCoroutineContextException actual constructor(context: CoroutineContext) : + RuntimeException(context.toString()) + diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt deleted file mode 100644 index 0d68b047f4..0000000000 --- a/kotlinx-coroutines-core/jvm/src/CoroutineExceptionHandlerImpl.kt +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines - -import java.util.* -import kotlin.coroutines.* - -/** - * A list of globally installed [CoroutineExceptionHandler] instances. - * - * Note that Android may have dummy [Thread.contextClassLoader] which is used by one-argument [ServiceLoader.load] function, - * see (https://stackoverflow.com/questions/13407006/android-class-loader-may-fail-for-processes-that-host-multiple-applications). - * So here we explicitly use two-argument `load` with a class-loader of [CoroutineExceptionHandler] class. - * - * We are explicitly using the `ServiceLoader.load(MyClass::class.java, MyClass::class.java.classLoader).iterator()` - * form of the ServiceLoader call to enable R8 optimization when compiled on Android. - */ -private val handlers: List = ServiceLoader.load( - CoroutineExceptionHandler::class.java, - CoroutineExceptionHandler::class.java.classLoader -).iterator().asSequence().toList() - -/** - * Private exception without stacktrace that is added to suppressed exceptions of the original exception - * when it is reported to the last-ditch current thread 'uncaughtExceptionHandler'. - * - * The purpose of this exception is to add an otherwise inaccessible diagnostic information and to - * be able to poke the failing coroutine context in the debugger. - */ -private class DiagnosticCoroutineContextException(@Transient private val context: CoroutineContext) : RuntimeException() { - override fun getLocalizedMessage(): String { - return context.toString() - } - - override fun fillInStackTrace(): Throwable { - // Prevent Android <= 6.0 bug, #1866 - stackTrace = emptyArray() - return this - } -} - -internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) { - // use additional extension handlers - for (handler in handlers) { - try { - handler.handleException(context, exception) - } catch (t: Throwable) { - // Use thread's handler if custom handler failed to handle exception - val currentThread = Thread.currentThread() - currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, handlerException(exception, t)) - } - } - - // use thread's handler - val currentThread = Thread.currentThread() - // addSuppressed is never user-defined and cannot normally throw with the only exception being OOM - // we do ignore that just in case to definitely deliver the exception - runCatching { exception.addSuppressed(DiagnosticCoroutineContextException(context)) } - currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception) -} diff --git a/kotlinx-coroutines-core/jvm/src/internal/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/jvm/src/internal/CoroutineExceptionHandlerImpl.kt new file mode 100644 index 0000000000..7f11898a09 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/src/internal/CoroutineExceptionHandlerImpl.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.internal + +import java.util.* +import kotlinx.coroutines.* +import kotlin.coroutines.* + +/** + * A list of globally installed [CoroutineExceptionHandler] instances. + * + * Note that Android may have dummy [Thread.contextClassLoader] which is used by one-argument [ServiceLoader.load] function, + * see (https://stackoverflow.com/questions/13407006/android-class-loader-may-fail-for-processes-that-host-multiple-applications). + * So here we explicitly use two-argument `load` with a class-loader of [CoroutineExceptionHandler] class. + * + * We are explicitly using the `ServiceLoader.load(MyClass::class.java, MyClass::class.java.classLoader).iterator()` + * form of the ServiceLoader call to enable R8 optimization when compiled on Android. + */ +internal actual val platformExceptionHandlers: Collection = ServiceLoader.load( + CoroutineExceptionHandler::class.java, + CoroutineExceptionHandler::class.java.classLoader +).iterator().asSequence().toList() + +internal actual fun ensurePlatformExceptionHandlerLoaded(callback: CoroutineExceptionHandler) { + // we use JVM's mechanism of ServiceLoader, so this should be a no-op on JVM. + // The only thing we do is make sure that the ServiceLoader did work correctly. + check(callback in platformExceptionHandlers) { "Exception handler was not found via a ServiceLoader" } +} + +internal actual fun propagateExceptionFinalResort(exception: Throwable) { + // use the thread's handler + val currentThread = Thread.currentThread() + currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception) +} + +// This implementation doesn't store a stacktrace, which is good because a stacktrace doesn't make sense for this. +internal actual class DiagnosticCoroutineContextException actual constructor(@Transient private val context: CoroutineContext) : RuntimeException() { + override fun getLocalizedMessage(): String { + return context.toString() + } + + override fun fillInStackTrace(): Throwable { + // Prevent Android <= 6.0 bug, #1866 + stackTrace = emptyArray() + return this + } +} diff --git a/kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt deleted file mode 100644 index 434813dc29..0000000000 --- a/kotlinx-coroutines-core/native/src/CoroutineExceptionHandlerImpl.kt +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines - -import kotlin.coroutines.* -import kotlin.native.* - -@OptIn(ExperimentalStdlibApi::class) -internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) { - // log exception - processUnhandledException(exception) -} diff --git a/kotlinx-coroutines-core/native/src/internal/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/native/src/internal/CoroutineExceptionHandlerImpl.kt new file mode 100644 index 0000000000..43d776cb54 --- /dev/null +++ b/kotlinx-coroutines-core/native/src/internal/CoroutineExceptionHandlerImpl.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.internal + +import kotlinx.coroutines.* +import kotlin.coroutines.* +import kotlin.native.* + +private val lock = SynchronizedObject() + +internal actual val platformExceptionHandlers: Collection + get() = synchronized(lock) { platformExceptionHandlers_ } + +private val platformExceptionHandlers_ = mutableSetOf() + +internal actual fun ensurePlatformExceptionHandlerLoaded(callback: CoroutineExceptionHandler) { + synchronized(lock) { + platformExceptionHandlers_ += callback + } +} + +@OptIn(ExperimentalStdlibApi::class) +internal actual fun propagateExceptionFinalResort(exception: Throwable) { + // log exception + processUnhandledException(exception) +} + +internal actual class DiagnosticCoroutineContextException actual constructor(context: CoroutineContext) : + RuntimeException(context.toString()) diff --git a/kotlinx-coroutines-test/common/src/TestScope.kt b/kotlinx-coroutines-test/common/src/TestScope.kt index a0a29a9553..713f511909 100644 --- a/kotlinx-coroutines-test/common/src/TestScope.kt +++ b/kotlinx-coroutines-test/common/src/TestScope.kt @@ -226,6 +226,14 @@ internal class TestScopeImpl(context: CoroutineContext) : throw IllegalStateException("Only a single call to `runTest` can be performed during one test.") entered = true check(!finished) + /** the order is important: [reportException] is only guaranteed not to throw if [entered] is `true` but + * [finished] is `false`. + * However, we also want [uncaughtExceptions] to be queried after the callback is registered, + * because the exception collector will be able to report the exceptions that arrived before this test but + * after the previous one, and learning about such exceptions as soon is possible is nice. */ + @Suppress("INVISIBLE_REFERENCE", "INVISIBLE_MEMBER") + run { ensurePlatformExceptionHandlerLoaded(ExceptionCollector) } + ExceptionCollector.addOnExceptionCallback(lock, this::reportException) uncaughtExceptions } if (exceptions.isNotEmpty()) { @@ -239,6 +247,8 @@ internal class TestScopeImpl(context: CoroutineContext) : /** Called at the end of the test. May only be called once. Returns the list of caught unhandled exceptions. */ fun leave(): List = synchronized(lock) { check(entered && !finished) + /** After [finished] becomes `true`, it is no longer valid to have [reportException] as the callback. */ + ExceptionCollector.removeOnExceptionCallback(lock) finished = true uncaughtExceptions } @@ -247,6 +257,8 @@ internal class TestScopeImpl(context: CoroutineContext) : fun legacyLeave(): List { val exceptions = synchronized(lock) { check(entered && !finished) + /** After [finished] becomes `true`, it is no longer valid to have [reportException] as the callback. */ + ExceptionCollector.removeOnExceptionCallback(lock) finished = true uncaughtExceptions } diff --git a/kotlinx-coroutines-test/common/src/internal/ExceptionCollector.kt b/kotlinx-coroutines-test/common/src/internal/ExceptionCollector.kt new file mode 100644 index 0000000000..90fa763523 --- /dev/null +++ b/kotlinx-coroutines-test/common/src/internal/ExceptionCollector.kt @@ -0,0 +1,98 @@ +/* + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.test.internal + +import kotlinx.coroutines.* +import kotlinx.coroutines.internal.* +import kotlin.coroutines.* + +/** + * If [addOnExceptionCallback] is called, the provided callback will be evaluated each time + * [handleCoroutineException] is executed and can't find a [CoroutineExceptionHandler] to + * process the exception. + * + * When a callback is registered once, even if it's later removed, the system starts to assume that + * other callbacks will eventually be registered, and so collects the exceptions. + * Once a new callback is registered, the collected exceptions are used with it. + * + * The callbacks in this object are the last resort before relying on platform-dependent + * ways to report uncaught exceptions from coroutines. + */ +internal object ExceptionCollector : AbstractCoroutineContextElement(CoroutineExceptionHandler), CoroutineExceptionHandler { + private val lock = SynchronizedObject() + private var enabled = false + private val unprocessedExceptions = mutableListOf() + private val callbacks = mutableMapOf Unit>() + + /** + * Registers [callback] to be executed when an uncaught exception happens. + * [owner] is a key by which to distinguish different callbacks. + */ + fun addOnExceptionCallback(owner: Any, callback: (Throwable) -> Unit) = synchronized(lock) { + enabled = true // never becomes `false` again + val previousValue = callbacks.put(owner, callback) + check(previousValue === null) + // try to process the exceptions using the newly-registered callback + unprocessedExceptions.forEach { reportException(it) } + unprocessedExceptions.clear() + } + + /** + * Unregisters the callback associated with [owner]. + */ + fun removeOnExceptionCallback(owner: Any) = synchronized(lock) { + val existingValue = callbacks.remove(owner) + check(existingValue !== null) + } + + /** + * Tries to handle the exception by propagating it to an interested consumer. + * Returns `true` if the exception does not need further processing. + * + * Doesn't throw. + */ + fun handleException(exception: Throwable): Boolean = synchronized(lock) { + if (!enabled) return false + if (reportException(exception)) return true + /** we don't return the result of the `add` function because we don't have a guarantee + * that a callback will eventually appear and collect the unprocessed exceptions, so + * we can't consider [exception] to be properly handled. */ + unprocessedExceptions.add(exception) + return false + } + + /** + * Try to report [exception] to the existing callbacks. + */ + private fun reportException(exception: Throwable): Boolean { + var executedACallback = false + for (callback in callbacks.values) { + callback(exception) + executedACallback = true + /** We don't leave the function here because we want to fan-out the exceptions to every interested consumer, + * it's not enough to have the exception processed by one of them. + * The reason is, it's less big of a deal to observe multiple concurrent reports of bad behavior than not + * to observe the report in the exact callback that is connected to that bad behavior. */ + } + return executedACallback + } + + @Suppress("INVISIBLE_MEMBER") + override fun handleException(context: CoroutineContext, exception: Throwable) { + if (handleException(exception)) { + throw ExceptionSuccessfullyProcessed + } + } + + override fun equals(other: Any?): Boolean = other is ExceptionCollector || other is ExceptionCollectorAsService +} + +/** + * A workaround for being unable to treat an object as a `ServiceLoader` service. + */ +internal class ExceptionCollectorAsService: CoroutineExceptionHandler by ExceptionCollector { + override fun equals(other: Any?): Boolean = other is ExceptionCollectorAsService || other is ExceptionCollector + override fun hashCode(): Int = ExceptionCollector.hashCode() +} diff --git a/kotlinx-coroutines-test/common/test/Helpers.kt b/kotlinx-coroutines-test/common/test/Helpers.kt index 98375b0905..345c66f91a 100644 --- a/kotlinx-coroutines-test/common/test/Helpers.kt +++ b/kotlinx-coroutines-test/common/test/Helpers.kt @@ -31,9 +31,32 @@ inline fun assertRunsFast(timeout: Duration, block: () -> T): T { inline fun assertRunsFast(block: () -> T): T = assertRunsFast(2.seconds, block) /** - * Passes [test] as an argument to [block], but as a function returning not a [TestResult] but [Unit]. + * Runs [test], and then invokes [block], passing to it the lambda that functionally behaves + * the same way [test] does. */ -expect fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult): TestResult +fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult): TestResult = testResultChain( + block = test, + after = { + block { it.getOrThrow() } + createTestResult { } + } +) + +/** + * Chains together [block] and [after], passing the result of [block] to [after]. + */ +expect fun testResultChain(block: () -> TestResult, after: (Result) -> TestResult): TestResult + +fun testResultChain(vararg chained: (Result) -> TestResult): TestResult = + if (chained.isEmpty()) { + createTestResult { } + } else { + testResultChain(block = { + chained[0](Result.success(Unit)) + }) { + testResultChain(*chained.drop(1).toTypedArray()) + } + } class TestException(message: String? = null): Exception(message) diff --git a/kotlinx-coroutines-test/common/test/TestScopeTest.kt b/kotlinx-coroutines-test/common/test/TestScopeTest.kt index a21e9168f1..433faef7ac 100644 --- a/kotlinx-coroutines-test/common/test/TestScopeTest.kt +++ b/kotlinx-coroutines-test/common/test/TestScopeTest.kt @@ -491,6 +491,62 @@ class TestScopeTest { } } + /* + * Tests that the [TestScope] exception reporting mechanism will report the exceptions that happen between + * different tests. + * + * This test must be ran manually, because such exceptions still go through the global exception handler + * (as there's no guarantee that another test will happen), and the global exception handler will + * log the exceptions or, on Native, crash the test suite. + */ + @Test + @Ignore + fun testReportingStrayUncaughtExceptionsBetweenTests() { + val thrown = TestException("x") + testResultChain({ + // register a handler for uncaught exceptions + runTest { } + }, { + GlobalScope.launch(start = CoroutineStart.UNDISPATCHED) { + throw thrown + } + runTest { + fail("unreached") + } + }, { + // this `runTest` will not report the exception + runTest { + when (val exception = it.exceptionOrNull()) { + is UncaughtExceptionsBeforeTest -> { + assertEquals(1, exception.suppressedExceptions.size) + assertSame(exception.suppressedExceptions[0], thrown) + } + else -> fail("unexpected exception: $exception") + } + } + }) + } + + /** + * Tests that the uncaught exceptions that happen during the test are reported. + */ + @Test + fun testReportingStrayUncaughtExceptionsDuringTest(): TestResult { + val thrown = TestException("x") + return testResultChain({ _ -> + runTest { + val job = launch(Dispatchers.Default + NonCancellable) { + throw thrown + } + job.join() + } + }, { + runTest { + assertEquals(thrown, it.exceptionOrNull()) + } + }) + } + companion object { internal val invalidContexts = listOf( Dispatchers.Default, // not a [TestDispatcher] diff --git a/kotlinx-coroutines-test/js/test/Helpers.kt b/kotlinx-coroutines-test/js/test/Helpers.kt index 5f19d1ac58..5fd0291c3b 100644 --- a/kotlinx-coroutines-test/js/test/Helpers.kt +++ b/kotlinx-coroutines-test/js/test/Helpers.kt @@ -1,20 +1,17 @@ /* - * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ package kotlinx.coroutines.test import kotlin.test.* -actual fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult): TestResult = - test().then( +actual fun testResultChain(block: () -> TestResult, after: (Result) -> TestResult): TestResult = + block().then( { - block { - } + after(Result.success(Unit)) }, { - block { - throw it - } + after(Result.failure(it)) }) actual typealias NoJs = Ignore diff --git a/kotlinx-coroutines-test/jvm/resources/META-INF/services/kotlinx.coroutines.CoroutineExceptionHandler b/kotlinx-coroutines-test/jvm/resources/META-INF/services/kotlinx.coroutines.CoroutineExceptionHandler new file mode 100644 index 0000000000..c9aaec2e60 --- /dev/null +++ b/kotlinx-coroutines-test/jvm/resources/META-INF/services/kotlinx.coroutines.CoroutineExceptionHandler @@ -0,0 +1 @@ +kotlinx.coroutines.test.internal.ExceptionCollectorAsService diff --git a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt index aeb0f35882..5330cd3ddf 100644 --- a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt +++ b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt @@ -59,7 +59,7 @@ public class TestCoroutineExceptionHandler : override fun handleException(context: CoroutineContext, exception: Throwable) { synchronized(_lock) { if (_coroutinesCleanedUp) { - handleCoroutineExceptionImpl(context, exception) + handleUncaughtCoroutineException(context, exception) } _exceptions += exception } diff --git a/kotlinx-coroutines-test/jvm/src/module-info.java b/kotlinx-coroutines-test/jvm/src/module-info.java index f67ce6a161..40ee87d8af 100644 --- a/kotlinx-coroutines-test/jvm/src/module-info.java +++ b/kotlinx-coroutines-test/jvm/src/module-info.java @@ -1,4 +1,6 @@ +import kotlinx.coroutines.CoroutineExceptionHandler; import kotlinx.coroutines.internal.MainDispatcherFactory; +import kotlinx.coroutines.test.internal.ExceptionCollectorAsService; import kotlinx.coroutines.test.internal.TestMainDispatcherFactory; module kotlinx.coroutines.test { @@ -8,4 +10,5 @@ exports kotlinx.coroutines.test; provides MainDispatcherFactory with TestMainDispatcherFactory; + provides CoroutineExceptionHandler with ExceptionCollectorAsService; } diff --git a/kotlinx-coroutines-test/jvm/test/HelpersJvm.kt b/kotlinx-coroutines-test/jvm/test/HelpersJvm.kt index e9aa3ff747..8d40b078a3 100644 --- a/kotlinx-coroutines-test/jvm/test/HelpersJvm.kt +++ b/kotlinx-coroutines-test/jvm/test/HelpersJvm.kt @@ -3,8 +3,11 @@ */ package kotlinx.coroutines.test -actual fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult) { - block { - test() +actual fun testResultChain(block: () -> TestResult, after: (Result) -> TestResult): TestResult { + try { + block() + after(Result.success(Unit)) + } catch (e: Throwable) { + after(Result.failure(e)) } } diff --git a/kotlinx-coroutines-test/native/test/Helpers.kt b/kotlinx-coroutines-test/native/test/Helpers.kt index ef478b7eb1..be615fb022 100644 --- a/kotlinx-coroutines-test/native/test/Helpers.kt +++ b/kotlinx-coroutines-test/native/test/Helpers.kt @@ -5,9 +5,12 @@ package kotlinx.coroutines.test import kotlin.test.* -actual fun testResultMap(block: (() -> Unit) -> Unit, test: () -> TestResult) { - block { - test() +actual fun testResultChain(block: () -> TestResult, after: (Result) -> TestResult): TestResult { + try { + block() + after(Result.success(Unit)) + } catch (e: Throwable) { + after(Result.failure(e)) } } From c176901a7d5b4a962747ed829ab42cb71134f2ad Mon Sep 17 00:00:00 2001 From: hfhbd Date: Fri, 3 Feb 2023 21:52:20 +0100 Subject: [PATCH 093/106] TestTimeSource: Expose comparable timemarks (#3617) --- kotlinx-coroutines-test/api/kotlinx-coroutines-test.api | 4 ++-- kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt | 2 +- kotlinx-coroutines-test/common/src/TestScope.kt | 2 +- .../common/test/TestCoroutineSchedulerTest.kt | 4 ++++ 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api b/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api index ac9edb90d4..bcee73e12e 100644 --- a/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api +++ b/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api @@ -69,7 +69,7 @@ public final class kotlinx/coroutines/test/TestCoroutineScheduler : kotlin/corou public final fun advanceTimeBy-LRDsOJo (J)V public final fun advanceUntilIdle ()V public final fun getCurrentTime ()J - public final fun getTimeSource ()Lkotlin/time/TimeSource; + public final fun getTimeSource ()Lkotlin/time/TimeSource$WithComparableMarks; public final fun runCurrent ()V } @@ -121,7 +121,7 @@ public final class kotlinx/coroutines/test/TestScopeKt { public static final fun advanceTimeBy-HG0u8IE (Lkotlinx/coroutines/test/TestScope;J)V public static final fun advanceUntilIdle (Lkotlinx/coroutines/test/TestScope;)V public static final fun getCurrentTime (Lkotlinx/coroutines/test/TestScope;)J - public static final fun getTestTimeSource (Lkotlinx/coroutines/test/TestScope;)Lkotlin/time/TimeSource; + public static final fun getTestTimeSource (Lkotlinx/coroutines/test/TestScope;)Lkotlin/time/TimeSource$WithComparableMarks; public static final fun runCurrent (Lkotlinx/coroutines/test/TestScope;)V } diff --git a/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt b/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt index 102498bad5..04e320d841 100644 --- a/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt +++ b/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt @@ -224,7 +224,7 @@ public class TestCoroutineScheduler : AbstractCoroutineContextElement(TestCorout * Returns the [TimeSource] representation of the virtual time of this scheduler. */ @ExperimentalTime - public val timeSource: TimeSource = object : AbstractLongTimeSource(DurationUnit.MILLISECONDS) { + public val timeSource: TimeSource.WithComparableMarks = object : AbstractLongTimeSource(DurationUnit.MILLISECONDS) { override fun read(): Long = currentTime } } diff --git a/kotlinx-coroutines-test/common/src/TestScope.kt b/kotlinx-coroutines-test/common/src/TestScope.kt index 713f511909..a5a36a8524 100644 --- a/kotlinx-coroutines-test/common/src/TestScope.kt +++ b/kotlinx-coroutines-test/common/src/TestScope.kt @@ -136,7 +136,7 @@ public fun TestScope.advanceTimeBy(delayTime: Duration): Unit = testScheduler.ad */ @ExperimentalCoroutinesApi @ExperimentalTime -public val TestScope.testTimeSource: TimeSource get() = testScheduler.timeSource +public val TestScope.testTimeSource: TimeSource.WithComparableMarks get() = testScheduler.timeSource /** * Creates a [TestScope]. diff --git a/kotlinx-coroutines-test/common/test/TestCoroutineSchedulerTest.kt b/kotlinx-coroutines-test/common/test/TestCoroutineSchedulerTest.kt index 7203dbd270..4aec77312d 100644 --- a/kotlinx-coroutines-test/common/test/TestCoroutineSchedulerTest.kt +++ b/kotlinx-coroutines-test/common/test/TestCoroutineSchedulerTest.kt @@ -315,10 +315,14 @@ class TestCoroutineSchedulerTest { @ExperimentalTime fun testAdvanceTimeSource() = runTest { val expected = 1.seconds + val before = testTimeSource.markNow() val actual = testTimeSource.measureTime { delay(expected) } assertEquals(expected, actual) + val after = testTimeSource.markNow() + assertTrue(before < after) + assertEquals(expected, after - before) } private fun forTestDispatchers(block: (TestDispatcher) -> Unit): Unit = From 4ce35363b287e26073fb8033d7843a793a033e6b Mon Sep 17 00:00:00 2001 From: Tomasz Dzieniak Date: Sun, 26 Feb 2023 14:22:21 +0100 Subject: [PATCH 094/106] Remove duplicated 'be' from Deferred kdoc (#3644) Thanks! --- kotlinx-coroutines-core/common/src/Deferred.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-core/common/src/Deferred.kt b/kotlinx-coroutines-core/common/src/Deferred.kt index 595700e2c7..2f106e9ed3 100644 --- a/kotlinx-coroutines-core/common/src/Deferred.kt +++ b/kotlinx-coroutines-core/common/src/Deferred.kt @@ -22,7 +22,7 @@ import kotlinx.coroutines.selects.* * Usually, a deferred value is created in _active_ state (it is created and started). * However, the [async][CoroutineScope.async] coroutine builder has an optional `start` parameter that creates a deferred value in _new_ state * when this parameter is set to [CoroutineStart.LAZY]. - * Such a deferred can be be made _active_ by invoking [start], [join], or [await]. + * Such a deferred can be made _active_ by invoking [start], [join], or [await]. * * A deferred value is a [Job]. A job in the * [coroutineContext](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/coroutine-context.html) From 2b865e2b44c1ca0b539cfac8be9c095ea39c7383 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Mon, 27 Feb 2023 12:42:57 +0300 Subject: [PATCH 095/106] Update K/N targets in accordance with official recommendations (#3640) More tier 3 targets added Fixes #3601 Fixes #812 Fixes #855 --- README.md | 4 +-- gradle/compile-native-multiplatform.gradle | 36 +++++++++++++++------- kotlinx-coroutines-core/build.gradle | 2 +- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index ec5eeca3e5..0143564a49 100644 --- a/README.md +++ b/README.md @@ -187,8 +187,8 @@ Kotlin/JS version of `kotlinx.coroutines` is published as Kotlin/Native version of `kotlinx.coroutines` is published as [`kotlinx-coroutines-core-$platform`](https://mvnrepository.com/search?q=kotlinx-coroutines-core-) where `$platform` is -the target Kotlin/Native platform. [List of currently supported targets](https://github.com/Kotlin/kotlinx.coroutines/blob/master/gradle/compile-native-multiplatform.gradle#L16). - +the target Kotlin/Native platform. +Targets are provided in accordance with [official K/N target support](https://kotlinlang.org/docs/native-target-support.html). ## Building and Contributing See [Contributing Guidelines](CONTRIBUTING.md). diff --git a/gradle/compile-native-multiplatform.gradle b/gradle/compile-native-multiplatform.gradle index 1aeb8d2c54..3b2758854f 100644 --- a/gradle/compile-native-multiplatform.gradle +++ b/gradle/compile-native-multiplatform.gradle @@ -15,22 +15,36 @@ kotlin { } targets { + // According to https://kotlinlang.org/docs/native-target-support.html + // Tier 1 addTarget(presets.linuxX64) - addTarget(presets.iosArm64) - addTarget(presets.iosArm32) - addTarget(presets.iosX64) addTarget(presets.macosX64) - addTarget(presets.mingwX64) - addTarget(presets.tvosArm64) - addTarget(presets.tvosX64) - addTarget(presets.watchosArm32) - addTarget(presets.watchosArm64) - addTarget(presets.watchosX86) - addTarget(presets.watchosX64) + addTarget(presets.macosArm64) addTarget(presets.iosSimulatorArm64) + addTarget(presets.iosX64) + + // Tier 2 + addTarget(presets.linuxArm64) addTarget(presets.watchosSimulatorArm64) + addTarget(presets.watchosX64) + addTarget(presets.watchosArm32) + addTarget(presets.watchosArm64) addTarget(presets.tvosSimulatorArm64) - addTarget(presets.macosArm64) + addTarget(presets.tvosX64) + addTarget(presets.tvosArm64) + addTarget(presets.iosArm64) + + // Tier 3 + addTarget(presets.androidNativeArm32) + addTarget(presets.androidNativeArm64) + addTarget(presets.androidNativeX86) + addTarget(presets.androidNativeX64) + addTarget(presets.mingwX64) + addTarget(presets.watchosDeviceArm64) + + // Deprecated, but were provided by coroutine; can be removed only when K/N drops the target + addTarget(presets.iosArm32) + addTarget(presets.watchosX86) } sourceSets { diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle index 84d9b0485d..2a6dbbc64f 100644 --- a/kotlinx-coroutines-core/build.gradle +++ b/kotlinx-coroutines-core/build.gradle @@ -84,7 +84,7 @@ void defineSourceSet(newName, dependsOn, includedInPred) { static boolean isNativeDarwin(String name) { return ["ios", "macos", "tvos", "watchos"].any { name.startsWith(it) } } -static boolean isNativeOther(String name) { return ["linux", "mingw"].any { name.startsWith(it) } } +static boolean isNativeOther(String name) { return ["linux", "mingw", "androidNative"].any { name.startsWith(it) } } defineSourceSet("concurrent", ["common"]) { it in ["jvm", "native"] } From cb0ef7102637fa337c75c971650493d2257c72ab Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Mon, 27 Feb 2023 18:04:23 +0300 Subject: [PATCH 096/106] Update the README of the test module to reflect the developments (#3645) --- kotlinx-coroutines-test/README.md | 83 ++++++++++++++++++++++++++----- 1 file changed, 71 insertions(+), 12 deletions(-) diff --git a/kotlinx-coroutines-test/README.md b/kotlinx-coroutines-test/README.md index f45ccd0cac..f83ff69f0b 100644 --- a/kotlinx-coroutines-test/README.md +++ b/kotlinx-coroutines-test/README.md @@ -107,6 +107,8 @@ on Kotlin/JS. The main differences are the following: * **The calls to `delay` are automatically skipped**, preserving the relative execution order of the tasks. This way, it's possible to make tests finish more-or-less immediately. +* **The execution times out after 10 seconds**, cancelling the test coroutine to prevent tests from hanging forever + and eating up the CI resources. * **Controlling the virtual time**: in case just skipping delays is not sufficient, it's possible to more carefully guide the execution, advancing the virtual time by a duration, draining the queue of the awaiting tasks, or running the tasks scheduled at the present moment. @@ -115,6 +117,31 @@ on Kotlin/JS. The main differences are the following: Sometimes, especially when working with third-party code, it's impossible to mock all the dispatchers in use. [runTest] will handle the situations where some code runs in dispatchers not integrated with the test module. +## Timeout + +Test automatically time out after 10 seconds. For example, this test will fail with a timeout exception: + +```kotlin +@Test +fun testHanging() = runTest { + CompletableDeferred().await() // will hang forever +} +``` + +In case the test is expected to take longer than 10 seconds, the timeout can be increased by passing the `timeout` +parameter: + +```kotlin +@Test +fun testTakingALongTime() = runTest(timeout = 30.seconds) { + val result = withContext(Dispatchers.Default) { + delay(20.seconds) // this delay is not in the test dispatcher and will not be skipped + 3 + } + assertEquals(3, result) +} +``` + ## Delay-skipping To test regular suspend functions, which may have a delay, just run them inside the [runTest] block. @@ -163,30 +190,35 @@ fun testWithMultipleDelays() = runTest { ## Controlling the virtual time -Inside [runTest], the following operations are supported: +Inside [runTest], the execution is scheduled by [TestCoroutineScheduler], which is a virtual time scheduler. +The scheduler has several special methods that allow controlling the virtual time: * `currentTime` gets the current virtual time. * `runCurrent()` runs the tasks that are scheduled at this point of virtual time. * `advanceUntilIdle()` runs all enqueued tasks until there are no more. * `advanceTimeBy(timeDelta)` runs the enqueued tasks until the current virtual time advances by `timeDelta`. +* `timeSource` returns a `TimeSource` that uses the virtual time. ```kotlin @Test fun testFoo() = runTest { launch { - println(1) // executes during runCurrent() - delay(1_000) // suspends until time is advanced by at least 1_000 - println(2) // executes during advanceTimeBy(2_000) - delay(500) // suspends until the time is advanced by another 500 ms - println(3) // also executes during advanceTimeBy(2_000) - delay(5_000) // will suspend by another 4_500 ms - println(4) // executes during advanceUntilIdle() + val workDuration = testScheduler.timeSource.measureTime { + println(1) // executes during runCurrent() + delay(1_000) // suspends until time is advanced by at least 1_000 + println(2) // executes during advanceTimeBy(2_000) + delay(500) // suspends until the time is advanced by another 500 ms + println(3) // also executes during advanceTimeBy(2_000) + delay(5_000) // will suspend by another 4_500 ms + println(4) // executes during advanceUntilIdle() + } + assertEquals(6500.milliseconds, workDuration) // the work took 6_500 ms of virtual time } // the child coroutine has not run yet - runCurrent() + testScheduler.runCurrent() // the child coroutine has called println(1), and is suspended on delay(1_000) - advanceTimeBy(2_000) // progress time, this will cause two calls to `delay` to resume + testScheduler.advanceTimeBy(2.seconds) // progress time, this will cause two calls to `delay` to resume // the child coroutine has called println(2) and println(3) and suspends for another 4_500 virtual milliseconds - advanceUntilIdle() // will run the child coroutine to completion + testScheduler.advanceUntilIdle() // will run the child coroutine to completion assertEquals(6500, currentTime) // the child coroutine finished at virtual time of 6_500 milliseconds } ``` @@ -265,6 +297,32 @@ fun testSubject() = scope.runTest { } ``` +## Running background work + +Sometimes, the fact that [runTest] waits for all the coroutines to finish is undesired. +For example, the system under test may need to receive data from coroutines that always run in the background. +Emulating such coroutines by launching them from the test body is not sufficient, because [runTest] will wait for them +to finish, which they never typically do. + +For these cases, there is a special coroutine scope: [TestScope.backgroundScope]. +Coroutines launched in it will be cancelled at the end of the test. + +```kotlin +@Test +fun testExampleBackgroundJob() = runTest { + val channel = Channel() + backgroundScope.launch { + var i = 0 + while (true) { + channel.send(i++) + } + } + repeat(100) { + assertEquals(it, channel.receive()) + } +} +``` + ## Eagerly entering `launch` and `async` blocks Some tests only test functionality and don't particularly care about the precise order in which coroutines are @@ -357,7 +415,7 @@ either dependency injection, a service locator, or a default parameter, if it is ### Status of the API -This API is experimental and it is may change before migrating out of experimental (while it is marked as +Many parts of the API is experimental, and it is may change before migrating out of experimental (while it is marked as [`@ExperimentalCoroutinesApi`][ExperimentalCoroutinesApi]). Changes during experimental may have deprecation applied when possible, but it is not advised to use the API in stable code before it leaves experimental due to possible breaking changes. @@ -388,6 +446,7 @@ If you have any suggestions for improvements to this experimental API please sha [setMain]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/set-main.html [TestScope.testScheduler]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-scope/test-scheduler.html [TestScope.runTest]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-test.html +[TestScope.backgroundScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/-test-scope/background-scope.html [runCurrent]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-test/kotlinx.coroutines.test/run-current.html From 2da68175b22e13b2f29b0293dcf3308659af8a29 Mon Sep 17 00:00:00 2001 From: Nikita Koval Date: Tue, 28 Feb 2023 15:05:35 +0100 Subject: [PATCH 097/106] Optimize `CancellableContinuationImpl.invokeOnCancellation(..)` for `Segment`s (#3084) * This optimization enables allocation-free channel operations, as we no longer have to allocate a cancellation handler for suspending channel operations * Add a sequential semaphore benchmark and a generalized version of `ChannelSinkBenchmark` that supports buffered channels and pre-allocates elements to isolate the effect Signed-off-by: Nikita Koval Co-authored-by: Vsevolod Tolstopyatov --- .../ChannelSinkNoAllocationsBenchmark.kt | 37 +++++ .../SequentialSemaphoreBenchmark.kt | 43 ++++++ .../api/kotlinx-coroutines-core.api | 4 +- .../common/src/CancellableContinuation.kt | 2 +- .../common/src/CancellableContinuationImpl.kt | 105 ++++++++++--- kotlinx-coroutines-core/common/src/Waiter.kt | 21 +++ .../common/src/channels/BufferedChannel.kt | 146 +++++++----------- .../src/channels/ConflatedBufferedChannel.kt | 6 +- .../src/internal/ConcurrentLinkedList.kt | 22 ++- .../common/src/selects/Select.kt | 68 ++++++-- .../common/src/sync/Mutex.kt | 12 +- .../common/src/sync/Semaphore.kt | 33 +--- .../CancellableContinuationHandlersTest.kt | 28 ++++ 13 files changed, 363 insertions(+), 164 deletions(-) create mode 100644 benchmarks/src/jmh/kotlin/benchmarks/ChannelSinkNoAllocationsBenchmark.kt create mode 100644 benchmarks/src/jmh/kotlin/benchmarks/SequentialSemaphoreBenchmark.kt create mode 100644 kotlinx-coroutines-core/common/src/Waiter.kt diff --git a/benchmarks/src/jmh/kotlin/benchmarks/ChannelSinkNoAllocationsBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/ChannelSinkNoAllocationsBenchmark.kt new file mode 100644 index 0000000000..dcba8383ad --- /dev/null +++ b/benchmarks/src/jmh/kotlin/benchmarks/ChannelSinkNoAllocationsBenchmark.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package benchmarks + +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.* +import org.openjdk.jmh.annotations.* +import java.util.concurrent.* +import kotlin.coroutines.* + +@Warmup(iterations = 3, time = 1) +@Measurement(iterations = 5, time = 1) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +@Fork(1) +open class ChannelSinkNoAllocationsBenchmark { + private val unconfined = Dispatchers.Unconfined + + @Benchmark + fun channelPipeline(): Int = runBlocking { + run(unconfined) + } + + private suspend inline fun run(context: CoroutineContext): Int { + var size = 0 + Channel.range(context).consumeEach { size++ } + return size + } + + private fun Channel.Factory.range(context: CoroutineContext) = GlobalScope.produce(context) { + for (i in 0 until 100_000) + send(Unit) // no allocations + } +} diff --git a/benchmarks/src/jmh/kotlin/benchmarks/SequentialSemaphoreBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/SequentialSemaphoreBenchmark.kt new file mode 100644 index 0000000000..6926db783a --- /dev/null +++ b/benchmarks/src/jmh/kotlin/benchmarks/SequentialSemaphoreBenchmark.kt @@ -0,0 +1,43 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package benchmarks + +import kotlinx.coroutines.* +import kotlinx.coroutines.sync.* +import org.openjdk.jmh.annotations.* +import java.util.concurrent.TimeUnit +import kotlin.test.* + +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 10, time = 1) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@State(Scope.Benchmark) +@Fork(1) +open class SequentialSemaphoreAsMutexBenchmark { + val s = Semaphore(1) + + @Benchmark + fun benchmark() : Unit = runBlocking { + val s = Semaphore(permits = 1, acquiredPermits = 1) + var step = 0 + launch(Dispatchers.Unconfined) { + repeat(N) { + assertEquals(it * 2, step) + step++ + s.acquire() + } + } + repeat(N) { + assertEquals(it * 2 + 1, step) + step++ + s.release() + } + } +} + +fun main() = SequentialSemaphoreAsMutexBenchmark().benchmark() + +private val N = 1_000_000 \ No newline at end of file diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index e880916cc6..b4f2928d03 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -51,7 +51,7 @@ public final class kotlinx/coroutines/CancellableContinuation$DefaultImpls { public static synthetic fun tryResume$default (Lkotlinx/coroutines/CancellableContinuation;Ljava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;)Ljava/lang/Object; } -public class kotlinx/coroutines/CancellableContinuationImpl : kotlin/coroutines/jvm/internal/CoroutineStackFrame, kotlinx/coroutines/CancellableContinuation, kotlinx/coroutines/channels/Waiter { +public class kotlinx/coroutines/CancellableContinuationImpl : kotlin/coroutines/jvm/internal/CoroutineStackFrame, kotlinx/coroutines/CancellableContinuation, kotlinx/coroutines/Waiter { public fun (Lkotlin/coroutines/Continuation;I)V public final fun callCancelHandler (Lkotlinx/coroutines/CancelHandler;Ljava/lang/Throwable;)V public final fun callOnCancellation (Lkotlin/jvm/functions/Function1;Ljava/lang/Throwable;)V @@ -64,6 +64,7 @@ public class kotlinx/coroutines/CancellableContinuationImpl : kotlin/coroutines/ public fun getStackTraceElement ()Ljava/lang/StackTraceElement; public fun initCancellability ()V public fun invokeOnCancellation (Lkotlin/jvm/functions/Function1;)V + public fun invokeOnCancellation (Lkotlinx/coroutines/internal/Segment;I)V public fun isActive ()Z public fun isCancelled ()Z public fun isCompleted ()Z @@ -1258,6 +1259,7 @@ public class kotlinx/coroutines/selects/SelectImplementation : kotlinx/coroutine public fun invoke (Lkotlinx/coroutines/selects/SelectClause1;Lkotlin/jvm/functions/Function2;)V public fun invoke (Lkotlinx/coroutines/selects/SelectClause2;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V public fun invoke (Lkotlinx/coroutines/selects/SelectClause2;Lkotlin/jvm/functions/Function2;)V + public fun invokeOnCancellation (Lkotlinx/coroutines/internal/Segment;I)V public fun onTimeout (JLkotlin/jvm/functions/Function1;)V public fun selectInRegistrationPhase (Ljava/lang/Object;)V public fun trySelect (Ljava/lang/Object;Ljava/lang/Object;)Z diff --git a/kotlinx-coroutines-core/common/src/CancellableContinuation.kt b/kotlinx-coroutines-core/common/src/CancellableContinuation.kt index 5e8d7f9102..478ff72044 100644 --- a/kotlinx-coroutines-core/common/src/CancellableContinuation.kt +++ b/kotlinx-coroutines-core/common/src/CancellableContinuation.kt @@ -328,7 +328,7 @@ public suspend inline fun suspendCancellableCoroutine( * [CancellableContinuationImpl] is reused. */ internal suspend inline fun suspendCancellableCoroutineReusable( - crossinline block: (CancellableContinuation) -> Unit + crossinline block: (CancellableContinuationImpl) -> Unit ): T = suspendCoroutineUninterceptedOrReturn { uCont -> val cancellable = getOrCreateCancellableContinuation(uCont.intercepted()) block(cancellable) diff --git a/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt index 423cb05d18..8006f3bf8e 100644 --- a/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt +++ b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt @@ -5,7 +5,6 @@ package kotlinx.coroutines import kotlinx.atomicfu.* -import kotlinx.coroutines.channels.Waiter import kotlinx.coroutines.internal.* import kotlin.coroutines.* import kotlin.coroutines.intrinsics.* @@ -15,6 +14,15 @@ private const val UNDECIDED = 0 private const val SUSPENDED = 1 private const val RESUMED = 2 +private const val DECISION_SHIFT = 29 +private const val INDEX_MASK = (1 shl DECISION_SHIFT) - 1 +private const val NO_INDEX = INDEX_MASK + +private inline val Int.decision get() = this shr DECISION_SHIFT +private inline val Int.index get() = this and INDEX_MASK +@Suppress("NOTHING_TO_INLINE") +private inline fun decisionAndIndex(decision: Int, index: Int) = (decision shl DECISION_SHIFT) + index + @JvmField internal val RESUME_TOKEN = Symbol("RESUME_TOKEN") @@ -44,7 +52,7 @@ internal open class CancellableContinuationImpl( * less dependencies. */ - /* decision state machine + /** decision state machine +-----------+ trySuspend +-----------+ | UNDECIDED | -------------> | SUSPENDED | @@ -56,9 +64,12 @@ internal open class CancellableContinuationImpl( | RESUMED | +-----------+ - Note: both tryResume and trySuspend can be invoked at most once, first invocation wins + Note: both tryResume and trySuspend can be invoked at most once, first invocation wins. + If the cancellation handler is specified via a [Segment] instance and the index in it + (so [Segment.onCancellation] should be called), the [_decisionAndIndex] field may store + this index additionally to the "decision" value. */ - private val _decision = atomic(UNDECIDED) + private val _decisionAndIndex = atomic(decisionAndIndex(UNDECIDED, NO_INDEX)) /* === Internal states === @@ -144,7 +155,7 @@ internal open class CancellableContinuationImpl( detachChild() return false } - _decision.value = UNDECIDED + _decisionAndIndex.value = decisionAndIndex(UNDECIDED, NO_INDEX) _state.value = Active return true } @@ -194,10 +205,13 @@ internal open class CancellableContinuationImpl( _state.loop { state -> if (state !is NotCompleted) return false // false if already complete or cancelling // Active -- update to final state - val update = CancelledContinuation(this, cause, handled = state is CancelHandler) + val update = CancelledContinuation(this, cause, handled = state is CancelHandler || state is Segment<*>) if (!_state.compareAndSet(state, update)) return@loop // retry on cas failure // Invoke cancel handler if it was present - (state as? CancelHandler)?.let { callCancelHandler(it, cause) } + when (state) { + is CancelHandler -> callCancelHandler(state, cause) + is Segment<*> -> callSegmentOnCancellation(state, cause) + } // Complete state update detachChildIfNonResuable() dispatchResume(resumeMode) // no need for additional cancellation checks @@ -234,6 +248,12 @@ internal open class CancellableContinuationImpl( fun callCancelHandler(handler: CancelHandler, cause: Throwable?) = callCancelHandlerSafely { handler.invoke(cause) } + private fun callSegmentOnCancellation(segment: Segment<*>, cause: Throwable?) { + val index = _decisionAndIndex.value.index + check(index != NO_INDEX) { "The index for Segment.onCancellation(..) is broken" } + callCancelHandlerSafely { segment.onCancellation(index, cause) } + } + fun callOnCancellation(onCancellation: (cause: Throwable) -> Unit, cause: Throwable) { try { onCancellation.invoke(cause) @@ -253,9 +273,9 @@ internal open class CancellableContinuationImpl( parent.getCancellationException() private fun trySuspend(): Boolean { - _decision.loop { decision -> - when (decision) { - UNDECIDED -> if (this._decision.compareAndSet(UNDECIDED, SUSPENDED)) return true + _decisionAndIndex.loop { cur -> + when (cur.decision) { + UNDECIDED -> if (this._decisionAndIndex.compareAndSet(cur, decisionAndIndex(SUSPENDED, cur.index))) return true RESUMED -> return false else -> error("Already suspended") } @@ -263,9 +283,9 @@ internal open class CancellableContinuationImpl( } private fun tryResume(): Boolean { - _decision.loop { decision -> - when (decision) { - UNDECIDED -> if (this._decision.compareAndSet(UNDECIDED, RESUMED)) return true + _decisionAndIndex.loop { cur -> + when (cur.decision) { + UNDECIDED -> if (this._decisionAndIndex.compareAndSet(cur, decisionAndIndex(RESUMED, cur.index))) return true SUSPENDED -> return false else -> error("Already resumed") } @@ -275,7 +295,7 @@ internal open class CancellableContinuationImpl( @PublishedApi internal fun getResult(): Any? { val isReusable = isReusable() - // trySuspend may fail either if 'block' has resumed/cancelled a continuation + // trySuspend may fail either if 'block' has resumed/cancelled a continuation, // or we got async cancellation from parent. if (trySuspend()) { /* @@ -350,14 +370,44 @@ internal open class CancellableContinuationImpl( override fun resume(value: T, onCancellation: ((cause: Throwable) -> Unit)?) = resumeImpl(value, resumeMode, onCancellation) + /** + * An optimized version for the code below that does not allocate + * a cancellation handler object and efficiently stores the specified + * [segment] and [index] in this [CancellableContinuationImpl]. + * + * The only difference is that `segment.onCancellation(..)` is never + * called if this continuation is already completed; thus, + * the semantics is similar to [BeforeResumeCancelHandler]. + * + * ``` + * invokeOnCancellation { cause -> + * segment.onCancellation(index, cause) + * } + * ``` + */ + override fun invokeOnCancellation(segment: Segment<*>, index: Int) { + _decisionAndIndex.update { + check(it.index == NO_INDEX) { + "invokeOnCancellation should be called at most once" + } + decisionAndIndex(it.decision, index) + } + invokeOnCancellationImpl(segment) + } + public override fun invokeOnCancellation(handler: CompletionHandler) { val cancelHandler = makeCancelHandler(handler) + invokeOnCancellationImpl(cancelHandler) + } + + private fun invokeOnCancellationImpl(handler: Any) { + assert { handler is CancelHandler || handler is Segment<*> } _state.loop { state -> when (state) { is Active -> { - if (_state.compareAndSet(state, cancelHandler)) return // quit on cas success + if (_state.compareAndSet(state, handler)) return // quit on cas success } - is CancelHandler -> multipleHandlersError(handler, state) + is CancelHandler, is Segment<*> -> multipleHandlersError(handler, state) is CompletedExceptionally -> { /* * Continuation was already cancelled or completed exceptionally. @@ -371,7 +421,13 @@ internal open class CancellableContinuationImpl( * because we play type tricks on Kotlin/JS and handler is not necessarily a function there */ if (state is CancelledContinuation) { - callCancelHandler(handler, (state as? CompletedExceptionally)?.cause) + val cause: Throwable? = (state as? CompletedExceptionally)?.cause + if (handler is CancelHandler) { + callCancelHandler(handler, cause) + } else { + val segment = handler as Segment<*> + callSegmentOnCancellation(segment, cause) + } } return } @@ -380,14 +436,16 @@ internal open class CancellableContinuationImpl( * Continuation was already completed, and might already have cancel handler. */ if (state.cancelHandler != null) multipleHandlersError(handler, state) - // BeforeResumeCancelHandler does not need to be called on a completed continuation - if (cancelHandler is BeforeResumeCancelHandler) return + // BeforeResumeCancelHandler and Segment.invokeOnCancellation(..) + // do NOT need to be called on completed continuation. + if (handler is BeforeResumeCancelHandler || handler is Segment<*>) return + handler as CancelHandler if (state.cancelled) { // Was already cancelled while being dispatched -- invoke the handler directly callCancelHandler(handler, state.cancelCause) return } - val update = state.copy(cancelHandler = cancelHandler) + val update = state.copy(cancelHandler = handler) if (_state.compareAndSet(state, update)) return // quit on cas success } else -> { @@ -396,15 +454,16 @@ internal open class CancellableContinuationImpl( * Change its state to CompletedContinuation, unless we have BeforeResumeCancelHandler which * does not need to be called in this case. */ - if (cancelHandler is BeforeResumeCancelHandler) return - val update = CompletedContinuation(state, cancelHandler = cancelHandler) + if (handler is BeforeResumeCancelHandler || handler is Segment<*>) return + handler as CancelHandler + val update = CompletedContinuation(state, cancelHandler = handler) if (_state.compareAndSet(state, update)) return // quit on cas success } } } } - private fun multipleHandlersError(handler: CompletionHandler, state: Any?) { + private fun multipleHandlersError(handler: Any, state: Any?) { error("It's prohibited to register multiple handlers, tried to register $handler, already has $state") } diff --git a/kotlinx-coroutines-core/common/src/Waiter.kt b/kotlinx-coroutines-core/common/src/Waiter.kt new file mode 100644 index 0000000000..79d3dbf564 --- /dev/null +++ b/kotlinx-coroutines-core/common/src/Waiter.kt @@ -0,0 +1,21 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import kotlinx.coroutines.internal.Segment +import kotlinx.coroutines.selects.* + +/** + * All waiters (such as [CancellableContinuationImpl] and [SelectInstance]) in synchronization and + * communication primitives, should implement this interface to make the code faster and easier to read. + */ +internal interface Waiter { + /** + * When this waiter is cancelled, [Segment.onCancellation] with + * the specified [segment] and [index] should be called. + * This function installs the corresponding cancellation handler. + */ + fun invokeOnCancellation(segment: Segment<*>, index: Int) +} diff --git a/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt b/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt index e30486fa8c..d5773df544 100644 --- a/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt @@ -160,38 +160,34 @@ internal open class BufferedChannel( // cancellation, it is guaranteed that the element // has been already buffered or passed to receiver. onRendezvousOrBuffered = { cont.resume(Unit) }, - // Clean the cell on suspension and invoke - // `onUndeliveredElement(..)` if needed. - onSuspend = { segm, i -> cont.prepareSenderForSuspension(segm, i) }, // If the channel is closed, call `onUndeliveredElement(..)` and complete the // continuation with the corresponding exception. onClosed = { onClosedSendOnNoWaiterSuspend(element, cont) }, ) } - private fun CancellableContinuation<*>.prepareSenderForSuspension( + private fun Waiter.prepareSenderForSuspension( /* The working cell is specified by the segment and the index in it. */ segment: ChannelSegment, index: Int ) { if (onUndeliveredElement == null) { - invokeOnCancellation(SenderOrReceiverCancellationHandler(segment, index).asHandler) + invokeOnCancellation(segment, index) } else { - invokeOnCancellation(SenderWithOnUndeliveredElementCancellationHandler(segment, index, context).asHandler) - } - } - - // TODO: Replace with a more efficient cancellation mechanism for segments when #3084 is finished. - private inner class SenderOrReceiverCancellationHandler( - private val segment: ChannelSegment, - private val index: Int - ) : BeforeResumeCancelHandler(), DisposableHandle { - override fun dispose() { - segment.onCancellation(index) + when (this) { + is CancellableContinuation<*> -> { + invokeOnCancellation(SenderWithOnUndeliveredElementCancellationHandler(segment, index, context).asHandler) + } + is SelectInstance<*> -> { + disposeOnCompletion(SenderWithOnUndeliveredElementCancellationHandler(segment, index, context)) + } + is SendBroadcast -> { + cont.invokeOnCancellation(SenderWithOnUndeliveredElementCancellationHandler(segment, index, cont.context).asHandler) + } + else -> error("unexpected sender: $this") + } } - - override fun invoke(cause: Throwable?) = dispose() } private inner class SenderWithOnUndeliveredElementCancellationHandler( @@ -227,8 +223,8 @@ internal open class BufferedChannel( // or the element has been buffered. onRendezvousOrBuffered = { success(Unit) }, // On suspension, the `INTERRUPTED_SEND` token has been installed, - // and this `trySend(e)` fails. According to the contract, - // we do not need to call [onUndeliveredElement] handler. + // and this `trySend(e)` must fail. According to the contract, + // we do not need to call the [onUndeliveredElement] handler. onSuspend = { segm, _ -> segm.onSlotCleaned() failure() @@ -256,7 +252,7 @@ internal open class BufferedChannel( element = element, waiter = SendBroadcast(cont), onRendezvousOrBuffered = { cont.resume(true) }, - onSuspend = { segm, i -> cont.prepareSenderForSuspension(segm, i) }, + onSuspend = { _, _ -> }, onClosed = { cont.resume(false) } ) } @@ -264,7 +260,9 @@ internal open class BufferedChannel( /** * Specifies waiting [sendBroadcast] operation. */ - private class SendBroadcast(val cont: CancellableContinuation) : Waiter + private class SendBroadcast( + val cont: CancellableContinuation + ) : Waiter by cont as CancellableContinuationImpl /** * Abstract send implementation. @@ -351,6 +349,7 @@ internal open class BufferedChannel( segment.onSlotCleaned() return onClosed() } + (waiter as? Waiter)?.prepareSenderForSuspension(segment, i) return onSuspend(segment, i) } RESULT_CLOSED -> { @@ -377,7 +376,7 @@ internal open class BufferedChannel( } } - private inline fun sendImplOnNoWaiter( + private inline fun sendImplOnNoWaiter( /* The working cell is specified by the segment and the index in it. */ segment: ChannelSegment, @@ -387,21 +386,18 @@ internal open class BufferedChannel( /* The global index of the cell. */ s: Long, /* The waiter to be stored in case of suspension. */ - waiter: Any, + waiter: Waiter, /* This lambda is invoked when the element has been buffered or a rendezvous with a receiver happens.*/ - onRendezvousOrBuffered: () -> R, - /* This lambda is called when the operation suspends in the - cell specified by the segment and the index in it. */ - onSuspend: (segm: ChannelSegment, i: Int) -> R, + onRendezvousOrBuffered: () -> Unit, /* This lambda is called when the channel is observed in the closed state. */ - onClosed: () -> R, - ): R = + onClosed: () -> Unit, + ) { // Update the cell again, now with the non-null waiter, // restarting the operation from the beginning on failure. // Check the `sendImpl(..)` function for the comments. - when(updateCellSend(segment, index, element, s, waiter, false)) { + when (updateCellSend(segment, index, element, s, waiter, false)) { RESULT_RENDEZVOUS -> { segment.cleanPrev() onRendezvousOrBuffered() @@ -410,7 +406,7 @@ internal open class BufferedChannel( onRendezvousOrBuffered() } RESULT_SUSPEND -> { - onSuspend(segment, index) + waiter.prepareSenderForSuspension(segment, index) } RESULT_CLOSED -> { if (s < receiversCounter) segment.cleanPrev() @@ -422,12 +418,13 @@ internal open class BufferedChannel( element = element, waiter = waiter, onRendezvousOrBuffered = onRendezvousOrBuffered, - onSuspend = onSuspend, + onSuspend = { _, _ -> }, onClosed = onClosed, ) } else -> error("unexpected") } + } private fun updateCellSend( /* The working cell is specified by @@ -737,14 +734,13 @@ internal open class BufferedChannel( val onCancellation = onUndeliveredElement?.bindCancellationFun(element, cont.context) cont.resume(element, onCancellation) }, - onSuspend = { segm, i, _ -> cont.prepareReceiverForSuspension(segm, i) }, onClosed = { onClosedReceiveOnNoWaiterSuspend(cont) }, ) } - private fun CancellableContinuation<*>.prepareReceiverForSuspension(segment: ChannelSegment, index: Int) { + private fun Waiter.prepareReceiverForSuspension(segment: ChannelSegment, index: Int) { onReceiveEnqueued() - invokeOnCancellation(SenderOrReceiverCancellationHandler(segment, index).asHandler) + invokeOnCancellation(segment, index) } private fun onClosedReceiveOnNoWaiterSuspend(cont: CancellableContinuation) { @@ -773,15 +769,14 @@ internal open class BufferedChannel( segment: ChannelSegment, index: Int, r: Long - ) = suspendCancellableCoroutineReusable> { cont -> - val waiter = ReceiveCatching(cont) + ) = suspendCancellableCoroutineReusable { cont -> + val waiter = ReceiveCatching(cont as CancellableContinuationImpl>) receiveImplOnNoWaiter( segment, index, r, waiter = waiter, onElementRetrieved = { element -> cont.resume(success(element), onUndeliveredElement?.bindCancellationFun(element, cont.context)) }, - onSuspend = { segm, i, _ -> cont.prepareReceiverForSuspension(segm, i) }, onClosed = { onClosedReceiveCatchingOnNoWaiterSuspend(cont) } ) } @@ -815,7 +810,7 @@ internal open class BufferedChannel( // Finish when an element is successfully retrieved. onElementRetrieved = { element -> success(element) }, // On suspension, the `INTERRUPTED_RCV` token has been - // installed, and this `tryReceive()` fails. + // installed, and this `tryReceive()` must fail. onSuspend = { segm, _, globalIndex -> // Emulate "cancelled" receive, thus invoking 'waitExpandBufferCompletion' manually, // because effectively there were no cancellation @@ -941,6 +936,7 @@ internal open class BufferedChannel( updCellResult === SUSPEND -> { // The operation has decided to suspend and // stored the specified waiter in the cell. + (waiter as? Waiter)?.prepareReceiverForSuspension(segment, i) onSuspend(segment, i, r) } updCellResult === FAILED -> { @@ -970,7 +966,7 @@ internal open class BufferedChannel( } } - private inline fun receiveImplOnNoWaiter( + private inline fun receiveImplOnNoWaiter( /* The working cell is specified by the segment and the index in it. */ segment: ChannelSegment, @@ -978,40 +974,37 @@ internal open class BufferedChannel( /* The global index of the cell. */ r: Long, /* The waiter to be stored in case of suspension. */ - waiter: W, + waiter: Waiter, /* This lambda is invoked when an element has been successfully retrieved, either from the buffer or by making a rendezvous with a suspended sender. */ - onElementRetrieved: (element: E) -> R, - /* This lambda is called when the operation suspends in the cell - specified by the segment and its global and in-segment indices. */ - onSuspend: (segm: ChannelSegment, i: Int, r: Long) -> R, + onElementRetrieved: (element: E) -> Unit, /* This lambda is called when the channel is observed in the closed state and no waiting senders is found, which means that it is closed for receiving. */ - onClosed: () -> R - ): R { + onClosed: () -> Unit + ) { // Update the cell with the non-null waiter, // restarting from the beginning on failure. // Check the `receiveImpl(..)` function for the comments. val updCellResult = updateCellReceive(segment, index, r, waiter) when { updCellResult === SUSPEND -> { - return onSuspend(segment, index, r) + waiter.prepareReceiverForSuspension(segment, index) } updCellResult === FAILED -> { if (r < sendersCounter) segment.cleanPrev() - return receiveImpl( + receiveImpl( waiter = waiter, onElementRetrieved = onElementRetrieved, - onSuspend = onSuspend, + onSuspend = { _, _, _ -> }, onClosed = onClosed ) } else -> { segment.cleanPrev() @Suppress("UNCHECKED_CAST") - return onElementRetrieved(updCellResult as E) + onElementRetrieved(updCellResult as E) } } } @@ -1498,22 +1491,10 @@ internal open class BufferedChannel( element = element as E, waiter = select, onRendezvousOrBuffered = { select.selectInRegistrationPhase(Unit) }, - onSuspend = { segm, i -> select.prepareSenderForSuspension(segm, i) }, + onSuspend = { _, _ -> }, onClosed = { onClosedSelectOnSend(element, select) } ) - private fun SelectInstance<*>.prepareSenderForSuspension( - // The working cell is specified by - // the segment and the index in it. - segment: ChannelSegment, - index: Int - ) { - if (onUndeliveredElement == null) { - disposeOnCompletion(SenderOrReceiverCancellationHandler(segment, index)) - } else { - disposeOnCompletion(SenderWithOnUndeliveredElementCancellationHandler(segment, index, context)) - } - } private fun onClosedSelectOnSend(element: E, select: SelectInstance<*>) { onUndeliveredElement?.callUndeliveredElement(element, select.context) @@ -1557,20 +1538,10 @@ internal open class BufferedChannel( receiveImpl( // <-- this is an inline function waiter = select, onElementRetrieved = { elem -> select.selectInRegistrationPhase(elem) }, - onSuspend = { segm, i, _ -> select.prepareReceiverForSuspension(segm, i) }, + onSuspend = { _, _, _ -> }, onClosed = { onClosedSelectOnReceive(select) } ) - private fun SelectInstance<*>.prepareReceiverForSuspension( - /* The working cell is specified by - the segment and the index in it. */ - segment: ChannelSegment, - index: Int - ) { - onReceiveEnqueued() - disposeOnCompletion(SenderOrReceiverCancellationHandler(segment, index)) - } - private fun onClosedSelectOnReceive(select: SelectInstance<*>) { select.selectInRegistrationPhase(CHANNEL_CLOSED) } @@ -1643,14 +1614,14 @@ internal open class BufferedChannel( // When `hasNext()` suspends, the location where the continuation // is stored is specified via the segment and the index in it. // We need this information in the cancellation handler below. - private var segment: ChannelSegment? = null + private var segment: Segment<*>? = null private var index = -1 /** * Invoked on cancellation, [BeforeResumeCancelHandler] implementation. */ override fun invoke(cause: Throwable?) { - segment?.onCancellation(index) + segment?.onCancellation(index, null) } // `hasNext()` is just a special receive operation. @@ -1706,18 +1677,16 @@ internal open class BufferedChannel( this.continuation = null cont.resume(true, onUndeliveredElement?.bindCancellationFun(element, cont.context)) }, - onSuspend = { segm, i, _ -> prepareForSuspension(segm, i) }, onClosed = { onClosedHasNextNoWaiterSuspend() } ) } - private fun prepareForSuspension(segment: ChannelSegment, index: Int) { + override fun invokeOnCancellation(segment: Segment<*>, index: Int) { this.segment = segment this.index = index // It is possible that this `hasNext()` invocation is already // resumed, and the `continuation` field is already updated to `null`. this.continuation?.invokeOnCancellation(this.asHandler) - onReceiveEnqueued() } private fun onClosedHasNextNoWaiterSuspend() { @@ -2859,6 +2828,10 @@ internal class ChannelSegment(id: Long, prev: ChannelSegment?, channel: Bu // # Cancellation Support # // ######################## + override fun onCancellation(index: Int, cause: Throwable?) { + onCancellation(index) + } + fun onSenderCancellationWithOnUndeliveredElement(index: Int, context: CoroutineContext) { // Read the element first. If the operation has not been successfully resumed // (this cancellation may be caused by prompt cancellation during dispatching), @@ -3062,8 +3035,8 @@ private class WaiterEB(@JvmField val waiter: Waiter) { * uses this wrapper for its continuation. */ private class ReceiveCatching( - @JvmField val cont: CancellableContinuation> -) : Waiter + @JvmField val cont: CancellableContinuationImpl> +) : Waiter by cont /* Internal results for [BufferedChannel.updateCellReceive]. @@ -3148,10 +3121,3 @@ private inline val Long.ebCompletedCounter get() = this and EB_COMPLETED_COUNTER private inline val Long.ebPauseExpandBuffers: Boolean get() = (this and EB_COMPLETED_PAUSE_EXPAND_BUFFERS_BIT) != 0L private fun constructEBCompletedAndPauseFlag(counter: Long, pauseEB: Boolean): Long = (if (pauseEB) EB_COMPLETED_PAUSE_EXPAND_BUFFERS_BIT else 0) + counter - -/** - * All waiters, such as [CancellableContinuationImpl], [SelectInstance], and - * [BufferedChannel.BufferedChannelIterator], should be marked with this interface - * to make the code faster and easier to read. - */ -internal interface Waiter diff --git a/kotlinx-coroutines-core/common/src/channels/ConflatedBufferedChannel.kt b/kotlinx-coroutines-core/common/src/channels/ConflatedBufferedChannel.kt index 6a9f23e958..699030725b 100644 --- a/kotlinx-coroutines-core/common/src/channels/ConflatedBufferedChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/ConflatedBufferedChannel.kt @@ -85,16 +85,16 @@ internal open class ConflatedBufferedChannel( waiter = BUFFERED, // Finish successfully when a rendezvous has happened // or the element has been buffered. - onRendezvousOrBuffered = { success(Unit) }, + onRendezvousOrBuffered = { return success(Unit) }, // In case the algorithm decided to suspend, the element // was added to the buffer. However, as the buffer is now // overflowed, the first (oldest) element has to be extracted. onSuspend = { segm, i -> dropFirstElementUntilTheSpecifiedCellIsInTheBuffer(segm.id * SEGMENT_SIZE + i) - success(Unit) + return success(Unit) }, // If the channel is closed, return the corresponding result. - onClosed = { closed(sendException) } + onClosed = { return closed(sendException) } ) @Suppress("UNCHECKED_CAST") diff --git a/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt b/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt index 4b27d8491a..f848e37881 100644 --- a/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt +++ b/kotlinx-coroutines-core/common/src/internal/ConcurrentLinkedList.kt @@ -188,8 +188,21 @@ internal abstract class ConcurrentLinkedListNode * Each segment in the list has a unique id and is created by the provided to [findSegmentAndMoveForward] method. * Essentially, this is a node in the Michael-Scott queue algorithm, * but with maintaining [prev] pointer for efficient [remove] implementation. + * + * NB: this class cannot be public or leak into user's code as public type as [CancellableContinuationImpl] + * instance-check it and uses a separate code-path for that. */ -internal abstract class Segment>(val id: Long, prev: S?, pointers: Int): ConcurrentLinkedListNode(prev) { +internal abstract class Segment>( + @JvmField val id: Long, prev: S?, pointers: Int +) : ConcurrentLinkedListNode(prev), + // Segments typically store waiting continuations. Thus, on cancellation, the corresponding + // slot should be cleaned and the segment should be removed if it becomes full of cancelled cells. + // To install such a handler efficiently, without creating an extra object, we allow storing + // segments as cancellation handlers in [CancellableContinuationImpl] state, putting the slot + // index in another field. The details are here: https://github.com/Kotlin/kotlinx.coroutines/pull/3084. + // For that, we need segments to implement this internal marker interface. + NotCompleted +{ /** * This property should return the number of slots in this segment, * it is used to define whether the segment is logically removed. @@ -213,6 +226,13 @@ internal abstract class Segment>(val id: Long, prev: S?, pointers // returns `true` if this segment is logically removed after the decrement. internal fun decPointers() = cleanedAndPointers.addAndGet(-(1 shl POINTERS_SHIFT)) == numberOfSlots && !isTail + /** + * This function is invoked on continuation cancellation when this segment + * with the specified [index] are installed as cancellation handler via + * `SegmentDisposable.disposeOnCancellation(Segment, Int)`. + */ + abstract fun onCancellation(index: Int, cause: Throwable?) + /** * Invoked on each slot clean-up; should not be invoked twice for the same slot. */ diff --git a/kotlinx-coroutines-core/common/src/selects/Select.kt b/kotlinx-coroutines-core/common/src/selects/Select.kt index b9d128b7f8..ce48de34a1 100644 --- a/kotlinx-coroutines-core/common/src/selects/Select.kt +++ b/kotlinx-coroutines-core/common/src/selects/Select.kt @@ -376,11 +376,24 @@ internal open class SelectImplementation constructor( private var clauses: MutableList>? = ArrayList(2) /** - * Stores the completion action provided through [disposeOnCompletion] during clause registration. - * After that, if the clause is successfully registered (so, it has not completed immediately), - * this [DisposableHandle] is stored into the corresponding [ClauseData] instance. + * Stores the completion action provided through [disposeOnCompletion] or [invokeOnCancellation] + * during clause registration. After that, if the clause is successfully registered + * (so, it has not completed immediately), this handler is stored into + * the corresponding [ClauseData] instance. + * + * Note that either [DisposableHandle] is provided, or a [Segment] instance with + * the index in it, which specify the location of storing this `select`. + * In the latter case, [Segment.onCancellation] should be called on completion/cancellation. + */ + private var disposableHandleOrSegment: Any? = null + + /** + * In case the disposable handle is specified via [Segment] + * and index in it, implying calling [Segment.onCancellation], + * the corresponding index is stored in this field. + * The segment is stored in [disposableHandleOrSegment]. */ - private var disposableHandle: DisposableHandle? = null + private var indexInSegment: Int = -1 /** * Stores the result passed via [selectInRegistrationPhase] during clause registration @@ -469,8 +482,10 @@ internal open class SelectImplementation constructor( // This also guarantees that the list of clauses cannot be cleared // in the registration phase, so it is safe to read it with "!!". if (!reregister) clauses!! += this - disposableHandle = this@SelectImplementation.disposableHandle - this@SelectImplementation.disposableHandle = null + disposableHandleOrSegment = this@SelectImplementation.disposableHandleOrSegment + indexInSegment = this@SelectImplementation.indexInSegment + this@SelectImplementation.disposableHandleOrSegment = null + this@SelectImplementation.indexInSegment = -1 } else { // This clause has been selected! // Update the state correspondingly. @@ -493,7 +508,23 @@ internal open class SelectImplementation constructor( } override fun disposeOnCompletion(disposableHandle: DisposableHandle) { - this.disposableHandle = disposableHandle + this.disposableHandleOrSegment = disposableHandle + } + + /** + * An optimized version for the code below that does not allocate + * a cancellation handler object and efficiently stores the specified + * [segment] and [index]. + * + * ``` + * disposeOnCompletion { + * segment.onCancellation(index, null) + * } + * ``` + */ + override fun invokeOnCancellation(segment: Segment<*>, index: Int) { + this.disposableHandleOrSegment = segment + this.indexInSegment = index } override fun selectInRegistrationPhase(internalResult: Any?) { @@ -556,7 +587,8 @@ internal open class SelectImplementation constructor( */ private fun reregisterClause(clauseObject: Any) { val clause = findClause(clauseObject)!! // it is guaranteed that the corresponding clause is presented - clause.disposableHandle = null + clause.disposableHandleOrSegment = null + clause.indexInSegment = -1 clause.register(reregister = true) } @@ -692,7 +724,7 @@ internal open class SelectImplementation constructor( // Invoke all cancellation handlers except for the // one related to the selected clause, if specified. clauses.forEach { clause -> - if (clause !== selectedClause) clause.disposableHandle?.dispose() + if (clause !== selectedClause) clause.dispose() } // We do need to clean all the data to avoid memory leaks. this.state.value = STATE_COMPLETED @@ -716,7 +748,7 @@ internal open class SelectImplementation constructor( // a concurrent clean-up procedure has already completed, and it is safe to finish. val clauses = this.clauses ?: return // Remove this `select` instance from all the clause object (channels, mutexes, etc.). - clauses.forEach { it.disposableHandle?.dispose() } + clauses.forEach { it.dispose() } // We do need to clean all the data to avoid memory leaks. this.internalResult = NO_RESULT this.clauses = null @@ -731,9 +763,11 @@ internal open class SelectImplementation constructor( private val processResFunc: ProcessResultFunction, private val param: Any?, // the user-specified param private val block: Any, // the user-specified block, which should be called if this clause becomes selected - @JvmField val onCancellationConstructor: OnCancellationConstructor?, - @JvmField var disposableHandle: DisposableHandle? = null + @JvmField val onCancellationConstructor: OnCancellationConstructor? ) { + @JvmField var disposableHandleOrSegment: Any? = null + @JvmField var indexInSegment: Int = -1 + /** * Tries to register the specified [select] instance in [clauseObject] and check * whether the registration succeeded or a rendezvous has happened during the registration. @@ -788,6 +822,16 @@ internal open class SelectImplementation constructor( } } + fun dispose() { + with(disposableHandleOrSegment) { + if (this is Segment<*>) { + this.onCancellation(indexInSegment, null) + } else { + (this as? DisposableHandle)?.dispose() + } + } + } + fun createOnCancellationAction(select: SelectInstance<*>, internalResult: Any?) = onCancellationConstructor?.invoke(select, param, internalResult) } diff --git a/kotlinx-coroutines-core/common/src/sync/Mutex.kt b/kotlinx-coroutines-core/common/src/sync/Mutex.kt index b32c67c0ec..5a75013c64 100644 --- a/kotlinx-coroutines-core/common/src/sync/Mutex.kt +++ b/kotlinx-coroutines-core/common/src/sync/Mutex.kt @@ -165,7 +165,7 @@ internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1 lockSuspend(owner) } - private suspend fun lockSuspend(owner: Any?) = suspendCancellableCoroutineReusable { cont -> + private suspend fun lockSuspend(owner: Any?) = suspendCancellableCoroutineReusable { cont -> val contWithOwner = CancellableContinuationWithOwner(cont, owner) acquire(contWithOwner) } @@ -230,7 +230,7 @@ internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1 if (owner != null && holdsLock(owner)) { select.selectInRegistrationPhase(ON_LOCK_ALREADY_LOCKED_BY_OWNER) } else { - onAcquireRegFunction(SelectInstanceWithOwner(select, owner), owner) + onAcquireRegFunction(SelectInstanceWithOwner(select as SelectInstanceInternal<*>, owner), owner) } } @@ -243,10 +243,10 @@ internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1 private inner class CancellableContinuationWithOwner( @JvmField - val cont: CancellableContinuation, + val cont: CancellableContinuationImpl, @JvmField val owner: Any? - ) : CancellableContinuation by cont { + ) : CancellableContinuation by cont, Waiter by cont { override fun tryResume(value: Unit, idempotent: Any?, onCancellation: ((cause: Throwable) -> Unit)?): Any? { assert { this@MutexImpl.owner.value === NO_OWNER } val token = cont.tryResume(value, idempotent) { @@ -270,10 +270,10 @@ internal open class MutexImpl(locked: Boolean) : SemaphoreImpl(1, if (locked) 1 private inner class SelectInstanceWithOwner( @JvmField - val select: SelectInstance, + val select: SelectInstanceInternal, @JvmField val owner: Any? - ) : SelectInstanceInternal by select as SelectInstanceInternal { + ) : SelectInstanceInternal by select { override fun trySelect(clauseObject: Any, result: Any?): Boolean { assert { this@MutexImpl.owner.value === NO_OWNER } return select.trySelect(clauseObject, result).also { success -> diff --git a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt index 82c1ed63f6..8ef888d801 100644 --- a/kotlinx-coroutines-core/common/src/sync/Semaphore.kt +++ b/kotlinx-coroutines-core/common/src/sync/Semaphore.kt @@ -195,7 +195,7 @@ internal open class SemaphoreImpl(private val permits: Int, acquiredPermits: Int @JsName("acquireCont") protected fun acquire(waiter: CancellableContinuation) = acquire( waiter = waiter, - suspend = { cont -> addAcquireToQueue(cont) }, + suspend = { cont -> addAcquireToQueue(cont as Waiter) }, onAcquired = { cont -> cont.resume(Unit, onCancellationRelease) } ) @@ -219,7 +219,7 @@ internal open class SemaphoreImpl(private val permits: Int, acquiredPermits: Int protected fun onAcquireRegFunction(select: SelectInstance<*>, ignoredParam: Any?) = acquire( waiter = select, - suspend = { s -> addAcquireToQueue(s) }, + suspend = { s -> addAcquireToQueue(s as Waiter) }, onAcquired = { s -> s.selectInRegistrationPhase(Unit) } ) @@ -281,7 +281,7 @@ internal open class SemaphoreImpl(private val permits: Int, acquiredPermits: Int /** * Returns `false` if the received permit cannot be used and the calling operation should restart. */ - private fun addAcquireToQueue(waiter: Any): Boolean { + private fun addAcquireToQueue(waiter: Waiter): Boolean { val curTail = this.tail.value val enqIdx = enqIdx.getAndIncrement() val createNewSegment = ::createSegment @@ -290,15 +290,7 @@ internal open class SemaphoreImpl(private val permits: Int, acquiredPermits: Int val i = (enqIdx % SEGMENT_SIZE).toInt() // the regular (fast) path -- if the cell is empty, try to install continuation if (segment.cas(i, null, waiter)) { // installed continuation successfully - when (waiter) { - is CancellableContinuation<*> -> { - waiter.invokeOnCancellation(CancelSemaphoreAcquisitionHandler(segment, i).asHandler) - } - is SelectInstance<*> -> { - waiter.disposeOnCompletion(CancelSemaphoreAcquisitionHandler(segment, i)) - } - else -> error("unexpected: $waiter") - } + waiter.invokeOnCancellation(segment, i) return true } // On CAS failure -- the cell must be either PERMIT or BROKEN @@ -364,20 +356,7 @@ internal open class SemaphoreImpl(private val permits: Int, acquiredPermits: Int } } -private class CancelSemaphoreAcquisitionHandler( - private val segment: SemaphoreSegment, - private val index: Int -) : CancelHandler(), DisposableHandle { - override fun invoke(cause: Throwable?) = dispose() - - override fun dispose() { - segment.cancel(index) - } - - override fun toString() = "CancelSemaphoreAcquisitionHandler[$segment, $index]" -} - -private fun createSegment(id: Long, prev: SemaphoreSegment) = SemaphoreSegment(id, prev, 0) +private fun createSegment(id: Long, prev: SemaphoreSegment?) = SemaphoreSegment(id, prev, 0) private class SemaphoreSegment(id: Long, prev: SemaphoreSegment?, pointers: Int) : Segment(id, prev, pointers) { val acquirers = atomicArrayOfNulls(SEGMENT_SIZE) @@ -399,7 +378,7 @@ private class SemaphoreSegment(id: Long, prev: SemaphoreSegment?, pointers: Int) // Cleans the acquirer slot located by the specified index // and removes this segment physically if all slots are cleaned. - fun cancel(index: Int) { + override fun onCancellation(index: Int, cause: Throwable?) { // Clean the slot set(index, CANCELLED) // Remove this segment if needed diff --git a/kotlinx-coroutines-core/common/test/CancellableContinuationHandlersTest.kt b/kotlinx-coroutines-core/common/test/CancellableContinuationHandlersTest.kt index 3c11182e00..bd6a44fff8 100644 --- a/kotlinx-coroutines-core/common/test/CancellableContinuationHandlersTest.kt +++ b/kotlinx-coroutines-core/common/test/CancellableContinuationHandlersTest.kt @@ -6,6 +6,7 @@ package kotlinx.coroutines +import kotlinx.coroutines.internal.* import kotlin.coroutines.* import kotlin.test.* @@ -159,4 +160,31 @@ class CancellableContinuationHandlersTest : TestBase() { } finish(3) } + + @Test + fun testSegmentAsHandler() = runTest { + class MySegment : Segment(0, null, 0) { + override val numberOfSlots: Int get() = 0 + + var invokeOnCancellationCalled = false + override fun onCancellation(index: Int, cause: Throwable?) { + invokeOnCancellationCalled = true + } + } + val s = MySegment() + expect(1) + try { + suspendCancellableCoroutine { c -> + expect(2) + c as CancellableContinuationImpl<*> + c.invokeOnCancellation(s, 0) + c.cancel() + } + } catch (e: CancellationException) { + expect(3) + } + expect(4) + check(s.invokeOnCancellationCalled) + finish(5) + } } From bc9850e38cf5b67af2f670f50fd25106ba44ee1f Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 3 Mar 2023 16:14:20 +0300 Subject: [PATCH 098/106] Deprecate BroadcastChannel and the corresponding API (#3647) Fixes #2680 --- .../common/src/channels/Broadcast.kt | 16 +++++++------- .../common/src/channels/BroadcastChannel.kt | 21 ++++++++++--------- .../common/src/channels/Channels.common.kt | 19 ++++++++++------- .../common/src/flow/Channels.kt | 3 ++- 4 files changed, 33 insertions(+), 26 deletions(-) diff --git a/kotlinx-coroutines-core/common/src/channels/Broadcast.kt b/kotlinx-coroutines-core/common/src/channels/Broadcast.kt index 0a76c82f6a..e7a58ccdc4 100644 --- a/kotlinx-coroutines-core/common/src/channels/Broadcast.kt +++ b/kotlinx-coroutines-core/common/src/channels/Broadcast.kt @@ -2,6 +2,8 @@ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ +@file:Suppress("DEPRECATION") + package kotlinx.coroutines.channels import kotlinx.coroutines.* @@ -35,13 +37,13 @@ import kotlin.coroutines.intrinsics.* * [send][BroadcastChannel.send] and [close][BroadcastChannel.close] operations that interfere with * the broadcasting coroutine in hard-to-specify ways. * - * **Note: This API is obsolete since 1.5.0.** It will be deprecated with warning in 1.6.0 - * and with error in 1.7.0. It is replaced with [Flow.shareIn][kotlinx.coroutines.flow.shareIn] - * operator. + * **Note: This API is obsolete since 1.5.0.** It is deprecated with warning in 1.7.0. + * It is replaced with [Flow.shareIn][kotlinx.coroutines.flow.shareIn] operator. * * @param start coroutine start option. The default value is [CoroutineStart.LAZY]. */ @ObsoleteCoroutinesApi +@Deprecated(level = DeprecationLevel.WARNING, message = "BroadcastChannel is deprecated in the favour of SharedFlow and is no longer supported") public fun ReceiveChannel.broadcast( capacity: Int = 1, start: CoroutineStart = CoroutineStart.LAZY @@ -98,12 +100,11 @@ public fun ReceiveChannel.broadcast( * * ### Future replacement * - * This API is obsolete since 1.5.0. + * This API is obsolete since 1.5.0 and deprecated with warning since 1.7.0. * This function has an inappropriate result type of [BroadcastChannel] which provides * [send][BroadcastChannel.send] and [close][BroadcastChannel.close] operations that interfere with - * the broadcasting coroutine in hard-to-specify ways. It will be deprecated with warning in 1.6.0 - * and with error in 1.7.0. It is replaced with [Flow.shareIn][kotlinx.coroutines.flow.shareIn] - * operator. + * the broadcasting coroutine in hard-to-specify ways. + * It is replaced with [Flow.shareIn][kotlinx.coroutines.flow.shareIn] operator. * * @param context additional to [CoroutineScope.coroutineContext] context of the coroutine. * @param capacity capacity of the channel's buffer (1 by default). @@ -112,6 +113,7 @@ public fun ReceiveChannel.broadcast( * @param block the coroutine code. */ @ObsoleteCoroutinesApi +@Deprecated(level = DeprecationLevel.WARNING, message = "BroadcastChannel is deprecated in the favour of SharedFlow and is no longer supported") public fun CoroutineScope.broadcast( context: CoroutineContext = EmptyCoroutineContext, capacity: Int = 1, diff --git a/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt index de67b60a2d..e3c3a30666 100644 --- a/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/BroadcastChannel.kt @@ -2,7 +2,7 @@ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("FunctionName") +@file:Suppress("FunctionName", "DEPRECATION") package kotlinx.coroutines.channels @@ -24,10 +24,11 @@ import kotlin.native.concurrent.* * See `BroadcastChannel()` factory function for the description of available * broadcast channel implementations. * - * **Note: This API is obsolete since 1.5.0.** It will be deprecated with warning in 1.6.0 - * and with error in 1.7.0. It is replaced with [SharedFlow][kotlinx.coroutines.flow.SharedFlow]. + * **Note: This API is obsolete since 1.5.0 and deprecated for removal since 1.7.0** + * It is replaced with [SharedFlow][kotlinx.coroutines.flow.SharedFlow]. */ @ObsoleteCoroutinesApi +@Deprecated(level = DeprecationLevel.WARNING, message = "BroadcastChannel is deprecated in the favour of SharedFlow and is no longer supported") public interface BroadcastChannel : SendChannel { /** * Subscribes to this [BroadcastChannel] and returns a channel to receive elements from it. @@ -64,11 +65,11 @@ public interface BroadcastChannel : SendChannel { * * when `capacity` is [BUFFERED] -- creates `ArrayBroadcastChannel` with a default capacity. * * otherwise -- throws [IllegalArgumentException]. * - * **Note: This API is obsolete since 1.5.0.** It will be deprecated with warning in 1.6.0 - * and with error in 1.7.0. It is replaced with [StateFlow][kotlinx.coroutines.flow.StateFlow] - * and [SharedFlow][kotlinx.coroutines.flow.SharedFlow]. + * **Note: This API is obsolete since 1.5.0 and deprecated for removal since 1.7.0** + * It is replaced with [SharedFlow][kotlinx.coroutines.flow.SharedFlow] and [StateFlow][kotlinx.coroutines.flow.StateFlow]. */ @ObsoleteCoroutinesApi +@Deprecated(level = DeprecationLevel.WARNING, message = "BroadcastChannel is deprecated in the favour of SharedFlow and StateFlow, and is no longer supported") public fun BroadcastChannel(capacity: Int): BroadcastChannel = when (capacity) { 0 -> throw IllegalArgumentException("Unsupported 0 capacity for BroadcastChannel") @@ -92,10 +93,11 @@ public fun BroadcastChannel(capacity: Int): BroadcastChannel = * In this implementation, [opening][openSubscription] and [closing][ReceiveChannel.cancel] subscription * takes linear time in the number of subscribers. * - * **Note: This API is obsolete since 1.5.0.** It will be deprecated with warning in 1.7.0 - * and with error in 1.8.0. It is replaced with [StateFlow][kotlinx.coroutines.flow.StateFlow]. + * **Note: This API is obsolete since 1.5.0 and deprecated for removal since 1.7.0** + * It is replaced with [SharedFlow][kotlinx.coroutines.flow.StateFlow]. */ @ObsoleteCoroutinesApi +@Deprecated(level = DeprecationLevel.WARNING, message = "ConflatedBroadcastChannel is deprecated in the favour of SharedFlow and is no longer supported") public class ConflatedBroadcastChannel private constructor( private val broadcast: BroadcastChannelImpl ) : BroadcastChannel by broadcast { @@ -408,5 +410,4 @@ internal class BroadcastChannelImpl( "SUBSCRIBERS=${subscribers.joinToString(separator = ";", prefix = "<", postfix = ">")}" } -@SharedImmutable -private val NO_ELEMENT = Symbol("NO_ELEMENT") \ No newline at end of file +private val NO_ELEMENT = Symbol("NO_ELEMENT") diff --git a/kotlinx-coroutines-core/common/src/channels/Channels.common.kt b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt index ac2e4cf6f0..6ff3b385e4 100644 --- a/kotlinx-coroutines-core/common/src/channels/Channels.common.kt +++ b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt @@ -3,7 +3,6 @@ */ @file:JvmMultifileClass @file:JvmName("ChannelsKt") -@file:Suppress("DEPRECATION_ERROR") @file:OptIn(ExperimentalContracts::class) package kotlinx.coroutines.channels @@ -22,10 +21,14 @@ internal const val DEFAULT_CLOSE_MESSAGE = "Channel was closed" * Opens subscription to this [BroadcastChannel] and makes sure that the given [block] consumes all elements * from it by always invoking [cancel][ReceiveChannel.cancel] after the execution of the block. * - * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.** - * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254). + * **Note: This API is obsolete since 1.5.0 and deprecated for removal since 1.7.0** + * It is replaced with [SharedFlow][kotlinx.coroutines.flow.SharedFlow]. + * + * Safe to remove in 1.9.0 as was inline before. */ @ObsoleteCoroutinesApi +@Suppress("DEPRECATION") +@Deprecated(level = DeprecationLevel.WARNING, message = "BroadcastChannel is deprecated in the favour of SharedFlow and is no longer supported") public inline fun BroadcastChannel.consume(block: ReceiveChannel.() -> R): R { val channel = openSubscription() try { @@ -51,7 +54,7 @@ public inline fun BroadcastChannel.consume(block: ReceiveChannel.() ReplaceWith("receiveCatching().getOrNull()"), DeprecationLevel.ERROR ) // Warning since 1.5.0, ERROR in 1.6.0, HIDDEN in 1.7.0 -@Suppress("EXTENSION_SHADOWED_BY_MEMBER") +@Suppress("EXTENSION_SHADOWED_BY_MEMBER", "DEPRECATION_ERROR") public suspend fun ReceiveChannel.receiveOrNull(): E? { return (this as ReceiveChannel).receiveOrNull() } @@ -63,6 +66,7 @@ public suspend fun ReceiveChannel.receiveOrNull(): E? { "Deprecated in the favour of 'onReceiveCatching'", level = DeprecationLevel.ERROR ) // Warning since 1.5.0, ERROR in 1.6.0, HIDDEN in 1.7.0 +@Suppress("DEPRECATION_ERROR") public fun ReceiveChannel.onReceiveOrNull(): SelectClause1 { return (this as ReceiveChannel).onReceiveOrNull } @@ -107,7 +111,6 @@ public suspend inline fun ReceiveChannel.consumeEach(action: (E) -> Unit) * The operation is _terminal_. * This function [consumes][ReceiveChannel.consume] all elements of the original [ReceiveChannel]. */ -@OptIn(ExperimentalStdlibApi::class) public suspend fun ReceiveChannel.toList(): List = buildList { consumeEach { add(it) @@ -117,10 +120,10 @@ public suspend fun ReceiveChannel.toList(): List = buildList { /** * Subscribes to this [BroadcastChannel] and performs the specified action for each received element. * - * **Note: This API will become obsolete in future updates with introduction of lazy asynchronous streams.** - * See [issue #254](https://github.com/Kotlin/kotlinx.coroutines/issues/254). + * **Note: This API is obsolete since 1.5.0 and deprecated for removal since 1.7.0** */ -@ObsoleteCoroutinesApi +@Deprecated(level = DeprecationLevel.WARNING, message = "BroadcastChannel is deprecated in the favour of SharedFlow and is no longer supported") +@Suppress("DEPRECATION") public suspend inline fun BroadcastChannel.consumeEach(action: (E) -> Unit): Unit = consume { for (element in this) action(element) diff --git a/kotlinx-coroutines-core/common/src/flow/Channels.kt b/kotlinx-coroutines-core/common/src/flow/Channels.kt index 3da0780fe7..289d2ea966 100644 --- a/kotlinx-coroutines-core/common/src/flow/Channels.kt +++ b/kotlinx-coroutines-core/common/src/flow/Channels.kt @@ -144,11 +144,12 @@ private class ChannelAsFlow( * 2) Flow consumer completes normally when the original channel completes (~is closed) normally. * 3) If the flow consumer fails with an exception, subscription is cancelled. */ +@Suppress("DEPRECATION") @Deprecated( level = DeprecationLevel.WARNING, message = "'BroadcastChannel' is obsolete and all corresponding operators are deprecated " + "in the favour of StateFlow and SharedFlow" -) // Since 1.5.0, was @FlowPreview, safe to remove in 1.7.0 +) // Since 1.5.0, was @FlowPreview, safe to remove in 1.8.0 public fun BroadcastChannel.asFlow(): Flow = flow { emitAll(openSubscription()) } From 2a050887fe42bd01455da86b7c5e1376fdad49d4 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 3 Mar 2023 17:40:46 +0300 Subject: [PATCH 099/106] Promote deprecation levels for 1.7.0 (#3637) --- .../api/kotlinx-coroutines-core.api | 6 +-- .../common/src/channels/Channel.kt | 6 +-- .../common/src/channels/Channels.common.kt | 5 +- .../common/src/flow/Channels.kt | 4 +- .../common/src/selects/Select.kt | 2 +- .../concurrent/src/channels/Channels.kt | 4 +- .../jvm/test/flow/ConsumeAsFlowLeakTest.kt | 48 ------------------- .../common/src/TestBuilders.kt | 4 +- .../api/kotlinx-coroutines-reactive.api | 6 +-- .../kotlinx-coroutines-reactive/src/Await.kt | 6 +-- .../api/kotlinx-coroutines-reactor.api | 12 ++--- .../kotlinx-coroutines-reactor/src/Convert.kt | 2 +- .../kotlinx-coroutines-reactor/src/Mono.kt | 10 ++-- .../api/kotlinx-coroutines-rx2.api | 6 +-- .../kotlinx-coroutines-rx2/src/RxAwait.kt | 4 +- .../kotlinx-coroutines-rx2/src/RxConvert.kt | 2 +- .../api/kotlinx-coroutines-rx3.api | 4 +- .../kotlinx-coroutines-rx3/src/RxAwait.kt | 4 +- 18 files changed, 44 insertions(+), 91 deletions(-) delete mode 100644 kotlinx-coroutines-core/jvm/test/flow/ConsumeAsFlowLeakTest.kt diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index b4f2928d03..d1a0eb8c25 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -753,10 +753,10 @@ public final class kotlinx/coroutines/channels/ChannelsKt { public static final synthetic fun maxWith (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Comparator;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun minWith (Lkotlinx/coroutines/channels/ReceiveChannel;Ljava/util/Comparator;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun none (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun onReceiveOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/selects/SelectClause1; - public static final fun receiveOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final synthetic fun onReceiveOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/selects/SelectClause1; + public static final synthetic fun receiveOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun requireNoNulls (Lkotlinx/coroutines/channels/ReceiveChannel;)Lkotlinx/coroutines/channels/ReceiveChannel; - public static final fun sendBlocking (Lkotlinx/coroutines/channels/SendChannel;Ljava/lang/Object;)V + public static final synthetic fun sendBlocking (Lkotlinx/coroutines/channels/SendChannel;Ljava/lang/Object;)V public static final synthetic fun single (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun singleOrNull (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final synthetic fun take (Lkotlinx/coroutines/channels/ReceiveChannel;ILkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/channels/ReceiveChannel; diff --git a/kotlinx-coroutines-core/common/src/channels/Channel.kt b/kotlinx-coroutines-core/common/src/channels/Channel.kt index 052bddb4d9..d6f752c740 100644 --- a/kotlinx-coroutines-core/common/src/channels/Channel.kt +++ b/kotlinx-coroutines-core/common/src/channels/Channel.kt @@ -165,7 +165,7 @@ public interface SendChannel { level = DeprecationLevel.ERROR, message = "Deprecated in the favour of 'trySend' method", replaceWith = ReplaceWith("trySend(element).isSuccess") - ) // Warning since 1.5.0, error since 1.6.0 + ) // Warning since 1.5.0, error since 1.6.0, not hidden until 1.8+ because API is quite widespread public fun offer(element: E): Boolean { val result = trySend(element) if (result.isSuccess) return true @@ -329,7 +329,7 @@ public interface ReceiveChannel { "Please note that the provided replacement does not rethrow channel's close cause as 'poll' did, " + "for the precise replacement please refer to the 'poll' documentation", replaceWith = ReplaceWith("tryReceive().getOrNull()") - ) // Warning since 1.5.0, error since 1.6.0 + ) // Warning since 1.5.0, error since 1.6.0, not hidden until 1.8+ because API is quite widespread public fun poll(): E? { val result = tryReceive() if (result.isSuccess) return result.getOrThrow() @@ -361,7 +361,7 @@ public interface ReceiveChannel { "for the detailed replacement please refer to the 'receiveOrNull' documentation", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith("receiveCatching().getOrNull()") - ) // Warning since 1.3.0, error in 1.5.0, will be hidden in 1.6.0 + ) // Warning since 1.3.0, error in 1.5.0, cannot be hidden due to deprecated extensions public suspend fun receiveOrNull(): E? = receiveCatching().getOrNull() /** diff --git a/kotlinx-coroutines-core/common/src/channels/Channels.common.kt b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt index 6ff3b385e4..ce454ff961 100644 --- a/kotlinx-coroutines-core/common/src/channels/Channels.common.kt +++ b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt @@ -52,10 +52,11 @@ public inline fun BroadcastChannel.consume(block: ReceiveChannel.() @Deprecated( "Deprecated in the favour of 'receiveCatching'", ReplaceWith("receiveCatching().getOrNull()"), - DeprecationLevel.ERROR + DeprecationLevel.HIDDEN ) // Warning since 1.5.0, ERROR in 1.6.0, HIDDEN in 1.7.0 @Suppress("EXTENSION_SHADOWED_BY_MEMBER", "DEPRECATION_ERROR") public suspend fun ReceiveChannel.receiveOrNull(): E? { + @Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE") return (this as ReceiveChannel).receiveOrNull() } @@ -64,7 +65,7 @@ public suspend fun ReceiveChannel.receiveOrNull(): E? { */ @Deprecated( "Deprecated in the favour of 'onReceiveCatching'", - level = DeprecationLevel.ERROR + level = DeprecationLevel.HIDDEN ) // Warning since 1.5.0, ERROR in 1.6.0, HIDDEN in 1.7.0 @Suppress("DEPRECATION_ERROR") public fun ReceiveChannel.onReceiveOrNull(): SelectClause1 { diff --git a/kotlinx-coroutines-core/common/src/flow/Channels.kt b/kotlinx-coroutines-core/common/src/flow/Channels.kt index 289d2ea966..a9566628b4 100644 --- a/kotlinx-coroutines-core/common/src/flow/Channels.kt +++ b/kotlinx-coroutines-core/common/src/flow/Channels.kt @@ -146,10 +146,10 @@ private class ChannelAsFlow( */ @Suppress("DEPRECATION") @Deprecated( - level = DeprecationLevel.WARNING, + level = DeprecationLevel.ERROR, message = "'BroadcastChannel' is obsolete and all corresponding operators are deprecated " + "in the favour of StateFlow and SharedFlow" -) // Since 1.5.0, was @FlowPreview, safe to remove in 1.8.0 +) // Since 1.5.0, ERROR since 1.7.0, was @FlowPreview, safe to remove in 1.8.0 public fun BroadcastChannel.asFlow(): Flow = flow { emitAll(openSubscription()) } diff --git a/kotlinx-coroutines-core/common/src/selects/Select.kt b/kotlinx-coroutines-core/common/src/selects/Select.kt index ce48de34a1..51ea522a97 100644 --- a/kotlinx-coroutines-core/common/src/selects/Select.kt +++ b/kotlinx-coroutines-core/common/src/selects/Select.kt @@ -110,7 +110,7 @@ public sealed interface SelectBuilder { @Deprecated( message = "Replaced with the same extension function", level = DeprecationLevel.ERROR, replaceWith = ReplaceWith(expression = "onTimeout", imports = ["kotlinx.coroutines.selects.onTimeout"]) - ) + ) // Since 1.7.0, was experimental public fun onTimeout(timeMillis: Long, block: suspend () -> R): Unit = onTimeout(timeMillis, block) } diff --git a/kotlinx-coroutines-core/concurrent/src/channels/Channels.kt b/kotlinx-coroutines-core/concurrent/src/channels/Channels.kt index 24422b5a6b..96d3546ba8 100644 --- a/kotlinx-coroutines-core/concurrent/src/channels/Channels.kt +++ b/kotlinx-coroutines-core/concurrent/src/channels/Channels.kt @@ -44,11 +44,11 @@ public fun SendChannel.trySendBlocking(element: E): ChannelResult { /** @suppress */ @Deprecated( - level = DeprecationLevel.ERROR, + level = DeprecationLevel.HIDDEN, message = "Deprecated in the favour of 'trySendBlocking'. " + "Consider handling the result of 'trySendBlocking' explicitly and rethrow exception if necessary", replaceWith = ReplaceWith("trySendBlocking(element)") -) // WARNING in 1.5.0, ERROR in 1.6.0, HIDDEN in 1.7.0 +) // WARNING in 1.5.0, ERROR in 1.6.0 public fun SendChannel.sendBlocking(element: E) { // fast path if (trySend(element).isSuccess) diff --git a/kotlinx-coroutines-core/jvm/test/flow/ConsumeAsFlowLeakTest.kt b/kotlinx-coroutines-core/jvm/test/flow/ConsumeAsFlowLeakTest.kt deleted file mode 100644 index c037be1e6d..0000000000 --- a/kotlinx-coroutines-core/jvm/test/flow/ConsumeAsFlowLeakTest.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2016-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. - */ - -package kotlinx.coroutines.flow - -import kotlinx.coroutines.* -import kotlinx.coroutines.channels.* -import org.junit.Test -import kotlin.test.* - -class ConsumeAsFlowLeakTest : TestBase() { - - private data class Box(val i: Int) - - // In companion to avoid references through runTest - companion object { - private val first = Box(4) - private val second = Box(5) - } - - // @Test //ignored until KT-33986 - fun testReferenceIsNotRetained() = testReferenceNotRetained(true) - - @Test - fun testReferenceIsNotRetainedNoSuspension() = testReferenceNotRetained(false) - - private fun testReferenceNotRetained(shouldSuspendOnSend: Boolean) = runTest { - val channel = BroadcastChannel(1) - val job = launch { - expect(2) - channel.asFlow().collect { - expect(it.i) - } - } - - expect(1) - yield() - expect(3) - channel.send(first) - if (shouldSuspendOnSend) yield() - channel.send(second) - yield() - FieldWalker.assertReachableCount(0, channel) { it === second } - finish(6) - job.cancelAndJoin() - } -} diff --git a/kotlinx-coroutines-test/common/src/TestBuilders.kt b/kotlinx-coroutines-test/common/src/TestBuilders.kt index 677450ba89..15cd1fba4a 100644 --- a/kotlinx-coroutines-test/common/src/TestBuilders.kt +++ b/kotlinx-coroutines-test/common/src/TestBuilders.kt @@ -285,7 +285,7 @@ public fun runTest( ReplaceWith("runTest(context, timeout = dispatchTimeoutMs.milliseconds, testBody)", "kotlin.time.Duration.Companion.milliseconds"), DeprecationLevel.WARNING -) +) // Warning since 1.7.0, was experimental in 1.6.x public fun runTest( context: CoroutineContext = EmptyCoroutineContext, dispatchTimeoutMs: Long, @@ -385,7 +385,7 @@ public fun TestScope.runTest( ReplaceWith("this.runTest(timeout = dispatchTimeoutMs.milliseconds, testBody)", "kotlin.time.Duration.Companion.milliseconds"), DeprecationLevel.WARNING -) +) // Warning since 1.7.0, was experimental in 1.6.x public fun TestScope.runTest( dispatchTimeoutMs: Long, testBody: suspend TestScope.() -> Unit diff --git a/reactive/kotlinx-coroutines-reactive/api/kotlinx-coroutines-reactive.api b/reactive/kotlinx-coroutines-reactive/api/kotlinx-coroutines-reactive.api index 4772be0579..3a2ea12d7a 100644 --- a/reactive/kotlinx-coroutines-reactive/api/kotlinx-coroutines-reactive.api +++ b/reactive/kotlinx-coroutines-reactive/api/kotlinx-coroutines-reactive.api @@ -5,9 +5,9 @@ public final class kotlinx/coroutines/reactive/AwaitKt { public static final fun awaitFirstOrNull (Lorg/reactivestreams/Publisher;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitLast (Lorg/reactivestreams/Publisher;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitSingle (Lorg/reactivestreams/Publisher;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun awaitSingleOrDefault (Lorg/reactivestreams/Publisher;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun awaitSingleOrElse (Lorg/reactivestreams/Publisher;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun awaitSingleOrNull (Lorg/reactivestreams/Publisher;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final synthetic fun awaitSingleOrDefault (Lorg/reactivestreams/Publisher;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final synthetic fun awaitSingleOrElse (Lorg/reactivestreams/Publisher;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final synthetic fun awaitSingleOrNull (Lorg/reactivestreams/Publisher;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; } public final class kotlinx/coroutines/reactive/ChannelKt { diff --git a/reactive/kotlinx-coroutines-reactive/src/Await.kt b/reactive/kotlinx-coroutines-reactive/src/Await.kt index 3d9a0f8567..446d986cb6 100644 --- a/reactive/kotlinx-coroutines-reactive/src/Await.kt +++ b/reactive/kotlinx-coroutines-reactive/src/Await.kt @@ -106,7 +106,7 @@ public suspend fun Publisher.awaitSingle(): T = awaitOne(Mode.SINGLE) @Deprecated( message = "Deprecated without a replacement due to its name incorrectly conveying the behavior. " + "Please consider using awaitFirstOrDefault().", - level = DeprecationLevel.ERROR + level = DeprecationLevel.HIDDEN ) // Warning since 1.5, error in 1.6, hidden in 1.7 public suspend fun Publisher.awaitSingleOrDefault(default: T): T = awaitOne(Mode.SINGLE_OR_DEFAULT, default) @@ -135,7 +135,7 @@ public suspend fun Publisher.awaitSingleOrDefault(default: T): T = awaitO message = "Deprecated without a replacement due to its name incorrectly conveying the behavior. " + "There is a specialized version for Reactor's Mono, please use that where applicable. " + "Alternatively, please consider using awaitFirstOrNull().", - level = DeprecationLevel.ERROR, + level = DeprecationLevel.HIDDEN, replaceWith = ReplaceWith("this.awaitSingleOrNull()", "kotlinx.coroutines.reactor") ) // Warning since 1.5, error in 1.6, hidden in 1.7 public suspend fun Publisher.awaitSingleOrNull(): T? = awaitOne(Mode.SINGLE_OR_DEFAULT) @@ -164,7 +164,7 @@ public suspend fun Publisher.awaitSingleOrNull(): T? = awaitOne(Mode.SING @Deprecated( message = "Deprecated without a replacement due to its name incorrectly conveying the behavior. " + "Please consider using awaitFirstOrElse().", - level = DeprecationLevel.ERROR + level = DeprecationLevel.HIDDEN ) // Warning since 1.5, error in 1.6, hidden in 1.7 public suspend fun Publisher.awaitSingleOrElse(defaultValue: () -> T): T = awaitOne(Mode.SINGLE_OR_DEFAULT) ?: defaultValue() diff --git a/reactive/kotlinx-coroutines-reactor/api/kotlinx-coroutines-reactor.api b/reactive/kotlinx-coroutines-reactor/api/kotlinx-coroutines-reactor.api index 4589117c94..5a881a128e 100644 --- a/reactive/kotlinx-coroutines-reactor/api/kotlinx-coroutines-reactor.api +++ b/reactive/kotlinx-coroutines-reactor/api/kotlinx-coroutines-reactor.api @@ -1,5 +1,5 @@ public final class kotlinx/coroutines/reactor/ConvertKt { - public static final fun asFlux (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;)Lreactor/core/publisher/Flux; + public static final synthetic fun asFlux (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;)Lreactor/core/publisher/Flux; public static synthetic fun asFlux$default (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lreactor/core/publisher/Flux; public static final fun asMono (Lkotlinx/coroutines/Deferred;Lkotlin/coroutines/CoroutineContext;)Lreactor/core/publisher/Mono; public static final fun asMono (Lkotlinx/coroutines/Job;Lkotlin/coroutines/CoroutineContext;)Lreactor/core/publisher/Mono; @@ -17,11 +17,11 @@ public final class kotlinx/coroutines/reactor/FluxKt { } public final class kotlinx/coroutines/reactor/MonoKt { - public static final fun awaitFirst (Lreactor/core/publisher/Mono;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun awaitFirstOrDefault (Lreactor/core/publisher/Mono;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun awaitFirstOrElse (Lreactor/core/publisher/Mono;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun awaitFirstOrNull (Lreactor/core/publisher/Mono;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun awaitLast (Lreactor/core/publisher/Mono;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final synthetic fun awaitFirst (Lreactor/core/publisher/Mono;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final synthetic fun awaitFirstOrDefault (Lreactor/core/publisher/Mono;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final synthetic fun awaitFirstOrElse (Lreactor/core/publisher/Mono;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final synthetic fun awaitFirstOrNull (Lreactor/core/publisher/Mono;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final synthetic fun awaitLast (Lreactor/core/publisher/Mono;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitSingle (Lreactor/core/publisher/Mono;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitSingleOrNull (Lreactor/core/publisher/Mono;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun mono (Lkotlin/coroutines/CoroutineContext;Lkotlin/jvm/functions/Function2;)Lreactor/core/publisher/Mono; diff --git a/reactive/kotlinx-coroutines-reactor/src/Convert.kt b/reactive/kotlinx-coroutines-reactor/src/Convert.kt index 3063d1dda3..efdedf78ea 100644 --- a/reactive/kotlinx-coroutines-reactor/src/Convert.kt +++ b/reactive/kotlinx-coroutines-reactor/src/Convert.kt @@ -45,7 +45,7 @@ public fun Deferred.asMono(context: CoroutineContext): Mono = mono(co * @suppress */ @Deprecated(message = "Deprecated in the favour of consumeAsFlow()", - level = DeprecationLevel.ERROR, + level = DeprecationLevel.HIDDEN, replaceWith = ReplaceWith("this.consumeAsFlow().asFlux(context)", imports = ["kotlinx.coroutines.flow.consumeAsFlow"])) public fun ReceiveChannel.asFlux(context: CoroutineContext = EmptyCoroutineContext): Flux = flux(context) { for (t in this@asFlux) diff --git a/reactive/kotlinx-coroutines-reactor/src/Mono.kt b/reactive/kotlinx-coroutines-reactor/src/Mono.kt index 45f3847364..27dea603e9 100644 --- a/reactive/kotlinx-coroutines-reactor/src/Mono.kt +++ b/reactive/kotlinx-coroutines-reactor/src/Mono.kt @@ -159,7 +159,7 @@ public fun CoroutineScope.mono( @Deprecated( message = "Mono produces at most one value, so the semantics of dropping the remaining elements are not useful. " + "Please use awaitSingle() instead.", - level = DeprecationLevel.ERROR, + level = DeprecationLevel.HIDDEN, replaceWith = ReplaceWith("this.awaitSingle()") ) // Warning since 1.5, error in 1.6 public suspend fun Mono.awaitFirst(): T = awaitSingle() @@ -183,7 +183,7 @@ public suspend fun Mono.awaitFirst(): T = awaitSingle() @Deprecated( message = "Mono produces at most one value, so the semantics of dropping the remaining elements are not useful. " + "Please use awaitSingleOrNull() instead.", - level = DeprecationLevel.ERROR, + level = DeprecationLevel.HIDDEN, replaceWith = ReplaceWith("this.awaitSingleOrNull() ?: default") ) // Warning since 1.5, error in 1.6 public suspend fun Mono.awaitFirstOrDefault(default: T): T = awaitSingleOrNull() ?: default @@ -207,7 +207,7 @@ public suspend fun Mono.awaitFirstOrDefault(default: T): T = awaitSingleO @Deprecated( message = "Mono produces at most one value, so the semantics of dropping the remaining elements are not useful. " + "Please use awaitSingleOrNull() instead.", - level = DeprecationLevel.ERROR, + level = DeprecationLevel.HIDDEN, replaceWith = ReplaceWith("this.awaitSingleOrNull()") ) // Warning since 1.5, error in 1.6 public suspend fun Mono.awaitFirstOrNull(): T? = awaitSingleOrNull() @@ -231,7 +231,7 @@ public suspend fun Mono.awaitFirstOrNull(): T? = awaitSingleOrNull() @Deprecated( message = "Mono produces at most one value, so the semantics of dropping the remaining elements are not useful. " + "Please use awaitSingleOrNull() instead.", - level = DeprecationLevel.ERROR, + level = DeprecationLevel.HIDDEN, replaceWith = ReplaceWith("this.awaitSingleOrNull() ?: defaultValue()") ) // Warning since 1.5, error in 1.6 public suspend fun Mono.awaitFirstOrElse(defaultValue: () -> T): T = awaitSingleOrNull() ?: defaultValue() @@ -255,7 +255,7 @@ public suspend fun Mono.awaitFirstOrElse(defaultValue: () -> T): T = awai @Deprecated( message = "Mono produces at most one value, so the last element is the same as the first. " + "Please use awaitSingle() instead.", - level = DeprecationLevel.ERROR, + level = DeprecationLevel.HIDDEN, replaceWith = ReplaceWith("this.awaitSingle()") ) // Warning since 1.5, error in 1.6 public suspend fun Mono.awaitLast(): T = awaitSingle() diff --git a/reactive/kotlinx-coroutines-rx2/api/kotlinx-coroutines-rx2.api b/reactive/kotlinx-coroutines-rx2/api/kotlinx-coroutines-rx2.api index c2d1c4bf1d..803ac90564 100644 --- a/reactive/kotlinx-coroutines-rx2/api/kotlinx-coroutines-rx2.api +++ b/reactive/kotlinx-coroutines-rx2/api/kotlinx-coroutines-rx2.api @@ -1,13 +1,13 @@ public final class kotlinx/coroutines/rx2/RxAwaitKt { public static final fun await (Lio/reactivex/CompletableSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun await (Lio/reactivex/MaybeSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final synthetic fun await (Lio/reactivex/MaybeSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun await (Lio/reactivex/SingleSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitFirst (Lio/reactivex/ObservableSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitFirstOrDefault (Lio/reactivex/ObservableSource;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitFirstOrElse (Lio/reactivex/ObservableSource;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitFirstOrNull (Lio/reactivex/ObservableSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitLast (Lio/reactivex/ObservableSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun awaitOrDefault (Lio/reactivex/MaybeSource;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final synthetic fun awaitOrDefault (Lio/reactivex/MaybeSource;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitSingle (Lio/reactivex/MaybeSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitSingle (Lio/reactivex/ObservableSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitSingleOrNull (Lio/reactivex/MaybeSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; @@ -35,7 +35,7 @@ public final class kotlinx/coroutines/rx2/RxConvertKt { public static final fun asFlowable (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;)Lio/reactivex/Flowable; public static synthetic fun asFlowable$default (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lio/reactivex/Flowable; public static final fun asMaybe (Lkotlinx/coroutines/Deferred;Lkotlin/coroutines/CoroutineContext;)Lio/reactivex/Maybe; - public static final fun asObservable (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;)Lio/reactivex/Observable; + public static final synthetic fun asObservable (Lkotlinx/coroutines/channels/ReceiveChannel;Lkotlin/coroutines/CoroutineContext;)Lio/reactivex/Observable; public static final fun asObservable (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;)Lio/reactivex/Observable; public static synthetic fun asObservable$default (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;ILjava/lang/Object;)Lio/reactivex/Observable; public static final fun asSingle (Lkotlinx/coroutines/Deferred;Lkotlin/coroutines/CoroutineContext;)Lio/reactivex/Single; diff --git a/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt b/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt index a891b3f5c2..aeaa1f4b26 100644 --- a/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt +++ b/reactive/kotlinx-coroutines-rx2/src/RxAwait.kt @@ -98,7 +98,7 @@ public suspend fun MaybeSource.awaitSingle(): T = awaitSingleOrNull() ?: */ @Deprecated( message = "Deprecated in favor of awaitSingleOrNull()", - level = DeprecationLevel.ERROR, + level = DeprecationLevel.HIDDEN, replaceWith = ReplaceWith("this.awaitSingleOrNull()") ) // Warning since 1.5, error in 1.6, hidden in 1.7 public suspend fun MaybeSource.await(): T? = awaitSingleOrNull() @@ -120,7 +120,7 @@ public suspend fun MaybeSource.await(): T? = awaitSingleOrNull() */ @Deprecated( message = "Deprecated in favor of awaitSingleOrNull()", - level = DeprecationLevel.ERROR, + level = DeprecationLevel.HIDDEN, replaceWith = ReplaceWith("this.awaitSingleOrNull() ?: default") ) // Warning since 1.5, error in 1.6, hidden in 1.7 public suspend fun MaybeSource.awaitOrDefault(default: T): T = awaitSingleOrNull() ?: default diff --git a/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt b/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt index 497c922ca5..a92d68e289 100644 --- a/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt +++ b/reactive/kotlinx-coroutines-rx2/src/RxConvert.kt @@ -141,7 +141,7 @@ public fun Flow.asFlowable(context: CoroutineContext = EmptyCoroutin @Deprecated( message = "Deprecated in the favour of Flow", - level = DeprecationLevel.ERROR, + level = DeprecationLevel.HIDDEN, replaceWith = ReplaceWith("this.consumeAsFlow().asObservable(context)", "kotlinx.coroutines.flow.consumeAsFlow") ) // Deprecated since 1.4.0 public fun ReceiveChannel.asObservable(context: CoroutineContext): Observable = rxObservable(context) { diff --git a/reactive/kotlinx-coroutines-rx3/api/kotlinx-coroutines-rx3.api b/reactive/kotlinx-coroutines-rx3/api/kotlinx-coroutines-rx3.api index 5776214b0a..f86276e195 100644 --- a/reactive/kotlinx-coroutines-rx3/api/kotlinx-coroutines-rx3.api +++ b/reactive/kotlinx-coroutines-rx3/api/kotlinx-coroutines-rx3.api @@ -1,13 +1,13 @@ public final class kotlinx/coroutines/rx3/RxAwaitKt { public static final fun await (Lio/reactivex/rxjava3/core/CompletableSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun await (Lio/reactivex/rxjava3/core/MaybeSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final synthetic fun await (Lio/reactivex/rxjava3/core/MaybeSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun await (Lio/reactivex/rxjava3/core/SingleSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitFirst (Lio/reactivex/rxjava3/core/ObservableSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitFirstOrDefault (Lio/reactivex/rxjava3/core/ObservableSource;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitFirstOrElse (Lio/reactivex/rxjava3/core/ObservableSource;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitFirstOrNull (Lio/reactivex/rxjava3/core/ObservableSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitLast (Lio/reactivex/rxjava3/core/ObservableSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; - public static final fun awaitOrDefault (Lio/reactivex/rxjava3/core/MaybeSource;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; + public static final synthetic fun awaitOrDefault (Lio/reactivex/rxjava3/core/MaybeSource;Ljava/lang/Object;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitSingle (Lio/reactivex/rxjava3/core/MaybeSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitSingle (Lio/reactivex/rxjava3/core/ObservableSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; public static final fun awaitSingleOrNull (Lio/reactivex/rxjava3/core/MaybeSource;Lkotlin/coroutines/Continuation;)Ljava/lang/Object; diff --git a/reactive/kotlinx-coroutines-rx3/src/RxAwait.kt b/reactive/kotlinx-coroutines-rx3/src/RxAwait.kt index 1c77bbe4f9..33ec848840 100644 --- a/reactive/kotlinx-coroutines-rx3/src/RxAwait.kt +++ b/reactive/kotlinx-coroutines-rx3/src/RxAwait.kt @@ -80,7 +80,7 @@ public suspend fun MaybeSource.awaitSingle(): T = awaitSingleOrNull */ @Deprecated( message = "Deprecated in favor of awaitSingleOrNull()", - level = DeprecationLevel.ERROR, + level = DeprecationLevel.HIDDEN, replaceWith = ReplaceWith("this.awaitSingleOrNull()") ) // Warning since 1.5, error in 1.6, hidden in 1.7 public suspend fun MaybeSource.await(): T? = awaitSingleOrNull() @@ -103,7 +103,7 @@ public suspend fun MaybeSource.await(): T? = awaitSingleOrNull() */ @Deprecated( message = "Deprecated in favor of awaitSingleOrNull()", - level = DeprecationLevel.ERROR, + level = DeprecationLevel.HIDDEN, replaceWith = ReplaceWith("this.awaitSingleOrNull() ?: default") ) // Warning since 1.5, error in 1.6, hidden in 1.7 public suspend fun MaybeSource.awaitOrDefault(default: T): T = awaitSingleOrNull() ?: default From b8d2794f79993ddb458b16bd7be90aecb358f993 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Fri, 3 Mar 2023 18:07:18 +0300 Subject: [PATCH 100/106] Promote the test module deprecation levels for 1.7.0 (#3655) --- .../jvm/src/migration/DelayController.kt | 18 ++++++++-------- .../src/migration/TestBuildersDeprecated.kt | 14 ++++++------- .../src/migration/TestCoroutineDispatcher.kt | 8 +++---- .../TestCoroutineExceptionHandler.kt | 6 +++--- .../jvm/src/migration/TestCoroutineScope.kt | 21 ++++++++++--------- .../jvm/test/migration/TestBuildersTest.kt | 4 ++-- .../TestCoroutineDispatcherOrderTest.kt | 4 ++-- .../migration/TestCoroutineDispatcherTest.kt | 4 ++-- .../TestCoroutineExceptionHandlerTest.kt | 4 ++-- .../jvm/test/migration/TestRunBlockingTest.kt | 4 ++-- 10 files changed, 44 insertions(+), 43 deletions(-) diff --git a/kotlinx-coroutines-test/jvm/src/migration/DelayController.kt b/kotlinx-coroutines-test/jvm/src/migration/DelayController.kt index 3ccf2cadd7..ab84da1c0e 100644 --- a/kotlinx-coroutines-test/jvm/src/migration/DelayController.kt +++ b/kotlinx-coroutines-test/jvm/src/migration/DelayController.kt @@ -1,7 +1,7 @@ /* * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("DEPRECATION") +@file:Suppress("DEPRECATION_ERROR") package kotlinx.coroutines.test @@ -21,7 +21,7 @@ import kotlinx.coroutines.* @ExperimentalCoroutinesApi @Deprecated( "Use `TestCoroutineScheduler` to control virtual time.", - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 public interface DelayController { @@ -111,7 +111,7 @@ public interface DelayController { */ @Deprecated( "Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.", - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 public suspend fun pauseDispatcher(block: suspend () -> Unit) @@ -124,7 +124,7 @@ public interface DelayController { */ @Deprecated( "Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.", - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 public fun pauseDispatcher() @@ -138,7 +138,7 @@ public interface DelayController { */ @Deprecated( "Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.", - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 public fun resumeDispatcher() @@ -151,7 +151,7 @@ internal interface SchedulerAsDelayController : DelayController { @Deprecated( "This property delegates to the test scheduler, which may cause confusing behavior unless made explicit.", ReplaceWith("this.scheduler.currentTime"), - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 override val currentTime: Long @@ -162,7 +162,7 @@ internal interface SchedulerAsDelayController : DelayController { @Deprecated( "This function delegates to the test scheduler, which may cause confusing behavior unless made explicit.", ReplaceWith("this.scheduler.apply { advanceTimeBy(delayTimeMillis); runCurrent() }"), - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 override fun advanceTimeBy(delayTimeMillis: Long): Long { @@ -176,7 +176,7 @@ internal interface SchedulerAsDelayController : DelayController { @Deprecated( "This function delegates to the test scheduler, which may cause confusing behavior unless made explicit.", ReplaceWith("this.scheduler.advanceUntilIdle()"), - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 override fun advanceUntilIdle(): Long { @@ -189,7 +189,7 @@ internal interface SchedulerAsDelayController : DelayController { @Deprecated( "This function delegates to the test scheduler, which may cause confusing behavior unless made explicit.", ReplaceWith("this.scheduler.runCurrent()"), - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 override fun runCurrent(): Unit = scheduler.runCurrent() diff --git a/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt b/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt index c7ef9cc8e7..7a98fd1ddc 100644 --- a/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt +++ b/kotlinx-coroutines-test/jvm/src/migration/TestBuildersDeprecated.kt @@ -56,7 +56,7 @@ import kotlin.time.Duration.Companion.milliseconds "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md", level = DeprecationLevel.WARNING ) -// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 +// Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.8.0 and removed as experimental in 1.9.0 public fun runBlockingTest( context: CoroutineContext = EmptyCoroutineContext, testBody: suspend TestCoroutineScope.() -> Unit @@ -77,7 +77,7 @@ public fun runBlockingTest( * A version of [runBlockingTest] that works with [TestScope]. */ @Deprecated("Use `runTest` instead to support completing from other dispatchers.", level = DeprecationLevel.WARNING) -// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 +// Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.8.0 and removed as experimental in 1.9.0 public fun runBlockingTestOnTestScope( context: CoroutineContext = EmptyCoroutineContext, testBody: suspend TestScope.() -> Unit @@ -126,7 +126,7 @@ public fun runBlockingTestOnTestScope( "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md", level = DeprecationLevel.WARNING ) -// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 +// Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.8.0 and removed as experimental in 1.9.0 public fun TestCoroutineScope.runBlockingTest(block: suspend TestCoroutineScope.() -> Unit): Unit = runBlockingTest(coroutineContext, block) @@ -134,7 +134,7 @@ public fun TestCoroutineScope.runBlockingTest(block: suspend TestCoroutineScope. * Convenience method for calling [runBlockingTestOnTestScope] on an existing [TestScope]. */ @Deprecated("Use `runTest` instead to support completing from other dispatchers.", level = DeprecationLevel.WARNING) -// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 +// Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.8.0 and removed as experimental in 1.9.0 public fun TestScope.runBlockingTest(block: suspend TestScope.() -> Unit): Unit = runBlockingTestOnTestScope(coroutineContext, block) @@ -152,7 +152,7 @@ public fun TestScope.runBlockingTest(block: suspend TestScope.() -> Unit): Unit "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md", level = DeprecationLevel.WARNING ) -// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 +// Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.8.0 and removed as experimental in 1.9.0 public fun TestCoroutineDispatcher.runBlockingTest(block: suspend TestCoroutineScope.() -> Unit): Unit = runBlockingTest(this, block) @@ -161,7 +161,7 @@ public fun TestCoroutineDispatcher.runBlockingTest(block: suspend TestCoroutineS */ @ExperimentalCoroutinesApi @Deprecated("Use `runTest` instead.", level = DeprecationLevel.WARNING) -// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 +// Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.8.0 and removed as experimental in 1.9.0 public fun runTestWithLegacyScope( context: CoroutineContext = EmptyCoroutineContext, dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS, @@ -200,7 +200,7 @@ public fun runTestWithLegacyScope( */ @ExperimentalCoroutinesApi @Deprecated("Use `TestScope.runTest` instead.", level = DeprecationLevel.WARNING) -// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 +// Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.8.0 and removed as experimental in 1.9.0 public fun TestCoroutineScope.runTest( dispatchTimeoutMs: Long = DEFAULT_DISPATCH_TIMEOUT_MS, block: suspend TestCoroutineScope.() -> Unit diff --git a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineDispatcher.kt b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineDispatcher.kt index 40a0f5dfab..3f049b6fda 100644 --- a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineDispatcher.kt +++ b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineDispatcher.kt @@ -24,7 +24,7 @@ import kotlin.coroutines.* @Deprecated("The execution order of `TestCoroutineDispatcher` can be confusing, and the mechanism of " + "pausing is typically misunderstood. Please use `StandardTestDispatcher` or `UnconfinedTestDispatcher` instead.", level = DeprecationLevel.WARNING) -// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 +// Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.8.0 and removed as experimental in 1.9.0 public class TestCoroutineDispatcher(public override val scheduler: TestCoroutineScheduler = TestCoroutineScheduler()): TestDispatcher(), Delay, SchedulerAsDelayController { @@ -63,7 +63,7 @@ public class TestCoroutineDispatcher(public override val scheduler: TestCoroutin /** @suppress */ @Deprecated( "Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.", - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) override suspend fun pauseDispatcher(block: suspend () -> Unit) { val previous = dispatchImmediately @@ -78,7 +78,7 @@ public class TestCoroutineDispatcher(public override val scheduler: TestCoroutin /** @suppress */ @Deprecated( "Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.", - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) override fun pauseDispatcher() { dispatchImmediately = false @@ -87,7 +87,7 @@ public class TestCoroutineDispatcher(public override val scheduler: TestCoroutin /** @suppress */ @Deprecated( "Please use a dispatcher that is paused by default, like `StandardTestDispatcher`.", - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) override fun resumeDispatcher() { dispatchImmediately = true diff --git a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt index 5330cd3ddf..150055f532 100644 --- a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt +++ b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineExceptionHandler.kt @@ -16,7 +16,7 @@ import kotlin.coroutines.* "Consider whether the default mechanism of handling uncaught exceptions is sufficient. " + "If not, try writing your own `CoroutineExceptionHandler` and " + "please report your use case at https://github.com/Kotlin/kotlinx.coroutines/issues.", - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 public interface UncaughtExceptionCaptor { @@ -42,11 +42,11 @@ public interface UncaughtExceptionCaptor { /** * An exception handler that captures uncaught exceptions in tests. */ -@Suppress("DEPRECATION") +@Suppress("DEPRECATION_ERROR") @Deprecated( "Deprecated for removal without a replacement. " + "It may be to define one's own `CoroutineExceptionHandler` if you just need to handle '" + - "uncaught exceptions without a special `TestCoroutineScope` integration.", level = DeprecationLevel.WARNING + "uncaught exceptions without a special `TestCoroutineScope` integration.", level = DeprecationLevel.ERROR ) // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 public class TestCoroutineExceptionHandler : diff --git a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineScope.kt b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineScope.kt index 5af83f5197..4a503c5eb2 100644 --- a/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineScope.kt +++ b/kotlinx-coroutines-test/jvm/src/migration/TestCoroutineScope.kt @@ -1,7 +1,7 @@ /* * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. */ -@file:Suppress("DEPRECATION") +@file:Suppress("DEPRECATION_ERROR", "DEPRECATION") package kotlinx.coroutines.test @@ -22,7 +22,7 @@ import kotlin.coroutines.* "Please see the migration guide for details: " + "https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-test/MIGRATION.md", level = DeprecationLevel.WARNING) -// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 +// Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.8.0 and removed as experimental in 1.9.0 public interface TestCoroutineScope : CoroutineScope { /** * Called after the test completes. @@ -45,7 +45,7 @@ public interface TestCoroutineScope : CoroutineScope { */ @ExperimentalCoroutinesApi @Deprecated("Please call `runTest`, which automatically performs the cleanup, instead of using this function.") - // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 + // Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.8.0 and removed as experimental in 1.9.0 public fun cleanupTestCoroutines() /** @@ -139,7 +139,7 @@ internal fun CoroutineContext.activeJobs(): Set { ), level = DeprecationLevel.WARNING ) -// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 +// Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.8.0 and removed as experimental in 1.9.0 public fun TestCoroutineScope(context: CoroutineContext = EmptyCoroutineContext): TestCoroutineScope { val scheduler = context[TestCoroutineScheduler] ?: TestCoroutineScheduler() return createTestCoroutineScope(TestCoroutineDispatcher(scheduler) + TestCoroutineExceptionHandler() + context) @@ -181,7 +181,7 @@ public fun TestCoroutineScope(context: CoroutineContext = EmptyCoroutineContext) "Please use TestScope() construction instead, or just runTest(), without creating a scope.", level = DeprecationLevel.WARNING ) -// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 +// Since 1.6.0, kept as warning in 1.7.0, ERROR in 1.8.0 and removed as experimental in 1.9.0 public fun createTestCoroutineScope(context: CoroutineContext = EmptyCoroutineContext): TestCoroutineScope { val ctxWithDispatcher = context.withDelaySkipping() var scope: TestCoroutineScopeImpl? = null @@ -239,7 +239,7 @@ public val TestCoroutineScope.currentTime: Long "The name of this function is misleading: it not only advances the time, but also runs the tasks " + "scheduled *at* the ending moment.", ReplaceWith("this.testScheduler.apply { advanceTimeBy(delayTimeMillis); runCurrent() }"), - DeprecationLevel.WARNING + DeprecationLevel.ERROR ) // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 public fun TestCoroutineScope.advanceTimeBy(delayTimeMillis: Long): Unit = @@ -283,7 +283,7 @@ public fun TestCoroutineScope.runCurrent() { "(this.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher(block)", "kotlin.coroutines.ContinuationInterceptor" ), - DeprecationLevel.WARNING + DeprecationLevel.ERROR ) // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 public suspend fun TestCoroutineScope.pauseDispatcher(block: suspend () -> Unit) { @@ -299,7 +299,7 @@ public suspend fun TestCoroutineScope.pauseDispatcher(block: suspend () -> Unit) "(this.coroutineContext[ContinuationInterceptor]!! as DelayController).pauseDispatcher()", "kotlin.coroutines.ContinuationInterceptor" ), - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 public fun TestCoroutineScope.pauseDispatcher() { @@ -315,7 +315,7 @@ public fun TestCoroutineScope.pauseDispatcher() { "(this.coroutineContext[ContinuationInterceptor]!! as DelayController).resumeDispatcher()", "kotlin.coroutines.ContinuationInterceptor" ), - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) // Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 public fun TestCoroutineScope.resumeDispatcher() { @@ -335,8 +335,9 @@ public fun TestCoroutineScope.resumeDispatcher() { "easily misused. It is only present for backward compatibility and will be removed in the subsequent " + "releases. If you need to check the list of exceptions, please consider creating your own " + "`CoroutineExceptionHandler`.", - level = DeprecationLevel.WARNING + level = DeprecationLevel.ERROR ) +// Since 1.6.0, ERROR in 1.7.0 and removed as experimental in 1.8.0 public val TestCoroutineScope.uncaughtExceptions: List get() = (coroutineContext[CoroutineExceptionHandler] as? UncaughtExceptionCaptor)?.uncaughtExceptions ?: emptyList() diff --git a/kotlinx-coroutines-test/jvm/test/migration/TestBuildersTest.kt b/kotlinx-coroutines-test/jvm/test/migration/TestBuildersTest.kt index 6d49a01fa4..4b0428d0fe 100644 --- a/kotlinx-coroutines-test/jvm/test/migration/TestBuildersTest.kt +++ b/kotlinx-coroutines-test/jvm/test/migration/TestBuildersTest.kt @@ -8,7 +8,7 @@ import kotlinx.coroutines.* import kotlin.coroutines.* import kotlin.test.* -@Suppress("DEPRECATION") +@Suppress("DEPRECATION", "DEPRECATION_ERROR") class TestBuildersTest { @Test @@ -129,4 +129,4 @@ class TestBuildersTest { assertEquals(4, calls) } -} \ No newline at end of file +} diff --git a/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineDispatcherOrderTest.kt b/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineDispatcherOrderTest.kt index 93fcd909cc..115c2729da 100644 --- a/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineDispatcherOrderTest.kt +++ b/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineDispatcherOrderTest.kt @@ -8,7 +8,7 @@ import kotlinx.atomicfu.* import kotlinx.coroutines.* import kotlin.test.* -@Suppress("DEPRECATION") +@Suppress("DEPRECATION", "DEPRECATION_ERROR") class TestCoroutineDispatcherOrderTest: OrderedExecutionTestBase() { @Test @@ -40,4 +40,4 @@ class TestCoroutineDispatcherOrderTest: OrderedExecutionTestBase() { scope.cleanupTestCoroutines() finish(9) } -} \ No newline at end of file +} diff --git a/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineDispatcherTest.kt b/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineDispatcherTest.kt index a78d923d34..ea9762ffd0 100644 --- a/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineDispatcherTest.kt +++ b/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineDispatcherTest.kt @@ -7,7 +7,7 @@ package kotlinx.coroutines.test import kotlinx.coroutines.* import kotlin.test.* -@Suppress("DEPRECATION") +@Suppress("DEPRECATION", "DEPRECATION_ERROR") class TestCoroutineDispatcherTest { @Test fun whenDispatcherPaused_doesNotAutoProgressCurrent() { @@ -74,4 +74,4 @@ class TestCoroutineDispatcherTest { assertFailsWith { subject.cleanupTestCoroutines() } } -} \ No newline at end of file +} diff --git a/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineExceptionHandlerTest.kt b/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineExceptionHandlerTest.kt index 20da130725..332634eafd 100644 --- a/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineExceptionHandlerTest.kt +++ b/kotlinx-coroutines-test/jvm/test/migration/TestCoroutineExceptionHandlerTest.kt @@ -6,7 +6,7 @@ package kotlinx.coroutines.test import kotlin.test.* -@Suppress("DEPRECATION") +@Suppress("DEPRECATION_ERROR") class TestCoroutineExceptionHandlerTest { @Test fun whenExceptionsCaught_availableViaProperty() { @@ -15,4 +15,4 @@ class TestCoroutineExceptionHandlerTest { subject.handleException(subject, expected) assertEquals(listOf(expected), subject.uncaughtExceptions) } -} \ No newline at end of file +} diff --git a/kotlinx-coroutines-test/jvm/test/migration/TestRunBlockingTest.kt b/kotlinx-coroutines-test/jvm/test/migration/TestRunBlockingTest.kt index af3b24892a..ebdd973b5b 100644 --- a/kotlinx-coroutines-test/jvm/test/migration/TestRunBlockingTest.kt +++ b/kotlinx-coroutines-test/jvm/test/migration/TestRunBlockingTest.kt @@ -7,7 +7,7 @@ package kotlinx.coroutines.test import kotlinx.coroutines.* import kotlin.test.* -@Suppress("DEPRECATION") +@Suppress("DEPRECATION", "DEPRECATION_ERROR") class TestRunBlockingTest { @Test @@ -437,4 +437,4 @@ class TestRunBlockingTest { } } } -} \ No newline at end of file +} From 46765ed6bd86dc50392828273a648a9d10ec6ea9 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 3 Mar 2023 18:23:42 +0300 Subject: [PATCH 101/106] =?UTF-8?q?Release=20reusability=20token=20when=20?= =?UTF-8?q?suspendCancellableCoroutineReusable's=20=E2=80=A6=20(#3634)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Release reusability token when suspendCancellableCoroutineReusable's block throws an exception Otherwise, the continuation instance is left in REUSABLE_CLAIMED state that asynchronous resumer awaits in an infinite spin-loop, potentially causing deadlock with 100% CPU consumption. Originally, the bug was reproduced on an old (pre-#3020) implementation where this very pattern was encountered: it was possible to fail the owner's invariant check right in the supplied 'block'. This is no longer the case, so the situation is emulated manually (but still is possible in production environments, e.g. when OOM is thrown). Also, suspendCancellableCoroutineReusable is removed from obsolete BroadcastChannel implementation. Fixes #3613 --- .../common/src/CancellableContinuation.kt | 11 +++- .../common/src/CancellableContinuationImpl.kt | 4 +- .../common/src/channels/BufferedChannel.kt | 4 +- ...cellableContinuationInvariantStressTest.kt | 53 +++++++++++++++++++ 4 files changed, 65 insertions(+), 7 deletions(-) create mode 100644 kotlinx-coroutines-core/jvm/test/ReusableCancellableContinuationInvariantStressTest.kt diff --git a/kotlinx-coroutines-core/common/src/CancellableContinuation.kt b/kotlinx-coroutines-core/common/src/CancellableContinuation.kt index 478ff72044..e3237e570c 100644 --- a/kotlinx-coroutines-core/common/src/CancellableContinuation.kt +++ b/kotlinx-coroutines-core/common/src/CancellableContinuation.kt @@ -331,12 +331,19 @@ internal suspend inline fun suspendCancellableCoroutineReusable( crossinline block: (CancellableContinuationImpl) -> Unit ): T = suspendCoroutineUninterceptedOrReturn { uCont -> val cancellable = getOrCreateCancellableContinuation(uCont.intercepted()) - block(cancellable) + try { + block(cancellable) + } catch (e: Throwable) { + // Here we catch any unexpected exception from user-supplied block (e.g. invariant violation) + // and release claimed continuation in order to leave it in a reasonable state (see #3613) + cancellable.releaseClaimedReusableContinuation() + throw e + } cancellable.getResult() } internal fun getOrCreateCancellableContinuation(delegate: Continuation): CancellableContinuationImpl { - // If used outside of our dispatcher + // If used outside our dispatcher if (delegate !is DispatchedContinuation) { return CancellableContinuationImpl(delegate, MODE_CANCELLABLE) } diff --git a/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt index 8006f3bf8e..098369e5ab 100644 --- a/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt +++ b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt @@ -357,8 +357,8 @@ internal open class CancellableContinuationImpl( * Tries to release reusable continuation. It can fail is there was an asynchronous cancellation, * in which case it detaches from the parent and cancels this continuation. */ - private fun releaseClaimedReusableContinuation() { - // Cannot be casted if e.g. invoked from `installParentHandleReusable` for context without dispatchers, but with Job in it + internal fun releaseClaimedReusableContinuation() { + // Cannot be cast if e.g. invoked from `installParentHandleReusable` for context without dispatchers, but with Job in it val cancellationCause = (delegate as? DispatchedContinuation<*>)?.tryReleaseClaimedContinuation(this) ?: return detachChild() cancel(cancellationCause) diff --git a/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt b/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt index d5773df544..edb1c3c9f7 100644 --- a/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt +++ b/kotlinx-coroutines-core/common/src/channels/BufferedChannel.kt @@ -241,10 +241,8 @@ internal open class BufferedChannel( * In case of coroutine cancellation, the element may be undelivered -- * the [onUndeliveredElement] feature is unsupported in this implementation. * - * Note that this implementation always invokes [suspendCancellableCoroutineReusable], - * as we do not care about broadcasts performance -- they are already deprecated. */ - internal open suspend fun sendBroadcast(element: E): Boolean = suspendCancellableCoroutineReusable { cont -> + internal open suspend fun sendBroadcast(element: E): Boolean = suspendCancellableCoroutine { cont -> check(onUndeliveredElement == null) { "the `onUndeliveredElement` feature is unsupported for `sendBroadcast(e)`" } diff --git a/kotlinx-coroutines-core/jvm/test/ReusableCancellableContinuationInvariantStressTest.kt b/kotlinx-coroutines-core/jvm/test/ReusableCancellableContinuationInvariantStressTest.kt new file mode 100644 index 0000000000..4d8116c982 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/ReusableCancellableContinuationInvariantStressTest.kt @@ -0,0 +1,53 @@ +/* + * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines + +import org.junit.Test +import java.util.concurrent.CountDownLatch +import java.util.concurrent.atomic.AtomicReference +import kotlin.coroutines.* + +// Stresses scenario from #3613 +class ReusableCancellableContinuationInvariantStressTest : TestBase() { + + // Tests have a timeout 10 sec because the bug they catch leads to an infinite spin-loop + + @Test(timeout = 10_000) + fun testExceptionFromSuspendReusable() = doTest { /* nothing */ } + + + @Test(timeout = 10_000) + fun testExceptionFromCancelledSuspendReusable() = doTest { it.cancel() } + + + @Suppress("SuspendFunctionOnCoroutineScope") + private inline fun doTest(crossinline block: (Job) -> Unit) { + runTest { + repeat(10_000) { + val latch = CountDownLatch(1) + val continuationToResume = AtomicReference?>(null) + val j1 = launch(Dispatchers.Default) { + latch.await() + suspendCancellableCoroutineReusable { + continuationToResume.set(it) + block(coroutineContext.job) + throw CancellationException() // Don't let getResult() chance to execute + } + } + + val j2 = launch(Dispatchers.Default) { + latch.await() + while (continuationToResume.get() == null) { + // spin + } + continuationToResume.get()!!.resume(Unit) + } + + latch.countDown() + joinAll(j1, j2) + } + } + } +} From 1b414a9cbaffab7f48643d3dcf6769eebaabb1e8 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 3 Mar 2023 18:24:49 +0300 Subject: [PATCH 102/106] Properly support atomicfu in JPMS (#3656) * Add module-info.java processing task to remove the `kotlinx-atomicfu` dependency * Remove module-info.java filter as it's actually no-op * Cleanup imports in Java9Modularity * Fix configuration cache issue in compileModuleInfoJava Co-authored-by: Alexander.Likhachev --- buildSrc/src/main/kotlin/Java9Modularity.kt | 83 ++++++++++--------- .../jvm/src/module-info.java | 1 + 2 files changed, 44 insertions(+), 40 deletions(-) diff --git a/buildSrc/src/main/kotlin/Java9Modularity.kt b/buildSrc/src/main/kotlin/Java9Modularity.kt index 9aeaa67f74..27f1bd38cc 100644 --- a/buildSrc/src/main/kotlin/Java9Modularity.kt +++ b/buildSrc/src/main/kotlin/Java9Modularity.kt @@ -5,15 +5,13 @@ import org.gradle.api.* import org.gradle.api.attributes.* import org.gradle.api.file.* -import org.gradle.api.provider.* -import org.gradle.api.specs.* +import org.gradle.api.tasks.* import org.gradle.api.tasks.bundling.* import org.gradle.api.tasks.compile.* import org.gradle.jvm.toolchain.* import org.gradle.kotlin.dsl.* -import org.gradle.api.logging.Logger +import org.gradle.work.* import org.jetbrains.kotlin.gradle.dsl.* -import java.io.* /** * This object configures the Java compilation of a JPMS (aka Jigsaw) module descriptor. @@ -27,30 +25,40 @@ import java.io.* */ object Java9Modularity { - private class ModuleInfoFilter( - private val compileKotlinTaskPath: String, - private val javaVersionProvider: Provider, - private val moduleInfoFile: File, - private val logger: Logger - ) : Spec { - private val isJava9Compatible - get() = javaVersionProvider.orNull?.isJava9Compatible == true - private var logged = false - - private fun logStatusOnce() { - if (logged) return - if (isJava9Compatible) { - logger.info("Module-info checking is enabled; $compileKotlinTaskPath is compiled using Java ${javaVersionProvider.get()}") - } else { - logger.info("Module-info checking is disabled") + /** + * Task that patches `module-info.java` and removes `requires kotlinx.atomicfu` directive. + * + * To have JPMS properly supported, Kotlin compiler **must** be supplied with the correct `module-info.java`. + * The correct module info has to contain `atomicfu` requirement because atomicfu plugin kicks-in **after** + * the compilation process. But `atomicfu` is compile-only dependency that shouldn't be present in the final + * `module-info.java` and that's exactly what this task ensures. + */ + abstract class ProcessModuleInfoFile : DefaultTask() { + @get:InputFile + @get:NormalizeLineEndings + abstract val moduleInfoFile: RegularFileProperty + + @get:OutputFile + abstract val processedModuleInfoFile: RegularFileProperty + + private val projectPath = project.path + + @TaskAction + fun process() { + val sourceFile = moduleInfoFile.get().asFile + if (!sourceFile.exists()) { + throw IllegalStateException("$sourceFile not found in $projectPath") + } + val outputFile = processedModuleInfoFile.get().asFile + sourceFile.useLines { lines -> + outputFile.outputStream().bufferedWriter().use { writer -> + for (line in lines) { + if ("kotlinx.atomicfu" in line) continue + writer.write(line) + writer.newLine() + } + } } - logged = true - } - - override fun isSatisfiedBy(element: FileTreeElement): Boolean { - logStatusOnce() - if (isJava9Compatible) return false - return element.file == moduleInfoFile } } @@ -74,12 +82,13 @@ object Java9Modularity { ) } + val processModuleInfoFile by tasks.registering(ProcessModuleInfoFile::class) { + moduleInfoFile.set(file("${target.name.ifEmpty { "." }}/src/module-info.java")) + processedModuleInfoFile.set(project.layout.buildDirectory.file("generated-sources/module-info-processor/module-info.java")) + } + val compileJavaModuleInfo = tasks.register("compileModuleInfoJava", JavaCompile::class.java) { val moduleName = project.name.replace('-', '.') // this module's name - val sourceFile = file("${target.name.ifEmpty { "." }}/src/module-info.java") - if (!sourceFile.exists()) { - throw IllegalStateException("$sourceFile not found in $project") - } val compileKotlinTask = compilation.compileTaskProvider.get() as? org.jetbrains.kotlin.gradle.tasks.KotlinCompile ?: error("Cannot access Kotlin compile task ${compilation.compileKotlinTaskName}") @@ -97,15 +106,9 @@ object Java9Modularity { // Note that we use the parent dir and an include filter, // this is needed for Gradle's module detection to work in // org.gradle.api.tasks.compile.JavaCompile.createSpec - source(sourceFile.parentFile) - include { it.file == sourceFile } - - // The Kotlin compiler will parse and check module dependencies, - // but it currently won't compile to a module-info.class file. - // Note that module checking only works on JDK 9+, - // because the JDK built-in base modules are not available in earlier versions. - val javaVersionProvider = compileKotlinTask.kotlinJavaToolchain.javaVersion - compileKotlinTask.exclude(ModuleInfoFilter(compileKotlinTask.path, javaVersionProvider, sourceFile, logger)) + source(processModuleInfoFile.map { it.processedModuleInfoFile.asFile.get().parentFile }) + val generatedModuleInfoFile = processModuleInfoFile.flatMap { it.processedModuleInfoFile.asFile } + include { it.file == generatedModuleInfoFile.get() } // Set the task outputs and destination directory outputs.dir(targetDir) diff --git a/kotlinx-coroutines-test/jvm/src/module-info.java b/kotlinx-coroutines-test/jvm/src/module-info.java index 40ee87d8af..9846263c6c 100644 --- a/kotlinx-coroutines-test/jvm/src/module-info.java +++ b/kotlinx-coroutines-test/jvm/src/module-info.java @@ -6,6 +6,7 @@ module kotlinx.coroutines.test { requires kotlin.stdlib; requires kotlinx.coroutines.core; + requires kotlinx.atomicfu; exports kotlinx.coroutines.test; From bddfb898975e6ac03eb758ef0cf874ad313d2405 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Fri, 3 Mar 2023 18:45:23 +0300 Subject: [PATCH 103/106] Introduce internal API to run the current work queue of the system dispatcher #3641) * Added internal API to run both Dispatchers.Default and Dispatchers.IO * Added internal API to check whether the current thread is IO * Reworked WorkQueue API to have the concept of polling and stealing in "exclusive" modes for the sake of these APIs Fixes #3439 Co-authored-by: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> --- .../api/kotlinx-coroutines-core.api | 2 + .../common/src/internal/LimitedDispatcher.kt | 10 +++ kotlinx-coroutines-core/jvm/src/EventLoop.kt | 78 ++++++++++++++++ .../jvm/src/scheduling/CoroutineScheduler.kt | 48 ++++++++-- .../jvm/src/scheduling/WorkQueue.kt | 69 ++++++++++---- ...CoroutineSchedulerInternalApiStressTest.kt | 89 +++++++++++++++++++ .../jvm/test/scheduling/WorkQueueTest.kt | 2 + 7 files changed, 271 insertions(+), 27 deletions(-) create mode 100644 kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerInternalApiStressTest.kt diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api index d1a0eb8c25..2e45655700 100644 --- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api +++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api @@ -311,7 +311,9 @@ public abstract interface class kotlinx/coroutines/DisposableHandle { } public final class kotlinx/coroutines/EventLoopKt { + public static final fun isIoDispatcherThread (Ljava/lang/Thread;)Z public static final fun processNextEventInCurrentThread ()J + public static final fun runSingleTaskFromCurrentSystemDispatcher ()J } public final class kotlinx/coroutines/ExceptionsKt { diff --git a/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt b/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt index 214480ea70..8d814d566d 100644 --- a/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt +++ b/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt @@ -13,6 +13,16 @@ import kotlin.jvm.* * The result of .limitedParallelism(x) call, a dispatcher * that wraps the given dispatcher, but limits the parallelism level, while * trying to emulate fairness. + * + * ### Implementation details + * + * By design, 'LimitedDispatcher' never [dispatches][CoroutineDispatcher.dispatch] originally sent tasks + * to the underlying dispatcher. Instead, it maintains its own queue of tasks sent to this dispatcher and + * dispatches at most [parallelism] "worker-loop" tasks that poll the underlying queue and cooperatively preempt + * in order to avoid starvation of the underlying dispatcher. + * + * Such behavior is crucial to be compatible with any underlying dispatcher implementation without + * direct cooperation. */ internal class LimitedDispatcher( private val dispatcher: CoroutineDispatcher, diff --git a/kotlinx-coroutines-core/jvm/src/EventLoop.kt b/kotlinx-coroutines-core/jvm/src/EventLoop.kt index 1ee651aa41..7d1078cf6f 100644 --- a/kotlinx-coroutines-core/jvm/src/EventLoop.kt +++ b/kotlinx-coroutines-core/jvm/src/EventLoop.kt @@ -4,6 +4,10 @@ package kotlinx.coroutines +import kotlinx.coroutines.Runnable +import kotlinx.coroutines.scheduling.* +import kotlinx.coroutines.scheduling.CoroutineScheduler + internal actual abstract class EventLoopImplPlatform: EventLoop() { protected abstract val thread: Thread @@ -45,6 +49,80 @@ internal actual fun createEventLoop(): EventLoop = BlockingEventLoop(Thread.curr */ @InternalCoroutinesApi public fun processNextEventInCurrentThread(): Long = + // This API is used in Ktor for serverless integration where a single thread awaits a blocking call + // (and, to avoid actual blocking, does something via this call), see #850 ThreadLocalEventLoop.currentOrNull()?.processNextEvent() ?: Long.MAX_VALUE internal actual inline fun platformAutoreleasePool(crossinline block: () -> Unit) = block() + +/** + * Retrieves and executes a single task from the current system dispatcher ([Dispatchers.Default] or [Dispatchers.IO]). + * Returns `0` if any task was executed, `>= 0` for number of nanoseconds to wait until invoking this method again + * (implying that there will be a task to steal in N nanoseconds), `-1` if there is no tasks in the corresponding dispatcher at all. + * + * ### Invariants + * + * - When invoked from [Dispatchers.Default] **thread** (even if the actual context is different dispatcher, + * [CoroutineDispatcher.limitedParallelism] or any in-place wrapper), it runs an arbitrary task that ended + * up being scheduled to [Dispatchers.Default] or its counterpart. Tasks scheduled to [Dispatchers.IO] + * **are not** executed[1]. + * - When invoked from [Dispatchers.IO] thread, the same rules apply, but for blocking tasks only. + * + * [1] -- this is purely technical limitation: the scheduler does not have "notify me when CPU token is available" API, + * and we cannot leave this method without leaving thread in its original state. + * + * ### Rationale + * + * This is an internal API that is intended to replace IDEA's core FJP decomposition. + * The following API is provided by IDEA core: + * ``` + * runDecomposedTaskAndJoinIt { // <- non-suspending call + * // spawn as many tasks as needed + * // these tasks can also invoke 'runDecomposedTaskAndJoinIt' + * } + * ``` + * The key observation here is that 'runDecomposedTaskAndJoinIt' can be invoked from `Dispatchers.Default` itself, + * thus blocking at least one thread. To avoid deadlocks and starvation during large hierarchical decompositions, + * 'runDecomposedTaskAndJoinIt' should not just block but also **help** execute the task or other tasks + * until an arbitrary condition is satisfied. + * + * See #3439 for additional details. + * + * ### Limitations and caveats + * + * - Executes tasks in-place, thus potentially leaking irrelevant thread-locals from the current thread + * - Is not 100% effective, because the caller should somehow "wait" (or do other work) for [Long] returned nanoseconds + * even when work arrives immediately after returning from this method. + * - When there is no more work, it's up to the caller to decide what to do. It's important to remember that + * work to current dispatcher may arrive **later** from external sources [1] + * + * [1] -- this is also a technicality that can be solved in kotlinx.coroutines itself, but unfortunately requires + * a tremendous effort. + * + * @throws IllegalStateException if the current thread is not system dispatcher thread + */ +@InternalCoroutinesApi +@DelicateCoroutinesApi +@PublishedApi +internal fun runSingleTaskFromCurrentSystemDispatcher(): Long { + val thread = Thread.currentThread() + if (thread !is CoroutineScheduler.Worker) throw IllegalStateException("Expected CoroutineScheduler.Worker, but got $thread") + return thread.runSingleTask() +} + +/** + * Checks whether the given thread belongs to Dispatchers.IO. + * Note that feature "is part of the Dispatchers.IO" is *dynamic*, meaning that the thread + * may change this status when switching between tasks. + * + * This function is inteded to be used on the result of `Thread.currentThread()` for diagnostic + * purposes, and is declared as an extension only to avoid top-level scope pollution. + */ +@InternalCoroutinesApi +@DelicateCoroutinesApi +@PublishedApi +internal fun Thread.isIoDispatcherThread(): Boolean { + if (this !is CoroutineScheduler.Worker) return false + return isIo() +} + diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt index 2bad139fe6..e08f3deedd 100644 --- a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt +++ b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt @@ -720,6 +720,30 @@ internal class CoroutineScheduler( tryReleaseCpu(WorkerState.TERMINATED) } + /** + * See [runSingleTaskFromCurrentSystemDispatcher] for rationale and details. + * This is a fine-tailored method for a specific use-case not expected to be used widely. + */ + fun runSingleTask(): Long { + val stateSnapshot = state + val isCpuThread = state == WorkerState.CPU_ACQUIRED + val task = if (isCpuThread) { + findCpuTask() + } else { + findBlockingTask() + } + if (task == null) { + if (minDelayUntilStealableTaskNs == 0L) return -1L + return minDelayUntilStealableTaskNs + } + runSafely(task) + if (!isCpuThread) decrementBlockingTasks() + assert { state == stateSnapshot} + return 0L + } + + fun isIo() = state == WorkerState.BLOCKING + // Counterpart to "tryUnpark" private fun tryPark() { if (!inStack()) { @@ -879,9 +903,21 @@ internal class CoroutineScheduler( * * Check if our queue has one (maybe mixed in with CPU tasks) * * Poll global and try steal */ + return findBlockingTask() + } + + // NB: ONLY for runSingleTask method + private fun findBlockingTask(): Task? { return localQueue.pollBlocking() ?: globalBlockingQueue.removeFirstOrNull() - ?: trySteal(blockingOnly = true) + ?: trySteal(STEAL_BLOCKING_ONLY) + } + + // NB: ONLY for runSingleTask method + private fun findCpuTask(): Task? { + return localQueue.pollCpu() + ?: globalBlockingQueue.removeFirstOrNull() + ?: trySteal(STEAL_CPU_ONLY) } private fun findAnyTask(scanLocalQueue: Boolean): Task? { @@ -897,7 +933,7 @@ internal class CoroutineScheduler( } else { pollGlobalQueues()?.let { return it } } - return trySteal(blockingOnly = false) + return trySteal(STEAL_ANY) } private fun pollGlobalQueues(): Task? { @@ -910,7 +946,7 @@ internal class CoroutineScheduler( } } - private fun trySteal(blockingOnly: Boolean): Task? { + private fun trySteal(stealingMode: StealingMode): Task? { val created = createdWorkers // 0 to await an initialization and 1 to avoid excess stealing on single-core machines if (created < 2) { @@ -924,11 +960,7 @@ internal class CoroutineScheduler( if (currentIndex > created) currentIndex = 1 val worker = workers[currentIndex] if (worker !== null && worker !== this) { - val stealResult = if (blockingOnly) { - worker.localQueue.tryStealBlocking(stolenTask) - } else { - worker.localQueue.trySteal(stolenTask) - } + val stealResult = worker.localQueue.trySteal(stealingMode, stolenTask) if (stealResult == TASK_STOLEN) { val result = stolenTask.element stolenTask.element = null diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt b/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt index 611f2b108a..a185410ab2 100644 --- a/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt +++ b/kotlinx-coroutines-core/jvm/src/scheduling/WorkQueue.kt @@ -16,6 +16,14 @@ internal const val MASK = BUFFER_CAPACITY - 1 // 128 by default internal const val TASK_STOLEN = -1L internal const val NOTHING_TO_STEAL = -2L +internal typealias StealingMode = Int +internal const val STEAL_ANY: StealingMode = 3 +internal const val STEAL_CPU_ONLY: StealingMode = 2 +internal const val STEAL_BLOCKING_ONLY: StealingMode = 1 + +internal inline val Task.maskForStealingMode: Int + get() = if (isBlocking) STEAL_BLOCKING_ONLY else STEAL_CPU_ONLY + /** * Tightly coupled with [CoroutineScheduler] queue of pending tasks, but extracted to separate file for simplicity. * At any moment queue is used only by [CoroutineScheduler.Worker] threads, has only one producer (worker owning this queue) @@ -107,42 +115,63 @@ internal class WorkQueue { * * Returns [NOTHING_TO_STEAL] if queue has nothing to steal, [TASK_STOLEN] if at least task was stolen * or positive value of how many nanoseconds should pass until the head of this queue will be available to steal. + * + * [StealingMode] controls what tasks to steal: + * * [STEAL_ANY] is default mode for scheduler, task from the head (in FIFO order) is stolen + * * [STEAL_BLOCKING_ONLY] is mode for stealing *an arbitrary* blocking task, which is used by the scheduler when helping in Dispatchers.IO mode + * * [STEAL_CPU_ONLY] is a kludge for `runSingleTaskFromCurrentSystemDispatcher` */ - fun trySteal(stolenTaskRef: ObjectRef): Long { - val task = pollBuffer() + fun trySteal(stealingMode: StealingMode, stolenTaskRef: ObjectRef): Long { + val task = when (stealingMode) { + STEAL_ANY -> pollBuffer() + else -> stealWithExclusiveMode(stealingMode) + } + if (task != null) { stolenTaskRef.element = task return TASK_STOLEN } - return tryStealLastScheduled(stolenTaskRef, blockingOnly = false) + return tryStealLastScheduled(stealingMode, stolenTaskRef) } - fun tryStealBlocking(stolenTaskRef: ObjectRef): Long { + // Steal only tasks of a particular kind, potentially invoking full queue scan + private fun stealWithExclusiveMode(stealingMode: StealingMode): Task? { var start = consumerIndex.value val end = producerIndex.value - - while (start != end && blockingTasksInBuffer.value > 0) { - stolenTaskRef.element = tryExtractBlockingTask(start++) ?: continue - return TASK_STOLEN + val onlyBlocking = stealingMode == STEAL_BLOCKING_ONLY + // Bail out if there is no blocking work for us + while (start != end) { + if (onlyBlocking && blockingTasksInBuffer.value == 0) return null + return tryExtractFromTheMiddle(start++, onlyBlocking) ?: continue } - return tryStealLastScheduled(stolenTaskRef, blockingOnly = true) + + return null } // Polls for blocking task, invoked only by the owner - fun pollBlocking(): Task? { + // NB: ONLY for runSingleTask method + fun pollBlocking(): Task? = pollWithExclusiveMode(onlyBlocking = true /* only blocking */) + + // Polls for CPU task, invoked only by the owner + // NB: ONLY for runSingleTask method + fun pollCpu(): Task? = pollWithExclusiveMode(onlyBlocking = false /* only cpu */) + + private fun pollWithExclusiveMode(/* Only blocking OR only CPU */ onlyBlocking: Boolean): Task? { while (true) { // Poll the slot val lastScheduled = lastScheduledTask.value ?: break - if (!lastScheduled.isBlocking) break + if (lastScheduled.isBlocking != onlyBlocking) break if (lastScheduledTask.compareAndSet(lastScheduled, null)) { return lastScheduled } // Failed -> someone else stole it } + // Failed to poll the slot, scan the queue val start = consumerIndex.value var end = producerIndex.value - - while (start != end && blockingTasksInBuffer.value > 0) { - val task = tryExtractBlockingTask(--end) + // Bail out if there is no blocking work for us + while (start != end) { + if (onlyBlocking && blockingTasksInBuffer.value == 0) return null + val task = tryExtractFromTheMiddle(--end, onlyBlocking) if (task != null) { return task } @@ -150,11 +179,11 @@ internal class WorkQueue { return null } - private fun tryExtractBlockingTask(index: Int): Task? { + private fun tryExtractFromTheMiddle(index: Int, onlyBlocking: Boolean): Task? { val arrayIndex = index and MASK val value = buffer[arrayIndex] - if (value != null && value.isBlocking && buffer.compareAndSet(arrayIndex, value, null)) { - blockingTasksInBuffer.decrementAndGet() + if (value != null && value.isBlocking == onlyBlocking && buffer.compareAndSet(arrayIndex, value, null)) { + if (onlyBlocking) blockingTasksInBuffer.decrementAndGet() return value } return null @@ -170,10 +199,12 @@ internal class WorkQueue { /** * Contract on return value is the same as for [trySteal] */ - private fun tryStealLastScheduled(stolenTaskRef: ObjectRef, blockingOnly: Boolean): Long { + private fun tryStealLastScheduled(stealingMode: StealingMode, stolenTaskRef: ObjectRef): Long { while (true) { val lastScheduled = lastScheduledTask.value ?: return NOTHING_TO_STEAL - if (blockingOnly && !lastScheduled.isBlocking) return NOTHING_TO_STEAL + if ((lastScheduled.maskForStealingMode and stealingMode) == 0) { + return NOTHING_TO_STEAL + } // TODO time wraparound ? val time = schedulerTimeSource.nanoTime() diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerInternalApiStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerInternalApiStressTest.kt new file mode 100644 index 0000000000..22b9b02916 --- /dev/null +++ b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerInternalApiStressTest.kt @@ -0,0 +1,89 @@ +/* + * Copyright 2016-2023 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.scheduling + +import kotlinx.coroutines.* +import kotlinx.coroutines.internal.AVAILABLE_PROCESSORS +import org.junit.Test +import java.util.* +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.CountDownLatch +import java.util.concurrent.CyclicBarrier +import java.util.concurrent.atomic.AtomicInteger +import kotlin.random.* +import kotlin.random.Random +import kotlin.test.* +import kotlin.time.* + +class CoroutineSchedulerInternalApiStressTest : TestBase() { + + @Test(timeout = 120_000L) + fun testHelpDefaultIoIsIsolated() = repeat(100 * stressTestMultiplierSqrt) { + val ioTaskMarker = ThreadLocal.withInitial { false } + runTest { + val jobToComplete = Job() + val expectedIterations = 100 + val completionLatch = CountDownLatch(1) + val tasksToCompleteJob = AtomicInteger(expectedIterations) + val observedIoThreads = Collections.newSetFromMap(ConcurrentHashMap()) + val observedDefaultThreads = Collections.newSetFromMap(ConcurrentHashMap()) + + val barrier = CyclicBarrier(AVAILABLE_PROCESSORS) + val spawners = ArrayList() + repeat(AVAILABLE_PROCESSORS - 1) { + // Launch CORES - 1 spawners + spawners += launch(Dispatchers.Default) { + barrier.await() + repeat(expectedIterations) { + launch { + val tasksLeft = tasksToCompleteJob.decrementAndGet() + if (tasksLeft < 0) return@launch // Leftovers are being executed all over the place + observedDefaultThreads.add(Thread.currentThread()) + if (tasksLeft == 0) { + // Verify threads first + try { + assertFalse(observedIoThreads.containsAll(observedDefaultThreads)) + } finally { + jobToComplete.complete() + } + } + } + + // Sometimes launch an IO task to mess with a scheduler + if (Random.nextInt(0..9) == 0) { + launch(Dispatchers.IO) { + ioTaskMarker.set(true) + observedIoThreads.add(Thread.currentThread()) + assertTrue(Thread.currentThread().isIoDispatcherThread()) + } + } + } + completionLatch.await() + } + } + + withContext(Dispatchers.Default) { + barrier.await() + var timesHelped = 0 + while (!jobToComplete.isCompleted) { + val result = runSingleTaskFromCurrentSystemDispatcher() + assertFalse(ioTaskMarker.get()) + if (result == 0L) { + ++timesHelped + continue + } else if (result >= 0L) { + Thread.sleep(result.toDuration(DurationUnit.NANOSECONDS).toDelayMillis()) + } else { + Thread.sleep(10) + } + } + completionLatch.countDown() +// assertEquals(100, timesHelped) +// assertTrue(Thread.currentThread() in observedDefaultThreads, observedDefaultThreads.toString()) + } + } + } +} + diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueTest.kt index 864e65a3a9..f690d3882f 100644 --- a/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueTest.kt +++ b/kotlinx-coroutines-core/jvm/test/scheduling/WorkQueueTest.kt @@ -107,3 +107,5 @@ internal fun GlobalQueue.drain(): List { } return result } + +internal fun WorkQueue.trySteal(stolenTaskRef: ObjectRef): Long = trySteal(STEAL_ANY, stolenTaskRef) From 20320338ccc66fa3421f3e1ee563d8a5b41ffcf1 Mon Sep 17 00:00:00 2001 From: Dmitry Khalanskiy <52952525+dkhalanskyjb@users.noreply.github.com> Date: Mon, 6 Mar 2023 14:54:02 +0300 Subject: [PATCH 104/106] Fix a broken test (#3659) The timeout was too low, the test body would never get a chance to run. --- kotlinx-coroutines-test/common/test/RunTestTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kotlinx-coroutines-test/common/test/RunTestTest.kt b/kotlinx-coroutines-test/common/test/RunTestTest.kt index 183eb8cb3a..da2bdcfc76 100644 --- a/kotlinx-coroutines-test/common/test/RunTestTest.kt +++ b/kotlinx-coroutines-test/common/test/RunTestTest.kt @@ -215,7 +215,7 @@ class RunTestTest { } } }) { - runTest(timeout = 1.milliseconds) { + runTest(timeout = 100.milliseconds) { coroutineContext[CoroutineExceptionHandler]!!.handleException(coroutineContext, TestException("A")) withContext(Dispatchers.Default) { delay(10000) From 9e7c38de1799e3667421f30fb2cdb22a10ad1a42 Mon Sep 17 00:00:00 2001 From: Ignat Beresnev Date: Mon, 6 Mar 2023 15:37:53 +0100 Subject: [PATCH 105/106] Update Dokka to 1.8.10 (#3661) --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index cb1ff7b9e6..9258cc1f34 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,7 +14,7 @@ atomicfu_version=0.20.0 knit_version=0.4.0 html_version=0.7.2 lincheck_version=2.16 -dokka_version=1.7.20 +dokka_version=1.8.10 byte_buddy_version=1.10.9 reactor_version=3.4.1 reactive_streams_version=1.0.3 From a3060c6293d8128a5967c0d2b6d6599d9a391263 Mon Sep 17 00:00:00 2001 From: Vsevolod Tolstopyatov Date: Wed, 8 Mar 2023 13:39:00 +0100 Subject: [PATCH 106/106] Version 1.7.0-Beta --- CHANGES.md | 56 +++++++++++++++++++++++++++ README.md | 12 +++--- gradle.properties | 2 +- integration-testing/gradle.properties | 2 +- kotlinx-coroutines-debug/README.md | 2 +- kotlinx-coroutines-test/README.md | 2 +- ui/coroutines-guide-ui.md | 2 +- 7 files changed, 67 insertions(+), 11 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 377270d1c5..c9ce6b27f8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,61 @@ # Change log for kotlinx.coroutines +## Version 1.7.0-Beta + +### Core API significant improvements + +* New `Channel` implementation with significant performance improvements across the API (#3621). +* New `select` operator implementation: faster, more lightweight, and more robust (#3020). +* `Mutex` and `Semaphore` now share the same underlying data structure (#3020). +* `Dispatchers.IO` is added to K/N (#3205) + * `newFixedThreadPool` and `Dispatchers.Default` implementations on K/N were wholly rewritten to support graceful growth under load (#3595). +* `kotlinx-coroutines-test` rework: + - Add the `timeout` parameter to `runTest` for the whole-test timeout, 10 seconds by default (#3270). This replaces the configuration of quiescence timeouts, which is now deprecated (#3603). + - The `withTimeout` exception messages indicate if the timeout used the virtual time (#3588). + - `TestCoroutineScheduler`, `runTest`, and `TestScope` API are promoted to stable (#3622). + - `runTest` now also fails if there were uncaught exceptions in coroutines not inherited from the test coroutine (#1205). + +### Breaking changes + +* Old K/N memory model is no longer supported (#3375). +* New generic upper bounds were added to reactive integration API where the language since 1.8.0 dictates (#3393). +* `kotlinx-coroutines-core` and `kotlinx-coroutines-jdk8` artifacts were merged into a single artifact (#3268). +* Artificial stackframes in stacktrace recovery no longer contain the `\b` symbol and are now navigable in IDE and supplied with proper documentation (#2291). +* `CoroutineContext.isActive` returns `true` for contexts without any job in them (#3300). + +### Bug fixes and improvements + +* Kotlin version is updated to 1.8.10. +* JPMS is supported (#2237). Thanks @lion7! +* `BroadcastChannel` and all the corresponding API are deprecated (#2680). +* Added all supported K/N targets (#3601, #812, #855). +* K/N `Dispatchers.Default` is backed by the number of threads equal to the number of available cores (#3366). +* Fixed an issue where some coroutines' internal exceptions were not properly serializable (#3328). +* Introduced `Job.parent` API (#3201). +* Fixed a bug when `TestScheduler` leaked cancelled jobs (#3398). +* `TestScope.timeSource` now provides comparable time marks (#3617). Thanks @hfhbd! +* Fixed an issue when cancelled `withTimeout` handles were preserved in JS runtime (#3440). +* Ensure `awaitFrame` only awaits a single frame when used from the main looper (#3432). Thanks @pablobaxter! +* Obsolete `Class-Path` attribute was removed from `kotlinx-coroutines-debug.jar` manifest (#3361). +* Fixed a bug when `updateThreadContext` operated on the parent context (#3411). +* Added new `Flow.filterIsInstance` extension (#3240). +* `Dispatchers.Default` thread name prefixes are now configurable with system property (#3231). +* Added `Flow.timeout` operator as `@FlowPreview` (#2624). Thanks @pablobaxter! +* Improved the performance of the `future` builder in case of exceptions (#3475). Thanks @He-Pin! +* `Mono.awaitSingleOrNull` now waits for the `onComplete` signal (#3487). +* `Channel.isClosedForSend` and `Channel.isClosedForReceive` are promoted from experimental to delicate (#3448). +* Fixed a data race in native `EventLoop` (#3547). +* `Dispatchers.IO.limitedParallelism(valueLargerThanIOSize)` no longer creates an additional wrapper (#3442). Thanks @dovchinnikov! +* Various `@FlowPreview` and `@ExperimentalCoroutinesApi` are promoted to experimental and stable respectively (#3542, #3097, #3548). +* Performance improvements in `Dispatchers.Default` and `Dispatchers.IO` (#3416, #3418). +* Fixed a bug when internal `suspendCancellableCoroutineReusable` might have hanged (#3613). +* Introduced internal API to process events in the current system dispatcher (#3439). +* Global `CoroutineExceptionHandler` is no longer invoked in case of unprocessed `future` failure (#3452). +* Performance improvements and reduced thread-local pressure for the `withContext` operator (#3592). +* Improved performance of `DebugProbes` (#3527). +* Fixed a bug when the coroutine debugger might have detected the state of a coroutine incorrectly (#3193). +* Various documentation improvements and fixes. + ## Version 1.6.4 * Added `TestScope.backgroundScope` for launching coroutines that perform work in the background and need to be cancelled at the end of the test (#3287). diff --git a/README.md b/README.md index 0143564a49..9c81ba214a 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![Kotlin Stable](https://kotl.in/badges/stable.svg)](https://kotlinlang.org/docs/components-stability.html) [![JetBrains official project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub) [![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0) -[![Download](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.6.4)](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.6.4/pom) +[![Download](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.7.0-Beta)](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.7.0-Beta/pom) [![Kotlin](https://img.shields.io/badge/kotlin-1.8.10-blue.svg?logo=kotlin)](http://kotlinlang.org) [![Slack channel](https://img.shields.io/badge/chat-slack-green.svg?logo=slack)](https://kotlinlang.slack.com/messages/coroutines/) @@ -85,7 +85,7 @@ Add dependencies (you can also add other modules that you need): org.jetbrains.kotlinx kotlinx-coroutines-core - 1.6.4 + 1.7.0-Beta ``` @@ -103,7 +103,7 @@ Add dependencies (you can also add other modules that you need): ```kotlin dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0-Beta") } ``` @@ -133,7 +133,7 @@ Add [`kotlinx-coroutines-android`](ui/kotlinx-coroutines-android) module as a dependency when using `kotlinx.coroutines` on Android: ```kotlin -implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") +implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0-Beta") ``` This gives you access to the Android [Dispatchers.Main] @@ -168,7 +168,7 @@ In common code that should get compiled for different platforms, you can add a d ```kotlin commonMain { dependencies { - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0-Beta") } } ``` @@ -180,7 +180,7 @@ Platform-specific dependencies are recommended to be used only for non-multiplat #### JS Kotlin/JS version of `kotlinx.coroutines` is published as -[`kotlinx-coroutines-core-js`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.6.4/jar) +[`kotlinx-coroutines-core-js`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.7.0-Beta/jar) (follow the link to get the dependency declaration snippet) and as [`kotlinx-coroutines-core`](https://www.npmjs.com/package/kotlinx-coroutines-core) NPM package. #### Native diff --git a/gradle.properties b/gradle.properties index 9258cc1f34..d31f646d0e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ # # Kotlin -version=1.6.4-SNAPSHOT +version=1.7.0-Beta-SNAPSHOT group=org.jetbrains.kotlinx kotlin_version=1.8.10 diff --git a/integration-testing/gradle.properties b/integration-testing/gradle.properties index 7d3f05c799..b8485eaeb2 100644 --- a/integration-testing/gradle.properties +++ b/integration-testing/gradle.properties @@ -1,5 +1,5 @@ kotlin_version=1.8.10 -coroutines_version=1.6.4-SNAPSHOT +coroutines_version=1.7.0-Beta-SNAPSHOT asm_version=9.3 kotlin.code.style=official diff --git a/kotlinx-coroutines-debug/README.md b/kotlinx-coroutines-debug/README.md index 758a0922ef..24d0084be4 100644 --- a/kotlinx-coroutines-debug/README.md +++ b/kotlinx-coroutines-debug/README.md @@ -61,7 +61,7 @@ stacktraces will be dumped to the console. ### Using as JVM agent Debug module can also be used as a standalone JVM agent to enable debug probes on the application startup. -You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.6.4.jar`. +You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.7.0-Beta.jar`. Additionally, on Linux and Mac OS X you can use `kill -5 $pid` command in order to force your application to print all alive coroutines. When used as Java agent, `"kotlinx.coroutines.debug.enable.creation.stack.trace"` system property can be used to control [DebugProbes.enableCreationStackTraces] along with agent startup. diff --git a/kotlinx-coroutines-test/README.md b/kotlinx-coroutines-test/README.md index f83ff69f0b..2bdf1d8216 100644 --- a/kotlinx-coroutines-test/README.md +++ b/kotlinx-coroutines-test/README.md @@ -26,7 +26,7 @@ Provided [TestDispatcher] implementations: Add `kotlinx-coroutines-test` to your project test dependencies: ``` dependencies { - testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.6.4' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.0-Beta' } ``` diff --git a/ui/coroutines-guide-ui.md b/ui/coroutines-guide-ui.md index 6cd791cf6f..127097e702 100644 --- a/ui/coroutines-guide-ui.md +++ b/ui/coroutines-guide-ui.md @@ -110,7 +110,7 @@ Add dependencies on `kotlinx-coroutines-android` module to the `dependencies { . `app/build.gradle` file: ```groovy -implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4" +implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0-Beta" ``` You can clone [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) project from GitHub onto your