diff --git a/CHANGES.md b/CHANGES.md
index c9ce6b27f8..acf2d2c28b 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,19 @@
# Change log for kotlinx.coroutines
+## Version 1.7.0-RC
+
+* Kotlin version is updated to 1.8.20.
+* Atomicfu version is updated to 0.20.2.
+* `JavaFx` version is updated to 17.0.2 in `kotlinx-coroutines-javafx` (#3671).
+* `previous-compilation-data.bin` file is removed from JAR resources (#3668).
+* `CoroutineDispatcher.asExecutor()` runs tasks without dispatching if the dispatcher is unconfined (#3683). Thanks @odedniv!
+* `SharedFlow.toMutableList` lint overload is undeprecated (#3706).
+* `Channel.invokeOnClose` is promoted to stable API (#3358).
+* Improved lock contention in `Dispatchers.Default` and `Dispatchers.IO` during the startup phase (#3652).
+* Fixed a bug that led to threads oversubscription in `Dispatchers.Default` (#3642).
+* Fixed a bug that allowed `limitedParallelism` to perform dispatches even after the underlying dispatcher was closed (#3672).
+* Restored binary compatibility of previously experimental `TestScope.runTest(Long)` (#3673).
+
## Version 1.7.0-Beta
### Core API significant improvements
diff --git a/README.md b/README.md
index e709730e19..283afb85ba 100644
--- a/README.md
+++ b/README.md
@@ -3,12 +3,12 @@
[![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.7.0-Beta)](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.7.0-Beta)
-[![Kotlin](https://img.shields.io/badge/kotlin-1.8.10-blue.svg?logo=kotlin)](http://kotlinlang.org)
+[![Download](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.7.0-RC)](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.7.0-RC)
+[![Kotlin](https://img.shields.io/badge/kotlin-1.8.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.8.10` release.
+This is a companion version for the Kotlin `1.8.20` release.
```kotlin
suspend fun main() = coroutineScope {
@@ -85,7 +85,7 @@ Add dependencies (you can also add other modules that you need):
org.jetbrains.kotlinx
kotlinx-coroutines-core
- 1.7.0-Beta
+ 1.7.0-RC
```
@@ -93,7 +93,7 @@ And make sure that you use the latest Kotlin version:
```xml
- 1.8.10
+ 1.8.20
```
@@ -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.7.0-Beta")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0-RC")
}
```
@@ -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.8.10"
+ kotlin("jvm") version "1.8.20"
// For build.gradle (Groovy DSL)
- id "org.jetbrains.kotlin.jvm" version "1.8.10"
+ id "org.jetbrains.kotlin.jvm" version "1.8.20"
}
```
@@ -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.7.0-Beta")
+implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0-RC")
```
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.7.0-Beta")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0-RC")
}
}
```
@@ -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://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.7.0-Beta)
+[`kotlinx-coroutines-core-js`](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.7.0-RC)
(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/build.gradle b/build.gradle
index e65558b151..0d17d9dd7b 100644
--- a/build.gradle
+++ b/build.gradle
@@ -5,7 +5,10 @@
import org.jetbrains.kotlin.config.KotlinCompilerVersion
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
-import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
+import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootExtension
+import org.jetbrains.kotlin.gradle.targets.js.nodejs.NodeJsRootPlugin
+import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnPlugin
+import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.dokka.gradle.DokkaTaskPartial
@@ -361,3 +364,15 @@ allprojects { subProject ->
tasks.named("dokkaHtmlMultiModule") {
pluginsMapConfiguration.set(["org.jetbrains.dokka.base.DokkaBase": """{ "templatesDir": "${projectDir.toString().replace('\\', '/')}/dokka-templates" }"""])
}
+
+if (CacheRedirector.enabled) {
+ def yarnRootExtension = rootProject.extensions.findByType(YarnRootExtension.class)
+ if (yarnRootExtension != null) {
+ yarnRootExtension.downloadBaseUrl = CacheRedirector.maybeRedirect(yarnRootExtension.downloadBaseUrl)
+ }
+
+ def nodeJsExtension = rootProject.extensions.findByType(NodeJsRootExtension.class)
+ if (nodeJsExtension != null) {
+ nodeJsExtension.nodeDownloadBaseUrl = CacheRedirector.maybeRedirect(nodeJsExtension.nodeDownloadBaseUrl)
+ }
+}
diff --git a/buildSrc/src/main/kotlin/CacheRedirector.kt b/buildSrc/src/main/kotlin/CacheRedirector.kt
index bcadd7364e..c0d835159c 100644
--- a/buildSrc/src/main/kotlin/CacheRedirector.kt
+++ b/buildSrc/src/main/kotlin/CacheRedirector.kt
@@ -26,31 +26,33 @@ private val mirroredUrls = listOf(
"https://dl.google.com/dl/android/studio/ide-zips",
"https://dl.google.com/go",
"https://download.jetbrains.com",
+ "https://github.com/yarnpkg/yarn/releases/download",
"https://jitpack.io",
- "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev",
"https://maven.pkg.jetbrains.space/kotlin/p/kotlin/bootstrap",
+ "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev",
"https://maven.pkg.jetbrains.space/kotlin/p/kotlin/eap",
+ "https://nodejs.org/dist",
"https://oss.sonatype.org/content/repositories/releases",
"https://oss.sonatype.org/content/repositories/snapshots",
"https://oss.sonatype.org/content/repositories/staging",
"https://packages.confluent.io/maven/",
"https://plugins.gradle.org/m2",
"https://plugins.jetbrains.com/maven",
- "https://repo1.maven.org/maven2",
"https://repo.grails.org/grails/core",
"https://repo.jenkins-ci.org/releases",
"https://repo.maven.apache.org/maven2",
"https://repo.spring.io/milestone",
"https://repo.typesafe.com/typesafe/ivy-releases",
+ "https://repo1.maven.org/maven2",
"https://services.gradle.org",
"https://www.exasol.com/artifactory/exasol-releases",
+ "https://www.jetbrains.com/intellij-repository/nightly",
+ "https://www.jetbrains.com/intellij-repository/releases",
+ "https://www.jetbrains.com/intellij-repository/snapshots",
"https://www.myget.org/F/intellij-go-snapshots/maven",
"https://www.myget.org/F/rd-model-snapshots/maven",
"https://www.myget.org/F/rd-snapshots/maven",
"https://www.python.org/ftp",
- "https://www.jetbrains.com/intellij-repository/nightly",
- "https://www.jetbrains.com/intellij-repository/releases",
- "https://www.jetbrains.com/intellij-repository/snapshots"
)
private val aliases = mapOf(
@@ -115,4 +117,13 @@ object CacheRedirector {
fun Project.configure() {
checkRedirect(repositories, displayName)
}
+
+ @JvmStatic
+ fun maybeRedirect(url: String): String {
+ if (!cacheRedirectorEnabled) return url
+ return URI(url).maybeRedirect()?.toString() ?: url
+ }
+
+ @JvmStatic
+ val isEnabled get() = cacheRedirectorEnabled
}
diff --git a/buildSrc/src/main/kotlin/Java9Modularity.kt b/buildSrc/src/main/kotlin/Java9Modularity.kt
index 27f1bd38cc..ccd7ef33dd 100644
--- a/buildSrc/src/main/kotlin/Java9Modularity.kt
+++ b/buildSrc/src/main/kotlin/Java9Modularity.kt
@@ -143,6 +143,8 @@ object Java9Modularity {
attributes("Multi-Release" to true)
}
from(compileJavaModuleInfo) {
+ // Include **only** file we are interested in as JavaCompile output also contains some tmp files
+ include("module-info.class")
into("META-INF/versions/9/")
}
}
diff --git a/gradle.properties b/gradle.properties
index d31f646d0e..966a90daf6 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,14 +3,14 @@
#
# Kotlin
-version=1.7.0-Beta-SNAPSHOT
+version=1.7.0-RC-SNAPSHOT
group=org.jetbrains.kotlinx
-kotlin_version=1.8.10
+kotlin_version=1.8.20
# Dependencies
junit_version=4.12
junit5_version=5.7.0
-atomicfu_version=0.20.0
+atomicfu_version=0.20.2
knit_version=0.4.0
html_version=0.7.2
lincheck_version=2.16
@@ -20,7 +20,7 @@ reactor_version=3.4.1
reactive_streams_version=1.0.3
rxjava2_version=2.2.8
rxjava3_version=3.0.2
-javafx_version=11.0.2
+javafx_version=17.0.2
javafx_plugin_version=0.0.8
binary_compatibility_validator_version=0.12.0
kover_version=0.6.1
@@ -30,7 +30,7 @@ jna_version=5.9.0
# Android versions
android_version=4.1.1.4
androidx_annotation_version=1.1.0
-robolectric_version=4.4
+robolectric_version=4.9
baksmali_version=2.2.7
# JS
diff --git a/integration-testing/gradle.properties b/integration-testing/gradle.properties
index b8485eaeb2..4644d2346a 100644
--- a/integration-testing/gradle.properties
+++ b/integration-testing/gradle.properties
@@ -1,5 +1,5 @@
-kotlin_version=1.8.10
-coroutines_version=1.7.0-Beta-SNAPSHOT
+kotlin_version=1.8.20
+coroutines_version=1.7.0-RC-SNAPSHOT
asm_version=9.3
kotlin.code.style=official
diff --git a/integration-testing/src/mavenTest/kotlin/MavenPublicationMetaInfValidator.kt b/integration-testing/src/mavenTest/kotlin/MavenPublicationMetaInfValidator.kt
new file mode 100644
index 0000000000..8ed2b823f7
--- /dev/null
+++ b/integration-testing/src/mavenTest/kotlin/MavenPublicationMetaInfValidator.kt
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.validator
+
+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 MavenPublicationMetaInfValidator {
+
+ @Test
+ fun testMetaInfCoreStructure() {
+ val clazz = Class.forName("kotlinx.coroutines.Job")
+ JarFile(clazz.protectionDomain.codeSource.location.file).checkMetaInfStructure(
+ setOf(
+ "MANIFEST.MF",
+ "kotlinx-coroutines-core.kotlin_module",
+ "com.android.tools/proguard/coroutines.pro",
+ "com.android.tools/r8/coroutines.pro",
+ "proguard/coroutines.pro",
+ "versions/9/module-info.class",
+ "kotlinx_coroutines_core.version"
+ )
+ )
+ }
+
+ @Test
+ fun testMetaInfAndroidStructure() {
+ val clazz = Class.forName("kotlinx.coroutines.android.HandlerDispatcher")
+ JarFile(clazz.protectionDomain.codeSource.location.file).checkMetaInfStructure(
+ setOf(
+ "MANIFEST.MF",
+ "kotlinx-coroutines-android.kotlin_module",
+ "services/kotlinx.coroutines.CoroutineExceptionHandler",
+ "services/kotlinx.coroutines.internal.MainDispatcherFactory",
+ "com.android.tools/r8-from-1.6.0/coroutines.pro",
+ "com.android.tools/r8-upto-3.0.0/coroutines.pro",
+ "com.android.tools/proguard/coroutines.pro",
+ "proguard/coroutines.pro",
+ "versions/9/module-info.class",
+ "kotlinx_coroutines_android.version"
+ )
+ )
+ }
+
+ private fun JarFile.checkMetaInfStructure(expected: Set) {
+ val actual = HashSet()
+ for (e in entries()) {
+ if (e.isDirectory() || !e.realName.contains("META-INF")) {
+ continue
+ }
+ val partialName = e.realName.substringAfter("META-INF/")
+ actual.add(partialName)
+ }
+
+ if (actual != expected) {
+ val intersection = actual.intersect(expected)
+ val mismatch = actual.subtract(intersection) + expected.subtract(intersection)
+ fail("Mismatched files: " + mismatch)
+ }
+
+ close()
+ }
+}
diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle
index 2a6dbbc64f..7693966a46 100644
--- a/kotlinx-coroutines-core/build.gradle
+++ b/kotlinx-coroutines-core/build.gradle
@@ -312,12 +312,6 @@ static void configureJvmForLincheck(task, additional = false) {
task moreTest(dependsOn: [jvmStressTest, jvmLincheckTest, jvmLincheckTestAdditional])
check.dependsOn moreTest
-tasks.jvmLincheckTest {
- kover {
- enabled = false // Always disabled, lincheck doesn't really support coverage
- }
-}
-
def commonKoverExcludes =
["kotlinx.coroutines.debug.*", // Tested by debug module
"kotlinx.coroutines.channels.ChannelsKt__DeprecatedKt.*", // Deprecated
@@ -326,6 +320,9 @@ def commonKoverExcludes =
]
kover {
+ instrumentation {
+ excludeTasks.add("jvmLincheckTest") // Always disabled, lincheck doesn't really support coverage
+ }
filters {
classes {
excludes.addAll(commonKoverExcludes)
diff --git a/kotlinx-coroutines-core/common/src/JobSupport.kt b/kotlinx-coroutines-core/common/src/JobSupport.kt
index 2950ed9814..00c0ec870f 100644
--- a/kotlinx-coroutines-core/common/src/JobSupport.kt
+++ b/kotlinx-coroutines-core/common/src/JobSupport.kt
@@ -208,7 +208,7 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
assert { state.isCompleting } // consistency check -- must be marked as completing
val proposedException = (proposedUpdate as? CompletedExceptionally)?.cause
// Create the final exception and seal the state so that no more exceptions can be added
- var wasCancelling = false // KLUDGE: we cannot have contract for our own expect fun synchronized
+ val wasCancelling: Boolean
val finalException = synchronized(state) {
wasCancelling = state.isCancelling
val exceptions = state.sealLocked(proposedException)
diff --git a/kotlinx-coroutines-core/common/src/channels/Channel.kt b/kotlinx-coroutines-core/common/src/channels/Channel.kt
index d6f752c740..b14e61bbd2 100644
--- a/kotlinx-coroutines-core/common/src/channels/Channel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Channel.kt
@@ -105,33 +105,40 @@ public interface SendChannel {
* If the channel is closed already, the handler is invoked immediately.
*
* The meaning of `cause` that is passed to the handler:
- * * `null` if the channel was closed or cancelled without the corresponding argument
- * * the cause of `close` or `cancel` otherwise.
+ * - `null` if the channel was closed normally without the corresponding argument.
+ * - Instance of [CancellationException] if the channel was cancelled normally without the corresponding argument.
+ * - The cause of `close` or `cancel` otherwise.
*
- * Example of usage (exception handling is omitted):
+ * ### Execution context and exception safety
*
+ * The [handler] is executed as part of the closing or cancelling operation, and only after the channel reaches its final state.
+ * This means that if the handler throws an exception or hangs, the channel will still be successfully closed or cancelled.
+ * Unhandled exceptions from [handler] are propagated to the closing or cancelling operation's caller.
+ *
+ * Example of usage:
* ```
- * val events = Channel(UNLIMITED)
+ * val events = Channel(UNLIMITED)
* callbackBasedApi.registerCallback { event ->
* events.trySend(event)
+ * .onClosed { /* channel is already closed, but the callback hasn't stopped yet */ }
* }
*
- * val uiUpdater = launch(Dispatchers.Main, parent = UILifecycle) {
- * events.consume {}
- * events.cancel()
+ * val uiUpdater = uiScope.launch(Dispatchers.Main) {
+ * events.consume { /* handle events */ }
* }
- *
+ * // Stop the callback after the channel is closed or cancelled
* events.invokeOnClose { callbackBasedApi.stop() }
* ```
*
- * **Note: This is an experimental api.** This function may change its semantics, parameters or return type in the future.
+ * **Stability note.** This function constitutes a stable API surface, with the only exception being
+ * that an [IllegalStateException] is thrown when multiple handlers are registered.
+ * This restriction could be lifted in the future.
*
- * @throws UnsupportedOperationException if the underlying channel doesn't support [invokeOnClose].
+ * @throws UnsupportedOperationException if the underlying channel does not support [invokeOnClose].
* Implementation note: currently, [invokeOnClose] is unsupported only by Rx-like integrations
*
* @throws IllegalStateException if another handler was already registered
*/
- @ExperimentalCoroutinesApi
public fun invokeOnClose(handler: (cause: Throwable?) -> Unit)
/**
@@ -388,7 +395,7 @@ public interface ReceiveChannel {
* The successful result represents a successful operation with a value of type [T], for example,
* the result of [Channel.receiveCatching] operation or a successfully sent element as a result of [Channel.trySend].
*
- * The failed result represents a failed operation attempt to a channel, but it doesn't necessary indicate that the channel is failed.
+ * The failed result represents a failed operation attempt to a channel, but it doesn't necessarily indicate that the channel is failed.
* E.g. when the channel is full, [Channel.trySend] returns failed result, but the channel itself is not in the failed state.
*
* The closed result represents an operation attempt to a closed channel and also implies that the operation has failed.
diff --git a/kotlinx-coroutines-core/common/src/flow/StateFlow.kt b/kotlinx-coroutines-core/common/src/flow/StateFlow.kt
index b65340a836..2f3de1565e 100644
--- a/kotlinx-coroutines-core/common/src/flow/StateFlow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/StateFlow.kt
@@ -319,8 +319,8 @@ private class StateFlowImpl(
updateState(expect ?: NULL, update ?: NULL)
private fun updateState(expectedState: Any?, newState: Any): Boolean {
- var curSequence = 0
- var curSlots: Array? = this.slots // benign race, we will not use it
+ var curSequence: Int
+ var curSlots: Array? // benign race, we will not use it
synchronized(this) {
val oldState = _state.value
if (expectedState != null && oldState != expectedState) return false // CAS support
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt b/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt
index d263d61227..5dcf0d01ed 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt
@@ -41,7 +41,7 @@ internal abstract class AbstractSharedFlow> : Sync
@Suppress("UNCHECKED_CAST")
protected fun allocateSlot(): S {
// Actually create slot under lock
- var subscriptionCount: SubscriptionCountStateFlow? = null
+ val subscriptionCount: SubscriptionCountStateFlow?
val slot = synchronized(this) {
val slots = when (val curSlots = slots) {
null -> createSlotArray(2).also { slots = it }
@@ -72,7 +72,7 @@ internal abstract class AbstractSharedFlow> : Sync
@Suppress("UNCHECKED_CAST")
protected fun freeSlot(slot: S) {
// Release slot under lock
- var subscriptionCount: SubscriptionCountStateFlow? = null
+ val subscriptionCount: SubscriptionCountStateFlow?
val resumes = synchronized(this) {
nCollectors--
subscriptionCount = _subscriptionCount // retrieve under lock if initialized
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt b/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt
index f7c7528d43..d7fec3fec0 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt
@@ -144,9 +144,19 @@ public inline fun SharedFlow.retryWhen(noinline predicate: suspend FlowCo
level = DeprecationLevel.WARNING
)
@InlineOnly
-public suspend inline fun SharedFlow.toList(destination: MutableList = ArrayList()): List =
+public suspend inline fun SharedFlow.toList(): List =
(this as Flow).toList()
+/**
+ * A specialized version of [Flow.toList] that returns [Nothing]
+ * to indicate that [SharedFlow] collection never completes.
+ */
+@InlineOnly
+public suspend inline fun SharedFlow.toList(destination: MutableList): Nothing {
+ (this as Flow).toList(destination)
+ throw IllegalStateException("this code is supposed to be unreachable")
+}
+
/**
* @suppress
*/
@@ -156,9 +166,19 @@ public suspend inline fun SharedFlow.toList(destination: MutableList =
level = DeprecationLevel.WARNING
)
@InlineOnly
-public suspend inline fun SharedFlow.toSet(destination: MutableSet = LinkedHashSet()): Set =
+public suspend inline fun SharedFlow.toSet(): Set =
(this as Flow).toSet()
+/**
+ * A specialized version of [Flow.toSet] that returns [Nothing]
+ * to indicate that [SharedFlow] collection never completes.
+ */
+@InlineOnly
+public suspend inline fun SharedFlow.toSet(destination: MutableSet): Nothing {
+ (this as Flow).toSet(destination)
+ throw IllegalStateException("this code is supposed to be unreachable")
+}
+
/**
* @suppress
*/
diff --git a/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt b/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt
index 8d814d566d..374e53b7b4 100644
--- a/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt
+++ b/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt
@@ -7,7 +7,6 @@ package kotlinx.coroutines.internal
import kotlinx.atomicfu.*
import kotlinx.coroutines.*
import kotlin.coroutines.*
-import kotlin.jvm.*
/**
* The result of .limitedParallelism(x) call, a dispatcher
@@ -27,7 +26,7 @@ import kotlin.jvm.*
internal class LimitedDispatcher(
private val dispatcher: CoroutineDispatcher,
private val parallelism: Int
-) : CoroutineDispatcher(), Runnable, Delay by (dispatcher as? Delay ?: DefaultDelay) {
+) : CoroutineDispatcher(), Delay by (dispatcher as? Delay ?: DefaultDelay) {
// Atomic is necessary here for the sake of K/N memory ordering,
// there is no need in atomic operations for this property
@@ -45,61 +44,37 @@ internal class LimitedDispatcher(
return super.limitedParallelism(parallelism)
}
- override fun run() {
- var fairnessCounter = 0
- while (true) {
- val task = queue.removeFirstOrNull()
- if (task != null) {
- try {
- task.run()
- } catch (e: Throwable) {
- handleCoroutineException(EmptyCoroutineContext, e)
- }
- // 16 is our out-of-thin-air constant to emulate fairness. Used in JS dispatchers as well
- if (++fairnessCounter >= 16 && dispatcher.isDispatchNeeded(this)) {
- // Do "yield" to let other views to execute their runnable as well
- // Note that we do not decrement 'runningWorkers' as we still committed to do our part of work
- dispatcher.dispatch(this, this)
- return
- }
- continue
- }
-
- synchronized(workerAllocationLock) {
- runningWorkers.decrementAndGet()
- if (queue.size == 0) return
- runningWorkers.incrementAndGet()
- fairnessCounter = 0
- }
- }
- }
-
override fun dispatch(context: CoroutineContext, block: Runnable) {
- dispatchInternal(block) {
- dispatcher.dispatch(this, this)
+ dispatchInternal(block) { worker ->
+ dispatcher.dispatch(this, worker)
}
}
@InternalCoroutinesApi
override fun dispatchYield(context: CoroutineContext, block: Runnable) {
- dispatchInternal(block) {
- dispatcher.dispatchYield(this, this)
+ dispatchInternal(block) { worker ->
+ dispatcher.dispatchYield(this, worker)
}
}
- private inline fun dispatchInternal(block: Runnable, dispatch: () -> Unit) {
+ /**
+ * Tries to dispatch the given [block].
+ * If there are not enough workers, it starts a new one via [startWorker].
+ */
+ private inline fun dispatchInternal(block: Runnable, startWorker: (Worker) -> Unit) {
// Add task to queue so running workers will be able to see that
- if (addAndTryDispatching(block)) return
- /*
- * Protect against the race when the number of workers is enough,
- * but one (because of synchronized serialization) attempts to complete,
- * and we just observed the number of running workers smaller than the actual
- * number (hit right between `--runningWorkers` and `++runningWorkers` in `run()`)
- */
+ queue.addLast(block)
+ if (runningWorkers.value >= parallelism) return
+ // allocation may fail if some workers were launched in parallel or a worker temporarily decreased
+ // `runningWorkers` when they observed an empty queue.
if (!tryAllocateWorker()) return
- dispatch()
+ val task = obtainTaskOrDeallocateWorker() ?: return
+ startWorker(Worker(task))
}
+ /**
+ * Tries to obtain the permit to start a new worker.
+ */
private fun tryAllocateWorker(): Boolean {
synchronized(workerAllocationLock) {
if (runningWorkers.value >= parallelism) return false
@@ -108,9 +83,49 @@ internal class LimitedDispatcher(
}
}
- private fun addAndTryDispatching(block: Runnable): Boolean {
- queue.addLast(block)
- return runningWorkers.value >= parallelism
+ /**
+ * Obtains the next task from the queue, or logically deallocates the worker if the queue is empty.
+ */
+ private fun obtainTaskOrDeallocateWorker(): Runnable? {
+ while (true) {
+ when (val nextTask = queue.removeFirstOrNull()) {
+ null -> synchronized(workerAllocationLock) {
+ runningWorkers.decrementAndGet()
+ if (queue.size == 0) return null
+ runningWorkers.incrementAndGet()
+ }
+ else -> return nextTask
+ }
+ }
+ }
+
+ /**
+ * A worker that polls the queue and runs tasks until there are no more of them.
+ *
+ * It always stores the next task to run. This is done in order to prevent the possibility of the fairness
+ * re-dispatch happening when there are no more tasks in the queue. This is important because, after all the
+ * actual tasks are done, nothing prevents the user from closing the dispatcher and making it incorrect to
+ * perform any more dispatches.
+ */
+ private inner class Worker(private var currentTask: Runnable) : Runnable {
+ override fun run() {
+ var fairnessCounter = 0
+ while (true) {
+ try {
+ currentTask.run()
+ } catch (e: Throwable) {
+ handleCoroutineException(EmptyCoroutineContext, e)
+ }
+ currentTask = obtainTaskOrDeallocateWorker() ?: return
+ // 16 is our out-of-thin-air constant to emulate fairness. Used in JS dispatchers as well
+ if (++fairnessCounter >= 16 && dispatcher.isDispatchNeeded(this@LimitedDispatcher)) {
+ // Do "yield" to let other views execute their runnable as well
+ // Note that we do not decrement 'runningWorkers' as we are still committed to our part of work
+ dispatcher.dispatch(this@LimitedDispatcher, this)
+ return
+ }
+ }
+ }
}
}
diff --git a/kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt b/kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt
index 059b234d8f..7107945a55 100644
--- a/kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt
+++ b/kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt
@@ -5,6 +5,7 @@
package kotlinx.coroutines.internal
import kotlinx.coroutines.*
+import kotlin.contracts.*
/**
* @suppress **This an internal API and should not be used from general code.**
@@ -16,4 +17,16 @@ public expect open class SynchronizedObject() // marker abstract class
* @suppress **This an internal API and should not be used from general code.**
*/
@InternalCoroutinesApi
-public expect inline fun synchronized(lock: SynchronizedObject, block: () -> T): T
+public expect inline fun synchronizedImpl(lock: SynchronizedObject, block: () -> T): T
+
+/**
+ * @suppress **This an internal API and should not be used from general code.**
+ */
+@OptIn(ExperimentalContracts::class)
+@InternalCoroutinesApi
+public inline fun synchronized(lock: SynchronizedObject, block: () -> T): T {
+ contract {
+ callsInPlace(block, InvocationKind.EXACTLY_ONCE)
+ }
+ return synchronizedImpl(lock, block)
+}
diff --git a/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt b/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt
index aeb6199134..1f9b6fa739 100644
--- a/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt
+++ b/kotlinx-coroutines-core/common/test/channels/BasicOperationsTest.kt
@@ -85,6 +85,45 @@ class BasicOperationsTest : TestBase() {
}
}
+ @Test
+ fun testCancelledChannelInvokeOnClose() {
+ val ch = Channel()
+ ch.invokeOnClose { assertIs(it) }
+ ch.cancel()
+ }
+
+ @Test
+ fun testCancelledChannelWithCauseInvokeOnClose() {
+ val ch = Channel()
+ ch.invokeOnClose { assertIs(it) }
+ ch.cancel(TimeoutCancellationException(""))
+ }
+
+ @Test
+ fun testThrowingInvokeOnClose() = runTest {
+ val channel = Channel()
+ channel.invokeOnClose {
+ assertNull(it)
+ expect(3)
+ throw TestException()
+ }
+
+ launch {
+ try {
+ expect(2)
+ channel.close()
+ } catch (e: TestException) {
+ expect(4)
+ }
+ }
+ expect(1)
+ yield()
+ assertTrue(channel.isClosedForReceive)
+ assertTrue(channel.isClosedForSend)
+ assertFalse(channel.close())
+ finish(5)
+ }
+
@Suppress("ReplaceAssertBooleanWithAssertEquality")
private suspend fun testReceiveCatching(kind: TestChannelKind) = coroutineScope {
reset()
@@ -124,7 +163,7 @@ class BasicOperationsTest : TestBase() {
channel.trySend(2)
.onSuccess { expectUnreached() }
.onClosed {
- assertTrue { it is ClosedSendChannelException}
+ assertTrue { it is ClosedSendChannelException }
if (!kind.isConflated) {
assertEquals(42, channel.receive())
}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/LintTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/LintTest.kt
new file mode 100644
index 0000000000..9cf6cbb958
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/LintTest.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2016-2023 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.*
+
+class LintTest: TestBase() {
+ /**
+ * Tests that using [SharedFlow.toList] and similar functions by passing a mutable collection does add values
+ * to the provided collection.
+ */
+ @Test
+ fun testSharedFlowToCollection() = runTest {
+ val sharedFlow = MutableSharedFlow()
+ val list = mutableListOf()
+ val set = mutableSetOf()
+ val jobs = listOf(suspend { sharedFlow.toList(list) }, { sharedFlow.toSet(set) }).map {
+ launch(Dispatchers.Unconfined) { it() }
+ }
+ repeat(10) {
+ sharedFlow.emit(it)
+ }
+ jobs.forEach { it.cancelAndJoin() }
+ assertEquals((0..9).toList(), list)
+ assertEquals((0..9).toSet(), set)
+ }
+}
diff --git a/kotlinx-coroutines-core/concurrent/test/LimitedParallelismConcurrentTest.kt b/kotlinx-coroutines-core/concurrent/test/LimitedParallelismConcurrentTest.kt
index 7d82a67baf..49fe93f608 100644
--- a/kotlinx-coroutines-core/concurrent/test/LimitedParallelismConcurrentTest.kt
+++ b/kotlinx-coroutines-core/concurrent/test/LimitedParallelismConcurrentTest.kt
@@ -7,6 +7,7 @@ package kotlinx.coroutines
import kotlinx.atomicfu.*
import kotlinx.coroutines.*
import kotlinx.coroutines.exceptions.*
+import kotlin.coroutines.*
import kotlin.test.*
class LimitedParallelismConcurrentTest : TestBase() {
@@ -58,4 +59,37 @@ class LimitedParallelismConcurrentTest : TestBase() {
joinAll(j1, j2)
executor.close()
}
+
+ /**
+ * Tests that, when no tasks are present, the limited dispatcher does not dispatch any tasks.
+ * This is important for the case when a dispatcher is closeable and the [CoroutineDispatcher.limitedParallelism]
+ * machinery could trigger a dispatch after the dispatcher is closed.
+ */
+ @Test
+ fun testNotDoingDispatchesWhenNoTasksArePresent() = runTest {
+ class NaggingDispatcher: CoroutineDispatcher() {
+ val closed = atomic(false)
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ if (closed.value)
+ fail("Dispatcher was closed, but still dispatched a task")
+ Dispatchers.Default.dispatch(context, block)
+ }
+ fun close() {
+ closed.value = true
+ }
+ }
+ repeat(stressTestMultiplier * 500_000) {
+ val dispatcher = NaggingDispatcher()
+ val view = dispatcher.limitedParallelism(1)
+ val deferred = CompletableDeferred()
+ val job = launch(view) {
+ deferred.await()
+ }
+ launch(Dispatchers.Default) {
+ deferred.complete(Unit)
+ }
+ job.join()
+ dispatcher.close()
+ }
+ }
}
diff --git a/kotlinx-coroutines-core/js/src/internal/Synchronized.kt b/kotlinx-coroutines-core/js/src/internal/Synchronized.kt
index dcbb20217d..05db52854f 100644
--- a/kotlinx-coroutines-core/js/src/internal/Synchronized.kt
+++ b/kotlinx-coroutines-core/js/src/internal/Synchronized.kt
@@ -16,5 +16,4 @@ public actual typealias SynchronizedObject = Any
* @suppress **This an internal API and should not be used from general code.**
*/
@InternalCoroutinesApi
-public actual inline fun synchronized(lock: SynchronizedObject, block: () -> T): T =
- block()
+public actual inline fun synchronizedImpl(lock: SynchronizedObject, block: () -> T): T = block()
diff --git a/kotlinx-coroutines-core/jvm/src/Executors.kt b/kotlinx-coroutines-core/jvm/src/Executors.kt
index 4e98e7bc98..121ba3f443 100644
--- a/kotlinx-coroutines-core/jvm/src/Executors.kt
+++ b/kotlinx-coroutines-core/jvm/src/Executors.kt
@@ -108,7 +108,14 @@ public fun CoroutineDispatcher.asExecutor(): Executor =
(this as? ExecutorCoroutineDispatcher)?.executor ?: DispatcherExecutor(this)
private class DispatcherExecutor(@JvmField val dispatcher: CoroutineDispatcher) : Executor {
- override fun execute(block: Runnable) = dispatcher.dispatch(EmptyCoroutineContext, block)
+ override fun execute(block: Runnable) {
+ if (dispatcher.isDispatchNeeded(EmptyCoroutineContext)) {
+ dispatcher.dispatch(EmptyCoroutineContext, block)
+ } else {
+ block.run()
+ }
+ }
+
override fun toString(): String = dispatcher.toString()
}
diff --git a/kotlinx-coroutines-core/jvm/src/internal/ResizableAtomicArray.kt b/kotlinx-coroutines-core/jvm/src/internal/ResizableAtomicArray.kt
index f949d9f5ea..6c98a9dad9 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/ResizableAtomicArray.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/ResizableAtomicArray.kt
@@ -28,11 +28,12 @@ internal class ResizableAtomicArray(initialLength: Int) {
val curLen = curArray.length()
if (index < curLen) {
curArray[index] = value
- } else {
- val newArray = AtomicReferenceArray((index + 1).coerceAtLeast(2 * curLen))
- for (i in 0 until curLen) newArray[i] = curArray[i]
- newArray[index] = value
- array = newArray // copy done
+ return
}
+ // It would be nice to copy array in batch instead of 1 by 1, but it seems like Java has no API for that
+ val newArray = AtomicReferenceArray((index + 1).coerceAtLeast(2 * curLen))
+ for (i in 0 until curLen) newArray[i] = curArray[i]
+ newArray[index] = value
+ array = newArray // copy done
}
}
diff --git a/kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt b/kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt
index 6284f3a099..5b12faaf09 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/Synchronized.kt
@@ -16,5 +16,5 @@ public actual typealias SynchronizedObject = Any
* @suppress **This an internal API and should not be used from general code.**
*/
@InternalCoroutinesApi
-public actual inline fun synchronized(lock: SynchronizedObject, block: () -> T): T =
+public actual inline fun synchronizedImpl(lock: SynchronizedObject, block: () -> T): T =
kotlin.synchronized(lock, block)
diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt
index e08f3deedd..5ca3de5726 100644
--- a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt
+++ b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt
@@ -252,22 +252,31 @@ internal class CoroutineScheduler(
/**
* State of worker threads.
- * [workers] is array of lazily created workers up to [maxPoolSize] workers.
+ * [workers] is a dynamic array of lazily created workers up to [maxPoolSize] workers.
* [createdWorkers] is count of already created workers (worker with index lesser than [createdWorkers] exists).
- * [blockingTasks] is count of pending (either in the queue or being executed) tasks
+ * [blockingTasks] is count of pending (either in the queue or being executed) blocking tasks.
+ *
+ * Workers array is also used as a lock for workers' creation and termination sequence.
*
* **NOTE**: `workers[0]` is always `null` (never used, works as sentinel value), so
* workers are 1-indexed, code path in [Worker.trySteal] is a bit faster and index swap during termination
- * works properly
+ * works properly.
+ *
+ * Initial size is `Dispatchers.Default` size * 2 to prevent unnecessary resizes for slightly or steadily loaded
+ * applications.
*/
@JvmField
- val workers = ResizableAtomicArray(corePoolSize + 1)
+ val workers = ResizableAtomicArray((corePoolSize + 1) * 2)
/**
* The `Long` value describing the state of workers in this pool.
- * Currently includes created, CPU-acquired, and blocking workers, each occupying [BLOCKING_SHIFT] bits.
+ * Currently, includes created, CPU-acquired, and blocking workers, each occupying [BLOCKING_SHIFT] bits.
+ *
+ * State layout (highest to lowest bits):
+ * | --- number of cpu permits, 22 bits --- | --- number of blocking tasks, 21 bits --- | --- number of created threads, 21 bits --- |
*/
private val controlState = atomic(corePoolSize.toLong() shl CPU_PERMITS_SHIFT)
+
private val createdWorkers: Int inline get() = (controlState.value and CREATED_MASK).toInt()
private val availableCpuPermits: Int inline get() = availableCpuPermits(controlState.value)
@@ -383,6 +392,10 @@ internal class CoroutineScheduler(
fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, tailDispatch: Boolean = false) {
trackTask() // this is needed for virtual time support
val task = createTask(block, taskContext)
+ val isBlockingTask = task.isBlocking
+ // Invariant: we increment counter **before** publishing the task
+ // so executing thread can safely decrement the number of blocking tasks
+ val stateSnapshot = if (isBlockingTask) incrementBlockingTasks() else 0
// try to submit the task to the local queue and act depending on the result
val currentWorker = currentWorker()
val notAdded = currentWorker.submitToLocalQueue(task, tailDispatch)
@@ -394,12 +407,12 @@ internal class CoroutineScheduler(
}
val skipUnpark = tailDispatch && currentWorker != null
// Checking 'task' instead of 'notAdded' is completely okay
- if (task.mode == TASK_NON_BLOCKING) {
+ if (isBlockingTask) {
+ // Use state snapshot to better estimate the number of running threads
+ signalBlockingWork(stateSnapshot, skipUnpark = skipUnpark)
+ } else {
if (skipUnpark) return
signalCpuWork()
- } else {
- // Increment blocking tasks anyway
- signalBlockingWork(skipUnpark = skipUnpark)
}
}
@@ -413,11 +426,11 @@ internal class CoroutineScheduler(
return TaskImpl(block, nanoTime, taskContext)
}
- private fun signalBlockingWork(skipUnpark: Boolean) {
- // Use state snapshot to avoid thread overprovision
- val stateSnapshot = incrementBlockingTasks()
+ // NB: should only be called from 'dispatch' method due to blocking tasks increment
+ private fun signalBlockingWork(stateSnapshot: Long, skipUnpark: Boolean) {
if (skipUnpark) return
if (tryUnpark()) return
+ // Use state snapshot to avoid accidental thread overprovision
if (tryCreateWorker(stateSnapshot)) return
tryUnpark() // Try unpark again in case there was race between permit release and parking
}
@@ -456,12 +469,13 @@ internal class CoroutineScheduler(
}
}
- /*
+ /**
* Returns the number of CPU workers after this function (including new worker) or
* 0 if no worker was created.
*/
private fun createNewWorker(): Int {
- synchronized(workers) {
+ val worker: Worker
+ return synchronized(workers) {
// Make sure we're not trying to resurrect terminated scheduler
if (isTerminated) return -1
val state = controlState.value
@@ -479,12 +493,11 @@ internal class CoroutineScheduler(
* 2) Make it observable by increment created workers count
* 3) Only then start the worker, otherwise it may miss its own creation
*/
- val worker = Worker(newIndex)
+ worker = Worker(newIndex)
workers.setSynchronized(newIndex, worker)
require(newIndex == incrementCreatedWorkers())
- worker.start()
- return cpuWorkers + 1
- }
+ cpuWorkers + 1
+ }.also { worker.start() } // Start worker when the lock is released to reduce contention, see #3652
}
/**
diff --git a/kotlinx-coroutines-core/jvm/test/ExecutorsTest.kt b/kotlinx-coroutines-core/jvm/test/ExecutorsTest.kt
index ebf08a03d0..6e5b18f7e9 100644
--- a/kotlinx-coroutines-core/jvm/test/ExecutorsTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/ExecutorsTest.kt
@@ -81,6 +81,22 @@ class ExecutorsTest : TestBase() {
finish(4)
}
+ @Test
+ fun testCustomDispatcherToExecutorDispatchNotNeeded() {
+ expect(1)
+ val dispatcher = object : CoroutineDispatcher() {
+ override fun isDispatchNeeded(context: CoroutineContext) = false
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ fail("should not dispatch")
+ }
+ }
+ dispatcher.asExecutor().execute {
+ expect(2)
+ }
+ finish(3)
+ }
+
@Test
fun testTwoThreads() {
val ctx1 = newSingleThreadContext("Ctx1")
@@ -106,4 +122,4 @@ class ExecutorsTest : TestBase() {
dispatcher.close()
check(executorService.isShutdown)
}
-}
\ No newline at end of file
+}
diff --git a/kotlinx-coroutines-core/jvm/test/TestBase.kt b/kotlinx-coroutines-core/jvm/test/TestBase.kt
index 6a013fa1da..f9e5466b44 100644
--- a/kotlinx-coroutines-core/jvm/test/TestBase.kt
+++ b/kotlinx-coroutines-core/jvm/test/TestBase.kt
@@ -97,10 +97,10 @@ public actual open class TestBase(private var disableOutCheck: Boolean) {
private fun printError(message: String, cause: Throwable) {
setError(cause)
- println("$message: $cause")
- cause.printStackTrace(System.out)
- println("--- Detected at ---")
- Throwable().printStackTrace(System.out)
+ System.err.println("$message: $cause")
+ cause.printStackTrace(System.err)
+ System.err.println("--- Detected at ---")
+ Throwable().printStackTrace(System.err)
}
/**
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerInternalApiStressTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerInternalApiStressTest.kt
index 22b9b02916..2ec714ffb6 100644
--- a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerInternalApiStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerInternalApiStressTest.kt
@@ -80,8 +80,8 @@ class CoroutineSchedulerInternalApiStressTest : TestBase() {
}
}
completionLatch.countDown()
-// assertEquals(100, timesHelped)
-// assertTrue(Thread.currentThread() in observedDefaultThreads, observedDefaultThreads.toString())
+ assertEquals(100, timesHelped)
+ assertTrue(Thread.currentThread() in observedDefaultThreads, observedDefaultThreads.toString())
}
}
}
diff --git a/kotlinx-coroutines-core/native/src/Debug.kt b/kotlinx-coroutines-core/native/src/Debug.kt
index f17c2ed7fc..e208addf88 100644
--- a/kotlinx-coroutines-core/native/src/Debug.kt
+++ b/kotlinx-coroutines-core/native/src/Debug.kt
@@ -9,6 +9,7 @@ import kotlin.native.*
internal actual val DEBUG: Boolean = false
+@OptIn(ExperimentalStdlibApi::class)
internal actual val Any.hexAddress: String get() = identityHashCode().toUInt().toString(16)
internal actual val Any.classSimpleName: String get() = this::class.simpleName ?: "Unknown"
diff --git a/kotlinx-coroutines-core/native/src/internal/Synchronized.kt b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt
index edbd3fde0c..8a8ecfe393 100644
--- a/kotlinx-coroutines-core/native/src/internal/Synchronized.kt
+++ b/kotlinx-coroutines-core/native/src/internal/Synchronized.kt
@@ -17,4 +17,4 @@ public actual typealias SynchronizedObject = kotlinx.atomicfu.locks.Synchronized
* @suppress **This an internal API and should not be used from general code.**
*/
@InternalCoroutinesApi
-public actual inline fun synchronized(lock: SynchronizedObject, block: () -> T): T = lock.withLock2(block)
+public actual inline fun synchronizedImpl(lock: SynchronizedObject, block: () -> T): T = lock.withLock2(block)
diff --git a/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt b/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt
index a5588d853d..edc0a13ce8 100644
--- a/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt
+++ b/kotlinx-coroutines-core/nativeDarwin/src/Dispatchers.kt
@@ -20,7 +20,7 @@ internal actual fun createDefaultDispatcher(): CoroutineDispatcher = DarwinGloba
private object DarwinGlobalQueueDispatcher : CoroutineDispatcher() {
override fun dispatch(context: CoroutineContext, block: Runnable) {
autoreleasepool {
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.convert(), 0)) {
+ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT.convert(), 0u)) {
block.run()
}
}
diff --git a/kotlinx-coroutines-debug/README.md b/kotlinx-coroutines-debug/README.md
index 24d0084be4..ff9ba9fffa 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.7.0-Beta.jar`.
+You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.7.0-RC.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 2bdf1d8216..f2805086bb 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.7.0-Beta'
+ testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.0-RC'
}
```
diff --git a/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api b/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api
index bcee73e12e..00d9fb659e 100644
--- a/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api
+++ b/kotlinx-coroutines-test/api/kotlinx-coroutines-test.api
@@ -22,6 +22,7 @@ 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 final synthetic fun runTest$default (Lkotlinx/coroutines/test/TestScope;Ljava/lang/Long;Lkotlin/jvm/functions/Function2;Ljava/lang/Integer;Ljava/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
diff --git a/kotlinx-coroutines-test/common/src/TestBuilders.kt b/kotlinx-coroutines-test/common/src/TestBuilders.kt
index 15cd1fba4a..8ae075a706 100644
--- a/kotlinx-coroutines-test/common/src/TestBuilders.kt
+++ b/kotlinx-coroutines-test/common/src/TestBuilders.kt
@@ -570,3 +570,16 @@ internal fun throwAll(head: Throwable?, other: List) {
}
internal expect fun dumpCoroutines()
+
+@Deprecated(
+ "This is for binary compatibility with the `runTest` overload that existed at some point",
+ level = DeprecationLevel.HIDDEN
+)
+@JvmName("runTest\$default")
+@Suppress("DEPRECATION", "UNUSED_PARAMETER")
+public fun TestScope.runTestLegacy(
+ dispatchTimeoutMs: Long?,
+ testBody: suspend TestScope.() -> Unit,
+ unused1: Int?,
+ unused2: Any?,
+): TestResult = runTest(dispatchTimeoutMs ?: 60_000, testBody)
diff --git a/ui/coroutines-guide-ui.md b/ui/coroutines-guide-ui.md
index 127097e702..e00d74edee 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.7.0-Beta"
+implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0-RC"
```
You can clone [kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines) project from GitHub onto your