diff --git a/CHANGES.md b/CHANGES.md
index 611e9c9c74..17805e5b7b 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,53 @@
# Change log for kotlinx.coroutines
+## Version 1.6.0-RC
+
+### kotlinx-coroutines-test rework
+
+* `kotlinx-coroutines-test` became a multiplatform library usable from K/JVM, K/JS, and K/N.
+* Its API was completely reworked to address long-standing issues with consistency, structured concurrency and correctness (#1203, #1609, #2379, #1749, #1204, #1390, #1222, #1395, #1881, #1910, #1772, #1626, #1742, #2082, #2102, #2405, #2462
+ ).
+* The old API is deprecated for removal, but the new API is based on the similar concepts ([README](kotlinx-coroutines-test/README.md)), and the migration path is designed to be graceful: [migration guide](kotlinx-coroutines-test/MIGRATION.md)
+
+### Dispatchers
+
+* * Introduced `CoroutineDispatcher.limitedParallelism` that allows obtaining a view of the original dispatcher with limited parallelism (#2919).
+* `Dispatchers.IO.limitedParallelism` usages ignore the bound on the parallelism level of `Dispatchers.IO` itself to avoid starvation (#2943).
+* Introduced new `Dispatchers.shutdown` method for containerized environments (#2558).
+* `newSingleThreadContext` and `newFixedThreadPoolContext` are promoted to delicate API (#2919).
+
+### Breaking changes
+
+* When racing with cancellation, the `future` builder no longer reports unhandled exceptions into the global `CoroutineExceptionHandler`. Thanks @vadimsemenov! (#2774, #2791).
+* `Mutex.onLock` is deprecated for removal (#2794).
+* `Dispatchers.Main` is now used as the default source of time for `delay` and `withTimeout` when present(#2972).
+ * To opt-out from this behaviour, `kotlinx.coroutines.main.delay` system property can be set to `false`.
+* Java target of coroutines build is now 8 instead of 6 (#1589).
+
+### Bug fixes and improvements
+
+* Kotlin is updated to 1.6.0.
+* Kotlin/Native [new memory model](https://blog.jetbrains.com/kotlin/2021/08/try-the-new-kotlin-native-memory-manager-development-preview/) is now supported in regular builds of coroutines conditionally depending on whether `kotlin.native.binary.memoryModel` is enabled (#2914).
+* Introduced `CopyableThreadContextElement` for mutable context elements shared among multiple coroutines. Thanks @yorickhenning! (#2893).
+* `transformWhile`, `awaitClose`, `ProducerScope`, `merge`, `runningFold`, `runingReduce`, and `scan` are promoted to stable API (#2971).
+* `SharedFlow.subscriptionCount` no longer conflates incoming updates and gives all subscribers a chance to observe a short-lived subscription (#2488, #2863, #2871).
+* `Flow` exception transparency mechanism is improved to be more exception-friendly (#3017, #2860).
+* Cancellation from `flat*` operators that leverage multiple coroutines is no longer propagated upstream (#2964).
+* `SharedFlow.collect` now returns `Nothing` (#2789, #2502).
+* `FlowCollector` is now `fun interface`, and corresponding inline extension is removed (#2790).
+* Deprecation level of all previously deprecated signatures is raised (#3024).
+* The version file is shipped with each JAR as a resource (#2941).
+* Unhandled exceptions on K/N are passed to the standard library function `processUnhandledException` (#2981).
+* A direct executor is used for `Task` callbacks in `kotlinx-coroutines-play-services` (#2990).
+* Metadata of coroutines artifacts leverages Gradle platform to have all versions of dependencies aligned (#2865).
+* Default `CoroutineExceptionHandler` is loaded eagerly and does not invoke `ServiceLoader` on its exception-handling path (#2552).
+* Fixed the R8 rules for `ServiceLoader` optimization (#2880).
+* Fixed BlockHound integration false-positives (#2894, #2866, #2937).
+* The exception recovery mechanism now uses `ClassValue` when available (#2997).
+* JNA is updated to 5.9.0 to support Apple M1 (#3001).
+* Obsolete method on internal `Delay` interface is deprecated (#2979).
+* Support of deprecated `CommonPool` is removed.
+
## Version 1.5.2
* Kotlin is updated to 1.5.30.
diff --git a/README.md b/README.md
index 5fc8973330..91cbf5362f 100644
--- a/README.md
+++ b/README.md
@@ -2,12 +2,12 @@
[![official JetBrains 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.5.2)](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.5.2/pom)
-[![Kotlin](https://img.shields.io/badge/kotlin-1.5.30-blue.svg?logo=kotlin)](http://kotlinlang.org)
+[![Download](https://img.shields.io/maven-central/v/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.6.0-RC)](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.6.0-RC/pom)
+[![Kotlin](https://img.shields.io/badge/kotlin-1.6.0-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.5.30` release.
+This is a companion version for the Kotlin `1.6.0` release.
```kotlin
suspend fun main() = coroutineScope {
@@ -83,7 +83,7 @@ Add dependencies (you can also add other modules that you need):
org.jetbrains.kotlinx
kotlinx-coroutines-core
- 1.5.2
+ 1.6.0-RC
```
@@ -91,7 +91,7 @@ And make sure that you use the latest Kotlin version:
```xml
- 1.5.30
+ 1.6.0
```
@@ -101,7 +101,7 @@ Add dependencies (you can also add other modules that you need):
```groovy
dependencies {
- implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-RC'
}
```
@@ -109,7 +109,7 @@ And make sure that you use the latest Kotlin version:
```groovy
buildscript {
- ext.kotlin_version = '1.5.30'
+ ext.kotlin_version = '1.6.0'
}
```
@@ -127,7 +127,7 @@ Add dependencies (you can also add other modules that you need):
```groovy
dependencies {
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-RC")
}
```
@@ -147,7 +147,7 @@ Add [`kotlinx-coroutines-android`](ui/kotlinx-coroutines-android)
module as a dependency when using `kotlinx.coroutines` on Android:
```groovy
-implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
+implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0-RC'
```
This gives you access to the Android [Dispatchers.Main]
@@ -180,7 +180,7 @@ In common code that should get compiled for different platforms, you can add a d
```groovy
commonMain {
dependencies {
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0-RC")
}
}
```
@@ -192,7 +192,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.5.2/jar)
+[`kotlinx-coroutines-core-js`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.6.0-RC/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/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts
index ce0bff1cdf..b7dcb57968 100644
--- a/benchmarks/build.gradle.kts
+++ b/benchmarks/build.gradle.kts
@@ -46,21 +46,28 @@ extensions.configure("jmh") {
// includeTests = false
}
-tasks.named("jmhJar") {
+val jmhJarTask = tasks.named("jmhJar") {
archiveBaseName by "benchmarks"
archiveClassifier by null
archiveVersion by null
destinationDirectory.file("$rootDir")
}
+tasks {
+ build {
+ dependsOn(jmhJarTask)
+ }
+}
+
dependencies {
- compile("org.openjdk.jmh:jmh-core:1.26")
- compile("io.projectreactor:reactor-core:${version("reactor")}")
- compile("io.reactivex.rxjava2:rxjava:2.1.9")
- compile("com.github.akarnokd:rxjava2-extensions:0.20.8")
+ implementation("org.openjdk.jmh:jmh-core:1.26")
+ implementation("io.projectreactor:reactor-core:${version("reactor")}")
+ implementation("io.reactivex.rxjava2:rxjava:2.1.9")
+ implementation("com.github.akarnokd:rxjava2-extensions:0.20.8")
- compile("com.typesafe.akka:akka-actor_2.12:2.5.0")
- compile(project(":kotlinx-coroutines-core"))
+ implementation("com.typesafe.akka:akka-actor_2.12:2.5.0")
+ implementation(project(":kotlinx-coroutines-core"))
+ implementation(project(":kotlinx-coroutines-reactive"))
// add jmh dependency on main
"jmhImplementation"(sourceSets.main.get().runtimeClasspath)
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt b/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt
index 9948a371bc..80e15a1b4f 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt
@@ -30,7 +30,7 @@ abstract class ParametrizedDispatcherBase : CoroutineScope {
coroutineContext = when {
dispatcher == "fjp" -> ForkJoinPool.commonPool().asCoroutineDispatcher()
dispatcher == "scheduler" -> {
- ExperimentalCoroutineDispatcher(CORES_COUNT).also { closeable = it }
+ Dispatchers.Default
}
dispatcher.startsWith("ftp") -> {
newFixedThreadPoolContext(dispatcher.substring(4).toInt(), dispatcher).also { closeable = it }
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt
index 40ddc8ec36..9e1bfc43bb 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt
@@ -6,13 +6,10 @@ package benchmarks
import benchmarks.common.*
import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.scheduling.ExperimentalCoroutineDispatcher
-import kotlinx.coroutines.sync.Semaphore
-import kotlinx.coroutines.sync.withPermit
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.sync.*
import org.openjdk.jmh.annotations.*
-import java.util.concurrent.ForkJoinPool
-import java.util.concurrent.TimeUnit
+import java.util.concurrent.*
@Warmup(iterations = 3, time = 500, timeUnit = TimeUnit.MICROSECONDS)
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MICROSECONDS)
@@ -84,7 +81,7 @@ open class SemaphoreBenchmark {
enum class SemaphoreBenchDispatcherCreator(val create: (parallelism: Int) -> CoroutineDispatcher) {
FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }),
- EXPERIMENTAL({ parallelism -> ExperimentalCoroutineDispatcher(corePoolSize = parallelism, maxPoolSize = parallelism) })
+ EXPERIMENTAL({ parallelism -> Dispatchers.Default }) // TODO doesn't take parallelism into account
}
private const val WORK_INSIDE = 80
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt
index e7bd1f5fb9..acfb3f3c6d 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt
@@ -30,7 +30,7 @@ open class SequencePlaysScrabble : ShakespearePlaysScrabble() {
val bonusForDoubleLetter: (String) -> Int = { word: String ->
toBeMaxed(word)
.map { letterScores[it - 'a'.toInt()] }
- .max()!!
+ .maxOrNull()!!
}
val score3: (String) -> Int = { word: String ->
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongWithBlockingContext.kt b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongWithBlockingContext.kt
index a6f0a473c1..d874f3bbe1 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongWithBlockingContext.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongWithBlockingContext.kt
@@ -27,10 +27,8 @@ import kotlin.coroutines.*
@State(Scope.Benchmark)
open class PingPongWithBlockingContext {
- @UseExperimental(InternalCoroutinesApi::class)
- private val experimental = ExperimentalCoroutineDispatcher(8)
- @UseExperimental(InternalCoroutinesApi::class)
- private val blocking = experimental.blocking(8)
+ private val experimental = Dispatchers.Default
+ private val blocking = Dispatchers.IO.limitedParallelism(8)
private val threadPool = newFixedThreadPoolContext(8, "PongCtx")
@TearDown
diff --git a/build.gradle b/build.gradle
index e4b12ff3ad..1bec7a3700 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,21 +2,16 @@
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+
+import org.jetbrains.kotlin.config.KotlinCompilerVersion
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.konan.target.HostManager
-import org.gradle.util.VersionNumber
import org.jetbrains.dokka.gradle.DokkaTaskPartial
-import org.jetbrains.dokka.gradle.DokkaMultiModuleTask
-apply plugin: 'jdk-convention'
-apply from: rootProject.file("gradle/opt-in.gradle")
+import static Projects.*
-def coreModule = "kotlinx-coroutines-core"
-// Not applicable for Kotlin plugin
-def sourceless = ['kotlinx.coroutines', 'kotlinx-coroutines-bom', 'integration-testing']
-def internal = ['kotlinx.coroutines', 'benchmarks', 'integration-testing']
-// Not published
-def unpublished = internal + ['example-frontend-js', 'android-unit-tests']
+apply plugin: 'jdk-convention'
buildscript {
/*
@@ -47,12 +42,6 @@ buildscript {
}
}
- if (using_snapshot_version) {
- repositories {
- mavenLocal()
- }
- }
-
repositories {
mavenCentral()
maven { url "https://plugins.gradle.org/m2/" }
@@ -67,6 +56,7 @@ buildscript {
classpath "org.jetbrains.kotlinx:kotlinx-knit:$knit_version"
classpath "com.moowork.gradle:gradle-node-plugin:$gradle_node_version"
classpath "org.jetbrains.kotlinx:binary-compatibility-validator:$binary_compatibility_validator_version"
+ classpath "ru.vyarus:gradle-animalsniffer-plugin:1.5.3" // Android API check
// JMH plugins
classpath "com.github.jengelman.gradle.plugins:shadow:5.1.0"
@@ -102,13 +92,6 @@ allprojects {
kotlin_version = rootProject.properties['kotlin_snapshot_version']
}
- if (using_snapshot_version) {
- repositories {
- mavenLocal()
- maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
- }
- }
-
ext.unpublished = unpublished
// This project property is set during nightly stress test
@@ -121,11 +104,13 @@ allprojects {
}
apply plugin: "binary-compatibility-validator"
+apply plugin: 'base'
+
apiValidation {
ignoredProjects += unpublished + ["kotlinx-coroutines-bom"]
if (build_snapshot_train) {
ignoredProjects.remove("example-frontend-js")
- ignoredProjects.add("kotlinx-coroutines-core")
+ ignoredProjects.add(coreModule)
}
ignoredPackages += "kotlinx.coroutines.internal"
}
@@ -139,30 +124,51 @@ allprojects {
*/
google()
mavenCentral()
+ maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
}
}
// Add dependency to core source sets. Core is configured in kx-core/build.gradle
configure(subprojects.findAll { !sourceless.contains(it.name) && it.name != coreModule }) {
evaluationDependsOn(":$coreModule")
- def platform = PlatformKt.platformOf(it)
- apply plugin: "kotlin-${platform}-conventions"
- dependencies {
- // See comment below for rationale, it will be replaced with "project" dependency
- api project(":$coreModule")
- // the only way IDEA can resolve test classes
- testImplementation project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs
+ if (isMultiplatform(it)) {
+ apply plugin: "kotlin-multiplatform"
+ apply from: rootProject.file("gradle/compile-jvm-multiplatform.gradle")
+ apply from: rootProject.file("gradle/compile-common.gradle")
+
+ if (rootProject.ext["native_targets_enabled"] as Boolean) {
+ apply from: rootProject.file("gradle/compile-native-multiplatform.gradle")
+ }
+
+ apply from: rootProject.file("gradle/compile-js-multiplatform.gradle")
+ apply from: rootProject.file("gradle/publish-npm-js.gradle")
+ kotlin.sourceSets.commonMain.dependencies {
+ api project(":$coreModule")
+ }
+ kotlin.sourceSets.jvmTest.dependencies {
+ implementation project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs
+ }
+ } else {
+ def platform = PlatformKt.platformOf(it)
+ apply plugin: "kotlin-${platform}-conventions"
+ dependencies {
+ api project(":$coreModule")
+ // the only way IDEA can resolve test classes
+ testImplementation project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs
+ }
}
}
+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(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all {
- kotlinOptions.freeCompilerArgs += optInAnnotations.collect { "-Xopt-in=" + it }
+ 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"
@@ -189,7 +195,7 @@ if (build_snapshot_train) {
}
println "Manifest of kotlin-compiler-embeddable.jar for coroutines"
- configure(subprojects.findAll { it.name == "kotlinx-coroutines-core" }) {
+ configure(subprojects.findAll { it.name == coreModule }) {
configurations.matching { it.name == "kotlinCompilerClasspath" }.all {
resolvedConfiguration.getFiles().findAll { it.name.contains("kotlin-compiler-embeddable") }.each {
def manifest = zipTree(it).matching {
@@ -206,9 +212,8 @@ if (build_snapshot_train) {
// Redefine source sets because we are not using 'kotlin/main/fqn' folder convention
configure(subprojects.findAll {
- !sourceless.contains(it.name) &&
+ !sourceless.contains(it.name) && !isMultiplatform(it) &&
it.name != "benchmarks" &&
- it.name != coreModule &&
it.name != "example-frontend-js"
}) {
// Pure JS and pure MPP doesn't have this notion and are configured separately
@@ -245,10 +250,44 @@ configure(subprojects.findAll { !unpublished.contains(it.name) }) {
}
}
}
+
+ def thisProject = it
+ if (thisProject.name in sourceless) {
+ return
+ }
+
+ def versionFileTask = thisProject.tasks.register("versionFileTask") {
+ def name = thisProject.name.replace("-", "_")
+ def versionFile = thisProject.layout.buildDirectory.file("${name}.version")
+ it.outputs.file(versionFile)
+
+ it.doLast {
+ versionFile.get().asFile.text = version.toString()
+ }
+ }
+
+ List jarTasks
+ if (isMultiplatform(it)) {
+ jarTasks = ["jvmJar", "metadataJar"]
+ } else if (it.name == "kotlinx-coroutines-debug") {
+ // We shadow debug module instead of just packaging it
+ jarTasks = ["shadowJar"]
+ } else {
+ jarTasks = ["jar"]
+ }
+
+ for (name in jarTasks) {
+ thisProject.tasks.named(name, Jar) {
+ it.dependsOn versionFileTask
+ it.from(versionFileTask) {
+ into("META-INF")
+ }
+ }
+ }
}
// Report Kotlin compiler version when building project
-println("Using Kotlin compiler version: $org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION")
+println("Using Kotlin compiler version: $KotlinCompilerVersion.VERSION")
// --------------- Cache redirector ---------------
@@ -262,7 +301,7 @@ def publishTasks = getTasksByName("publish", true) + getTasksByName("publishNpm"
task deploy(dependsOn: publishTasks)
-apply plugin: 'base'
+apply plugin: 'animalsniffer-convention'
clean.dependsOn gradle.includedBuilds.collect { it.task(':clean') }
@@ -302,12 +341,12 @@ allprojects { subProject ->
.matching {
// Excluding substituted project itself because of circular dependencies, but still do it
// for "*Test*" configurations
- subProject.name != "kotlinx-coroutines-core" || it.name.contains("Test")
+ subProject.name != coreModule || it.name.contains("Test")
}
.configureEach { conf ->
conf.resolutionStrategy.dependencySubstitution {
- substitute(module("org.jetbrains.kotlinx:kotlinx-coroutines-core"))
- .using(project(":kotlinx-coroutines-core"))
+ substitute(module("org.jetbrains.kotlinx:$coreModule"))
+ .using(project(":$coreModule"))
.because("Because Kotlin compiler embeddable leaks coroutines into the runtime classpath, " +
"triggering all sort of incompatible class changes errors")
}
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index c54e226af1..c2808c004a 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -46,4 +46,5 @@ dependencies {
implementation(kotlin("gradle-plugin", version("kotlin")))
implementation("org.jetbrains.dokka:dokka-gradle-plugin:${version("dokka")}")
implementation("org.jetbrains.dokka:dokka-core:${version("dokka")}")
+ implementation("ru.vyarus:gradle-animalsniffer-plugin:1.5.3") // Android API check
}
diff --git a/buildSrc/src/main/kotlin/OptInPreset.kt b/buildSrc/src/main/kotlin/OptInPreset.kt
new file mode 100644
index 0000000000..fdcdb8ecf8
--- /dev/null
+++ b/buildSrc/src/main/kotlin/OptInPreset.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:JvmName("OptInPreset")
+
+val optInAnnotations = listOf(
+ "kotlin.RequiresOptIn",
+ "kotlin.experimental.ExperimentalTypeInference",
+ "kotlin.ExperimentalMultiplatform",
+ "kotlinx.coroutines.DelicateCoroutinesApi",
+ "kotlinx.coroutines.ExperimentalCoroutinesApi",
+ "kotlinx.coroutines.ObsoleteCoroutinesApi",
+ "kotlinx.coroutines.InternalCoroutinesApi",
+ "kotlinx.coroutines.FlowPreview")
diff --git a/buildSrc/src/main/kotlin/Projects.kt b/buildSrc/src/main/kotlin/Projects.kt
index dd284b6132..d19e00ca46 100644
--- a/buildSrc/src/main/kotlin/Projects.kt
+++ b/buildSrc/src/main/kotlin/Projects.kt
@@ -1,8 +1,31 @@
/*
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-
-import org.gradle.api.Project
+@file:JvmName("Projects")
+import org.gradle.api.*
fun Project.version(target: String): String =
property("${target}_version") as String
+
+val coreModule = "kotlinx-coroutines-core"
+val testModule = "kotlinx-coroutines-test"
+
+val multiplatform = setOf(coreModule, testModule)
+// Not applicable for Kotlin plugin
+val sourceless = setOf("kotlinx.coroutines", "kotlinx-coroutines-bom", "integration-testing")
+val internal = setOf("kotlinx.coroutines", "benchmarks", "integration-testing")
+// Not published
+val unpublished = internal + setOf("example-frontend-js", "android-unit-tests")
+
+val Project.isMultiplatform: Boolean get() = name in multiplatform
+
+// Projects that we do not check for Android API level 14 check due to various limitations
+val androidNonCompatibleProjects = setOf(
+ "kotlinx-coroutines-debug",
+ "kotlinx-coroutines-swing",
+ "kotlinx-coroutines-javafx",
+ "kotlinx-coroutines-jdk8",
+ "kotlinx-coroutines-jdk9",
+ "kotlinx-coroutines-reactor",
+ "kotlinx-coroutines-test"
+)
diff --git a/buildSrc/src/main/kotlin/SourceSets.kt b/buildSrc/src/main/kotlin/SourceSets.kt
new file mode 100644
index 0000000000..3ad1dd4dcc
--- /dev/null
+++ b/buildSrc/src/main/kotlin/SourceSets.kt
@@ -0,0 +1,19 @@
+/*
+ * 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.*
+
+fun KotlinSourceSet.configureMultiplatform() {
+ val srcDir = if (name.endsWith("Main")) "src" else "test"
+ val platform = name.dropLast(4)
+ kotlin.srcDir("$platform/$srcDir")
+ if (name == "jvmMain") {
+ resources.srcDir("$platform/resources")
+ } else if (name == "jvmTest") {
+ resources.srcDir("$platform/test-resources")
+ }
+ languageSettings {
+ optInAnnotations.forEach { optIn(it) }
+ progressiveMode = true
+ }
+}
diff --git a/buildSrc/src/main/kotlin/animalsniffer-convention.gradle.kts b/buildSrc/src/main/kotlin/animalsniffer-convention.gradle.kts
new file mode 100644
index 0000000000..32b4931e57
--- /dev/null
+++ b/buildSrc/src/main/kotlin/animalsniffer-convention.gradle.kts
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+import ru.vyarus.gradle.plugin.animalsniffer.*
+
+subprojects {
+ // Skip JDK 8 projects or unpublished ones
+ if (!shouldSniff()) return@subprojects
+ apply(plugin = "ru.vyarus.animalsniffer")
+ configure {
+ sourceSets = listOf((project.extensions.getByName("sourceSets") as SourceSetContainer).getByName("main"))
+ }
+ val signature: Configuration by configurations
+ dependencies {
+ signature("net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature")
+ signature("org.codehaus.mojo.signature:java17:1.0@signature")
+ }
+}
+
+fun Project.shouldSniff(): Boolean {
+ // Skip all non-JVM projects
+ if (platformOf(project) != "jvm") return false
+ val name = project.name
+ if (name in unpublished || name in sourceless || name in androidNonCompatibleProjects) return false
+ return true
+}
diff --git a/buildSrc/src/main/kotlin/bom-conventions.gradle.kts b/buildSrc/src/main/kotlin/bom-conventions.gradle.kts
new file mode 100644
index 0000000000..45f30edff1
--- /dev/null
+++ b/buildSrc/src/main/kotlin/bom-conventions.gradle.kts
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+import org.gradle.kotlin.dsl.*
+import org.jetbrains.kotlin.gradle.dsl.*
+
+
+configure(subprojects.filter { it.name !in unpublished }) {
+ if (name == "kotlinx-coroutines-bom" || name == "kotlinx.coroutines") return@configure
+ if (isMultiplatform) {
+ kotlinExtension.sourceSets.getByName("jvmMain").dependencies {
+ api(project.dependencies.platform(project(":kotlinx-coroutines-bom")))
+ }
+ } else {
+ dependencies {
+ "api"(platform(project(":kotlinx-coroutines-bom")))
+ }
+ }
+}
diff --git a/buildSrc/src/main/kotlin/kotlin-jvm-conventions.gradle.kts b/buildSrc/src/main/kotlin/kotlin-jvm-conventions.gradle.kts
index c7744f8702..90847f4567 100644
--- a/buildSrc/src/main/kotlin/kotlin-jvm-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/kotlin-jvm-conventions.gradle.kts
@@ -11,8 +11,8 @@ plugins {
}
java {
- sourceCompatibility = JavaVersion.VERSION_1_6
- targetCompatibility = JavaVersion.VERSION_1_6
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
diff --git a/coroutines-guide.md b/coroutines-guide.md
index 3b4707cf83..3cc035ae6a 100644
--- a/coroutines-guide.md
+++ b/coroutines-guide.md
@@ -1,14 +1,3 @@
The main coroutines guide has moved to the [docs folder](docs/topics/coroutines-guide.md) and split up into smaller documents.
-## Table of contents
-
-
-
-
-
-
-
-
-
-
-
+It is recommended to read the guide on the [kotlinlang website](https://kotlinlang.org/docs/coroutines-guide.html), with proper HTML formatting and runnable samples.
diff --git a/gradle.properties b/gradle.properties
index 26e5147c51..1d64c83611 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,14 +3,14 @@
#
# Kotlin
-version=1.5.2-SNAPSHOT
+version=1.6.0-RC-SNAPSHOT
group=org.jetbrains.kotlinx
-kotlin_version=1.5.30
+kotlin_version=1.6.0
# Dependencies
junit_version=4.12
junit5_version=5.7.0
-atomicfu_version=0.16.3
+atomicfu_version=0.17.0
knit_version=0.3.0
html_version=0.7.2
lincheck_version=2.14
@@ -22,9 +22,9 @@ 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.7.0
+binary_compatibility_validator_version=0.8.0-RC
blockhound_version=1.0.2.RELEASE
-jna_version=5.5.0
+jna_version=5.9.0
# Android versions
android_version=4.1.1.4
@@ -53,7 +53,7 @@ jekyll_version=4.0
# JS IR backend sometimes crashes with out-of-memory
# TODO: Remove once KT-37187 is fixed
-org.gradle.jvmargs=-Xmx4g
+org.gradle.jvmargs=-Xmx3g
kotlin.mpp.enableCompatibilityMetadataVariant=true
kotlin.mpp.stability.nowarn=true
diff --git a/gradle/compile-jvm-multiplatform.gradle b/gradle/compile-jvm-multiplatform.gradle
index 5e65042746..88b717976d 100644
--- a/gradle/compile-jvm-multiplatform.gradle
+++ b/gradle/compile-jvm-multiplatform.gradle
@@ -2,12 +2,16 @@
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-sourceCompatibility = 1.6
-targetCompatibility = 1.6
+sourceCompatibility = 1.8
+targetCompatibility = 1.8
kotlin {
jvm {}
sourceSets {
+ jvmMain.dependencies {
+ compileOnly "org.codehaus.mojo:animal-sniffer-annotations:1.20"
+ }
+
jvmTest.dependencies {
api "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
// Workaround to make addSuppressed work in tests
diff --git a/gradle/dokka.gradle.kts b/gradle/dokka.gradle.kts
index 659890a30b..a4926f7e61 100644
--- a/gradle/dokka.gradle.kts
+++ b/gradle/dokka.gradle.kts
@@ -37,7 +37,7 @@ tasks.withType(DokkaTaskPartial::class).configureEach {
packageListUrl.set(rootProject.projectDir.toPath().resolve("site/stdlib.package.list").toUri().toURL())
}
- if (project.name != "kotlinx-coroutines-core") {
+ if (project.name != "kotlinx-coroutines-core" && project.name != "kotlinx-coroutines-test") {
dependsOn(project.configurations["compileClasspath"])
doFirst {
// resolve classpath only during execution
diff --git a/gradle/opt-in.gradle b/gradle/opt-in.gradle
deleted file mode 100644
index 22f022dbb5..0000000000
--- a/gradle/opt-in.gradle
+++ /dev/null
@@ -1,13 +0,0 @@
-/*
- * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-ext.optInAnnotations = [
- "kotlin.RequiresOptIn",
- "kotlin.experimental.ExperimentalTypeInference",
- "kotlin.ExperimentalMultiplatform",
- "kotlinx.coroutines.DelicateCoroutinesApi",
- "kotlinx.coroutines.ExperimentalCoroutinesApi",
- "kotlinx.coroutines.ObsoleteCoroutinesApi",
- "kotlinx.coroutines.InternalCoroutinesApi",
- "kotlinx.coroutines.FlowPreview"]
diff --git a/gradle/publish.gradle b/gradle/publish.gradle
index 3a0a4224ab..fa2bbb8544 100644
--- a/gradle/publish.gradle
+++ b/gradle/publish.gradle
@@ -12,7 +12,7 @@ apply plugin: 'signing'
// ------------- tasks
-def isMultiplatform = project.name == "kotlinx-coroutines-core"
+def isMultiplatform = project.name == "kotlinx-coroutines-core" || project.name == "kotlinx-coroutines-test"
def isBom = project.name == "kotlinx-coroutines-bom"
if (!isBom) {
diff --git a/integration-testing/build.gradle b/integration-testing/build.gradle
index 6efa3a14e6..aea4d45203 100644
--- a/integration-testing/build.gradle
+++ b/integration-testing/build.gradle
@@ -58,6 +58,7 @@ task npmTest(type: Test) {
}
task mavenTest(type: Test) {
+ environment "version", version
def sourceSet = sourceSets.mavenTest
dependsOn(project(':').getTasksByName("publishToMavenLocal", true))
testClassesDirs = sourceSet.output.classesDirs
@@ -81,6 +82,7 @@ task debugAgentTest(type: Test) {
jvmArgs ('-javaagent:' + project(':kotlinx-coroutines-debug').shadowJar.outputs.files.getFiles()[0])
testClassesDirs = sourceSet.output.classesDirs
classpath = sourceSet.runtimeClasspath
+ systemProperties project.properties.subMap(["overwrite.probes"])
}
task coreAgentTest(type: Test) {
diff --git a/integration-testing/src/mavenTest/kotlin/MavenPublicationValidator.kt b/integration-testing/src/mavenTest/kotlin/MavenPublicationAtomicfuValidator.kt
similarity index 97%
rename from integration-testing/src/mavenTest/kotlin/MavenPublicationValidator.kt
rename to integration-testing/src/mavenTest/kotlin/MavenPublicationAtomicfuValidator.kt
index 39d6598b55..dbb1921d80 100644
--- a/integration-testing/src/mavenTest/kotlin/MavenPublicationValidator.kt
+++ b/integration-testing/src/mavenTest/kotlin/MavenPublicationAtomicfuValidator.kt
@@ -8,7 +8,7 @@ import org.junit.*
import org.junit.Assert.assertTrue
import java.util.jar.*
-class MavenPublicationValidator {
+class MavenPublicationAtomicfuValidator {
private val ATOMIC_FU_REF = "Lkotlinx/atomicfu/".toByteArray()
@Test
diff --git a/integration-testing/src/mavenTest/kotlin/MavenPublicationVersionValidator.kt b/integration-testing/src/mavenTest/kotlin/MavenPublicationVersionValidator.kt
new file mode 100644
index 0000000000..da87d4cc59
--- /dev/null
+++ b/integration-testing/src/mavenTest/kotlin/MavenPublicationVersionValidator.kt
@@ -0,0 +1,40 @@
+/*
+ * 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.*
+import org.junit.Test
+import java.util.jar.*
+import kotlin.test.*
+
+class MavenPublicationVersionValidator {
+
+ @Test
+ fun testMppJar() {
+ val clazz = Class.forName("kotlinx.coroutines.Job")
+ JarFile(clazz.protectionDomain.codeSource.location.file).checkForVersion("kotlinx_coroutines_core.version")
+ }
+
+ @Test
+ fun testAndroidJar() {
+ val clazz = Class.forName("kotlinx.coroutines.android.HandlerDispatcher")
+ JarFile(clazz.protectionDomain.codeSource.location.file).checkForVersion("kotlinx_coroutines_android.version")
+ }
+
+ private fun JarFile.checkForVersion(file: String) {
+ val actualFile = "META-INF/$file"
+ val version = System.getenv("version")
+ use {
+ for (e in entries()) {
+ if (e.name == actualFile) {
+ val string = getInputStream(e).readAllBytes().decodeToString()
+ assertEquals(version, string)
+ return
+ }
+ }
+ error("File $file not found")
+ }
+ }
+}
diff --git a/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt b/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt
index 8f11e0a916..d214cc6b1a 100644
--- a/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt
+++ b/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt
@@ -14,7 +14,7 @@ import kotlin.coroutines.*
/**
* Starts [block] in a new coroutine and returns a [ListenableFuture] pointing to its result.
*
- * The coroutine is immediately started. Passing [CoroutineStart.LAZY] to [start] throws
+ * The coroutine is started immediately. Passing [CoroutineStart.LAZY] to [start] throws
* [IllegalArgumentException], because Futures don't have a way to start lazily.
*
* When the created coroutine [isCompleted][Job.isCompleted], it will try to
@@ -35,10 +35,12 @@ import kotlin.coroutines.*
* See [newCoroutineContext][CoroutineScope.newCoroutineContext] for a description of debugging
* facilities.
*
- * Note that the error and cancellation semantics of [future] are _subtly different_ than [asListenableFuture]'s.
- * In particular, any exception that happens in the coroutine after returned future is
- * successfully cancelled will be passed to the [CoroutineExceptionHandler] from the [context].
- * See [ListenableFutureCoroutine] for details.
+ * Note that the error and cancellation semantics of [future] are _different_ than [async]'s.
+ * In contrast to [Deferred], [Future] doesn't have an intermediate `Cancelling` state. If
+ * the returned `Future` is successfully cancelled, and `block` throws afterward, the thrown
+ * error is dropped, and getting the `Future`'s value will throw a `CancellationException` with
+ * no cause. This is to match the specification and behavior of
+ * `java.util.concurrent.FutureTask`.
*
* @param context added overlaying [CoroutineScope.coroutineContext] to form the new context.
* @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
@@ -241,8 +243,8 @@ public suspend fun ListenableFuture.await(): T {
return suspendCancellableCoroutine { cont: CancellableContinuation ->
addListener(
- ToContinuation(this, cont),
- MoreExecutors.directExecutor())
+ ToContinuation(this, cont),
+ MoreExecutors.directExecutor())
cont.invokeOnCancellation {
cancel(false)
}
@@ -284,16 +286,13 @@ private class ToContinuation(
* By documented contract, a [Future] has been cancelled if
* and only if its `isCancelled()` method returns true.
*
- * Any error that occurs after successfully cancelling a [ListenableFuture] will be passed
- * to the [CoroutineExceptionHandler] from the context. The contract of [Future] does not permit
- * it to return an error after it is successfully cancelled.
- *
- * By calling [asListenableFuture] on a [Deferred], any error that occurs after successfully
- * cancelling the [ListenableFuture] representation of the [Deferred] will _not_ be passed to
- * the [CoroutineExceptionHandler]. Cancelling a [Deferred] places that [Deferred] in the
- * cancelling/cancelled states defined by [Job], which _can_ show the error. It's assumed that
- * the [Deferred] pointing to the task will be used to observe any error outcome occurring after
- * cancellation.
+ * Any error that occurs after successfully cancelling a [ListenableFuture] is lost.
+ * The contract of [Future] does not permit it to return an error after it is successfully cancelled.
+ * On the other hand, we can't report an unhandled exception to [CoroutineExceptionHandler],
+ * otherwise [Future.cancel] can lead to an app crash which arguably is a contract violation.
+ * In contrast to [Future] which can't change its outcome after a successful cancellation,
+ * cancelling a [Deferred] places that [Deferred] in the cancelling/cancelled states defined by [Job],
+ * which _can_ show the error.
*
* This may be counterintuitive, but it maintains the error and cancellation contracts of both
* the [Deferred] and [ListenableFuture] types, while permitting both kinds of promise to point
@@ -312,10 +311,14 @@ private class ListenableFutureCoroutine(
}
override fun onCancelled(cause: Throwable, handled: Boolean) {
- if (!future.completeExceptionallyOrCancel(cause) && !handled) {
- // prevents loss of exception that was not handled by parent & could not be set to JobListenableFuture
- handleCoroutineException(context, cause)
- }
+ // Note: if future was cancelled in a race with a cancellation of this
+ // coroutine, and the future was successfully cancelled first, the cause of coroutine
+ // cancellation is dropped in this promise. A Future can only be completed once.
+ //
+ // This is consistent with FutureTask behaviour. A race between a Future.cancel() and
+ // a FutureTask.setException() for the same Future will similarly drop the
+ // cause of a failure-after-cancellation.
+ future.completeExceptionallyOrCancel(cause)
}
}
diff --git a/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt b/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt
index 69ba193071..511b1b0322 100644
--- a/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt
+++ b/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt
@@ -555,11 +555,7 @@ class ListenableFutureTest : TestBase() {
}
@Test
- fun testUnhandledExceptionOnExternalCancellation() = runTest(
- unhandled = listOf(
- { it -> it is TestException } // exception is unhandled because there is no parent
- )
- ) {
+ fun testUnhandledExceptionOnExternalCancellation() = runTest {
expect(1)
// No parent here (NonCancellable), so nowhere to propagate exception
val result = future(NonCancellable + Dispatchers.Unconfined) {
@@ -567,7 +563,7 @@ class ListenableFutureTest : TestBase() {
delay(Long.MAX_VALUE)
} finally {
expect(2)
- throw TestException() // this exception cannot be handled
+ throw TestException() // this exception cannot be handled and is set to be lost.
}
}
result.cancel(true)
@@ -708,23 +704,6 @@ class ListenableFutureTest : TestBase() {
assertEquals(testException, thrown.cause)
}
- @Test
- fun stressTestJobListenableFutureIsCancelledDoesNotThrow() = runTest {
- repeat(1000) {
- val deferred = CompletableDeferred()
- val asListenableFuture = deferred.asListenableFuture()
- // We heed two threads to test a race condition.
- withContext(Dispatchers.Default) {
- val cancellationJob = launch {
- asListenableFuture.cancel(false)
- }
- while (!cancellationJob.isCompleted) {
- asListenableFuture.isCancelled // Shouldn't throw.
- }
- }
- }
- }
-
private inline fun ListenableFuture<*>.checkFutureException() {
val e = assertFailsWith { get() }
val cause = e.cause!!
@@ -775,4 +754,61 @@ class ListenableFutureTest : TestBase() {
assertEquals(count, completed.get())
}
}
+
+ @Test
+ fun testFuturePropagatesExceptionToParentAfterCancellation() = runTest {
+ val throwLatch = CompletableDeferred()
+ val cancelLatch = CompletableDeferred()
+ val parent = Job()
+ val scope = CoroutineScope(parent)
+ val exception = TestException("propagated to parent")
+ val future = scope.future {
+ cancelLatch.complete(true)
+ withContext(NonCancellable) {
+ throwLatch.await()
+ throw exception
+ }
+ }
+ cancelLatch.await()
+ future.cancel(true)
+ throwLatch.complete(true)
+ parent.join()
+ assertTrue(parent.isCancelled)
+ assertEquals(exception, parent.getCancellationException().cause)
+ }
+
+ // Stress tests.
+
+ @Test
+ fun testFutureDoesNotReportToCoroutineExceptionHandler() = runTest {
+ repeat(1000) {
+ supervisorScope { // Don't propagate failures in children to parent and other children.
+ val innerFuture = SettableFuture.create()
+ val outerFuture = async { innerFuture.await() }
+
+ withContext(Dispatchers.Default) {
+ launch { innerFuture.setException(TestException("can be lost")) }
+ launch { outerFuture.cancel() }
+ // nothing should be reported to CoroutineExceptionHandler, otherwise `Future.cancel` contract violation.
+ }
+ }
+ }
+ }
+
+ @Test
+ fun testJobListenableFutureIsCancelledDoesNotThrow() = runTest {
+ repeat(1000) {
+ val deferred = CompletableDeferred()
+ val asListenableFuture = deferred.asListenableFuture()
+ // We heed two threads to test a race condition.
+ withContext(Dispatchers.Default) {
+ val cancellationJob = launch {
+ asListenableFuture.cancel(false)
+ }
+ while (!cancellationJob.isCompleted) {
+ asListenableFuture.isCancelled // Shouldn't throw.
+ }
+ }
+ }
+ }
}
diff --git a/integration/kotlinx-coroutines-play-services/README.md b/integration/kotlinx-coroutines-play-services/README.md
index e5e0e613b3..647dafd2c1 100644
--- a/integration/kotlinx-coroutines-play-services/README.md
+++ b/integration/kotlinx-coroutines-play-services/README.md
@@ -34,6 +34,12 @@ val currentLocationTask = fusedLocationProviderClient.getCurrentLocation(PRIORIT
val currentLocation = currentLocationTask.await(cancellationTokenSource) // cancelling `await` also cancels `currentLocationTask`, and vice versa
```
-[asDeferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/com.google.android.gms.tasks.-task/as-deferred.html
-[await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/com.google.android.gms.tasks.-task/await.html
-[asTask]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/kotlinx.coroutines.-deferred/as-task.html
+
+
+
+
+[asDeferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/as-deferred.html
+[await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/await.html
+[asTask]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/as-task.html
+
+
diff --git a/integration/kotlinx-coroutines-play-services/src/Tasks.kt b/integration/kotlinx-coroutines-play-services/src/Tasks.kt
index c37ac7a02d..0451d7beb8 100644
--- a/integration/kotlinx-coroutines-play-services/src/Tasks.kt
+++ b/integration/kotlinx-coroutines-play-services/src/Tasks.kt
@@ -8,6 +8,8 @@ package kotlinx.coroutines.tasks
import com.google.android.gms.tasks.*
import kotlinx.coroutines.*
+import java.lang.Runnable
+import java.util.concurrent.Executor
import kotlin.coroutines.*
/**
@@ -71,7 +73,8 @@ private fun Task.asDeferredImpl(cancellationTokenSource: CancellationToke
deferred.completeExceptionally(e)
}
} else {
- addOnCompleteListener {
+ // Run the callback directly to avoid unnecessarily scheduling on the main thread.
+ addOnCompleteListener(DirectExecutor) {
val e = it.exception
if (e == null) {
@Suppress("UNCHECKED_CAST")
@@ -114,7 +117,8 @@ public suspend fun Task.await(): T = awaitImpl(null)
* leads to an unspecified behaviour.
*/
@ExperimentalCoroutinesApi // Since 1.5.1, tentatively until 1.6.0
-public suspend fun Task.await(cancellationTokenSource: CancellationTokenSource): T = awaitImpl(cancellationTokenSource)
+public suspend fun Task.await(cancellationTokenSource: CancellationTokenSource): T =
+ awaitImpl(cancellationTokenSource)
private suspend fun Task.awaitImpl(cancellationTokenSource: CancellationTokenSource?): T {
// fast path
@@ -133,7 +137,8 @@ private suspend fun Task.awaitImpl(cancellationTokenSource: CancellationT
}
return suspendCancellableCoroutine { cont ->
- addOnCompleteListener {
+ // Run the callback directly to avoid unnecessarily scheduling on the main thread.
+ addOnCompleteListener(DirectExecutor) {
val e = it.exception
if (e == null) {
@Suppress("UNCHECKED_CAST")
@@ -150,3 +155,12 @@ private suspend fun Task.awaitImpl(cancellationTokenSource: CancellationT
}
}
}
+
+/**
+ * An [Executor] that just directly executes the [Runnable].
+ */
+private object DirectExecutor : Executor {
+ override fun execute(r: Runnable) {
+ r.run()
+ }
+}
diff --git a/integration/kotlinx-coroutines-play-services/test/FakeAndroid.kt b/integration/kotlinx-coroutines-play-services/test/FakeAndroid.kt
index 6026ffd75d..e286ee197b 100644
--- a/integration/kotlinx-coroutines-play-services/test/FakeAndroid.kt
+++ b/integration/kotlinx-coroutines-play-services/test/FakeAndroid.kt
@@ -2,10 +2,17 @@ package android.os
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
+import java.util.concurrent.*
class Handler(val looper: Looper) {
fun post(r: Runnable): Boolean {
- GlobalScope.launch { r.run() }
+ try {
+ GlobalScope.launch { r.run() }
+ } catch (e: RejectedExecutionException) {
+ // Execute leftover callbacks in place for tests
+ r.run()
+ }
+
return true
}
}
diff --git a/integration/kotlinx-coroutines-play-services/test/TaskTest.kt b/integration/kotlinx-coroutines-play-services/test/TaskTest.kt
index b125192e93..34fbe23b55 100644
--- a/integration/kotlinx-coroutines-play-services/test/TaskTest.kt
+++ b/integration/kotlinx-coroutines-play-services/test/TaskTest.kt
@@ -45,8 +45,8 @@ class TaskTest : TestBase() {
}
@Test
- fun testCancelledAsTask() {
- val deferred = GlobalScope.async {
+ fun testCancelledAsTask() = runTest {
+ val deferred = async(Dispatchers.Default) {
delay(100)
}.apply { cancel() }
@@ -60,8 +60,8 @@ class TaskTest : TestBase() {
}
@Test
- fun testThrowingAsTask() {
- val deferred = GlobalScope.async {
+ fun testThrowingAsTask() = runTest({ e -> e is TestException }) {
+ val deferred = async(Dispatchers.Default) {
throw TestException("Fail")
}
diff --git a/js/example-frontend-js/src/ExampleMain.kt b/js/example-frontend-js/src/ExampleMain.kt
index d4e530b04a..67c6ef04e7 100644
--- a/js/example-frontend-js/src/ExampleMain.kt
+++ b/js/example-frontend-js/src/ExampleMain.kt
@@ -8,7 +8,7 @@ import kotlinx.html.div
import kotlinx.html.dom.*
import kotlinx.html.js.onClickFunction
import org.w3c.dom.*
-import kotlin.browser.*
+import kotlinx.browser.*
import kotlin.coroutines.*
import kotlin.math.*
import kotlin.random.Random
diff --git a/kotlinx-coroutines-core/README.md b/kotlinx-coroutines-core/README.md
index c21e5048f6..c06cd358ad 100644
--- a/kotlinx-coroutines-core/README.md
+++ b/kotlinx-coroutines-core/README.md
@@ -57,7 +57,6 @@ helper function. [NonCancellable] job object is provided to suppress cancellatio
| [SendChannel][kotlinx.coroutines.channels.SendChannel] | [send][kotlinx.coroutines.channels.SendChannel.send] | [onSend][kotlinx.coroutines.channels.SendChannel.onSend] | [trySend][kotlinx.coroutines.channels.SendChannel.trySend]
| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receive][kotlinx.coroutines.channels.ReceiveChannel.receive] | [onReceive][kotlinx.coroutines.channels.ReceiveChannel.onReceive] | [tryReceive][kotlinx.coroutines.channels.ReceiveChannel.tryReceive]
| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receiveCatching][kotlinx.coroutines.channels.receiveCatching] | [onReceiveCatching][kotlinx.coroutines.channels.onReceiveCatching] | [tryReceive][kotlinx.coroutines.channels.ReceiveChannel.tryReceive]
-| [Mutex][kotlinx.coroutines.sync.Mutex] | [lock][kotlinx.coroutines.sync.Mutex.lock] | [onLock][kotlinx.coroutines.sync.Mutex.onLock] | [tryLock][kotlinx.coroutines.sync.Mutex.tryLock]
| none | [delay][kotlinx.coroutines.delay] | [onTimeout][kotlinx.coroutines.selects.SelectBuilder.onTimeout] | none
# Package kotlinx.coroutines
@@ -121,8 +120,6 @@ Obsolete and deprecated module to test coroutines. Replaced with `kotlinx-corout
[kotlinx.coroutines.sync.Mutex]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/index.html
[kotlinx.coroutines.sync.Mutex.lock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/lock.html
-[kotlinx.coroutines.sync.Mutex.onLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/on-lock.html
-[kotlinx.coroutines.sync.Mutex.tryLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/try-lock.html
diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api
index 50bfb60d62..ee4d8bfc09 100644
--- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api
+++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api
@@ -140,6 +140,17 @@ public final class kotlinx/coroutines/CompletionHandlerException : java/lang/Run
public fun (Ljava/lang/String;Ljava/lang/Throwable;)V
}
+public abstract interface class kotlinx/coroutines/CopyableThreadContextElement : kotlinx/coroutines/ThreadContextElement {
+ public abstract fun copyForChildCoroutine ()Lkotlinx/coroutines/CopyableThreadContextElement;
+}
+
+public final class kotlinx/coroutines/CopyableThreadContextElement$DefaultImpls {
+ public static fun fold (Lkotlinx/coroutines/CopyableThreadContextElement;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+ public static fun get (Lkotlinx/coroutines/CopyableThreadContextElement;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+ public static fun minusKey (Lkotlinx/coroutines/CopyableThreadContextElement;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+ public static fun plus (Lkotlinx/coroutines/CopyableThreadContextElement;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+}
+
public abstract interface class kotlinx/coroutines/CopyableThrowable {
public abstract fun createCopy ()Ljava/lang/Throwable;
}
@@ -156,6 +167,7 @@ public abstract class kotlinx/coroutines/CoroutineDispatcher : kotlin/coroutines
public fun get (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
public final fun interceptContinuation (Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
public fun isDispatchNeeded (Lkotlin/coroutines/CoroutineContext;)Z
+ public fun limitedParallelism (I)Lkotlinx/coroutines/CoroutineDispatcher;
public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
public final fun plus (Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineDispatcher;
public final fun releaseInterceptedContinuation (Lkotlin/coroutines/Continuation;)V
@@ -279,6 +291,7 @@ public final class kotlinx/coroutines/Dispatchers {
public static final fun getIO ()Lkotlinx/coroutines/CoroutineDispatcher;
public static final fun getMain ()Lkotlinx/coroutines/MainCoroutineDispatcher;
public static final fun getUnconfined ()Lkotlinx/coroutines/CoroutineDispatcher;
+ public final fun shutdown ()V
}
public final class kotlinx/coroutines/DispatchersKt {
@@ -367,7 +380,6 @@ public final class kotlinx/coroutines/Job$Key : kotlin/coroutines/CoroutineConte
}
public final class kotlinx/coroutines/JobKt {
- public static final fun DisposableHandle (Lkotlin/jvm/functions/Function0;)Lkotlinx/coroutines/DisposableHandle;
public static final fun Job (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/CompletableJob;
public static final synthetic fun Job (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job;
public static synthetic fun Job$default (Lkotlinx/coroutines/Job;ILjava/lang/Object;)Lkotlinx/coroutines/CompletableJob;
@@ -447,6 +459,7 @@ public class kotlinx/coroutines/JobSupport : kotlinx/coroutines/ChildJob, kotlin
public abstract class kotlinx/coroutines/MainCoroutineDispatcher : kotlinx/coroutines/CoroutineDispatcher {
public fun ()V
public abstract fun getImmediate ()Lkotlinx/coroutines/MainCoroutineDispatcher;
+ public fun limitedParallelism (I)Lkotlinx/coroutines/CoroutineDispatcher;
public fun toString ()Ljava/lang/String;
protected final fun toStringInternalImpl ()Ljava/lang/String;
}
@@ -543,6 +556,15 @@ public final class kotlinx/coroutines/TimeoutKt {
public static final fun withTimeoutOrNull-KLykuaI (JLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
+public final class kotlinx/coroutines/YieldContext : kotlin/coroutines/AbstractCoroutineContextElement {
+ public static final field Key Lkotlinx/coroutines/YieldContext$Key;
+ public field dispatcherWasUnconfined Z
+ public fun ()V
+}
+
+public final class kotlinx/coroutines/YieldContext$Key : kotlin/coroutines/CoroutineContext$Key {
+}
+
public final class kotlinx/coroutines/YieldKt {
public static final fun yield (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
@@ -887,8 +909,6 @@ public final class kotlinx/coroutines/flow/FlowKt {
public static final fun asFlow ([Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
public static final fun asSharedFlow (Lkotlinx/coroutines/flow/MutableSharedFlow;)Lkotlinx/coroutines/flow/SharedFlow;
public static final fun asStateFlow (Lkotlinx/coroutines/flow/MutableStateFlow;)Lkotlinx/coroutines/flow/StateFlow;
- public static final fun broadcastIn (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineStart;)Lkotlinx/coroutines/channels/BroadcastChannel;
- public static synthetic fun broadcastIn$default (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineStart;ILjava/lang/Object;)Lkotlinx/coroutines/channels/BroadcastChannel;
public static final synthetic fun buffer (Lkotlinx/coroutines/flow/Flow;I)Lkotlinx/coroutines/flow/Flow;
public static final fun buffer (Lkotlinx/coroutines/flow/Flow;ILkotlinx/coroutines/channels/BufferOverflow;)Lkotlinx/coroutines/flow/Flow;
public static synthetic fun buffer$default (Lkotlinx/coroutines/flow/Flow;IILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
@@ -900,6 +920,7 @@ public final class kotlinx/coroutines/flow/FlowKt {
public static final fun channelFlow (Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
public static final fun collect (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun collect (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final fun collect (Lkotlinx/coroutines/flow/SharedFlow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun collectIndexed (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun collectLatest (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun combine (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
@@ -958,10 +979,6 @@ public final class kotlinx/coroutines/flow/FlowKt {
public static final fun flowOf (Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
public static final fun flowOf ([Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
public static final fun flowOn (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/flow/Flow;
- public static final fun flowViaChannel (ILkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
- public static synthetic fun flowViaChannel$default (ILkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
- public static final fun flowWith (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
- public static synthetic fun flowWith$default (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
public static final fun fold (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun forEach (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)V
public static final fun getDEFAULT_CONCURRENCY ()I
@@ -978,8 +995,6 @@ public final class kotlinx/coroutines/flow/FlowKt {
public static final fun onCompletion (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
public static final fun onEach (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
public static final fun onEmpty (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
- public static final fun onErrorCollect (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
- public static synthetic fun onErrorCollect$default (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
public static final fun onErrorResume (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
public static final fun onErrorResumeNext (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
public static final fun onErrorReturn (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
@@ -995,9 +1010,7 @@ public final class kotlinx/coroutines/flow/FlowKt {
public static final fun reduce (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun replay (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
public static final fun replay (Lkotlinx/coroutines/flow/Flow;I)Lkotlinx/coroutines/flow/Flow;
- public static final synthetic fun retry (Lkotlinx/coroutines/flow/Flow;ILkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
public static final fun retry (Lkotlinx/coroutines/flow/Flow;JLkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
- public static synthetic fun retry$default (Lkotlinx/coroutines/flow/Flow;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
public static synthetic fun retry$default (Lkotlinx/coroutines/flow/Flow;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
public static final fun retryWhen (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function4;)Lkotlinx/coroutines/flow/Flow;
public static final fun runningFold (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
@@ -1061,6 +1074,7 @@ public abstract interface class kotlinx/coroutines/flow/MutableStateFlow : kotli
}
public abstract interface class kotlinx/coroutines/flow/SharedFlow : kotlinx/coroutines/flow/Flow {
+ public abstract fun collect (Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun getReplayCache ()Ljava/util/List;
}
diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle
index c45ca08cef..0d4d708962 100644
--- a/kotlinx-coroutines-core/build.gradle
+++ b/kotlinx-coroutines-core/build.gradle
@@ -70,39 +70,65 @@ if (rootProject.ext.native_targets_enabled) {
* because JMV-only projects depend on core, thus core should always be initialized before configuration.
*/
kotlin {
- configure(sourceSets) {
- def srcDir = name.endsWith('Main') ? 'src' : 'test'
- def platform = name[0..-5]
- kotlin.srcDirs = ["$platform/$srcDir"]
- if (name == "jvmMain") {
- resources.srcDirs = ["$platform/resources"]
- } else if (name == "jvmTest") {
- resources.srcDirs = ["$platform/test-resources"]
+ sourceSets.forEach {
+ SourceSetsKt.configureMultiplatform(it)
+ }
+
+ /*
+ * 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)
+ *
+ * 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 {
+ // 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"]
}
- languageSettings {
- progressiveMode = true
- optInAnnotations.each { useExperimentalAnnotation(it) }
+
+ 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" }
+ }
}
- }
- configure(targets) {
- // Configure additional binaries and test runs -- one for each OS
- if (["macos", "linux", "mingw"].any { name.startsWith(it) }) {
- binaries {
- // 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"]
- // Configure a separate test where code runs in background
- test("background", [org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType.DEBUG]) {
- freeCompilerArgs += ["-e", "kotlinx.coroutines.mainBackground"]
- }
+ binaries.test("worker", [DEBUG]) {
+ def thisTest = it
+ freeCompilerArgs += ["-e", "kotlinx.coroutines.mainBackground"]
+ testRuns.create("worker") {
+ setExecutionSourceFrom(thisTest)
+ executionTask.configure { targetName = "$targetName worker" }
}
- testRuns {
- background { setExecutionSourceFrom(binaries.backgroundDebugTest) }
+ }
+
+ binaries.test("workerWithNewMM", [DEBUG]) {
+ def thisTest = it
+ optimized = true
+ freeCompilerArgs += ["-e", "kotlinx.coroutines.mainBackground"]
+ binaryOptions["memoryModel"] = "experimental"
+ testRuns.create("workerWithNewMM") {
+ setExecutionSourceFrom(thisTest)
+ executionTask.configure { targetName = "$targetName worker with new MM" }
}
}
}
+
+ jvm {
+ // For animal sniffer
+ withJava()
+ }
}
+
configurations {
configureKotlinJvmPlatform(kotlinCompilerPluginClasspath)
}
@@ -175,12 +201,6 @@ task checkJdk16() {
}
}
-tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
- kotlinOptions.jdkHome = System.env.JDK_16
- // only fail when actually trying to compile, not during project setup phase
- dependsOn(checkJdk16)
-}
-
jvmTest {
minHeapSize = '1g'
maxHeapSize = '1g'
@@ -246,7 +266,7 @@ task jvmLincheckTest(type: Test, dependsOn: compileTestKotlinJvm) {
static void configureJvmForLincheck(task) {
task.minHeapSize = '1g'
- task.maxHeapSize = '6g' // we may need more space for building an interleaving tree in the model checking mode
+ 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'
@@ -257,7 +277,6 @@ task jdk16Test(type: Test, dependsOn: [compileTestKotlinJvm, checkJdk16]) {
classpath = files { jvmTest.classpath }
testClassesDirs = files { jvmTest.testClassesDirs }
executable = "$System.env.JDK_16/bin/java"
- exclude '**/*LFStressTest.*' // lock-freedom tests use LockFreedomTestEnvironment which needs JDK8
exclude '**/*LincheckTest.*' // Lincheck tests use LinChecker which needs JDK8
exclude '**/exceptions/**' // exceptions tests check suppressed exception which needs JDK8
exclude '**/ExceptionsGuideTest.*'
diff --git a/kotlinx-coroutines-core/common/README.md b/kotlinx-coroutines-core/common/README.md
index fcfe334c62..b09c44c75e 100644
--- a/kotlinx-coroutines-core/common/README.md
+++ b/kotlinx-coroutines-core/common/README.md
@@ -60,17 +60,12 @@ helper function. [NonCancellable] job object is provided to suppress cancellatio
| [SendChannel][kotlinx.coroutines.channels.SendChannel] | [send][kotlinx.coroutines.channels.SendChannel.send] | [onSend][kotlinx.coroutines.channels.SendChannel.onSend] | [trySend][kotlinx.coroutines.channels.SendChannel.trySend]
| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receive][kotlinx.coroutines.channels.ReceiveChannel.receive] | [onReceive][kotlinx.coroutines.channels.ReceiveChannel.onReceive] | [tryReceive][kotlinx.coroutines.channels.ReceiveChannel.tryReceive]
| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receiveCatching][kotlinx.coroutines.channels.ReceiveChannel.receiveCatching] | [onReceiveCatching][kotlinx.coroutines.channels.ReceiveChannel.onReceiveCatching] | [tryReceive][kotlinx.coroutines.channels.ReceiveChannel.tryReceive]
-| [Mutex][kotlinx.coroutines.sync.Mutex] | [lock][kotlinx.coroutines.sync.Mutex.lock] | [onLock][kotlinx.coroutines.sync.Mutex.onLock] | [tryLock][kotlinx.coroutines.sync.Mutex.tryLock]
| none | [delay] | [onTimeout][kotlinx.coroutines.selects.SelectBuilder.onTimeout] | none
This module provides debugging facilities for coroutines (run JVM with `-ea` or `-Dkotlinx.coroutines.debug` options)
and [newCoroutineContext] function to write user-defined coroutine builders that work with these
debugging facilities. See [DEBUG_PROPERTY_NAME] for more details.
-This module provides a special CoroutineContext type [TestCoroutineCoroutineContext][kotlinx.coroutines.test.TestCoroutineContext] that
-allows the writer of code that contains Coroutines with delays and timeouts to write non-flaky unit-tests for that code allowing these tests to
-terminate in near zero time. See the documentation for this class for more information.
-
# Package kotlinx.coroutines
General-purpose coroutine builders, contexts, and helper functions.
@@ -131,8 +126,6 @@ Low-level primitives for finer-grained control of coroutines.
[kotlinx.coroutines.sync.Mutex]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/index.html
[kotlinx.coroutines.sync.Mutex.lock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/lock.html
-[kotlinx.coroutines.sync.Mutex.onLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/on-lock.html
-[kotlinx.coroutines.sync.Mutex.tryLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/try-lock.html
@@ -157,8 +150,4 @@ Low-level primitives for finer-grained control of coroutines.
[kotlinx.coroutines.selects.select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
[kotlinx.coroutines.selects.SelectBuilder.onTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/on-timeout.html
-
-
-[kotlinx.coroutines.test.TestCoroutineContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.test/-test-coroutine-context/index.html
-
diff --git a/kotlinx-coroutines-core/common/src/Annotations.kt b/kotlinx-coroutines-core/common/src/Annotations.kt
index 724cc8cb87..bacce39408 100644
--- a/kotlinx-coroutines-core/common/src/Annotations.kt
+++ b/kotlinx-coroutines-core/common/src/Annotations.kt
@@ -30,6 +30,19 @@ public annotation class DelicateCoroutinesApi
*/
@MustBeDocumented
@Retention(value = AnnotationRetention.BINARY)
+@Target(
+ AnnotationTarget.CLASS,
+ AnnotationTarget.ANNOTATION_CLASS,
+ AnnotationTarget.PROPERTY,
+ AnnotationTarget.FIELD,
+ AnnotationTarget.LOCAL_VARIABLE,
+ AnnotationTarget.VALUE_PARAMETER,
+ AnnotationTarget.CONSTRUCTOR,
+ AnnotationTarget.FUNCTION,
+ AnnotationTarget.PROPERTY_GETTER,
+ AnnotationTarget.PROPERTY_SETTER,
+ AnnotationTarget.TYPEALIAS
+)
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
public annotation class ExperimentalCoroutinesApi
diff --git a/kotlinx-coroutines-core/common/src/CloseableCoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/CloseableCoroutineDispatcher.kt
new file mode 100644
index 0000000000..9c6703291a
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/CloseableCoroutineDispatcher.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+/**
+ * [CoroutineDispatcher] that provides a method to close it,
+ * causing the rejection of any new tasks and cleanup of all underlying resources
+ * associated with the current dispatcher.
+ * Examples of closeable dispatchers are dispatchers backed by `java.lang.Executor` and
+ * by `kotlin.native.Worker`.
+ *
+ * **The `CloseableCoroutineDispatcher` class is not stable for inheritance in 3rd party libraries**, as new methods
+ * might be added to this interface in the future, but is stable for use.
+ */
+@ExperimentalCoroutinesApi
+public expect abstract class CloseableCoroutineDispatcher() : CoroutineDispatcher {
+
+ /**
+ * 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.
+ *
+ * Invocations of `close` are idempotent and thread-safe.
+ */
+ public abstract fun close()
+}
diff --git a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt
index 68b4b1a393..da094e152d 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt
@@ -12,8 +12,7 @@ import kotlin.coroutines.*
*/
public expect fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext
-internal expect fun createDefaultDispatcher(): CoroutineDispatcher
-
+@PublishedApi
@Suppress("PropertyName")
internal expect val DefaultDelay: Delay
diff --git a/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
index d5613d4110..c91e944b91 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
@@ -61,6 +61,45 @@ public abstract class CoroutineDispatcher :
*/
public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true
+ /**
+ * Creates a view of the current dispatcher that limits the parallelism to the given [value][parallelism].
+ * The resulting view uses the original dispatcher for execution, but with the guarantee that
+ * no more than [parallelism] coroutines are executed at the same time.
+ *
+ * This method does not impose restrictions on the number of views or the total sum of parallelism values,
+ * each view controls its own parallelism independently with the guarantee that the effective parallelism
+ * of all views cannot exceed the actual parallelism of the original dispatcher.
+ *
+ * ### Limitations
+ *
+ * The default implementation of `limitedParallelism` does not support direct dispatchers,
+ * such as executing the given runnable in place during [dispatch] calls.
+ * Any dispatcher that may return `false` from [isDispatchNeeded] is considered direct.
+ * For direct dispatchers, it is recommended to override this method
+ * and provide a domain-specific implementation or to throw an [UnsupportedOperationException].
+ *
+ * ### Example of usage
+ * ```
+ * private val backgroundDispatcher = newFixedThreadPoolContext(4, "App Background")
+ * // At most 2 threads will be processing images as it is really slow and CPU-intensive
+ * private val imageProcessingDispatcher = backgroundDispatcher.limitedParallelism(2)
+ * // At most 3 threads will be processing JSON to avoid image processing starvation
+ * private val imageProcessingDispatcher = backgroundDispatcher.limitedParallelism(3)
+ * // At most 1 thread will be doing IO
+ * private val fileWriterDispatcher = backgroundDispatcher.limitedParallelism(1)
+ * ```
+ * is 6. Yet at most 4 coroutines can be executed simultaneously as each view limits only its own parallelism.
+ *
+ * Note that this example was structured in such a way that it illustrates the parallelism guarantees.
+ * In practice, it is usually better to use [Dispatchers.IO] or [Dispatchers.Default] instead of creating a
+ * `backgroundDispatcher`. It is both possible and advised to call `limitedParallelism` on them.
+ */
+ @ExperimentalCoroutinesApi
+ public open fun limitedParallelism(parallelism: Int): CoroutineDispatcher {
+ parallelism.checkParallelism()
+ return LimitedDispatcher(this, parallelism)
+ }
+
/**
* Dispatches execution of a runnable [block] onto another thread in the given [context].
* This method should guarantee that the given [block] will be eventually invoked,
diff --git a/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt b/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt
index 49923a92e7..819f205b17 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt
@@ -1,13 +1,19 @@
/*
* 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.jvm.*
internal expect fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable)
+/**
+ * JVM kludge: trigger loading of all the classes and service loading
+ * **before** any exception occur because it may be OOM, SOE or VerifyError
+ */
+internal expect fun initializeDefaultExceptionHandlers()
+
/**
* 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
diff --git a/kotlinx-coroutines-core/common/src/CoroutineScope.kt b/kotlinx-coroutines-core/common/src/CoroutineScope.kt
index 3ed233bfb9..21d2a6e000 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineScope.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineScope.kt
@@ -12,7 +12,7 @@ import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*
/**
- * Defines a scope for new coroutines. Every **coroutine builder** (like [launch], [async], etc)
+ * Defines a scope for new coroutines. Every **coroutine builder** (like [launch], [async], etc.)
* is an extension on [CoroutineScope] and inherits its [coroutineContext][CoroutineScope.coroutineContext]
* to automatically propagate all its elements and cancellation.
*
@@ -28,8 +28,8 @@ import kotlin.coroutines.intrinsics.*
* By convention, the [context of a scope][CoroutineScope.coroutineContext] should contain an instance of a
* [job][Job] to enforce the discipline of **structured concurrency** with propagation of cancellation.
*
- * Every coroutine builder (like [launch], [async], etc)
- * and every scoping function (like [coroutineScope], [withContext], etc) provides _its own_ scope
+ * Every coroutine builder (like [launch], [async], and others)
+ * and every scoping function (like [coroutineScope] and [withContext]) provides _its own_ scope
* with its own [Job] instance into the inner block of code it runs.
* By convention, they all wait for all the coroutines inside their block to complete before completing themselves,
* thus enforcing the structured concurrency. See [Job] documentation for more details.
@@ -269,8 +269,8 @@ public suspend fun coroutineScope(block: suspend CoroutineScope.() -> R): R
* Creates a [CoroutineScope] that wraps the given coroutine [context].
*
* If the given [context] does not contain a [Job] element, then a default `Job()` is created.
- * This way, cancellation or failure of any child coroutine in this scope cancels all the other children,
- * just like inside [coroutineScope] block.
+ * This way, failure of any child coroutine in this scope or [cancellation][CoroutineScope.cancel] of the scope itself
+ * cancels all the scope's children, just like inside [coroutineScope] block.
*/
@Suppress("FunctionName")
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
diff --git a/kotlinx-coroutines-core/common/src/Delay.kt b/kotlinx-coroutines-core/common/src/Delay.kt
index 4543c5dda1..d95dc52f9f 100644
--- a/kotlinx-coroutines-core/common/src/Delay.kt
+++ b/kotlinx-coroutines-core/common/src/Delay.kt
@@ -19,15 +19,12 @@ import kotlin.time.*
*/
@InternalCoroutinesApi
public interface Delay {
- /**
- * Delays coroutine for a given time without blocking a thread and resumes it after a specified time.
- *
- * This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, 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.
- */
+
+ /** @suppress **/
+ @Deprecated(
+ message = "Deprecated without replacement as an internal method never intended for public use",
+ level = DeprecationLevel.ERROR
+ ) // Error since 1.6.0
public suspend fun delay(time: Long) {
if (time <= 0) return // don't delay
return suspendCancellableCoroutine { scheduleResumeAfterDelay(time, it) }
@@ -54,8 +51,6 @@ public interface Delay {
* Schedules invocation of a specified [block] after a specified delay [timeMillis].
* The resulting [DisposableHandle] can be used to [dispose][DisposableHandle.dispose] of this invocation
* request if it is not needed anymore.
- *
- * This implementation uses a built-in single-threaded scheduled executor service.
*/
public fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle =
DefaultDelay.invokeOnTimeout(timeMillis, block, context)
diff --git a/kotlinx-coroutines-core/common/src/EventLoop.common.kt b/kotlinx-coroutines-core/common/src/EventLoop.common.kt
index e6a57c927a..f4f61b25d4 100644
--- a/kotlinx-coroutines-core/common/src/EventLoop.common.kt
+++ b/kotlinx-coroutines-core/common/src/EventLoop.common.kt
@@ -115,7 +115,12 @@ internal abstract class EventLoop : CoroutineDispatcher() {
}
}
- protected open fun shutdown() {}
+ final override fun limitedParallelism(parallelism: Int): CoroutineDispatcher {
+ parallelism.checkParallelism()
+ return this
+ }
+
+ open fun shutdown() {}
}
@ThreadLocal
@@ -271,7 +276,7 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay {
// then process one event from queue
val task = dequeue()
if (task != null) {
- task.run()
+ platformAutoreleasePool { task.run() }
return 0
}
return nextTime
@@ -279,7 +284,7 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay {
public final override fun dispatch(context: CoroutineContext, block: Runnable) = enqueue(block)
- public fun enqueue(task: Runnable) {
+ open fun enqueue(task: Runnable) {
if (enqueueImpl(task)) {
// todo: we should unpark only when this delayed task became first in the queue
unpark()
@@ -526,3 +531,13 @@ internal expect object DefaultExecutor {
public fun enqueue(task: Runnable)
}
+/**
+ * Used by Darwin targets to wrap a [Runnable.run] call in an Objective-C Autorelease Pool. It is a no-op on JVM, JS and
+ * non-Darwin native targets.
+ *
+ * Coroutines on Darwin targets can call into the Objective-C world, where a callee may push a to-be-returned object to
+ * the Autorelease Pool, so as to avoid a premature ARC release before it reaches the caller. This means the pool must
+ * be eventually drained to avoid leaks. Since Kotlin Coroutines does not use [NSRunLoop], which provides automatic
+ * pool management, it must manage the pool creation and pool drainage manually.
+ */
+internal expect inline fun platformAutoreleasePool(crossinline block: () -> Unit)
diff --git a/kotlinx-coroutines-core/common/src/Job.kt b/kotlinx-coroutines-core/common/src/Job.kt
index 9552153aa9..085ef7e8af 100644
--- a/kotlinx-coroutines-core/common/src/Job.kt
+++ b/kotlinx-coroutines-core/common/src/Job.kt
@@ -113,7 +113,13 @@ public interface Job : CoroutineContext.Element {
/**
* Key for [Job] instance in the coroutine context.
*/
- public companion object Key : CoroutineContext.Key
+ public companion object Key : CoroutineContext.Key {
+ init {
+ // `Job` will necessarily be accessed early, so this is as good a place as any for the
+ // initialization logic that we want to happen as soon as possible
+ initializeDefaultExceptionHandlers()
+ }
+ }
// ------------ state query ------------
@@ -387,7 +393,7 @@ public fun Job0(parent: Job? = null): Job = Job(parent)
/**
* A handle to an allocated object that can be disposed to make it eligible for garbage collection.
*/
-public interface DisposableHandle {
+public fun interface DisposableHandle {
/**
* Disposes the corresponding object, making it eligible for garbage collection.
* Repeated invocation of this function has no effect.
@@ -395,18 +401,6 @@ public interface DisposableHandle {
public fun dispose()
}
-/**
- * @suppress **This an internal API and should not be used from general code.**
- */
-@Suppress("FunctionName")
-@InternalCoroutinesApi
-public inline fun DisposableHandle(crossinline block: () -> Unit): DisposableHandle =
- object : DisposableHandle {
- override fun dispose() {
- block()
- }
- }
-
// -------------------- Parent-child communication --------------------
/**
diff --git a/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt
index 602da6e0b5..a7065ccd15 100644
--- a/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt
+++ b/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt
@@ -4,6 +4,8 @@
package kotlinx.coroutines
+import kotlinx.coroutines.internal.*
+
/**
* Base class for special [CoroutineDispatcher] which is confined to application "Main" or "UI" thread
* and used for any UI-based activities. Instance of `MainDispatcher` can be obtained by [Dispatchers.Main].
@@ -51,6 +53,12 @@ public abstract class MainCoroutineDispatcher : CoroutineDispatcher() {
*/
override fun toString(): String = toStringInternalImpl() ?: "$classSimpleName@$hexAddress"
+ override fun limitedParallelism(parallelism: Int): CoroutineDispatcher {
+ parallelism.checkParallelism()
+ // MainCoroutineDispatcher is single-threaded -- short-circuit any attempts to limit it
+ return this
+ }
+
/**
* Internal method for more specific [toString] implementations. It returns non-null
* string if this dispatcher is set in the platform as the main one.
diff --git a/kotlinx-coroutines-core/common/src/Unconfined.kt b/kotlinx-coroutines-core/common/src/Unconfined.kt
index 4f48645895..5837ae83f3 100644
--- a/kotlinx-coroutines-core/common/src/Unconfined.kt
+++ b/kotlinx-coroutines-core/common/src/Unconfined.kt
@@ -11,10 +11,16 @@ import kotlin.jvm.*
* A coroutine dispatcher that is not confined to any specific thread.
*/
internal object Unconfined : CoroutineDispatcher() {
+
+ @ExperimentalCoroutinesApi
+ override fun limitedParallelism(parallelism: Int): CoroutineDispatcher {
+ throw UnsupportedOperationException("limitedParallelism is not supported for Dispatchers.Unconfined")
+ }
+
override fun isDispatchNeeded(context: CoroutineContext): Boolean = false
override fun dispatch(context: CoroutineContext, block: Runnable) {
- // It can only be called by the "yield" function. See also code of "yield" function.
+ /** It can only be called by the [yield] function. See also code of [yield] function. */
val yieldContext = context[YieldContext]
if (yieldContext != null) {
// report to "yield" that it is an unconfined dispatcher and don't call "block.run()"
@@ -32,6 +38,7 @@ internal object Unconfined : CoroutineDispatcher() {
/**
* Used to detect calls to [Unconfined.dispatch] from [yield] function.
*/
+@PublishedApi
internal class YieldContext : AbstractCoroutineContextElement(Key) {
companion object Key : CoroutineContext.Key
diff --git a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
index 4751296c87..b92ced6ab7 100644
--- a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
@@ -136,7 +136,7 @@ internal abstract class AbstractSendChannel(
return sendSuspend(element)
}
- @Suppress("DEPRECATION")
+ @Suppress("DEPRECATION", "DEPRECATION_ERROR")
override fun offer(element: E): Boolean {
// Temporary migration for offer users who rely on onUndeliveredElement
try {
diff --git a/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt
index 600eb6a951..0a96f75380 100644
--- a/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt
@@ -33,6 +33,11 @@ internal class ArrayBroadcastChannel(
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)
@@ -60,6 +65,7 @@ internal class ArrayBroadcastChannel(
get() = _size.value
set(value) { _size.value = value }
+ @Suppress("DEPRECATION")
private val subscribers = subscriberList>()
override val isBufferAlwaysFull: Boolean get() = false
diff --git a/kotlinx-coroutines-core/common/src/channels/Channel.kt b/kotlinx-coroutines-core/common/src/channels/Channel.kt
index b15c4262ef..68ed5f1e78 100644
--- a/kotlinx-coroutines-core/common/src/channels/Channel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Channel.kt
@@ -64,7 +64,6 @@ public interface SendChannel {
*/
public val onSend: SelectClause2>
-
/**
* Immediately adds the specified [element] to this channel, if this doesn't violate its capacity restrictions,
* and returns the successful result. Otherwise, returns failed or closed result.
@@ -158,10 +157,10 @@ public interface SendChannel {
* @suppress **Deprecated**.
*/
@Deprecated(
- level = DeprecationLevel.WARNING,
+ level = DeprecationLevel.ERROR,
message = "Deprecated in the favour of 'trySend' method",
replaceWith = ReplaceWith("trySend(element).isSuccess")
- ) // Warning since 1.5.0
+ ) // Warning since 1.5.0, error since 1.6.0
public fun offer(element: E): Boolean {
val result = trySend(element)
if (result.isSuccess) return true
@@ -314,12 +313,12 @@ public interface ReceiveChannel {
* @suppress **Deprecated**.
*/
@Deprecated(
- level = DeprecationLevel.WARNING,
+ level = DeprecationLevel.ERROR,
message = "Deprecated in the favour of 'tryReceive'. " +
"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
+ ) // Warning since 1.5.0, error since 1.6.0
public fun poll(): E? {
val result = tryReceive()
if (result.isSuccess) return result.getOrThrow()
@@ -365,7 +364,7 @@ public interface ReceiveChannel {
message = "Deprecated in favor of onReceiveCatching extension",
level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("onReceiveCatching")
- ) // Warning since 1.3.0, error in 1.5.0, will be hidden or removed in 1.6.0
+ ) // 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 {
diff --git a/kotlinx-coroutines-core/common/src/channels/Channels.common.kt b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt
index e0b4f9d2a5..a78e2f186d 100644
--- a/kotlinx-coroutines-core/common/src/channels/Channels.common.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt
@@ -50,7 +50,7 @@ public inline fun BroadcastChannel.consume(block: ReceiveChannel.()
@Deprecated(
"Deprecated in the favour of 'receiveCatching'",
ReplaceWith("receiveCatching().getOrNull()"),
- DeprecationLevel.WARNING
+ DeprecationLevel.ERROR
) // 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? {
@@ -63,7 +63,7 @@ public suspend fun ReceiveChannel.receiveOrNull(): E? {
*/
@Deprecated(
"Deprecated in the favour of 'onReceiveCatching'",
- level = DeprecationLevel.WARNING
+ 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")
diff --git a/kotlinx-coroutines-core/common/src/channels/Produce.kt b/kotlinx-coroutines-core/common/src/channels/Produce.kt
index 3342fb6ec9..da8f884be1 100644
--- a/kotlinx-coroutines-core/common/src/channels/Produce.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Produce.kt
@@ -6,14 +6,11 @@ package kotlinx.coroutines.channels
import kotlinx.coroutines.*
import kotlin.coroutines.*
+import kotlinx.coroutines.flow.*
/**
- * Scope for the [produce][CoroutineScope.produce] coroutine builder.
- *
- * **Note: This is an experimental api.** Behavior of producers that work as children in a parent scope with respect
- * to cancellation and error handling may change in the future.
+ * Scope for the [produce][CoroutineScope.produce], [callbackFlow] and [channelFlow] builders.
*/
-@ExperimentalCoroutinesApi
public interface ProducerScope : CoroutineScope, SendChannel {
/**
* A reference to the channel this coroutine [sends][send] elements to.
@@ -45,7 +42,6 @@ public interface ProducerScope : CoroutineScope, SendChannel {
* }
* ```
*/
-@ExperimentalCoroutinesApi
public suspend fun ProducerScope<*>.awaitClose(block: () -> Unit = {}) {
check(kotlin.coroutines.coroutineContext[Job] === this) { "awaitClose() can only be invoked from the producer context" }
try {
@@ -137,7 +133,7 @@ internal fun CoroutineScope.produce(
return coroutine
}
-internal open class ProducerCoroutine(
+private class ProducerCoroutine(
parentContext: CoroutineContext, channel: Channel
) : ChannelCoroutine(parentContext, channel, true, active = true), ProducerScope {
override val isActive: Boolean
diff --git a/kotlinx-coroutines-core/common/src/flow/Builders.kt b/kotlinx-coroutines-core/common/src/flow/Builders.kt
index 66b55a90c0..c4b55e104b 100644
--- a/kotlinx-coroutines-core/common/src/flow/Builders.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Builders.kt
@@ -198,25 +198,6 @@ public fun LongRange.asFlow(): Flow = flow {
}
}
-/**
- * @suppress
- */
-@FlowPreview
-@Deprecated(
- message = "Use channelFlow with awaitClose { } instead of flowViaChannel and invokeOnClose { }.",
- level = DeprecationLevel.ERROR
-) // To be removed in 1.4.x
-@Suppress("DeprecatedCallableAddReplaceWith")
-public fun flowViaChannel(
- bufferSize: Int = BUFFERED,
- @BuilderInference block: CoroutineScope.(channel: SendChannel) -> Unit
-): Flow {
- return channelFlow {
- block(channel)
- awaitClose()
- }.buffer(bufferSize)
-}
-
/**
* Creates an instance of a _cold_ [Flow] with elements that are sent to a [SendChannel]
* provided to the builder's [block] of code via [ProducerScope]. It allows elements to be
diff --git a/kotlinx-coroutines-core/common/src/flow/Channels.kt b/kotlinx-coroutines-core/common/src/flow/Channels.kt
index 9c6051d36d..51ed4270c0 100644
--- a/kotlinx-coroutines-core/common/src/flow/Channels.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Channels.kt
@@ -178,41 +178,6 @@ public fun BroadcastChannel.asFlow(): Flow = flow {
emitAll(openSubscription())
}
-/**
- * ### Deprecated
- *
- * **This API is deprecated.** The [BroadcastChannel] provides a complex channel-like API for hot flows.
- * [SharedFlow] is an easier-to-use and more flow-centric API for the same purposes, so using
- * [shareIn] operator is preferred. It is not a direct replacement, so please
- * study [shareIn] documentation to see what kind of shared flow fits your use-case. As a rule of thumb:
- *
- * * Replace `broadcastIn(scope)` and `broadcastIn(scope, CoroutineStart.LAZY)` with `shareIn(scope, 0, SharingStarted.Lazily)`.
- * * Replace `broadcastIn(scope, CoroutineStart.DEFAULT)` with `shareIn(scope, 0, SharingStarted.Eagerly)`.
- */
-@Deprecated(
- message = "Use shareIn operator and the resulting SharedFlow as a replacement for BroadcastChannel",
- replaceWith = ReplaceWith("this.shareIn(scope, SharingStarted.Lazily, 0)"),
- level = DeprecationLevel.ERROR
-) // WARNING in 1.4.0, error in 1.5.0, removed in 1.6.0 (was @FlowPreview)
-public fun Flow.broadcastIn(
- scope: CoroutineScope,
- start: CoroutineStart = CoroutineStart.LAZY
-): BroadcastChannel {
- // Backwards compatibility with operator fusing
- val channelFlow = asChannelFlow()
- val capacity = when (channelFlow.onBufferOverflow) {
- BufferOverflow.SUSPEND -> channelFlow.produceCapacity
- BufferOverflow.DROP_OLDEST -> Channel.CONFLATED
- BufferOverflow.DROP_LATEST ->
- throw IllegalArgumentException("Broadcast channel does not support BufferOverflow.DROP_LATEST")
- }
- return scope.broadcast(channelFlow.context, capacity = capacity, start = start) {
- collect { value ->
- send(value)
- }
- }
-}
-
/**
* Creates a [produce] coroutine that collects the given flow.
*
diff --git a/kotlinx-coroutines-core/common/src/flow/Flow.kt b/kotlinx-coroutines-core/common/src/flow/Flow.kt
index 0ccd343ead..259f477de0 100644
--- a/kotlinx-coroutines-core/common/src/flow/Flow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Flow.kt
@@ -131,10 +131,12 @@ import kotlin.coroutines.*
*
* ### Exception transparency
*
- * Flow implementations never catch or handle exceptions that occur in downstream flows. From the implementation standpoint
- * it means that calls to [emit][FlowCollector.emit] and [emitAll] shall never be wrapped into
- * `try { ... } catch { ... }` blocks. Exception handling in flows shall be performed with
- * [catch][Flow.catch] operator and it is designed to only catch exceptions coming from upstream flows while passing
+ * When `emit` or `emitAll` throws, the Flow implementations must immediately stop emitting new values and finish with an exception.
+ * For diagnostics or application-specific purposes, the exception may be different from the one thrown by the emit operation,
+ * suppressing the original exception as discussed below.
+ * If there is a need to emit values after the downstream failed, please use the [catch][Flow.catch] operator.
+ *
+ * The [catch][Flow.catch] operator only catches upstream exceptions, but passes
* all downstream exceptions. Similarly, terminal operators like [collect][Flow.collect]
* throw any unhandled exceptions that occur in their code or in upstream flows, for example:
*
@@ -147,6 +149,13 @@ import kotlin.coroutines.*
* ```
* The same reasoning can be applied to the [onCompletion] operator that is a declarative replacement for the `finally` block.
*
+ * All exception-handling Flow operators follow the principle of exception suppression:
+ *
+ * If the upstream flow throws an exception during its completion when the downstream exception has been thrown,
+ * the downstream exception becomes superseded and suppressed by the upstream exception, being a semantic
+ * equivalent of throwing from `finally` block. However, this doesn't affect the operation of the exception-handling operators,
+ * which consider the downstream exception to be the root cause and behave as if the upstream didn't throw anything.
+ *
* Failure to adhere to the exception transparency requirement can lead to strange behaviors which make
* it hard to reason about the code because an exception in the `collect { ... }` could be somehow "caught"
* by an upstream flow, limiting the ability of local reasoning about the code.
diff --git a/kotlinx-coroutines-core/common/src/flow/Migration.kt b/kotlinx-coroutines-core/common/src/flow/Migration.kt
index 6278081a5d..64effbf395 100644
--- a/kotlinx-coroutines-core/common/src/flow/Migration.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Migration.kt
@@ -260,7 +260,7 @@ public fun Flow.skip(count: Int): Flow = noImpl()
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Flow analogue of 'forEach' is 'collect'",
- replaceWith = ReplaceWith("collect(block)")
+ replaceWith = ReplaceWith("collect(action)")
)
public fun Flow.forEach(action: suspend (value: T) -> Unit): Unit = noImpl()
diff --git a/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt b/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt
index d79e203464..41d05a6868 100644
--- a/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt
@@ -129,6 +129,19 @@ public interface SharedFlow : Flow {
* A snapshot of the replay cache.
*/
public val replayCache: List
+
+ /**
+ * Accepts the given [collector] and [emits][FlowCollector.emit] values into it.
+ * This method should never be used directly. To emit values from a shared flow into a specific collector, either `collector.emitAll(flow)` or `collect { ... }` extension
+ * should be used.
+ *
+ * **A shared flow never completes**. A call to [Flow.collect] or any other terminal operator
+ * on a shared flow never completes normally.
+ *
+ * @see [Flow.collect]
+ */
+ @InternalCoroutinesApi
+ override suspend fun collect(collector: FlowCollector): Nothing
}
/**
@@ -198,6 +211,8 @@ public interface MutableSharedFlow : SharedFlow, FlowCollector {
* }
* .launchIn(scope) // launch it
* ```
+ *
+ * Implementation note: the resulting flow **does not** conflate subscription count.
*/
public val subscriptionCount: StateFlow
@@ -253,7 +268,7 @@ public fun MutableSharedFlow(
// ------------------------------------ Implementation ------------------------------------
-private class SharedFlowSlot : AbstractSharedFlowSlot>() {
+internal class SharedFlowSlot : AbstractSharedFlowSlot>() {
@JvmField
var index = -1L // current "to-be-emitted" index, -1 means the slot is free now
@@ -275,7 +290,7 @@ private class SharedFlowSlot : AbstractSharedFlowSlot>() {
}
}
-private class SharedFlowImpl(
+internal open class SharedFlowImpl(
private val replay: Int,
private val bufferCapacity: Int,
private val onBufferOverflow: BufferOverflow
@@ -334,8 +349,15 @@ private class SharedFlowImpl(
result
}
+ /*
+ * A tweak for SubscriptionCountStateFlow to get the latest value.
+ */
+ @Suppress("UNCHECKED_CAST")
+ protected val lastReplayedLocked: T
+ get() = buffer!!.getBufferAt(replayIndex + replaySize - 1) as T
+
@Suppress("UNCHECKED_CAST")
- override suspend fun collect(collector: FlowCollector) {
+ override suspend fun collect(collector: FlowCollector): Nothing {
val slot = allocateSlot()
try {
if (collector is SubscribedFlowCollector) collector.onSubscription()
diff --git a/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt b/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt
index f4c6f2ee8d..a5ae63667f 100644
--- a/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt
+++ b/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt
@@ -5,7 +5,7 @@
package kotlinx.coroutines.flow
import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.internal.*
+import kotlinx.coroutines.internal.IgnoreJreRequirement
import kotlin.time.*
/**
@@ -204,5 +204,6 @@ private class StartedWhileSubscribed(
stopTimeout == other.stopTimeout &&
replayExpiration == other.replayExpiration
+ @IgnoreJreRequirement // desugared hashcode implementation
override fun hashCode(): Int = stopTimeout.hashCode() * 31 + replayExpiration.hashCode()
}
diff --git a/kotlinx-coroutines-core/common/src/flow/StateFlow.kt b/kotlinx-coroutines-core/common/src/flow/StateFlow.kt
index 9e82e78771..be6cbd6bbd 100644
--- a/kotlinx-coroutines-core/common/src/flow/StateFlow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/StateFlow.kt
@@ -380,7 +380,7 @@ private class StateFlowImpl(
throw UnsupportedOperationException("MutableStateFlow.resetReplayCache is not supported")
}
- override suspend fun collect(collector: FlowCollector) {
+ override suspend fun collect(collector: FlowCollector): Nothing {
val slot = allocateSlot()
try {
if (collector is SubscribedFlowCollector) collector.onSubscription()
@@ -415,10 +415,6 @@ private class StateFlowImpl(
fuseStateFlow(context, capacity, onBufferOverflow)
}
-internal fun MutableStateFlow.increment(delta: Int) {
- update { it + delta }
-}
-
internal fun StateFlow.fuseStateFlow(
context: CoroutineContext,
capacity: Int,
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt b/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt
index 7114cc08d3..39ca98391f 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt
@@ -4,6 +4,7 @@
package kotlinx.coroutines.flow.internal
+import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.internal.*
import kotlin.coroutines.*
@@ -26,12 +27,12 @@ internal abstract class AbstractSharedFlow> : Sync
protected var nCollectors = 0 // number of allocated (!free) slots
private set
private var nextIndex = 0 // oracle for the next free slot index
- private var _subscriptionCount: MutableStateFlow? = null // init on first need
+ private var _subscriptionCount: SubscriptionCountStateFlow? = null // init on first need
val subscriptionCount: StateFlow
get() = synchronized(this) {
// allocate under lock in sync with nCollectors variable
- _subscriptionCount ?: MutableStateFlow(nCollectors).also {
+ _subscriptionCount ?: SubscriptionCountStateFlow(nCollectors).also {
_subscriptionCount = it
}
}
@@ -43,7 +44,7 @@ internal abstract class AbstractSharedFlow> : Sync
@Suppress("UNCHECKED_CAST")
protected fun allocateSlot(): S {
// Actually create slot under lock
- var subscriptionCount: MutableStateFlow? = null
+ var subscriptionCount: SubscriptionCountStateFlow? = null
val slot = synchronized(this) {
val slots = when (val curSlots = slots) {
null -> createSlotArray(2).also { slots = it }
@@ -74,7 +75,7 @@ internal abstract class AbstractSharedFlow> : Sync
@Suppress("UNCHECKED_CAST")
protected fun freeSlot(slot: S) {
// Release slot under lock
- var subscriptionCount: MutableStateFlow? = null
+ var subscriptionCount: SubscriptionCountStateFlow? = null
val resumes = synchronized(this) {
nCollectors--
subscriptionCount = _subscriptionCount // retrieve under lock if initialized
@@ -83,10 +84,10 @@ internal abstract class AbstractSharedFlow> : Sync
(slot as AbstractSharedFlowSlot).freeLocked(this)
}
/*
- Resume suspended coroutines.
- This can happens when the subscriber that was freed was a slow one and was holding up buffer.
- When this subscriber was freed, previously queued emitted can now wake up and are resumed here.
- */
+ * Resume suspended coroutines.
+ * This can happen when the subscriber that was freed was a slow one and was holding up buffer.
+ * When this subscriber was freed, previously queued emitted can now wake up and are resumed here.
+ */
for (cont in resumes) cont?.resume(Unit)
// decrement subscription count
subscriptionCount?.increment(-1)
@@ -99,3 +100,35 @@ internal abstract class AbstractSharedFlow> : Sync
}
}
}
+
+/**
+ * [StateFlow] that represents the number of subscriptions.
+ *
+ * It is exposed as a regular [StateFlow] in our public API, but it is implemented as [SharedFlow] undercover to
+ * avoid conflations of consecutive updates because the subscription count is very sensitive to it.
+ *
+ * The importance of non-conflating can be demonstrated with the following example:
+ * ```
+ * val shared = flowOf(239).stateIn(this, SharingStarted.Lazily, 42) // stateIn for the sake of the initial value
+ * println(shared.first())
+ * yield()
+ * println(shared.first())
+ * ```
+ * If the flow is shared within the same dispatcher (e.g. Main) or with a slow/throttled one,
+ * the `SharingStarted.Lazily` will never be able to start the source: `first` sees the initial value and immediately
+ * unsubscribes, leaving the asynchronous `SharingStarted` with conflated zero.
+ *
+ * To avoid that (especially in a more complex scenarios), we do not conflate subscription updates.
+ */
+private class SubscriptionCountStateFlow(initialValue: Int) : StateFlow,
+ SharedFlowImpl(1, Int.MAX_VALUE, BufferOverflow.DROP_OLDEST)
+{
+ init { tryEmit(initialValue) }
+
+ override val value: Int
+ get() = synchronized(this) { lastReplayedLocked }
+
+ fun increment(delta: Int) = synchronized(this) {
+ tryEmit(lastReplayedLocked + delta)
+ }
+}
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt b/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt
index b395525620..9a81eefa2d 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/FlowCoroutine.kt
@@ -51,33 +51,11 @@ internal fun scopedFlow(@BuilderInference block: suspend CoroutineScope.(Flo
flowScope { block(this@flow) }
}
-internal fun CoroutineScope.flowProduce(
- context: CoroutineContext,
- capacity: Int = 0,
- @BuilderInference block: suspend ProducerScope.() -> Unit
-): ReceiveChannel {
- val channel = Channel(capacity)
- val newContext = newCoroutineContext(context)
- val coroutine = FlowProduceCoroutine(newContext, channel)
- coroutine.start(CoroutineStart.ATOMIC, coroutine, block)
- return coroutine
-}
-
private class FlowCoroutine(
context: CoroutineContext,
uCont: Continuation
) : ScopeCoroutine(context, uCont) {
- public override fun childCancelled(cause: Throwable): Boolean {
- if (cause is ChildCancelledException) return true
- return cancelImpl(cause)
- }
-}
-
-private class FlowProduceCoroutine(
- parentContext: CoroutineContext,
- channel: Channel
-) : ProducerCoroutine(parentContext, channel) {
- public override fun childCancelled(cause: Throwable): Boolean {
+ override fun childCancelled(cause: Throwable): Boolean {
if (cause is ChildCancelledException) return true
return cancelImpl(cause)
}
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/Merge.kt b/kotlinx-coroutines-core/common/src/flow/internal/Merge.kt
index 9eca8aa0c2..c18adba3b7 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/Merge.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/Merge.kt
@@ -22,7 +22,7 @@ internal class ChannelFlowTransformLatest(
override suspend fun flowCollect(collector: FlowCollector) {
assert { collector is SendingCollector } // So cancellation behaviour is not leaking into the downstream
- flowScope {
+ coroutineScope {
var previousFlow: Job? = null
flow.collect { value ->
previousFlow?.apply {
@@ -49,7 +49,7 @@ internal class ChannelFlowMerge(
ChannelFlowMerge(flow, concurrency, context, capacity, onBufferOverflow)
override fun produceImpl(scope: CoroutineScope): ReceiveChannel {
- return scope.flowProduce(context, capacity, block = collectToFun)
+ return scope.produce(context, capacity, block = collectToFun)
}
override suspend fun collectTo(scope: ProducerScope) {
@@ -87,7 +87,7 @@ internal class ChannelLimitedFlowMerge(
ChannelLimitedFlowMerge(flows, context, capacity, onBufferOverflow)
override fun produceImpl(scope: CoroutineScope): ReceiveChannel {
- return scope.flowProduce(context, capacity, block = collectToFun)
+ return scope.produce(context, capacity, block = collectToFun)
}
override suspend fun collectTo(scope: ProducerScope) {
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Context.kt b/kotlinx-coroutines-core/common/src/flow/operators/Context.kt
index 04342ed074..ace01e1d44 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Context.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Context.kt
@@ -277,64 +277,6 @@ private class CancellableFlowImpl(private val flow: Flow) : CancellableFlo
}
}
-/**
- * The operator that changes the context where all transformations applied to the given flow within a [builder] are executed.
- * This operator is context preserving and does not affect the context of the preceding and subsequent operations.
- *
- * Example:
- *
- * ```
- * flow // not affected
- * .map { ... } // Not affected
- * .flowWith(Dispatchers.IO) {
- * map { ... } // in IO
- * .filter { ... } // in IO
- * }
- * .map { ... } // Not affected
- * ```
- *
- * For more explanation of context preservation please refer to [Flow] documentation.
- *
- * This operator is deprecated without replacement because it was discovered that it doesn't play well with coroutines
- * and flow semantics:
- *
- * 1) It doesn't prevent context elements from the downstream to leak into its body
- * ```
- * flowOf(1).flowWith(EmptyCoroutineContext) {
- * onEach { println(kotlin.coroutines.coroutineContext[CoroutineName]) } // Will print 42
- * }.flowOn(CoroutineName(42))
- * ```
- * 2) To avoid such leaks, new primitive should be introduced to `kotlinx.coroutines` -- the subtraction of contexts.
- * And this will become a new concept to learn, maintain and explain.
- * 3) It defers the execution of declarative [builder] until the moment of [collection][Flow.collect] similarly
- * to `Observable.defer`. But it is unexpected because nothing in the name `flowWith` reflects this fact.
- * 4) It can be confused with [flowOn] operator, though [flowWith] is much rarer.
- *
- * @suppress
- */
-@FlowPreview
-@Deprecated(message = "flowWith is deprecated without replacement, please refer to its KDoc for an explanation", level = DeprecationLevel.ERROR) // Error in beta release, removal in 1.4
-public fun Flow.flowWith(
- flowContext: CoroutineContext,
- bufferSize: Int = BUFFERED,
- builder: Flow.() -> Flow
-): Flow {
- checkFlowContext(flowContext)
- val source = this
- return unsafeFlow {
- /**
- * Here we should remove a Job instance from the context.
- * All builders are written using scoping and no global coroutines are launched, so it is safe not to provide explicit Job.
- * It is also necessary not to mess with cancellation if multiple flowWith are used.
- */
- val originalContext = currentCoroutineContext().minusKey(Job)
- val prepared = source.flowOn(originalContext).buffer(bufferSize)
- builder(prepared).flowOn(flowContext).buffer(bufferSize).collect { value ->
- return@collect emit(value)
- }
- }
-}
-
private fun checkFlowContext(context: CoroutineContext) {
require(context[Job] == null) {
"Flow context cannot contain job in it. Had $context"
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt b/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt
index fed5962bd5..e893f44ea5 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Delay.kt
@@ -23,6 +23,7 @@ import kotlin.time.*
----- INCLUDE .*
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
+import kotlin.time.Duration.Companion.milliseconds
fun main() = runBlocking {
----- SUFFIX .*
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt b/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt
index 608221e09f..30512f407d 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Errors.kt
@@ -60,35 +60,6 @@ public fun Flow.catch(action: suspend FlowCollector.(cause: Throwable)
if (exception != null) action(exception)
}
-/**
- * @suppress **Deprecated**: Use `(Throwable) -> Boolean` functional type
- */
-@Deprecated(
- level = DeprecationLevel.ERROR,
- message = "Use (Throwable) -> Boolean functional type",
- replaceWith = ReplaceWith("(Throwable) -> Boolean")
-)
-public typealias ExceptionPredicate = (Throwable) -> Boolean
-
-/**
- * Switches to the [fallback] flow if the original flow throws an exception that matches the [predicate].
- * Cancellation exceptions that were caused by the direct [cancel] call are not handled by this operator.
- *
- * @suppress **Deprecated**: Use `catch { e -> if (predicate(e)) emitAll(fallback) else throw e }`
- */
-@Deprecated(
- level = DeprecationLevel.ERROR,
- message = "Use catch { e -> if (predicate(e)) emitAll(fallback) else throw e }",
- replaceWith = ReplaceWith("catch { e -> if (predicate(e)) emitAll(fallback) else throw e }")
-)
-public fun Flow.onErrorCollect(
- fallback: Flow,
- predicate: (Throwable) -> Boolean = { true }
-): Flow = catch { e ->
- if (!predicate(e)) throw e
- emitAll(fallback)
-}
-
/**
* Retries collection of the given flow up to [retries] times when an exception that matches the
* given [predicate] occurs in the upstream flow. This operator is *transparent* to exceptions that occur
@@ -124,16 +95,6 @@ public fun Flow.retry(
return retryWhen { cause, attempt -> attempt < retries && predicate(cause) }
}
-@FlowPreview
-@Deprecated(level = DeprecationLevel.HIDDEN, message = "binary compatibility with retries: Int preview version")
-public fun Flow.retry(
- retries: Int = Int.MAX_VALUE,
- predicate: (Throwable) -> Boolean = { true }
-): Flow {
- require(retries > 0) { "Expected positive amount of retries, but had $retries" }
- return retryWhen { cause, attempt -> predicate(cause) && attempt < retries }
-}
-
/**
* Retries collection of the given flow when an exception occurs in the upstream flow and the
* [predicate] returns true. The predicate also receives an `attempt` number as parameter,
@@ -186,6 +147,7 @@ public fun Flow.retryWhen(predicate: suspend FlowCollector.(cause: Thr
}
// Return exception from upstream or null
+@Suppress("NAME_SHADOWING")
internal suspend fun Flow.catchImpl(
collector: FlowCollector
): Throwable? {
@@ -200,6 +162,8 @@ internal suspend fun Flow.catchImpl(
}
}
} catch (e: Throwable) {
+ // Otherwise, smartcast is impossible
+ val fromDownstream = fromDownstream
/*
* First check ensures that we catch an original exception, not one rethrown by an operator.
* Seconds check ignores cancellation causes, they cannot be caught.
@@ -207,7 +171,41 @@ internal suspend fun Flow.catchImpl(
if (e.isSameExceptionAs(fromDownstream) || e.isCancellationCause(coroutineContext)) {
throw e // Rethrow exceptions from downstream and cancellation causes
} else {
- return e // not from downstream
+ /*
+ * The exception came from the upstream [semi-] independently.
+ * For pure failures, when the downstream functions normally, we handle the exception as intended.
+ * But if the downstream has failed prior to or concurrently
+ * with the upstream, we forcefully rethrow it, preserving the contextual information and ensuring that it's not lost.
+ */
+ if (fromDownstream == null) {
+ return e
+ }
+ /*
+ * We consider the upstream exception as the superseding one when both upstream and downstream
+ * fail, suppressing the downstream exception, and operating similarly to `finally` block with
+ * the useful addition of adding the original downstream exception to suppressed ones.
+ *
+ * That's important for the following scenarios:
+ * ```
+ * flow {
+ * val resource = ...
+ * try {
+ * ... emit as well ...
+ * } finally {
+ * resource.close() // Throws in the shutdown sequence when 'collect' already has thrown an exception
+ * }
+ * }.catch { } // or retry
+ * .collect { ... }
+ * ```
+ * when *the downstream* throws.
+ */
+ if (e is CancellationException) {
+ fromDownstream.addSuppressed(e)
+ throw fromDownstream
+ } else {
+ e.addSuppressed(fromDownstream)
+ throw e
+ }
}
}
return null
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt b/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt
index 8fbf1a2b0e..734464b557 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt
@@ -112,7 +112,6 @@ public fun Flow.takeWhile(predicate: suspend (T) -> Boolean): Flow = f
* }
* ```
*/
-@ExperimentalCoroutinesApi
public fun Flow.transformWhile(
@BuilderInference transform: suspend FlowCollector.(value: T) -> Boolean
): Flow =
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt b/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt
index 432160f340..35c44d0895 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Merge.kt
@@ -61,7 +61,7 @@ public fun Flow.flatMapConcat(transform: suspend (value: T) -> Flow
* its concurrent merging so that only one properly configured channel is used for execution of merging logic.
*
* @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].
+ * at the same time. By default, it is equal to [DEFAULT_CONCURRENCY].
*/
@FlowPreview
public fun Flow.flatMapMerge(
@@ -71,8 +71,7 @@ public fun Flow.flatMapMerge(
map(transform).flattenMerge(concurrency)
/**
- * Flattens the given flow of flows into a single flow in a sequentially manner, without interleaving nested flows.
- * This method is conceptually identical to `flattenMerge(concurrency = 1)` but has faster implementation.
+ * Flattens the given flow of flows into a single flow in a sequential manner, without interleaving nested flows.
*
* Inner flows are collected by this operator *sequentially*.
*/
@@ -90,7 +89,6 @@ public fun Flow>.flattenConcat(): Flow = flow {
* Applications of [flowOn], [buffer], and [produceIn] _after_ this operator are fused with
* its concurrent merging so that only one properly configured channel is used for execution of merging logic.
*/
-@ExperimentalCoroutinesApi
public fun Iterable>.merge(): Flow {
/*
* This is a fuseable implementation of the following operator:
@@ -114,14 +112,13 @@ public fun Iterable>.merge(): Flow {
* Applications of [flowOn], [buffer], and [produceIn] _after_ this operator are fused with
* its concurrent merging so that only one properly configured channel is used for execution of merging logic.
*/
-@ExperimentalCoroutinesApi
public fun merge(vararg flows: Flow): Flow = flows.asIterable().merge()
/**
* Flattens the given flow of flows into a single flow with a [concurrency] limit on the number of
* concurrently collected flows.
*
- * If [concurrency] is more than 1, then inner flows are be collected by this operator *concurrently*.
+ * If [concurrency] is more than 1, then inner flows are collected by this operator *concurrently*.
* With `concurrency == 1` this operator is identical to [flattenConcat].
*
* ### Operator fusion
@@ -133,7 +130,7 @@ public fun merge(vararg flows: Flow): Flow = flows.asIterable().merge(
* and size of its output buffer can be changed by applying subsequent [buffer] operator.
*
* @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].
+ * at the same time. By default, it is equal to [DEFAULT_CONCURRENCY].
*/
@FlowPreview
public fun Flow>.flattenMerge(concurrency: Int = DEFAULT_CONCURRENCY): Flow {
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Share.kt b/kotlinx-coroutines-core/common/src/flow/operators/Share.kt
index 4fa74d8e50..2b690e3c04 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Share.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Share.kt
@@ -197,8 +197,16 @@ private fun CoroutineScope.launchSharing(
shared: MutableSharedFlow,
started: SharingStarted,
initialValue: T
-): Job =
- launch(context) { // the single coroutine to rule the sharing
+): Job {
+ /*
+ * Conditional start: in the case when sharing and subscribing happens in the same dispatcher, we want to
+ * have the following invariants preserved:
+ * * Delayed sharing strategies have a chance to immediately observe consecutive subscriptions.
+ * E.g. in the cases like `flow.shareIn(...); flow.take(1)` we want sharing strategy to see the initial subscription
+ * * Eager sharing does not start immediately, so the subscribers have actual chance to subscribe _prior_ to sharing.
+ */
+ val start = if (started == SharingStarted.Eagerly) CoroutineStart.DEFAULT else CoroutineStart.UNDISPATCHED
+ return launch(context, start = start) { // the single coroutine to rule the sharing
// Optimize common built-in started strategies
when {
started === SharingStarted.Eagerly -> {
@@ -230,6 +238,7 @@ private fun CoroutineScope.launchSharing(
}
}
}
+}
// -------------------------------- stateIn --------------------------------
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt
index a47ae776ca..9b97193227 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Transform.kt
@@ -85,7 +85,6 @@ public fun Flow.onEach(action: suspend (T) -> Unit): Flow = transform
*
* This function is an alias to [runningFold] operator.
*/
-@ExperimentalCoroutinesApi
public fun Flow.scan(initial: R, @BuilderInference operation: suspend (accumulator: R, value: T) -> R): Flow = runningFold(initial, operation)
/**
@@ -97,7 +96,6 @@ public fun Flow.scan(initial: R, @BuilderInference operation: suspend
* ```
* will produce `[], [1], [1, 2], [1, 2, 3]]`.
*/
-@ExperimentalCoroutinesApi
public fun Flow.runningFold(initial: R, @BuilderInference operation: suspend (accumulator: R, value: T) -> R): Flow = flow {
var accumulator: R = initial
emit(accumulator)
@@ -118,7 +116,6 @@ public fun Flow.runningFold(initial: R, @BuilderInference operation: s
* ```
* will produce `[1, 3, 6, 10]`
*/
-@ExperimentalCoroutinesApi
public fun Flow.runningReduce(operation: suspend (accumulator: T, value: T) -> T): Flow = flow {
var accumulator: Any? = NULL
collect { value ->
diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt
index 771f8332c3..91d410b9a2 100644
--- a/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt
+++ b/kotlinx-coroutines-core/common/src/flow/terminal/Collect.kt
@@ -73,6 +73,19 @@ public suspend inline fun Flow.collect(crossinline action: suspend (value
override suspend fun emit(value: T) = action(value)
})
+/**
+ * Terminal flow operator that collects the given [SharedFlow] with the provided [action].
+ * If any exception occurs during `collect` or in the provided flow, this exception is rethrown from this method.
+ *
+ * This is a counterpart of a regular [Flow.collect] extension, only different in the return type
+ * so that any code below `collect` produces a compilation warning.
+ */
+public suspend inline fun SharedFlow.collect(crossinline action: suspend (value: T) -> Unit): Nothing {
+ collect(object : FlowCollector {
+ override suspend fun emit(value: T) = action(value)
+ })
+}
+
/**
* Terminal flow operator that collects the given flow with a provided [action] that takes the index of an element (zero-based) and the element.
* If any exception occurs during collect or in the provided flow, this exception is rethrown from this method.
diff --git a/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt b/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt
index 9f2699ae48..fb254a0ebc 100644
--- a/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt
+++ b/kotlinx-coroutines-core/common/src/internal/Concurrent.common.kt
@@ -8,10 +8,12 @@ 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 ArrayBroadcastChannel
+ * 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() {
diff --git a/kotlinx-coroutines-core/common/src/internal/InternalAnnotations.common.kt b/kotlinx-coroutines-core/common/src/internal/InternalAnnotations.common.kt
new file mode 100644
index 0000000000..1df81c9cc4
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/InternalAnnotations.common.kt
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.internal
+
+// Ignore JRE requirements for animal-sniffer, compileOnly dependency
+@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
+@OptionalExpectation
+internal expect annotation class IgnoreJreRequirement()
diff --git a/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt b/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt
new file mode 100644
index 0000000000..892375b89f
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt
@@ -0,0 +1,105 @@
+/*
+ * 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 kotlinx.coroutines.*
+import kotlin.coroutines.*
+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.
+ */
+internal class LimitedDispatcher(
+ private val dispatcher: CoroutineDispatcher,
+ private val parallelism: Int
+) : CoroutineDispatcher(), Runnable, Delay by (dispatcher as? Delay ?: DefaultDelay) {
+
+ @Volatile
+ private var runningWorkers = 0
+
+ private val queue = LockFreeTaskQueue(singleConsumer = false)
+
+ @ExperimentalCoroutinesApi
+ override fun limitedParallelism(parallelism: Int): CoroutineDispatcher {
+ parallelism.checkParallelism()
+ if (parallelism >= this.parallelism) return this
+ 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
+ }
+
+ @Suppress("CAST_NEVER_SUCCEEDS")
+ synchronized(this as SynchronizedObject) {
+ --runningWorkers
+ if (queue.size == 0) return
+ ++runningWorkers
+ fairnessCounter = 0
+ }
+ }
+ }
+
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ dispatchInternal(block) {
+ dispatcher.dispatch(this, this)
+ }
+ }
+
+ @InternalCoroutinesApi
+ override fun dispatchYield(context: CoroutineContext, block: Runnable) {
+ dispatchInternal(block) {
+ dispatcher.dispatchYield(this, this)
+ }
+ }
+
+ private inline fun dispatchInternal(block: Runnable, dispatch: () -> 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()`)
+ */
+ if (!tryAllocateWorker()) return
+ dispatch()
+ }
+
+ private fun tryAllocateWorker(): Boolean {
+ @Suppress("CAST_NEVER_SUCCEEDS")
+ synchronized(this as SynchronizedObject) {
+ if (runningWorkers >= parallelism) return false
+ ++runningWorkers
+ return true
+ }
+ }
+
+ private fun addAndTryDispatching(block: Runnable): Boolean {
+ queue.addLast(block)
+ return runningWorkers >= parallelism
+ }
+}
+
+// Save a few bytecode ops
+internal fun Int.checkParallelism() = require(this >= 1) { "Expected positive parallelism level, but got $this" }
diff --git a/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt b/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt
index 0e1d1b473a..8b20ade1f0 100644
--- a/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt
+++ b/kotlinx-coroutines-core/common/src/internal/LockFreeLinkedList.common.kt
@@ -43,7 +43,7 @@ public expect open class LockFreeLinkedListNode() {
public expect open class LockFreeLinkedListHead() : LockFreeLinkedListNode {
public val isEmpty: Boolean
public inline fun forEach(block: (T) -> Unit)
- public final override fun remove(): Boolean // Actual return type is Nothing, KT-27534
+ public final override fun remove(): Nothing
}
/** @suppress **This is unstable API and it is subject to change.** */
diff --git a/kotlinx-coroutines-core/common/src/internal/Symbol.kt b/kotlinx-coroutines-core/common/src/internal/Symbol.kt
index 84db2ef6cc..b629951fbd 100644
--- a/kotlinx-coroutines-core/common/src/internal/Symbol.kt
+++ b/kotlinx-coroutines-core/common/src/internal/Symbol.kt
@@ -4,12 +4,14 @@
package kotlinx.coroutines.internal
+import kotlin.jvm.*
+
/**
* A symbol class that is used to define unique constants that are self-explanatory in debugger.
*
* @suppress **This is unstable API and it is subject to change.**
*/
-internal class Symbol(val symbol: String) {
+internal class Symbol(@JvmField val symbol: String) {
override fun toString(): String = "<$symbol>"
@Suppress("UNCHECKED_CAST", "NOTHING_TO_INLINE")
diff --git a/kotlinx-coroutines-core/common/src/selects/Select.kt b/kotlinx-coroutines-core/common/src/selects/Select.kt
index a7172707e2..a313de3d5d 100644
--- a/kotlinx-coroutines-core/common/src/selects/Select.kt
+++ b/kotlinx-coroutines-core/common/src/selects/Select.kt
@@ -186,7 +186,6 @@ public interface SelectInstance {
* | [SendChannel] | [send][SendChannel.send] | [onSend][SendChannel.onSend]
* | [ReceiveChannel] | [receive][ReceiveChannel.receive] | [onReceive][ReceiveChannel.onReceive]
* | [ReceiveChannel] | [receiveCatching][ReceiveChannel.receiveCatching] | [onReceiveCatching][ReceiveChannel.onReceiveCatching]
- * | [Mutex] | [lock][Mutex.lock] | [onLock][Mutex.onLock]
* | none | [delay] | [onTimeout][SelectBuilder.onTimeout]
*
* This suspending function is cancellable. If the [Job] of the current coroutine is cancelled or completed while this
diff --git a/kotlinx-coroutines-core/common/src/sync/Mutex.kt b/kotlinx-coroutines-core/common/src/sync/Mutex.kt
index 19584e0981..d2a2fcd41b 100644
--- a/kotlinx-coroutines-core/common/src/sync/Mutex.kt
+++ b/kotlinx-coroutines-core/common/src/sync/Mutex.kt
@@ -52,8 +52,7 @@ 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.
*
- * This function can be used in [select] invocation with [onLock] clause.
- * Use [tryLock] to try acquire lock without waiting.
+ * Use [tryLock] to try acquiring a lock without waiting.
*
* This function is fair; suspended callers are resumed in first-in-first-out order.
*
@@ -63,10 +62,10 @@ public interface Mutex {
public suspend fun lock(owner: Any? = null)
/**
- * 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 for removal without built-in replacement.
*/
+ @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
/**
@@ -370,7 +369,7 @@ internal class MutexImpl(locked: Boolean) : Mutex, SelectClause2 {
private abstract inner class LockWaiter(
@JvmField val owner: Any?
) : LockFreeLinkedListNode(), DisposableHandle {
- private val isTaken = atomic(false)
+ private val isTaken = atomic(false)
fun take(): Boolean = isTaken.compareAndSet(false, true)
final override fun dispose() { remove() }
abstract fun tryResumeLockWaiter(): Boolean
diff --git a/kotlinx-coroutines-core/common/test/DelayDurationTest.kt b/kotlinx-coroutines-core/common/test/DelayDurationTest.kt
index 3dd55bde84..1c6c189a44 100644
--- a/kotlinx-coroutines-core/common/test/DelayDurationTest.kt
+++ b/kotlinx-coroutines-core/common/test/DelayDurationTest.kt
@@ -10,6 +10,8 @@ package kotlinx.coroutines
import kotlin.test.*
import kotlin.time.*
+import kotlin.time.Duration.Companion.seconds
+import kotlin.time.Duration.Companion.nanoseconds
@ExperimentalTime
class DelayDurationTest : TestBase() {
diff --git a/kotlinx-coroutines-core/common/test/EmptyContext.kt b/kotlinx-coroutines-core/common/test/EmptyContext.kt
index ad78429d2b..97efec34c9 100644
--- a/kotlinx-coroutines-core/common/test/EmptyContext.kt
+++ b/kotlinx-coroutines-core/common/test/EmptyContext.kt
@@ -7,10 +7,6 @@ package kotlinx.coroutines
import kotlinx.coroutines.intrinsics.*
import kotlin.coroutines.*
-suspend fun withEmptyContext(block: suspend () -> T): T {
- val baseline = Result.failure(IllegalStateException("Block was suspended"))
- var result: Result = baseline
- block.startCoroutineUnintercepted(Continuation(EmptyCoroutineContext) { result = it })
- while (result == baseline) yield()
- return result.getOrThrow()
+suspend fun withEmptyContext(block: suspend () -> T): T = suspendCoroutine { cont ->
+ block.startCoroutineUnintercepted(Continuation(EmptyCoroutineContext) { cont.resumeWith(it) })
}
diff --git a/kotlinx-coroutines-core/common/test/TestBase.common.kt b/kotlinx-coroutines-core/common/test/TestBase.common.kt
index 71c45769cb..8b7024a60a 100644
--- a/kotlinx-coroutines-core/common/test/TestBase.common.kt
+++ b/kotlinx-coroutines-core/common/test/TestBase.common.kt
@@ -7,11 +7,13 @@
package kotlinx.coroutines
import kotlinx.coroutines.flow.*
+import kotlinx.coroutines.internal.*
import kotlin.coroutines.*
import kotlin.test.*
public expect val isStressTest: Boolean
public expect val stressTestMultiplier: Int
+public expect val stressTestMultiplierSqrt: Int
/**
* The result of a multiplatform asynchronous test.
@@ -20,6 +22,8 @@ public expect val stressTestMultiplier: Int
@Suppress("NO_ACTUAL_FOR_EXPECT")
public expect class TestResult
+public expect val isNative: Boolean
+
public expect open class TestBase constructor() {
/*
* In common tests we emulate parameterized tests
diff --git a/kotlinx-coroutines-core/common/test/flow/FlowInvariantsTest.kt b/kotlinx-coroutines-core/common/test/flow/FlowInvariantsTest.kt
index b5f1bf7bb8..5f39d3200d 100644
--- a/kotlinx-coroutines-core/common/test/flow/FlowInvariantsTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/FlowInvariantsTest.kt
@@ -235,7 +235,7 @@ class FlowInvariantsTest : TestBase() {
}
expectUnreached()
} catch (e: IllegalStateException) {
- assertTrue(e.message!!.contains("Flow invariant is violated"))
+ assertTrue(e.message!!.contains("Flow invariant is violated"), "But had: ${e.message}")
finish(2)
}
}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt
index 447eb73b5d..ad91e49898 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/CatchTest.kt
@@ -144,4 +144,61 @@ class CatchTest : TestBase() {
.collect()
finish(9)
}
+
+ @Test
+ fun testUpstreamExceptionConcurrentWithDownstream() = runTest {
+ val flow = flow {
+ try {
+ expect(1)
+ emit(1)
+ } finally {
+ expect(3)
+ throw TestException()
+ }
+ }.catch { expectUnreached() }.onEach {
+ expect(2)
+ throw TestException2()
+ }
+
+ assertFailsWith(flow)
+ finish(4)
+ }
+
+ @Test
+ fun testUpstreamExceptionConcurrentWithDownstreamCancellation() = runTest {
+ val flow = flow {
+ try {
+ expect(1)
+ emit(1)
+ } finally {
+ expect(3)
+ throw TestException()
+ }
+ }.catch { expectUnreached() }.onEach {
+ expect(2)
+ throw CancellationException("")
+ }
+
+ assertFailsWith(flow)
+ finish(4)
+ }
+
+ @Test
+ fun testUpstreamCancellationIsIgnoredWhenDownstreamFails() = runTest {
+ val flow = flow {
+ try {
+ expect(1)
+ emit(1)
+ } finally {
+ expect(3)
+ throw CancellationException("")
+ }
+ }.catch { expectUnreached() }.onEach {
+ expect(2)
+ throw TestException("")
+ }
+
+ assertFailsWith(flow)
+ finish(4)
+ }
}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/DropTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/DropTest.kt
index 1c5a305352..dfa2827447 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/DropTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/DropTest.kt
@@ -48,10 +48,9 @@ class DropTest : TestBase() {
expectUnreached()
}
}.drop(1)
- .map {
+ .map {
expect(4)
throw TestException()
- 42
}.catch { emit(42) }
expect(1)
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FilterTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FilterTest.kt
index 3de5d54a6d..f52416d823 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/FilterTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FilterTest.kt
@@ -38,7 +38,6 @@ class FilterTest : TestBase() {
}.filter {
latch.receive()
throw TestException()
- true
}.catch { emit(42) }
assertEquals(42, flow.single())
@@ -74,7 +73,6 @@ class FilterTest : TestBase() {
}.filterNot {
latch.receive()
throw TestException()
- true
}.catch { emit(42) }
assertEquals(42, flow.single())
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt
index 4095172dab..f09db120a8 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeBaseTest.kt
@@ -72,7 +72,7 @@ abstract class FlatMapMergeBaseTest : FlatMapBaseTest() {
emit(2)
expectUnreached()
}.flatMap {
- if (it == 1) flow {
+ if (it == 1) flow {
expect(5)
latch.send(Unit)
hang { expect(7) }
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeFastPathTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeFastPathTest.kt
index a92189c45c..f810221848 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeFastPathTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeFastPathTest.kt
@@ -39,19 +39,14 @@ class FlatMapMergeFastPathTest : FlatMapMergeBaseTest() {
@Test
fun testCancellationExceptionDownstream() = runTest {
- val flow = flow {
- emit(1)
- hang { expect(2) }
- }.flatMapMerge {
+ val flow = flowOf(1, 2, 3).flatMapMerge {
flow {
emit(it)
- expect(1)
throw CancellationException("")
}
}.buffer(64)
- assertFailsWith(flow)
- finish(3)
+ assertEquals(listOf(1, 2, 3), flow.toList())
}
@Test
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeTest.kt
index 7470289ece..c2ce346d9b 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlatMapMergeTest.kt
@@ -69,19 +69,14 @@ class FlatMapMergeTest : FlatMapMergeBaseTest() {
@Test
fun testCancellationExceptionDownstream() = runTest {
- val flow = flow {
- emit(1)
- hang { expect(2) }
- }.flatMapMerge {
+ val flow = flowOf(1, 2, 3).flatMapMerge {
flow {
emit(it)
- expect(1)
throw CancellationException("")
}
}
- assertFailsWith(flow)
- finish(3)
+ assertEquals(listOf(1, 2, 3), flow.toList())
}
@Test
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlattenConcatTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlattenConcatTest.kt
index 084af5b9bb..4ec7cc3cd1 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/FlattenConcatTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlattenConcatTest.kt
@@ -36,4 +36,17 @@ class FlattenConcatTest : FlatMapBaseTest() {
consumer.cancelAndJoin()
finish(2)
}
+
+ @Test
+ fun testCancellation() = runTest {
+ val flow = flow {
+ repeat(5) {
+ emit(flow {
+ if (it == 2) throw CancellationException("")
+ emit(1)
+ })
+ }
+ }
+ assertFailsWith(flow.flattenConcat())
+ }
}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/FlowOnTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/FlowOnTest.kt
index 68653281cc..8fba8456e8 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/FlowOnTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/FlowOnTest.kt
@@ -83,7 +83,7 @@ class FlowOnTest : TestBase() {
}.map {
expect(2)
assertEquals("throwing", it)
- throw TestException(); it
+ throw TestException()
}.flowOn(NamedDispatchers("throwing"))
assertFailsWith(flow)
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/MapNotNullTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/MapNotNullTest.kt
index 893811df15..d8bb480054 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/MapNotNullTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/MapNotNullTest.kt
@@ -39,10 +39,9 @@ class MapNotNullTest : TestBase() {
}
emit(1)
}
- }.mapNotNull {
+ }.mapNotNull {
latch.receive()
throw TestException()
- it + 1
}.catch { emit(42) }
assertEquals(42, flow.single())
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/MergeTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/MergeTest.kt
index 1248188554..f084798487 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/MergeTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/MergeTest.kt
@@ -45,6 +45,64 @@ abstract class MergeTest : TestBase() {
assertEquals(listOf("source"), result)
}
+ @Test
+ fun testOneSourceCancelled() = runTest {
+ val flow = flow {
+ expect(1)
+ emit(1)
+ expect(2)
+ yield()
+ throw CancellationException("")
+ }
+
+ val otherFlow = flow {
+ repeat(5) {
+ emit(1)
+ yield()
+ }
+
+ expect(3)
+ }
+
+ val result = listOf(flow, otherFlow).merge().toList()
+ assertEquals(MutableList(6) { 1 }, result)
+ finish(4)
+ }
+
+ @Test
+ fun testOneSourceCancelledNonFused() = runTest {
+ val flow = flow {
+ expect(1)
+ emit(1)
+ expect(2)
+ yield()
+ throw CancellationException("")
+ }
+
+ val otherFlow = flow {
+ repeat(5) {
+ emit(1)
+ yield()
+ }
+
+ expect(3)
+ }
+
+ val result = listOf(flow, otherFlow).nonFuseableMerge().toList()
+ assertEquals(MutableList(6) { 1 }, result)
+ finish(4)
+ }
+
+ private fun Iterable>.nonFuseableMerge(): Flow {
+ return channelFlow {
+ forEach { flow ->
+ launch {
+ flow.collect { send(it) }
+ }
+ }
+ }
+ }
+
@Test
fun testIsolatedContext() = runTest {
val flow = flow {
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/RetryTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/RetryTest.kt
index b8a6b198fb..e5dde1b7fc 100644
--- a/kotlinx-coroutines-core/common/test/flow/operators/RetryTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/operators/RetryTest.kt
@@ -104,4 +104,61 @@ class RetryTest : TestBase() {
job.cancelAndJoin()
finish(3)
}
-}
\ No newline at end of file
+
+ @Test
+ fun testUpstreamExceptionConcurrentWithDownstream() = runTest {
+ val flow = flow {
+ try {
+ expect(1)
+ emit(1)
+ } finally {
+ expect(3)
+ throw TestException()
+ }
+ }.retry { expectUnreached(); true }.onEach {
+ expect(2)
+ throw TestException2()
+ }
+
+ assertFailsWith