diff --git a/CHANGES.md b/CHANGES.md
index 8b843f9889..37eb34984f 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,24 @@
# Change log for kotlinx.coroutines
+## Version 1.10.1
+
+* Fixed binary incompatibility introduced for non-JVM targets in #4261 (#4309).
+
+## Version 1.10.0
+
+* Kotlin was updated to 2.1.0 (#4284).
+* Introduced `Flow.any`, `Flow.all`, and `Flow.none` (#4212). Thanks, @CLOVIS-AI!
+* Reorganized `kotlinx-coroutines-debug` and `kotlinx-coroutines-core` code to avoid a split package between the two artifacts (#4247). Note that directly referencing `kotlinx.coroutines.debug.AgentPremain` must now be replaced with `kotlinx.coroutines.debug.internal.AgentPremain`. Thanks, @sellmair!
+* No longer shade byte-buddy in `kotlinx-coroutines-debug`, reducing the artifact size and simplifying the build configuration of client code. Thanks, @sellmair!
+* Fixed `NullPointerException` when using Java-deserialized `kotlinx-coroutines-core` exceptions (#4291). Thanks, @AlexRiedler!
+* Properly report exceptions thrown by `CoroutineDispatcher.dispatch` instead of raising internal errors (#4091). Thanks, @zuevmaxim!
+* Fixed a bug that delayed scheduling of a `Dispatchers.Default` or `Dispatchers.IO` task after a `yield()` in rare scenarios (#4248).
+* Fixed a bug that prevented the `main()` coroutine on Wasm/WASI from executing after a `delay()` call in some scenarios (#4239).
+* Fixed scheduling of `runBlocking` tasks on Kotlin/Native that arrive after the `runBlocking` block was exited (#4245).
+* Fixed some terminal `Flow` operators sometimes resuming without taking cancellation into account (#4254). Thanks, @jxdabc!
+* Fixed a bug on the JVM that caused coroutine-bound `ThreadLocal` values not to get cleaned when using non-`CoroutineDispatcher` continuation interceptors (#4296).
+* Small tweaks, fixes, and documentation improvements.
+
## Version 1.9.0
### Features
diff --git a/README.md b/README.md
index 89df68e0bc..d39cf315e1 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,9 @@
[](https://kotlinlang.org/docs/components-stability.html)
[](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
[](https://www.apache.org/licenses/LICENSE-2.0)
-[](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.9.0)
+[](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.10.1)
[](http://kotlinlang.org)
+[](https://kotlinlang.org/api/kotlinx.coroutines/)
[](https://kotlinlang.slack.com/messages/coroutines/)
Library support for Kotlin coroutines with [multiplatform](#multiplatform) support.
@@ -85,7 +86,7 @@ Add dependencies (you can also add other modules that you need):
org.jetbrains.kotlinx
kotlinx-coroutines-core
- 1.9.0
+ 1.10.1
```
@@ -103,7 +104,7 @@ Add dependencies (you can also add other modules that you need):
```kotlin
dependencies {
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1")
}
```
@@ -133,7 +134,7 @@ Add [`kotlinx-coroutines-android`](ui/kotlinx-coroutines-android)
module as a dependency when using `kotlinx.coroutines` on Android:
```kotlin
-implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.0")
+implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.1")
```
This gives you access to the Android [Dispatchers.Main]
@@ -168,7 +169,7 @@ In common code that should get compiled for different platforms, you can add a d
```kotlin
commonMain {
dependencies {
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.9.0")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.10.1")
}
}
```
@@ -178,7 +179,7 @@ Platform-specific dependencies are recommended to be used only for non-multiplat
#### JS
Kotlin/JS version of `kotlinx.coroutines` is published as
-[`kotlinx-coroutines-core-js`](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.9.0)
+[`kotlinx-coroutines-core-js`](https://central.sonatype.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.10.1)
(follow the link to get the dependency declaration snippet).
#### Native
diff --git a/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts
index 7006c915d9..2b124f6208 100644
--- a/benchmarks/build.gradle.kts
+++ b/benchmarks/build.gradle.kts
@@ -4,7 +4,6 @@ import org.jetbrains.kotlin.gradle.tasks.*
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
plugins {
- id("com.github.johnrengelman.shadow")
id("me.champeau.jmh")
}
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ShakespearePlaysScrabble.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ShakespearePlaysScrabble.kt
index a2e5d2c178..6990c0c5cd 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ShakespearePlaysScrabble.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/ShakespearePlaysScrabble.kt
@@ -66,7 +66,7 @@ abstract class ShakespearePlaysScrabble {
private fun readResource(path: String) =
BufferedReader(InputStreamReader(GZIPInputStream(this.javaClass.classLoader.getResourceAsStream(path)))).lines()
- .map { it.toLowerCase() }.collect(Collectors.toSet())
+ .map { it.lowercase() }.collect(Collectors.toSet())
init {
val expected = listOf(120 to listOf("jezebel", "quickly"),
diff --git a/build.gradle.kts b/build.gradle.kts
index caef1260ac..dc4a7f12f6 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -27,9 +27,6 @@ buildscript {
classpath("ru.vyarus:gradle-animalsniffer-plugin:${version("animalsniffer")}") // Android API check
classpath("org.jetbrains.kotlin:atomicfu:${version("kotlin")}")
classpath("org.jetbrains.kotlinx:kover-gradle-plugin:${version("kover")}")
-
- // JMH plugins
- classpath("gradle.plugin.com.github.johnrengelman:shadow:${version("shadow")}")
}
with(CacheRedirector) { buildscript.configureBuildScript(rootProject) }
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index d8c7c19735..6d2efb6ea4 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -65,4 +65,5 @@ dependencies {
}
implementation("org.jetbrains.kotlinx:kotlinx-benchmark-plugin:0.4.9")
implementation("org.jetbrains.kotlinx:kotlinx-knit:${version("knit")}")
+ implementation("org.jetbrains.kotlinx:atomicfu-gradle-plugin:${version("atomicfu")}")
}
diff --git a/buildSrc/src/main/kotlin/CommunityProjectsBuild.kt b/buildSrc/src/main/kotlin/CommunityProjectsBuild.kt
index fb86422b5e..0a6b90a5d7 100644
--- a/buildSrc/src/main/kotlin/CommunityProjectsBuild.kt
+++ b/buildSrc/src/main/kotlin/CommunityProjectsBuild.kt
@@ -134,20 +134,16 @@ fun getOverriddenKotlinVersion(project: Project): String? =
/**
* Checks if the project is built with a snapshot version of Kotlin compiler.
*/
-fun isSnapshotTrainEnabled(project: Project): Boolean =
- when (project.rootProject.properties["build_snapshot_train"]) {
- null -> false
- "" -> false
- else -> true
- }
+fun isSnapshotTrainEnabled(project: Project): Boolean {
+ val buildSnapshotTrain = project.rootProject.properties["build_snapshot_train"] as? String
+ return !buildSnapshotTrain.isNullOrBlank()
+}
fun shouldUseLocalMaven(project: Project): Boolean {
- var someDependencyIsSnapshot = false
- project.rootProject.properties.forEach { key, value ->
- if (key.endsWith("_version") && value is String && value.endsWith("-SNAPSHOT")) {
- println("NOTE: USING SNAPSHOT VERSION: $key=$value")
- someDependencyIsSnapshot = true
+ val hasSnapshotDependency = project.rootProject.properties.any { (key, value) ->
+ key.endsWith("_version") && value is String && value.endsWith("-SNAPSHOT").also {
+ if (it) println("NOTE: USING SNAPSHOT VERSION: $key=$value")
}
}
- return isSnapshotTrainEnabled(project) || someDependencyIsSnapshot
+ return hasSnapshotDependency || isSnapshotTrainEnabled(project)
}
diff --git a/buildSrc/src/main/kotlin/atomicfu-conventions.gradle.kts b/buildSrc/src/main/kotlin/atomicfu-conventions.gradle.kts
new file mode 100644
index 0000000000..a499c8ceda
--- /dev/null
+++ b/buildSrc/src/main/kotlin/atomicfu-conventions.gradle.kts
@@ -0,0 +1,8 @@
+plugins {
+ id("org.jetbrains.kotlinx.atomicfu")
+}
+
+// Workaround for KT-71203. Can be removed after https://github.com/Kotlin/kotlinx-atomicfu/issues/431
+atomicfu {
+ transformJs = false
+}
diff --git a/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts b/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts
index c03deb1a02..dfbcd85590 100644
--- a/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/configure-compilation-conventions.gradle.kts
@@ -3,7 +3,7 @@ import org.jetbrains.kotlin.gradle.tasks.*
configure(subprojects) {
val project = this
if (name in sourceless) return@configure
- apply(plugin = "org.jetbrains.kotlinx.atomicfu")
+ apply(plugin = "atomicfu-conventions")
tasks.withType>().configureEach {
val isMainTaskName = name.startsWith("compileKotlin")
compilerOptions {
diff --git a/buildSrc/src/main/kotlin/version-file-conventions.gradle.kts b/buildSrc/src/main/kotlin/version-file-conventions.gradle.kts
index 587e184b30..5a807ef5a7 100644
--- a/buildSrc/src/main/kotlin/version-file-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/version-file-conventions.gradle.kts
@@ -3,10 +3,6 @@ import org.gradle.api.tasks.bundling.*
configure(subprojects.filter { !unpublished.contains(it.name) && it.name !in sourceless }) {
val project = this
val jarTaskName = when {
- project.name == "kotlinx-coroutines-debug" -> {
- project.apply(plugin = "com.github.johnrengelman.shadow")
- "shadowJar"
- }
isMultiplatform -> "jvmJar"
else -> "jar"
}
diff --git a/docs/cfg/buildprofiles.xml b/docs/cfg/buildprofiles.xml
index d4a99434cd..d1a081cad8 100644
--- a/docs/cfg/buildprofiles.xml
+++ b/docs/cfg/buildprofiles.xml
@@ -1,10 +1,9 @@
-
-
-
- true
- https://github.com/Kotlin/kotlinx.coroutines/edit/master/docs/
- true
-
-
-
+
+
+ true
+ https://github.com/Kotlin/kotlinx.coroutines/edit/master/docs/
+ true
+
+
+
\ No newline at end of file
diff --git a/docs/kc.tree b/docs/kc.tree
index 9fa1e11307..0ee8a4ce10 100644
--- a/docs/kc.tree
+++ b/docs/kc.tree
@@ -1,25 +1,19 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/docs/project.ihp b/docs/project.ihp
deleted file mode 100644
index d8da718e83..0000000000
--- a/docs/project.ihp
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/docs/topics/cancellation-and-timeouts.md b/docs/topics/cancellation-and-timeouts.md
index c574e12624..ac99d5fee3 100644
--- a/docs/topics/cancellation-and-timeouts.md
+++ b/docs/topics/cancellation-and-timeouts.md
@@ -1,4 +1,5 @@
+https://github.com/Kotlin/kotlinx.coroutines/edit/master/docs/topics/
[//]: # (title: Cancellation and timeouts)
@@ -31,10 +32,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-01.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-cancel-01.kt).
>
-{type="note"}
+{style="note"}
It produces the following output:
@@ -85,10 +86,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-02.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-cancel-02.kt).
>
-{type="note"}
+{style="note"}
Run it to see that it continues to print "I'm sleeping" even after cancellation
until the job completes by itself after five iterations.
@@ -130,10 +131,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-cancel-03.kt).
>
-{type="note"}
+{style="note"}
While catching `Exception` is an anti-pattern, this issue may surface in more subtle ways, like when using the
[`runCatching`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/run-catching.html) function,
@@ -172,10 +173,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt).
>
-{type="note"}
+{style="note"}
As you can see, now this loop is cancelled. [isActive] is an extension property
available inside the coroutine via the [CoroutineScope] object.
@@ -188,7 +189,7 @@ main: I'm tired of waiting!
main: Now I can quit.
-->
-## Closing resources with `finally`
+## Closing resources with finally
Cancellable suspending functions throw [CancellationException] on cancellation, which can be handled in
the usual way. For example, the `try {...} finally {...}` expression and Kotlin's `use` function execute their
@@ -217,10 +218,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt).
>
-{type="note"}
+{style="note"}
Both [join][Job.join] and [cancelAndJoin] wait for all finalization actions to complete,
so the example above produces the following output:
@@ -272,10 +273,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt).
>
-{type="note"}
+{style="note"}
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt).
>
-{type="note"}
+{style="note"}
It produces the following output:
@@ -353,10 +354,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt).
>
-{type="note"}
+{style="note"}
There is no longer an exception when running this code:
@@ -414,10 +415,10 @@ fun main() {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt).
>
-{type="note"}
+{style="note"}
@@ -428,7 +429,7 @@ of your machine. You may need to tweak the timeout in this example to actually s
> since it always happens from the same thread, the one used by `runBlocking`.
> More on that will be explained in the chapter on coroutine context.
>
-{type="note"}
+{style="note"}
To work around this problem you can store a reference to the resource in a variable instead of returning it
from the `withTimeout` block.
@@ -467,10 +468,10 @@ fun main() {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-10.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-cancel-10.kt).
>
-{type="note"}
+{style="note"}
This example always prints zero. Resources do not leak.
diff --git a/docs/topics/channels.md b/docs/topics/channels.md
index 402fb5a170..090f9e6990 100644
--- a/docs/topics/channels.md
+++ b/docs/topics/channels.md
@@ -1,4 +1,5 @@
+https://github.com/Kotlin/kotlinx.coroutines/edit/master/docs/topics/
[//]: # (title: Channels)
@@ -30,10 +31,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-channel-01.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-01.kt).
>
-{type="note"}
+{style="note"}
The output of this code is:
@@ -76,10 +77,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-channel-02.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-02.kt).
>
-{type="note"}
+{style="note"}
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-03.kt).
>
-{type="note"}
+{style="note"}
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-04.kt).
>
-{type="note"}
+{style="note"}
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-05.kt).
>
-{type="note"}
+{style="note"}
The output of this code is:
@@ -359,10 +360,10 @@ fun CoroutineScope.launchProcessor(id: Int, channel: ReceiveChannel) = laun
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-channel-06.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-06.kt).
>
-{type="note"}
+{style="note"}
The output will be similar to the following one, albeit the processor ids that receive
each specific integer may be different:
@@ -434,10 +435,10 @@ suspend fun sendString(channel: SendChannel, s: String, time: Long) {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-channel-07.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-07.kt).
>
-{type="note"}
+{style="note"}
The output is:
@@ -484,10 +485,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-channel-08.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-08.kt).
>
-{type="note"}
+{style="note"}
It prints "sending" _five_ times using a buffered channel with capacity of _four_:
@@ -537,10 +538,10 @@ suspend fun player(name: String, table: Channel) {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-channel-09.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-09.kt).
>
-{type="note"}
+{style="note"}
The "ping" coroutine is started first, so it is the first one to receive the ball. Even though "ping"
coroutine immediately starts receiving the ball again after sending it back to the table, the ball gets
@@ -601,10 +602,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-channel-10.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-channel-10.kt).
>
-{type="note"}
+{style="note"}
It prints following lines:
diff --git a/docs/topics/composing-suspending-functions.md b/docs/topics/composing-suspending-functions.md
index e1255c8d07..7e8c249f21 100644
--- a/docs/topics/composing-suspending-functions.md
+++ b/docs/topics/composing-suspending-functions.md
@@ -1,4 +1,5 @@
+https://github.com/Kotlin/kotlinx.coroutines/edit/master/docs/topics/
[//]: # (title: Composing suspending functions)
@@ -59,10 +60,10 @@ suspend fun doSomethingUsefulTwo(): Int {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-01.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-compose-01.kt).
>
-{type="note"}
+{style="note"}
It produces something like this:
@@ -110,10 +111,10 @@ suspend fun doSomethingUsefulTwo(): Int {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-02.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-compose-02.kt).
>
-{type="note"}
+{style="note"}
It produces something like this:
@@ -163,10 +164,10 @@ suspend fun doSomethingUsefulTwo(): Int {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-03.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-compose-03.kt).
>
-{type="note"}
+{style="note"}
It produces something like this:
@@ -193,7 +194,7 @@ standard `lazy` function in cases when computation of the value involves suspend
> in other programming languages. Using this style with Kotlin coroutines is **strongly discouraged** for the
> reasons explained below.
>
-{type="note"}
+{style="note"}
We can define async-style functions that invoke `doSomethingUsefulOne` and `doSomethingUsefulTwo`
_asynchronously_ using the [async] coroutine builder using a [GlobalScope] reference to
@@ -205,7 +206,7 @@ to use the resulting deferred value to get the result.
> [GlobalScope] is a delicate API that can backfire in non-trivial ways, one of which will be explained
> below, so you must explicitly opt-in into using `GlobalScope` with `@OptIn(DelicateCoroutinesApi::class)`.
>
-{type="note"}
+{style="note"}
```kotlin
// The result type of somethingUsefulOneAsync is Deferred
@@ -271,10 +272,10 @@ suspend fun doSomethingUsefulTwo(): Int {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt).
>
-{type="note"}
+{style="note"}
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-compose-05.kt).
>
-{type="note"}
+{style="note"}
We still have concurrent execution of both operations, as evident from the output of the above `main` function:
@@ -384,10 +385,10 @@ suspend fun failedConcurrentSum(): Int = coroutineScope {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-compose-06.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-compose-06.kt).
>
-{type="note"}
+{style="note"}
Note how both the first `async` and the awaiting parent are cancelled on failure of one of the children
(namely, `two`):
diff --git a/docs/topics/coroutine-context-and-dispatchers.md b/docs/topics/coroutine-context-and-dispatchers.md
index ffbb364289..89498af00e 100644
--- a/docs/topics/coroutine-context-and-dispatchers.md
+++ b/docs/topics/coroutine-context-and-dispatchers.md
@@ -1,4 +1,5 @@
+https://github.com/Kotlin/kotlinx.coroutines/edit/master/docs/topics/
[//]: # (title: Coroutine context and dispatchers)
@@ -42,10 +43,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-01.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-01.kt).
>
-{type="note"}
+{style="note"}
It produces the following output (maybe in different order):
@@ -104,10 +105,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-02.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-02.kt).
>
-{type="note"}
+{style="note"}
Produces the output:
@@ -129,7 +130,7 @@ function is using.
> because some operation in a coroutine must be performed right away.
> The unconfined dispatcher should not be used in general code.
>
-{type="note"}
+{style="note"}
## Debugging coroutines and threads
@@ -143,7 +144,7 @@ The Coroutine Debugger of the Kotlin plugin simplifies debugging coroutines in I
> Debugging works for versions 1.3.8 or later of `kotlinx-coroutines-core`.
>
-{type="note"}
+{style="note"}
The **Debug** tool window contains the **Coroutines** tab. In this tab, you can find information about both currently running and suspended coroutines.
The coroutines are grouped by the dispatcher they are running on.
@@ -190,10 +191,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-03.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-03.kt).
>
-{type="note"}
+{style="note"}
There are three coroutines. The main coroutine (#1) inside `runBlocking`
and two coroutines computing the deferred values `a` (#2) and `b` (#3).
@@ -215,7 +216,7 @@ is consecutively assigned to all created coroutines when the debugging mode is o
> Debugging mode is also turned on when JVM is run with `-ea` option.
> You can read more about debugging facilities in the documentation of the [DEBUG_PROPERTY_NAME] property.
>
-{type="note"}
+{style="note"}
## Jumping between threads
@@ -240,10 +241,10 @@ fun main() {
}
}
```
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-04.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-04.kt).
>
-{type="note"}
+{style="note"}
It demonstrates several new techniques. One is using [runBlocking] with an explicitly specified context, and
the other one is using the [withContext] function to change the context of a coroutine while still staying in the
@@ -275,10 +276,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-05.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-05.kt).
>
-{type="note"}
+{style="note"}
In the [debug mode](#debugging-coroutines-and-threads), it outputs something like this:
@@ -337,10 +338,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-06.kt).
>
-{type="note"}
+{style="note"}
The output of this code is:
@@ -379,10 +380,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-07.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-07.kt).
>
-{type="note"}
+{style="note"}
The result is going to be:
@@ -430,10 +431,10 @@ fun main() = runBlocking(CoroutineName("main")) {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-08.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-08.kt).
>
-{type="note"}
+{style="note"}
The output it produces with `-Dkotlinx.coroutines.debug` JVM option is similar to:
@@ -464,10 +465,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-09.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-09.kt).
>
-{type="note"}
+{style="note"}
The output of this code with the `-Dkotlinx.coroutines.debug` JVM option is:
@@ -559,10 +560,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-10.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-10.kt).
>
-{type="note"}
+{style="note"}
The output of this example is:
@@ -581,7 +582,7 @@ by a single invocation of `job.cancel()` in `Activity.destroy()`.
> Note, that Android has first-party support for coroutine scope in all entities with the lifecycle.
> See [the corresponding documentation](https://developer.android.com/topic/libraries/architecture/coroutines#lifecyclescope).
>
-{type="note"}
+{style="note"}
### Thread-local data
@@ -614,10 +615,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-context-11.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-context-11.kt).
>
-{type="note"}
+{style="note"}
In this example we launch a new coroutine in a background thread pool using [Dispatchers.Default], so
it works on a different thread from the thread pool, but it still has the value of the thread local variable
diff --git a/docs/topics/coroutines-and-channels.md b/docs/topics/coroutines-and-channels.md
index e3cb7e5a42..45081dc8bc 100644
--- a/docs/topics/coroutines-and-channels.md
+++ b/docs/topics/coroutines-and-channels.md
@@ -1,3 +1,5 @@
+https://github.com/Kotlin/kotlinx.coroutines/edit/master/docs/topics/
+
[//]: # (title: Coroutines and channels − tutorial)
In this tutorial, you'll learn how to use coroutines in IntelliJ IDEA to perform network requests without blocking the
@@ -5,7 +7,7 @@ underlying thread or callbacks.
> No prior knowledge of coroutines is required, but you're expected to be familiar with basic Kotlin syntax.
>
-{type="tip"}
+{style="tip"}
You'll learn:
@@ -18,7 +20,7 @@ this tutorial works similarly for any other libraries that support coroutines.
> You can find solutions for all of the tasks on the `solutions` branch of the [project's repository](http://github.com/kotlin-hands-on/intro-coroutines).
>
-{type="tip"}
+{style="tip"}
## Before you start
@@ -186,13 +188,13 @@ The corresponding test file `test/tasks/AggregationKtTest.kt` shows an example o
> You can jump between the source code and the test class automatically by using the [IntelliJ IDEA shortcut](https://www.jetbrains.com/help/idea/create-tests.html#test-code-navigation)
> `Ctrl+Shift+T` / `⇧ ⌘ T`.
>
-{type="tip"}
+{style="tip"}
After implementing this task, the resulting list for the "kotlin" organization should be similar to the following:
{width=500}
-#### Solution for task 1 {initial-collapse-state="collapsed"}
+#### Solution for task 1 {initial-collapse-state="collapsed" collapsible="true"}
1. To group users by login, use [`groupBy()`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/group-by.html),
which returns a map from a login to all occurrences of the user with this login in different repositories.
@@ -269,7 +271,7 @@ nothing changes.
Fix the `loadContributorsBackground()` function in `src/tasks/Request2Background.kt` so that the resulting list is shown
in the UI.
-#### Solution for task 2 {initial-collapse-state="collapsed"}
+#### Solution for task 2 {initial-collapse-state="collapsed" collapsible="true"}
If you try to load the contributors, you can see in the log that the contributors are loaded but the result isn't displayed.
To fix this, call `updateResults()` on the resulting list of users:
@@ -340,7 +342,7 @@ Think about why the given code doesn't work as expected and try to fix it, or se
Rewrite the code in the `src/tasks/Request3Callbacks.kt` file so that the loaded list of contributors is shown.
-#### The first attempted solution for task 3 {initial-collapse-state="collapsed"}
+#### The first attempted solution for task 3 {initial-collapse-state="collapsed" collapsible="true"}
In the current solution, many requests are started concurrently, which decreases the total loading time. However,
the result isn't loaded. This is because the `updateResults()` callback is called right after all of the loading requests are started,
@@ -369,7 +371,7 @@ for ((index, repo) in repos.withIndex()) { // #1
However, this code also fails to achieve our objective. Try to find the answer yourself, or see the solution below.
-#### The second attempted solution for task 3 {initial-collapse-state="collapsed"}
+#### The second attempted solution for task 3 {initial-collapse-state="collapsed" collapsible="true"}
Since the loading requests are started concurrently, there's no guarantee that the result for the last one comes last. The
results can come in any order.
@@ -401,7 +403,7 @@ for (repo in repos) {
This code uses a synchronized version of the list and `AtomicInteger()` because, in general, there's no guarantee that
different callbacks that process `getRepoContributors()` requests will always be called from the same thread.
-#### The third attempted solution for task 3 {initial-collapse-state="collapsed"}
+#### The third attempted solution for task 3 {initial-collapse-state="collapsed" collapsible="true"}
An even better solution is to use the `CountDownLatch` class. It stores a counter initialized with the number of
repositories. This counter is decremented after processing each repository. It then waits until the latch is counted
@@ -429,7 +431,7 @@ and error-prone, especially when several underlying threads and synchronization
> necessary dependencies and solutions for using RxJava can be found in a separate `rx` branch. It is also possible to
> complete this tutorial and implement or check the proposed Rx versions for a proper comparison.
>
-{type="tip"}
+{style="tip"}
## Suspending functions
@@ -482,7 +484,7 @@ new API.
> result in an error with the message "Suspend function 'getOrgRepos' should be called only from a coroutine or another
> suspend function".
>
-{type="note"}
+{style="note"}
1. Copy the implementation of `loadContributorsBlocking()` that is defined in `src/tasks/Request1Blocking.kt`
into the `loadContributorsSuspend()` that is defined in `src/tasks/Request4Suspend.kt`.
@@ -490,7 +492,7 @@ new API.
3. Run the program by choosing the _SUSPEND_ option and ensure that the UI is still responsive while the GitHub requests
are performed.
-#### Solution for task 4 {initial-collapse-state="collapsed"}
+#### Solution for task 4 {initial-collapse-state="collapsed" collapsible="true"}
Replace `.getOrgReposCall(req.org).execute()` with `.getOrgRepos(req.org)` and repeat the same replacement for the
second "contributors" request:
@@ -528,7 +530,7 @@ thread -> coroutine
> Coroutines are often called lightweight threads because you can run code on coroutines, similar to how you run code on
> threads. The operations that were blocking before (and had to be avoided) can now suspend the coroutine instead.
>
-{type="note"}
+{style="note"}
### Starting a new coroutine
@@ -640,7 +642,7 @@ tests.
> Watch [this video](https://www.youtube.com/watch?v=zEZc5AmHQhk) for a better understanding of coroutines.
>
-{type="tip"}
+{style="tip"}
If there is a list of deferred objects, you can call `awaitAll()` to await the results of all of them:
@@ -673,7 +675,7 @@ What's more, `async` explicitly emphasizes which parts run concurrently in the c
In the `Request5Concurrent.kt` file, implement a `loadContributorsConcurrent()` function by using the
previous `loadContributorsSuspend()` function.
-#### Tip for task 5 {initial-collapse-state="collapsed"}
+#### Tip for task 5 {initial-collapse-state="collapsed" collapsible="true"}
You can only start a new coroutine inside a coroutine scope. Copy the content
from `loadContributorsSuspend()` to the `coroutineScope` call so that you can call `async` functions there:
@@ -698,7 +700,7 @@ val deferreds: List>> = repos.map { repo ->
deferreds.awaitAll() // List>
```
-#### Solution for task 5 {initial-collapse-state="collapsed"}
+#### Solution for task 5 {initial-collapse-state="collapsed" collapsible="true"}
Wrap each "contributors" request with `async` to create as many coroutines as there are repositories. `async`
returns `Deferred>`. This is not an issue because creating new coroutines is not very resource-intensive, so you can
@@ -1042,7 +1044,7 @@ top-level coroutine. All the nested coroutines then inherit the context and modi
> use `CoroutineDispatchers.Main` by default for the top coroutine and then to explicitly put a different dispatcher when
> you need to run the code on a different thread.
>
-{type="tip"}
+{style="tip"}
## Showing progress
@@ -1098,7 +1100,7 @@ progress. Base it on the `loadContributorsSuspend()` function from `Request4Susp
* The total number of contributions for each user should be increased when the data for each new
repository is loaded.
-#### Solution for task 6 {initial-collapse-state="collapsed"}
+#### Solution for task 6 {initial-collapse-state="collapsed" collapsible="true"}
To store the intermediate list of loaded contributors in the "aggregated" state, define an `allUsers` variable which
stores the list of users, and then update it after contributors for each new repository are loaded:
@@ -1267,7 +1269,7 @@ fun log(message: Any?) {
> Watch [this video](https://www.youtube.com/watch?v=HpWQUoVURWQ) for a better understanding of channels.
>
-{type="tip"}
+{style="tip"}
### Task 7
@@ -1277,7 +1279,7 @@ contributors concurrently and shows intermediate progress at the same time.
Use the previous functions, `loadContributorsConcurrent()` from `Request5Concurrent.kt`
and `loadContributorsProgress()` from `Request6Progress.kt`.
-#### Tip for task 7 {initial-collapse-state="collapsed"}
+#### Tip for task 7 {initial-collapse-state="collapsed" collapsible="true"}
Different coroutines that concurrently receive contributor lists for different repositories can send all of the received
results to the same channel:
@@ -1304,7 +1306,7 @@ repeat(repos.size) {
Since the `receive()` calls are sequential, no additional synchronization is needed.
-#### Solution for task 7 {initial-collapse-state="collapsed"}
+#### Solution for task 7 {initial-collapse-state="collapsed" collapsible="true"}
As with the `loadContributorsProgress()` function, you can create an `allUsers` variable to store the intermediate
states of the "all contributors" list.
@@ -1459,7 +1461,7 @@ which allows for more flexibility and easier testing.
> The testing API that supports virtual time is [Experimental](components-stability.md) and may change in the future.
>
-{type="warning"}
+{style="warning"}
By default, the compiler shows warnings if you use the experimental testing API. To suppress these warnings, annotate
the test function or the whole class containing the tests with `@OptIn(ExperimentalCoroutinesApi::class)`.
@@ -1486,7 +1488,7 @@ Refactor the following tests in `tests/tasks/` to use virtual time instead of re
Compare the total running times before and after applying your refactoring.
-#### Tip for task 8 {initial-collapse-state="collapsed"}
+#### Tip for task 8 {initial-collapse-state="collapsed" collapsible="true"}
1. Replace the `runBlocking` invocation with `runTest`, and replace `System.currentTimeMillis()` with `currentTime`:
@@ -1503,7 +1505,7 @@ Compare the total running times before and after applying your refactoring.
2. Uncomment the assertions that check the exact virtual time.
3. Don't forget to add `@UseExperimental(ExperimentalCoroutinesApi::class)`.
-#### Solution for task 8 {initial-collapse-state="collapsed"}
+#### Solution for task 8 {initial-collapse-state="collapsed" collapsible="true"}
Here are the solutions for the concurrent and channels cases:
@@ -1547,7 +1549,7 @@ can see the difference in tests that use virtual time.
> The tests for the remaining "suspend" and "progress" tasks are very similar – you can find them in the project's
> `solutions` branch.
>
-{type="tip"}
+{style="tip"}
## What's next
diff --git a/docs/topics/coroutines-basics.md b/docs/topics/coroutines-basics.md
index 8a5b4304ad..68a1a01957 100644
--- a/docs/topics/coroutines-basics.md
+++ b/docs/topics/coroutines-basics.md
@@ -1,4 +1,5 @@
+https://github.com/Kotlin/kotlinx.coroutines/edit/master/docs/topics/
[//]: # (title: Coroutines basics)
@@ -29,10 +30,10 @@ fun main() = runBlocking { // this: CoroutineScope
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-basic-01.kt).
>
-{type="note"}
+{style="note"}
You will see the following result:
@@ -59,7 +60,7 @@ the code with coroutines inside of `runBlocking { ... }` curly braces. This is h
If you remove or forget `runBlocking` in this code, you'll get an error on the [launch] call, since `launch`
is declared only on the [CoroutineScope]:
-```Plain Text
+```
Unresolved reference: launch
```
@@ -104,10 +105,10 @@ suspend fun doWorld() {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-basic-02.kt).
>
-{type="note"}
+{style="note"}
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-basic-03.kt).
>
-{type="note"}
+{style="note"}
This code also prints:
@@ -189,10 +190,10 @@ suspend fun doWorld() = coroutineScope { // this: CoroutineScope
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-basic-04.kt).
>
-{type="note"}
+{style="note"}
Both pieces of code inside `launch { ... }` blocks execute _concurrently_, with
`World 1` printed first, after a second from start, and `World 2` printed next, after two seconds from start.
@@ -230,10 +231,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-basic-05.kt).
>
-{type="note"}
+{style="note"}
This code produces:
@@ -266,10 +267,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt).
>
-{type="note"}
+{style="note"}
diff --git a/docs/topics/coroutines-guide.md b/docs/topics/coroutines-guide.md
index fd95c38dee..a77acf43f8 100644
--- a/docs/topics/coroutines-guide.md
+++ b/docs/topics/coroutines-guide.md
@@ -1,3 +1,6 @@
+https://github.com/Kotlin/kotlinx.coroutines/edit/master/docs/topics/
+
+
[//]: # (title: Coroutines guide)
Kotlin provides only minimal low-level APIs in its standard library to enable other
diff --git a/docs/topics/debug-coroutines-with-idea.md b/docs/topics/debug-coroutines-with-idea.md
index b31aa79f51..9ac49fddbb 100644
--- a/docs/topics/debug-coroutines-with-idea.md
+++ b/docs/topics/debug-coroutines-with-idea.md
@@ -1,3 +1,6 @@
+https://github.com/Kotlin/kotlinx.coroutines/edit/master/docs/topics/
+
+
[//]: # (title: Debug coroutines using IntelliJ IDEA – tutorial)
This tutorial demonstrates how to create Kotlin coroutines and debug them using IntelliJ IDEA.
@@ -113,4 +116,4 @@ You can disable this behavior with the `-Xdebug` compiler option.
> __Never use this flag in production__: `-Xdebug` can [cause memory leaks](https://youtrack.jetbrains.com/issue/KT-48678/Coroutine-debugger-disable-was-optimised-out-compiler-feature#focus=Comments-27-6015585.0-0).
>
-{type="warning"}
\ No newline at end of file
+{style="warning"}
\ No newline at end of file
diff --git a/docs/topics/debug-flow-with-idea.md b/docs/topics/debug-flow-with-idea.md
index 0aa78b1780..4e2541bc89 100644
--- a/docs/topics/debug-flow-with-idea.md
+++ b/docs/topics/debug-flow-with-idea.md
@@ -1,3 +1,6 @@
+https://github.com/Kotlin/kotlinx.coroutines/edit/master/docs/topics/
+
+
[//]: # (title: Debug Kotlin Flow using IntelliJ IDEA – tutorial)
This tutorial demonstrates how to create Kotlin Flow and debug it using IntelliJ IDEA.
@@ -114,7 +117,7 @@ You can disable this behavior with the `-Xdebug` compiler option.
> __Never use this flag in production__: `-Xdebug` can [cause memory leaks](https://youtrack.jetbrains.com/issue/KT-48678/Coroutine-debugger-disable-was-optimised-out-compiler-feature#focus=Comments-27-6015585.0-0).
>
-{type="warning"}
+{style="warning"}
## Add a concurrently running coroutine
diff --git a/docs/topics/exception-handling.md b/docs/topics/exception-handling.md
index f79740c95f..936688ebf3 100644
--- a/docs/topics/exception-handling.md
+++ b/docs/topics/exception-handling.md
@@ -1,4 +1,5 @@
+https://github.com/Kotlin/kotlinx.coroutines/edit/master/docs/topics/
[//]: # (title: Coroutine exceptions handling)
@@ -23,7 +24,7 @@ It can be demonstrated by a simple example that creates root coroutines using th
> whole application is one of the rare legitimate uses for `GlobalScope`, so you must explicitly opt-in into
> using `GlobalScope` with `@OptIn(DelicateCoroutinesApi::class)`.
>
-{type="note"}
+{style="note"}
```kotlin
import kotlinx.coroutines.*
@@ -51,10 +52,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt).
>
-{type="note"}
+{style="note"}
The output of this code is (with [debug](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/coroutine-context-and-dispatchers.md#debugging-coroutines-and-threads)):
@@ -89,7 +90,7 @@ so its `CoroutineExceptionHandler` has no effect either.
> Coroutines running in supervision scope do not propagate exceptions to their parent and are
> excluded from this rule. A further [Supervision](#supervision) section of this document gives more details.
>
-{type="note"}
+{style="note"}
```kotlin
import kotlinx.coroutines.*
@@ -111,10 +112,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt).
>
-{type="note"}
+{style="note"}
The output of this code is:
@@ -156,10 +157,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-03.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-03.kt).
>
-{type="note"}
+{style="note"}
The output of this code is:
@@ -181,7 +182,7 @@ This behaviour cannot be overridden and is used to provide stable coroutines hie
> is launched in the scope of the main [runBlocking], since the main coroutine is going to be always cancelled
> when its child completes with exception despite the installed handler.
>
-{type="note"}
+{style="note"}
The original exception is handled by the parent only when all its children terminate,
which is demonstrated by the following example.
@@ -218,10 +219,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt).
>
-{type="note"}
+{style="note"}
The output of this code is:
@@ -271,10 +272,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt).
>
-{type="note"}
+{style="note"}
The output of this code is:
@@ -287,7 +288,7 @@ CoroutineExceptionHandler got java.io.IOException with suppressed [java.lang.Ari
> Note that this mechanism currently only works on Java version 1.7+.
> The JS and Native restrictions are temporary and will be lifted in the future.
>
-{type="note"}
+{style="note"}
Cancellation exceptions are transparent and are unwrapped by default:
@@ -321,10 +322,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt).
>
-{type="note"}
+{style="note"}
The output of this code is:
@@ -387,10 +388,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-supervision-01.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-supervision-01.kt).
>
-{type="note"}
+{style="note"}
The output of this code is:
@@ -437,10 +438,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-supervision-02.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-supervision-02.kt).
>
-{type="note"}
+{style="note"}
The output of this code is:
@@ -483,10 +484,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-supervision-03.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-supervision-03.kt).
>
-{type="note"}
+{style="note"}
The output of this code is:
diff --git a/docs/topics/flow.md b/docs/topics/flow.md
index c436c2cfc9..3f8c694943 100644
--- a/docs/topics/flow.md
+++ b/docs/topics/flow.md
@@ -1,4 +1,5 @@
+https://github.com/Kotlin/kotlinx.coroutines/edit/master/docs/topics/
[//]: # (title: Asynchronous Flow)
@@ -19,10 +20,10 @@ fun main() {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-01.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-01.kt).
>
-{type="note"}
+{style="note"}
This code outputs:
@@ -52,10 +53,10 @@ fun main() {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-02.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-02.kt).
>
-{type="note"}
+{style="note"}
This code outputs the same numbers, but it waits 100ms before printing each one.
@@ -86,10 +87,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-03.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-03.kt).
>
-{type="note"}
+{style="note"}
This code prints the numbers after waiting for a second.
@@ -130,10 +131,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-04.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-04.kt).
>
-{type="note"}
+{style="note"}
This code waits 100ms before printing each number without blocking the main thread. This is verified
by printing "I'm not blocked" every 100ms from a separate coroutine that is running in the main thread:
@@ -160,7 +161,7 @@ Notice the following differences in the code with the [Flow] from the earlier ex
> We can replace [delay] with `Thread.sleep` in the body of `simple`'s `flow { ... }` and see that the main
> thread is blocked in this case.
>
-{type="note"}
+{style="note"}
## Flows are cold
@@ -191,10 +192,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-05.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-05.kt).
>
-{type="note"}
+{style="note"}
Which prints:
@@ -247,10 +248,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-06.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-06.kt).
>
-{type="note"}
+{style="note"}
Notice how only two numbers get emitted by the flow in the `simple` function, producing the following output:
@@ -288,10 +289,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-07.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-07.kt).
>
-{type="note"}
+{style="note"}
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-08.kt).
>
-{type="note"}
+{style="note"}
It produces the following three lines, each appearing one second after the previous:
@@ -378,10 +379,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-09.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-09.kt).
>
-{type="note"}
+{style="note"}
The output of this code is:
@@ -426,10 +427,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-10.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-10.kt).
>
-{type="note"}
+{style="note"}
The output of this code clearly shows that the execution of the `flow { ... }` body in the `numbers()` function
stopped after emitting the second number:
@@ -467,10 +468,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-11.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-11.kt).
>
-{type="note"}
+{style="note"}
Prints a single number:
@@ -511,10 +512,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-12.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-12.kt).
>
-{type="note"}
+{style="note"}
Producing:
@@ -574,10 +575,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-13.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-13.kt).
>
-{type="note"}
+{style="note"}
Running this code produces:
@@ -624,10 +625,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-14.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-14.kt).
>
-{type="note"}
+{style="note"}
This code produces the following exception:
@@ -670,10 +671,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-15.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-15.kt).
>
-{type="note"}
+{style="note"}
Notice how `flow { ... }` works in the background thread, while collection happens in the main thread:
@@ -725,10 +726,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-16.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-16.kt).
>
-{type="note"}
+{style="note"}
It produces something like this, with the whole collection taking around 1200 ms (three numbers, 400 ms for each):
@@ -771,10 +772,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-17.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-17.kt).
>
-{type="note"}
+{style="note"}
It produces the same numbers just faster, as we have effectively created a processing pipeline,
having to only wait 100 ms for the first number and then spending only 300 ms to process
@@ -792,7 +793,7 @@ Collected in 1071 ms
> Note that the [flowOn] operator uses the same buffering mechanism when it has to change a [CoroutineDispatcher],
> but here we explicitly request buffering without changing the execution context.
>
-{type="note"}
+{style="note"}
### Conflation
@@ -827,10 +828,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-18.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-18.kt).
>
-{type="note"}
+{style="note"}
We see that while the first number was still being processed the second, and third were already produced, so
the second one was _conflated_ and only the most recent (the third one) was delivered to the collector:
@@ -877,10 +878,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-19.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-19.kt).
>
-{type="note"}
+{style="note"}
Since the body of [collectLatest] takes 300 ms, but new values are emitted every 100 ms, we see that the block
is run on every value, but completes only for the last value:
@@ -918,10 +919,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-20.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-20.kt).
>
-{type="note"}
+{style="note"}
This example prints:
@@ -947,7 +948,7 @@ albeit results that are printed every 400 ms:
> We use a [onEach] intermediate operator in this example to delay each element and make the code
> that emits sample flows more declarative and shorter.
>
-{type="note"}
+{style="note"}
```kotlin
import kotlinx.coroutines.*
@@ -966,10 +967,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-21.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-21.kt).
>
-{type="note"}
+{style="note"}
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-22.kt).
>
-{type="note"}
+{style="note"}
We get quite a different output, where a line is printed at each emission from either `nums` or `strs` flows:
@@ -1070,10 +1071,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-23.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-23.kt).
>
-{type="note"}
+{style="note"}
The sequential nature of [flatMapConcat] is clearly seen in the output:
@@ -1118,10 +1119,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-24.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-24.kt).
>
-{type="note"}
+{style="note"}
The concurrent nature of [flatMapMerge] is obvious:
@@ -1140,7 +1141,7 @@ The concurrent nature of [flatMapMerge] is obvious:
> collects the resulting flows concurrently, it is the equivalent of performing a sequential
> `map { requestFlow(it) }` first and then calling [flattenMerge] on the result.
>
-{type="note"}
+{style="note"}
### flatMapLatest
@@ -1171,10 +1172,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-25.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-25.kt).
>
-{type="note"}
+{style="note"}
The output here in this example is a good demonstration of how [flatMapLatest] works:
@@ -1193,7 +1194,7 @@ The output here in this example is a good demonstration of how [flatMapLatest] w
> and cannot be cancelled. However, a differnce in output would be visible if we were to use suspending functions
> like `delay` in `requestFlow`.
>
-{type="note"}
+{style="note"}
## Flow exceptions
@@ -1229,10 +1230,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-26.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-26.kt).
>
-{type="note"}
+{style="note"}
This code successfully catches an exception in [collect] terminal operator and,
as we see, no more values are emitted after that:
@@ -1280,10 +1281,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-27.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-27.kt).
>
-{type="note"}
+{style="note"}
This exception is still caught and collection is stopped:
@@ -1339,10 +1340,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-28.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-28.kt).
>
-{type="note"}
+{style="note"}
The output of the example is the same, even though we do not have `try/catch` around the code anymore.
@@ -1382,10 +1383,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-29.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-29.kt).
>
-{type="note"}
+{style="note"}
A "Caught ..." message is not printed despite there being a `catch` operator:
@@ -1429,10 +1430,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-30.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-30.kt).
>
-{type="note"}
+{style="note"}
Now we can see that a "Caught ..." message is printed and so we can catch all the exceptions without explicitly
using a `try/catch` block:
@@ -1473,10 +1474,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-31.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-31.kt).
>
-{type="note"}
+{style="note"}
This code prints three numbers produced by the `simple` flow followed by a "Done" string:
@@ -1511,10 +1512,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-32.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-32.kt).
>
-{type="note"}
+{style="note"}
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-33.kt).
>
-{type="note"}
+{style="note"}
As you may expect, it prints:
@@ -1588,10 +1589,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-34.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-34.kt).
>
-{type="note"}
+{style="note"}
We can see the completion cause is not null, because the flow was aborted due to downstream exception:
@@ -1637,10 +1638,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-35.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-35.kt).
>
-{type="note"}
+{style="note"}
As you can see, it prints:
@@ -1674,10 +1675,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-36.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-36.kt).
>
-{type="note"}
+{style="note"}
It prints:
@@ -1730,10 +1731,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-37.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-37.kt).
>
-{type="note"}
+{style="note"}
We get only numbers up to 3 and a [CancellationException] after trying to emit number 4:
@@ -1768,10 +1769,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-38.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-38.kt).
>
-{type="note"}
+{style="note"}
All numbers from 1 to 5 are collected and cancellation gets detected only before return from `runBlocking`:
@@ -1806,10 +1807,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code from [here](../../kotlinx-coroutines-core/jvm/test/guide/example-flow-39.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-flow-39.kt).
>
-{type="note"}
+{style="note"}
With the `cancellable` operator only the numbers from 1 to 3 are collected:
diff --git a/docs/topics/select-expression.md b/docs/topics/select-expression.md
index 0e95ab63ea..ded445b260 100644
--- a/docs/topics/select-expression.md
+++ b/docs/topics/select-expression.md
@@ -1,4 +1,5 @@
+https://github.com/Kotlin/kotlinx.coroutines/edit/master/docs/topics/
[//]: # (title: Select expression \(experimental\))
@@ -9,7 +10,7 @@ the first one that becomes available.
> evolve in the upcoming updates of the `kotlinx.coroutines` library with potentially
> breaking changes.
>
-{type="note"}
+{style="note"}
## Selecting from channels
@@ -98,10 +99,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-select-01.kt).
>
-{type="note"}
+{style="note"}
The result of this code is:
@@ -193,10 +194,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-select-02.kt).
>
-{type="note"}
+{style="note"}
The result of this code is quite interesting, so we'll analyze it in more detail:
@@ -278,10 +279,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-select-03.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-select-03.kt).
>
-{type="note"}
+{style="note"}
So let us see what happens:
@@ -362,10 +363,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-select-04.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-select-04.kt).
>
-{type="note"}
+{style="note"}
The output is:
@@ -471,10 +472,10 @@ fun main() = runBlocking {
}
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-select-05.kt).
>
-{type="note"}
+{style="note"}
The result of this code:
diff --git a/docs/topics/shared-mutable-state-and-concurrency.md b/docs/topics/shared-mutable-state-and-concurrency.md
index fad13d64bc..133c9e2cfe 100644
--- a/docs/topics/shared-mutable-state-and-concurrency.md
+++ b/docs/topics/shared-mutable-state-and-concurrency.md
@@ -1,4 +1,5 @@
+https://github.com/Kotlin/kotlinx.coroutines/edit/master/docs/topics/
[//]: # (title: Shared mutable state and concurrency)
@@ -67,10 +68,10 @@ fun main() = runBlocking {
//sampleEnd
```
{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
-
-> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-sync-01.kt).
+
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-sync-01.kt).
>
-{type="note"}
+{style="note"}
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-sync-02.kt).
>
-{type="note"}
+{style="note"}
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-sync-03.kt).
>
-{type="note"}
+{style="note"}
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-sync-04.kt).
>
-{type="note"}
+{style="note"}
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-sync-05.kt).
>
-{type="note"}
+{style="note"}
+> You can get the full code [here](https://github.com/Kotlin/kotlinx.coroutines/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-sync-06.kt).
>
-{type="note"}
+{style="note"}
| Resumed |
@@ -45,18 +113,24 @@ import kotlin.coroutines.intrinsics.*
@SubclassOptInRequired(InternalForInheritanceCoroutinesApi::class)
public interface CancellableContinuation : Continuation {
/**
- * Returns `true` when this continuation is active -- it has not completed or cancelled yet.
+ * Returns `true` when this continuation is active -- it was created,
+ * but not yet [resumed][Continuation.resumeWith] or [cancelled][CancellableContinuation.cancel].
+ *
+ * This state implies that [isCompleted] and [isCancelled] are `false`,
+ * but this can change immediately after the invocation because of parallel calls to [cancel] and [resume].
*/
public val isActive: Boolean
/**
- * Returns `true` when this continuation has completed for any reason. A cancelled continuation
- * is also considered complete.
+ * Returns `true` when this continuation was completed -- [resumed][Continuation.resumeWith] or
+ * [cancelled][CancellableContinuation.cancel].
+ *
+ * This state implies that [isActive] is `false`.
*/
public val isCompleted: Boolean
/**
- * Returns `true` if this continuation was [cancelled][cancel].
+ * Returns `true` if this continuation was [cancelled][CancellableContinuation.cancel].
*
* It implies that [isActive] is `false` and [isCompleted] is `true`.
*/
@@ -124,6 +198,7 @@ public interface CancellableContinuation : Continuation {
/**
* Cancels this continuation with an optional cancellation `cause`. The result is `true` if this continuation was
* cancelled as a result of this invocation, and `false` otherwise.
+ * [cancel] might return `false` when the continuation was either [resumed][resume] or already [cancelled][cancel].
*/
public fun cancel(cause: Throwable? = null): Boolean
@@ -243,7 +318,7 @@ internal fun CancellableContinuation.invokeOnCancellation(handler: Cancel
/**
* Suspends the coroutine like [suspendCoroutine], but providing a [CancellableContinuation] to
* the [block]. This function throws a [CancellationException] if the [Job] of the coroutine is
- * cancelled or completed while it is suspended.
+ * cancelled or completed while it is suspended, or if [CancellableContinuation.cancel] is invoked.
*
* A typical use of this function is to suspend a coroutine while waiting for a result
* from a single-shot callback API and to return the result to the caller.
diff --git a/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt
index c768a6ea0f..3dc07f1e0f 100644
--- a/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt
+++ b/kotlinx-coroutines-core/common/src/CancellableContinuationImpl.kt
@@ -210,7 +210,7 @@ internal open class CancellableContinuationImpl(
is Segment<*> -> callSegmentOnCancellation(state, cause)
}
// Complete state update
- detachChildIfNonResuable()
+ detachChildIfNonReusable()
dispatchResume(resumeMode) // no need for additional cancellation checks
return true
}
@@ -220,7 +220,7 @@ internal open class CancellableContinuationImpl(
if (cancelLater(cause)) return
cancel(cause)
// Even if cancellation has failed, we should detach child to avoid potential leak
- detachChildIfNonResuable()
+ detachChildIfNonReusable()
}
private inline fun callCancelHandlerSafely(block: () -> Unit) {
@@ -500,7 +500,7 @@ internal open class CancellableContinuationImpl(
is NotCompleted -> {
val update = resumedState(state, proposedUpdate, resumeMode, onCancellation, idempotent = null)
if (!_state.compareAndSet(state, update)) return@loop // retry on cas failure
- detachChildIfNonResuable()
+ detachChildIfNonReusable()
dispatchResume(resumeMode) // dispatch resume, but it might get cancelled in process
return // done
}
@@ -536,7 +536,7 @@ internal open class CancellableContinuationImpl(
is NotCompleted -> {
val update = resumedState(state, proposedUpdate, resumeMode, onCancellation, idempotent)
if (!_state.compareAndSet(state, update)) return@loop // retry on cas failure
- detachChildIfNonResuable()
+ detachChildIfNonReusable()
return RESUME_TOKEN
}
is CompletedContinuation<*> -> {
@@ -557,7 +557,7 @@ internal open class CancellableContinuationImpl(
}
// Unregister from parent job
- private fun detachChildIfNonResuable() {
+ private fun detachChildIfNonReusable() {
// If instance is reusable, do not detach on every reuse, #releaseInterceptedContinuation will do it for us in the end
if (!isReusable()) detachChild()
}
diff --git a/kotlinx-coroutines-core/common/src/CompletableDeferred.kt b/kotlinx-coroutines-core/common/src/CompletableDeferred.kt
index 53c3da839a..2788ce8298 100644
--- a/kotlinx-coroutines-core/common/src/CompletableDeferred.kt
+++ b/kotlinx-coroutines-core/common/src/CompletableDeferred.kt
@@ -17,7 +17,7 @@ import kotlinx.coroutines.selects.*
* be safely invoked from concurrent coroutines without external synchronization.
*/
@OptIn(ExperimentalSubclassOptIn::class)
-@SubclassOptInRequired(markerClass = InternalForInheritanceCoroutinesApi::class)
+@SubclassOptInRequired(InternalForInheritanceCoroutinesApi::class)
public interface CompletableDeferred : Deferred {
/**
* Completes this deferred value with a given [value]. The result is `true` if this deferred was
diff --git a/kotlinx-coroutines-core/common/src/CompletableJob.kt b/kotlinx-coroutines-core/common/src/CompletableJob.kt
index 911f75fa5d..b484bebee7 100644
--- a/kotlinx-coroutines-core/common/src/CompletableJob.kt
+++ b/kotlinx-coroutines-core/common/src/CompletableJob.kt
@@ -11,7 +11,7 @@ package kotlinx.coroutines
* as new methods might be added to this interface in the future, but is stable for use.
*/
@OptIn(ExperimentalSubclassOptIn::class)
-@SubclassOptInRequired(markerClass = InternalForInheritanceCoroutinesApi::class)
+@SubclassOptInRequired(InternalForInheritanceCoroutinesApi::class)
public interface CompletableJob : Job {
/**
* Completes this job. The result is `true` if this job was completed as a result of this invocation and
diff --git a/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
index 37b68760a0..45573f30cc 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
@@ -222,10 +222,13 @@ public abstract class CoroutineDispatcher :
* Though the `yield` marker may be passed as a part of [context], this
* is a separate method for performance reasons.
*
+ * Implementation note: this entry-point is used for `Dispatchers.IO` and [Dispatchers.Default]
+ * unerlying implementations, see overrides for this method.
+ *
* @suppress **This an internal API and should not be used from general code.**
*/
@InternalCoroutinesApi
- public open fun dispatchYield(context: CoroutineContext, block: Runnable): Unit = dispatch(context, block)
+ public open fun dispatchYield(context: CoroutineContext, block: Runnable): Unit = safeDispatch(context, block)
/**
* Returns a continuation that wraps the provided [continuation], thus intercepting all resumptions.
diff --git a/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt b/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt
index e6f1d9e63c..0899eb6fb6 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineExceptionHandler.kt
@@ -16,18 +16,19 @@ import kotlin.coroutines.*
*/
@InternalCoroutinesApi
public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
+ val reportException = if (exception is DispatchException) exception.cause else exception
// Invoke an exception handler from the context if present
try {
context[CoroutineExceptionHandler]?.let {
- it.handleException(context, exception)
+ it.handleException(context, reportException)
return
}
} catch (t: Throwable) {
- handleUncaughtCoroutineException(context, handlerException(exception, t))
+ handleUncaughtCoroutineException(context, handlerException(reportException, t))
return
}
// If a handler is not present in the context or an exception was thrown, fallback to the global handler
- handleUncaughtCoroutineException(context, exception)
+ handleUncaughtCoroutineException(context, reportException)
}
internal fun handlerException(originalException: Throwable, thrownException: Throwable): Throwable {
diff --git a/kotlinx-coroutines-core/common/src/CoroutineScope.kt b/kotlinx-coroutines-core/common/src/CoroutineScope.kt
index 2d37d15bb7..973429ca06 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineScope.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineScope.kt
@@ -87,7 +87,13 @@ public interface CoroutineScope {
* Adds the specified coroutine context to this scope, overriding existing elements in the current
* scope's context with the corresponding keys.
*
- * This is a shorthand for `CoroutineScope(thisScope.coroutineContext + context)`.
+ * This is a shorthand for `CoroutineScope(thisScope.coroutineContext + context)` and can be used as
+ * a combinator with existing constructors:
+ * ```
+ * class MyActivity {
+ * val uiScope = MainScope() + CoroutineName("MyActivity")
+ * }
+ * ```
*/
public operator fun CoroutineScope.plus(context: CoroutineContext): CoroutineScope =
ContextScope(coroutineContext + context)
@@ -117,13 +123,30 @@ public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatch
/**
* Returns `true` when the current [Job] is still active (has not completed and was not cancelled yet).
*
- * Check this property in long-running computation loops to support cancellation:
+ * Coroutine cancallation [is cooperative](https://kotlinlang.org/docs/cancellation-and-timeouts.html#cancellation-is-cooperative)
+ * and normally, it's checked if a coroutine is cancelled when it *suspends*, for example,
+ * when trying to read from a [channel][kotlinx.coroutines.channels.Channel] that is empty.
+ *
+ * Sometimes, a coroutine does not need to perform suspending operations, but still wants to be cooperative
+ * and respect cancellation.
+ *
+ * The [isActive] property is inteded to be used for scenarios like this:
* ```
- * while (isActive) {
- * // do some computation
+ * val watchdogDispatcher = Dispatchers.IO.limitParallelism(1)
+ * fun backgroundWork() {
+ * println("Doing bookkeeping in the background in a non-suspending manner")
+ * Thread.sleep(100L) // Sleep 100ms
+ * }
+ * // Part of some non-trivial CoroutineScope-confined lifecycle
+ * launch(watchdogDispatcher) {
+ * while (isActive) {
+ * // Repetitively do some background work that is non-suspending
+ * backgroundWork()
+ * }
* }
* ```
*
+ * This function returns `true` if there is no [job][Job] in the scope's [coroutineContext][CoroutineScope.coroutineContext].
* This property is a shortcut for `coroutineContext.isActive` in the scope when
* [CoroutineScope] is available.
* See [coroutineContext][kotlin.coroutines.coroutineContext],
@@ -258,6 +281,7 @@ public suspend fun coroutineScope(block: suspend CoroutineScope.() -> R): R
}
return suspendCoroutineUninterceptedOrReturn { uCont ->
val coroutine = ScopeCoroutine(uCont.context, uCont)
+ @Suppress("LEAKED_IN_PLACE_LAMBDA") // Contract is preserved, invoked immediately or throws
coroutine.startUndispatchedOrReturn(coroutine, block)
}
}
@@ -292,35 +316,59 @@ public fun CoroutineScope.cancel(cause: CancellationException? = null) {
public fun CoroutineScope.cancel(message: String, cause: Throwable? = null): Unit = cancel(CancellationException(message, cause))
/**
- * Ensures that current scope is [active][CoroutineScope.isActive].
+ * Throws the [CancellationException] that was the scope's cancellation cause if the scope is no longer [active][CoroutineScope.isActive].
*
- * If the job is no longer active, throws [CancellationException].
- * If the job was cancelled, thrown exception contains the original cancellation cause.
- * This function does not do anything if there is no [Job] in the scope's [coroutineContext][CoroutineScope.coroutineContext].
+ * Coroutine cancallation [is cooperative](https://kotlinlang.org/docs/cancellation-and-timeouts.html#cancellation-is-cooperative)
+ * and normally, it's checked if a coroutine is cancelled when it *suspends*, for example,
+ * when trying to read from a [channel][kotlinx.coroutines.channels.Channel] that is empty.
+ *
+ * Sometimes, a coroutine does not need to perform suspending operations, but still wants to be cooperative
+ * and respect cancellation.
*
- * This method is a drop-in replacement for the following code, but with more precise exception:
+ * [ensureActive] function is inteded to be used for these scenarios and immediately bubble up the cancellation exception:
* ```
- * if (!isActive) {
- * throw CancellationException()
+ * val watchdogDispatcher = Dispatchers.IO.limitParallelism(1)
+ * fun backgroundWork() {
+ * println("Doing bookkeeping in the background in a non-suspending manner")
+ * Thread.sleep(100L) // Sleep 100ms
+ * }
+ * fun postBackgroundCleanup() = println("Doing something else")
+ * // Part of some non-trivial CoroutineScope-confined lifecycle
+ * launch(watchdogDispatcher) {
+ * while (true) {
+ * // Repeatatively do some background work that is non-suspending
+ * backgroundWork()
+ * ensureActive() // Bail out if the scope was cancelled
+ * postBackgroundCleanup() // Won't be invoked if the scope was cancelled
+ * }
* }
* ```
+ * This function does not do anything if there is no [Job] in the scope's [coroutineContext][CoroutineScope.coroutineContext].
*
+ * @see CoroutineScope.isActive
* @see CoroutineContext.ensureActive
*/
public fun CoroutineScope.ensureActive(): Unit = coroutineContext.ensureActive()
-
/**
* Returns the current [CoroutineContext] retrieved by using [kotlin.coroutines.coroutineContext].
- * This function is an alias to avoid name clash with [CoroutineScope.coroutineContext] in a receiver position:
+ * This function is an alias to avoid name clash with [CoroutineScope.coroutineContext]:
*
* ```
- * launch { // this: CoroutineScope
- * val flow = flow {
- * coroutineContext // Resolves into the context of outer launch, which is incorrect, see KT-38033
- * currentCoroutineContext() // Retrieves actual context where the flow is collected
- * }
+ * // ANTIPATTERN! DO NOT WRITE SUCH A CODE
+ * suspend fun CoroutineScope.suspendFunWithScope() {
+ * // Name of the CoroutineScope.coroutineContext in 'this' position, same as `this.coroutineContext`
+ * println(coroutineContext[CoroutineName])
+ * // Name of the context that invoked this suspend function, same as `kotlin.coroutines.coroutineContext`
+ * println(currentCoroutineContext()[CoroutineName])
+ * }
+ *
+ * withContext(CoroutineName("Caller")) {
+ * // Will print 'CoroutineName("Receiver")' and 'CoroutineName("Caller")'
+ * CoroutineScope("Receiver").suspendFunWithScope()
* }
* ```
+ *
+ * This function should always be preferred over [kotlin.coroutines.coroutineContext] property even when there is no explicit clash.
*/
public suspend inline fun currentCoroutineContext(): CoroutineContext = coroutineContext
diff --git a/kotlinx-coroutines-core/common/src/Deferred.kt b/kotlinx-coroutines-core/common/src/Deferred.kt
index 867899b6c5..0404cdd4ee 100644
--- a/kotlinx-coroutines-core/common/src/Deferred.kt
+++ b/kotlinx-coroutines-core/common/src/Deferred.kt
@@ -28,7 +28,7 @@ import kotlinx.coroutines.selects.*
* be safely invoked from concurrent coroutines without external synchronization.
*/
@OptIn(ExperimentalSubclassOptIn::class)
-@SubclassOptInRequired(markerClass = InternalForInheritanceCoroutinesApi::class)
+@SubclassOptInRequired(InternalForInheritanceCoroutinesApi::class)
public interface Deferred : Job {
/**
diff --git a/kotlinx-coroutines-core/common/src/Job.kt b/kotlinx-coroutines-core/common/src/Job.kt
index 2c42687625..27665f6eef 100644
--- a/kotlinx-coroutines-core/common/src/Job.kt
+++ b/kotlinx-coroutines-core/common/src/Job.kt
@@ -101,7 +101,7 @@ import kotlin.jvm.*
* be safely invoked from concurrent coroutines without external synchronization.
*/
@OptIn(ExperimentalSubclassOptIn::class)
-@SubclassOptInRequired(markerClass = InternalForInheritanceCoroutinesApi::class)
+@SubclassOptInRequired(InternalForInheritanceCoroutinesApi::class)
public interface Job : CoroutineContext.Element {
/**
* Key for [Job] instance in the coroutine context.
diff --git a/kotlinx-coroutines-core/common/src/Runnable.common.kt b/kotlinx-coroutines-core/common/src/Runnable.common.kt
index 462ed6a7fe..d8a6304e55 100644
--- a/kotlinx-coroutines-core/common/src/Runnable.common.kt
+++ b/kotlinx-coroutines-core/common/src/Runnable.common.kt
@@ -2,16 +2,13 @@ package kotlinx.coroutines
/**
* A runnable task for [CoroutineDispatcher.dispatch].
+ *
+ * It is equivalent to the type `() -> Unit`, but on the JVM, it is represented as a `java.lang.Runnable`,
+ * making it easier to wrap the interfaces that expect `java.lang.Runnable` into a [CoroutineDispatcher].
*/
-public expect interface Runnable {
+public expect fun interface Runnable {
/**
* @suppress
*/
public fun run()
}
-
-/**
- * Creates [Runnable] task instance.
- */
-@Suppress("FunctionName")
-public expect inline fun Runnable(crossinline block: () -> Unit): Runnable
diff --git a/kotlinx-coroutines-core/common/src/Supervisor.kt b/kotlinx-coroutines-core/common/src/Supervisor.kt
index 730050b5ab..c1d2145680 100644
--- a/kotlinx-coroutines-core/common/src/Supervisor.kt
+++ b/kotlinx-coroutines-core/common/src/Supervisor.kt
@@ -53,6 +53,7 @@ public suspend fun supervisorScope(block: suspend CoroutineScope.() -> R): R
}
return suspendCoroutineUninterceptedOrReturn { uCont ->
val coroutine = SupervisorCoroutine(uCont.context, uCont)
+ @Suppress("LEAKED_IN_PLACE_LAMBDA") // Contract is preserved, invoked immediately or throws
coroutine.startUndispatchedOrReturn(coroutine, block)
}
}
diff --git a/kotlinx-coroutines-core/common/src/Timeout.kt b/kotlinx-coroutines-core/common/src/Timeout.kt
index c63136575b..d2fcca5521 100644
--- a/kotlinx-coroutines-core/common/src/Timeout.kt
+++ b/kotlinx-coroutines-core/common/src/Timeout.kt
@@ -40,6 +40,7 @@ public suspend fun withTimeout(timeMillis: Long, block: suspend CoroutineSco
}
if (timeMillis <= 0L) throw TimeoutCancellationException("Timed out immediately")
return suspendCoroutineUninterceptedOrReturn { uCont ->
+ @Suppress("LEAKED_IN_PLACE_LAMBDA") // Contract is preserved, invoked immediately or throws
setupTimeout(TimeoutCoroutine(timeMillis, uCont), block)
}
}
diff --git a/kotlinx-coroutines-core/common/src/Yield.kt b/kotlinx-coroutines-core/common/src/Yield.kt
index 0598228640..717a6c7000 100644
--- a/kotlinx-coroutines-core/common/src/Yield.kt
+++ b/kotlinx-coroutines-core/common/src/Yield.kt
@@ -4,29 +4,149 @@ import kotlinx.coroutines.internal.*
import kotlin.coroutines.intrinsics.*
/**
- * Yields the thread (or thread pool) of the current coroutine dispatcher
- * to other coroutines on the same dispatcher to run if possible.
+ * Suspends this coroutine and immediately schedules it for further execution.
*
+ * A coroutine run uninterrupted on a thread until the coroutine *suspend*,
+ * giving other coroutines a chance to use that thread for their own computations.
+ * Normally, coroutines suspend whenever they wait for something to happen:
+ * for example, trying to receive a value from a channel that's currently empty will suspend.
+ * Sometimes, a coroutine does not need to wait for anything,
+ * but we still want it to give other coroutines a chance to run.
+ * Calling [yield] has this effect:
+ *
+ * ```
+ * fun updateProgressBar(value: Int, marker: String) {
+ * print(marker)
+ * }
+ * val singleThreadedDispatcher = Dispatchers.Default.limitedParallelism(1)
+ * withContext(singleThreadedDispatcher) {
+ * launch {
+ * repeat(5) {
+ * updateProgressBar(it, "A")
+ * yield()
+ * }
+ * }
+ * launch {
+ * repeat(5) {
+ * updateProgressBar(it, "B")
+ * yield()
+ * }
+ * }
+ * }
+ * ```
+ *
+ * In this example, without the [yield], first, `A` would run its five stages of work to completion, and only then
+ * would `B` even start executing.
+ * With both `yield` calls, the coroutines share the single thread with each other after each stage of work.
+ * This is useful when several coroutines running on the same thread (or thread pool) must regularly publish
+ * their results for the program to stay responsive.
+ *
* This suspending function is cancellable: if the [Job] of the current coroutine is cancelled while
* [yield] is invoked or while waiting for dispatch, it immediately resumes with [CancellationException].
* There is a **prompt cancellation guarantee**: even if this function is ready to return the result, but was cancelled
* while suspended, [CancellationException] will be thrown. See [suspendCancellableCoroutine] for low-level details.
*
- * **Note**: This function always [checks for cancellation][ensureActive] even when it does not suspend.
+ * **Note**: if there is only a single coroutine executing on the current dispatcher,
+ * it is possible that [yield] will not actually suspend.
+ * However, even in that case, the [check for cancellation][ensureActive] still happens.
+ *
+ * **Note**: if there is no [CoroutineDispatcher] in the context, it does not suspend.
+ *
+ * ## Pitfall: using `yield` to wait for something to happen
+ *
+ * Using `yield` for anything except a way to ensure responsiveness is often a problem.
+ * When possible, it is recommended to structure the code in terms of coroutines waiting for some events instead of
+ * yielding.
+ * Below, we list the common problems involving [yield] and outline how to avoid them.
+ *
+ * ### Case 1: using `yield` to ensure a specific interleaving of actions
+ *
+ * ```
+ * val singleThreadedDispatcher = Dispatchers.Default.limitedParallelism(1)
+ * withContext(singleThreadedDispatcher) {
+ * var value: Int? = null
+ * val job = launch { // a new coroutine on the same dispatcher
+ * // yield() // uncomment to see the crash
+ * value = 42
+ * println("2. Value provided")
+ * }
+ * check(value == null)
+ * println("No value yet!")
+ * println("1. Awaiting the value...")
+ * // ANTIPATTERN! DO NOT WRITE SUCH CODE!
+ * yield() // allow the other coroutine to run
+ * // job.join() // would work more reliably in this scenario!
+ * check(value != null)
+ * println("3. Obtained $value")
+ * }
+ * ```
+ *
+ * Here, [yield] allows `singleThreadedDispatcher` to execute the task that ultimately provides the `value`.
+ * Without the [yield], the `value != null` check would be executed directly after `Awaiting the value` is printed.
+ * However, if the value-producing coroutine is modified to suspend before providing the value, this will
+ * no longer work; explicitly waiting for the coroutine to finish via [Job.join] instead is robust against such changes.
+ *
+ * Therefore, it is an antipattern to use `yield` to synchronize code across several coroutines.
+ *
+ * ### Case 2: using `yield` in a loop to wait for something to happen
+ *
+ * ```
+ * val singleThreadedDispatcher = Dispatchers.Default.limitedParallelism(1)
+ * withContext(singleThreadedDispatcher) {
+ * var value: Int? = null
+ * val job = launch { // a new coroutine on the same dispatcher
+ * delay(1.seconds)
+ * value = 42
+ * }
+ * // ANTIPATTERN! DO NOT WRITE SUCH CODE!
+ * while (value == null) {
+ * yield() // allow the other coroutines to run
+ * }
+ * println("Obtained $value")
+ * }
+ * ```
+ *
+ * This example will lead to correct results no matter how much the value-producing coroutine suspends,
+ * but it is still flawed.
+ * For the one second that it takes for the other coroutine to obtain the value,
+ * `value == null` would be constantly re-checked, leading to unjustified resource consumption.
+ *
+ * In this specific case, [CompletableDeferred] can be used instead:
+ *
+ * ```
+ * val singleThreadedDispatcher = Dispatchers.Default.limitedParallelism(1)
+ * withContext(singleThreadedDispatcher) {
+ * val deferred = CompletableDeferred()
+ * val job = launch { // a new coroutine on the same dispatcher
+ * delay(1.seconds)
+ * deferred.complete(42)
+ * }
+ * val value = deferred.await()
+ * println("Obtained $value")
+ * }
+ * ```
+ *
+ * `while (channel.isEmpty) { yield() }; channel.receive()` can be replaced with just `channel.receive()`;
+ * `while (job.isActive) { yield() }` can be replaced with [`job.join()`][Job.join];
+ * in both cases, this will avoid the unnecessary work of checking the loop conditions.
+ * In general, seek ways to allow a coroutine to stay suspended until it actually has useful work to do.
+ *
+ * ## Implementation details
*
- * ### Implementation details
+ * Some coroutine dispatchers include optimizations that make yielding different from normal suspensions.
+ * For example, when yielding, [Dispatchers.Unconfined] checks whether there are any other coroutines in the event
+ * loop where the current coroutine executes; if not, the sole coroutine continues to execute without suspending.
+ * Also, `Dispatchers.IO` and `Dispatchers.Default` on the JVM tweak the scheduling behavior to improve liveness
+ * when `yield()` is used in a loop.
*
- * If the coroutine dispatcher is [Unconfined][Dispatchers.Unconfined], this
- * functions suspends only when there are other unconfined coroutines working and forming an event-loop.
- * For other dispatchers, this function calls [CoroutineDispatcher.dispatch] and
- * always suspends to be resumed later regardless of the result of [CoroutineDispatcher.isDispatchNeeded].
- * If there is no [CoroutineDispatcher] in the context, it does not suspend.
+ * For custom implementations of [CoroutineDispatcher], this function checks [CoroutineDispatcher.isDispatchNeeded] and
+ * then invokes [CoroutineDispatcher.dispatch] regardless of the result; no way is provided to change this behavior.
*/
public suspend fun yield(): Unit = suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
val context = uCont.context
context.ensureActive()
val cont = uCont.intercepted() as? DispatchedContinuation ?: return@sc Unit
- if (cont.dispatcher.isDispatchNeeded(context)) {
+ if (cont.dispatcher.safeIsDispatchNeeded(context)) {
// this is a regular dispatcher -- do simple dispatchYield
cont.dispatchYield(context, Unit)
} else {
diff --git a/kotlinx-coroutines-core/common/src/channels/BufferOverflow.kt b/kotlinx-coroutines-core/common/src/channels/BufferOverflow.kt
index ecb02d870b..652f8d7c1e 100644
--- a/kotlinx-coroutines-core/common/src/channels/BufferOverflow.kt
+++ b/kotlinx-coroutines-core/common/src/channels/BufferOverflow.kt
@@ -13,7 +13,7 @@ package kotlinx.coroutines.channels
*/
public enum class BufferOverflow {
/**
- * Suspend on buffer overflow.
+ * Suspend until free space appears in the buffer.
*
* Use this to create backpressure, forcing the producers to slow down creation of new values in response to
* consumers not being able to process the incoming values in time.
diff --git a/kotlinx-coroutines-core/common/src/channels/Channel.kt b/kotlinx-coroutines-core/common/src/channels/Channel.kt
index 830d786259..b4b80fb789 100644
--- a/kotlinx-coroutines-core/common/src/channels/Channel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Channel.kt
@@ -15,15 +15,60 @@ import kotlin.internal.*
import kotlin.jvm.*
/**
- * Sender's interface to [Channel].
+ * Sender's interface to a [Channel].
+ *
+ * Combined, [SendChannel] and [ReceiveChannel] define the complete [Channel] interface.
+ *
+ * It is not expected that this interface will be implemented directly.
+ * Instead, the existing [Channel] implementations can be used or delegated to.
*/
public interface SendChannel {
/**
* Returns `true` if this channel was closed by an invocation of [close] or its receiving side was [cancelled][ReceiveChannel.cancel].
* This means that calling [send] will result in an exception.
*
- * Note that if this property returns `false`, it does not guarantee that consecutive call to [send] will succeed, as the
- * channel can be concurrently closed right after the check. For such scenarios, it is recommended to use [trySend] instead.
+ * Note that if this property returns `false`, it does not guarantee that a subsequent call to [send] will succeed,
+ * as the channel can be concurrently closed right after the check.
+ * For such scenarios, [trySend] is the more robust solution: it attempts to send the element and returns
+ * a result that says whether the channel was closed, and if not, whether sending a value was successful.
+ *
+ * ```
+ * // DANGER! THIS CHECK IS NOT RELIABLE!
+ * if (!channel.isClosedForSend) {
+ * channel.send(element) // can still fail!
+ * } else {
+ * println("Can not send: the channel is closed")
+ * }
+ * // DO THIS INSTEAD:
+ * channel.trySend(element).onClosed {
+ * println("Can not send: the channel is closed")
+ * }
+ * ```
+ *
+ * The primary intended usage of this property is skipping some portions of code that should not be executed if the
+ * channel is already known to be closed.
+ * For example:
+ *
+ * ```
+ * if (channel.isClosedForSend) {
+ * // fast path
+ * return
+ * } else {
+ * // slow path: actually computing the value
+ * val nextElement = run {
+ * // some heavy computation
+ * }
+ * channel.send(nextElement) // can fail anyway,
+ * // but at least we tried to avoid the computation
+ * }
+ * ```
+ *
+ * However, in many cases, even that can be achieved more idiomatically by cancelling the coroutine producing the
+ * elements to send.
+ * See [produce] for a way to launch a coroutine that produces elements and cancels itself when the channel is
+ * closed.
+ *
+ * [isClosedForSend] can also be used for assertions and diagnostics to verify the expected state of the channel.
*
* @see SendChannel.trySend
* @see SendChannel.close
@@ -33,13 +78,33 @@ public interface SendChannel {
public val isClosedForSend: Boolean
/**
- * Sends the specified [element] to this channel, suspending the caller while the buffer of this channel is full
- * or if it does not exist, or throws an exception if the channel [is closed for `send`][isClosedForSend] (see [close] for details).
+ * Sends the specified [element] to this channel.
+ *
+ * This function suspends if it does not manage to pass the element to the channel's buffer
+ * (or directly the receiving side if there's no buffer),
+ * and it can be cancelled with or without having successfully passed the element.
+ * See the "Suspending and cancellation" section below for details.
+ * If the channel is [closed][close], an exception is thrown (see below).
+ *
+ * ```
+ * val channel = Channel()
+ * launch {
+ * check(channel.receive() == 5)
+ * }
+ * channel.send(5) // suspends until 5 is received
+ * ```
*
- * [Closing][close] a channel _after_ this function has suspended does not cause this suspended [send] invocation
- * to abort, because closing a channel is conceptually like sending a special "close token" over this channel.
- * All elements sent over the channel are delivered in first-in first-out order. The sent element
- * will be delivered to receivers before the close token.
+ * ## Suspending and cancellation
+ *
+ * If the [BufferOverflow] strategy of this channel is [BufferOverflow.SUSPEND],
+ * this function may suspend.
+ * The exact scenarios differ depending on the channel's capacity:
+ * - If the channel is [rendezvous][RENDEZVOUS],
+ * the sender will be suspended until the receiver calls [ReceiveChannel.receive].
+ * - If the channel is [unlimited][UNLIMITED] or [conflated][CONFLATED],
+ * the sender will never be suspended even with the [BufferOverflow.SUSPEND] strategy.
+ * - If the channel is buffered (either [BUFFERED] or uses a non-default buffer capacity),
+ * the sender will be suspended until the buffer has free space.
*
* This suspending function is cancellable: if the [Job] of the current coroutine is cancelled while this
* suspending function is waiting, this function immediately resumes with [CancellationException].
@@ -47,74 +112,182 @@ public interface SendChannel {
* while suspended, [CancellationException] will be thrown. See [suspendCancellableCoroutine] for low-level details.
*
* Because of the prompt cancellation guarantee, an exception does not always mean a failure to deliver the element.
- * See "Undelivered elements" section in [Channel] documentation for details on handling undelivered elements.
+ * See the "Undelivered elements" section in the [Channel] documentation
+ * for details on handling undelivered elements.
*
* Note that this function does not check for cancellation when it is not suspended.
- * Use [yield] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
+ * Use [ensureActive] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed:
+ *
+ * ```
+ * // because of UNLIMITED, sending to this channel never suspends
+ * val channel = Channel(Channel.UNLIMITED)
+ * val job = launch {
+ * while (isActive) {
+ * channel.send(42)
+ * }
+ * // the loop exits when the job is cancelled
+ * }
+ * ```
+ *
+ * This isn't needed if other cancellable functions are called inside the loop, like [delay].
+ *
+ * ## Sending to a closed channel
+ *
+ * If a channel was [closed][close] before [send] was called and no cause was specified,
+ * an [ClosedSendChannelException] will be thrown from [send].
+ * If a channel was [closed][close] with a cause before [send] was called,
+ * then [send] will rethrow the same (in the `===` sense) exception that was passed to [close].
+ *
+ * In both cases, it is guaranteed that the element was not delivered to the consumer,
+ * and the `onUndeliveredElement` callback will be called.
+ * See the "Undelivered elements" section in the [Channel] documentation
+ * for details on handling undelivered elements.
+ *
+ * [Closing][close] a channel _after_ this function suspends does not cause this suspended [send] invocation
+ * to abort: although subsequent invocations of [send] fail, the existing ones will continue to completion,
+ * unless the sending coroutine is cancelled.
+ *
+ * ## Related
*
* This function can be used in [select] invocations with the [onSend] clause.
- * Use [trySend] to try sending to this channel without waiting.
+ * Use [trySend] to try sending to this channel without waiting and throwing.
*/
public suspend fun send(element: E)
/**
- * Clause for the [select] expression of the [send] suspending function that selects when the element that is specified
- * as the parameter is sent to the channel. When the clause is selected, the reference to this channel
- * is passed into the corresponding block.
+ * Clause for the [select] expression of the [send] suspending function that selects when the element that is
+ * specified as the parameter is sent to the channel.
+ * When the clause is selected, the reference to this channel is passed into the corresponding block.
*
- * The [select] invocation fails with an exception if the channel [is closed for `send`][isClosedForSend] (see [close] for details).
+ * The [select] invocation fails with an exception if the channel [is closed for `send`][isClosedForSend] before
+ * the [select] suspends (see the "Sending to a closed channel" section of [send]).
+ *
+ * Example:
+ * ```
+ * val sendChannels = List(4) { index ->
+ * Channel(onUndeliveredElement = {
+ * println("Undelivered element $it for $index")
+ * }).also { channel ->
+ * // launch a consumer for this channel
+ * launch {
+ * withTimeout(1.seconds) {
+ * println("Consumer $index receives: ${channel.receive()}")
+ * }
+ * }
+ * }
+ * }
+ * val element = 42
+ * select {
+ * for (channel in sendChannels) {
+ * channel.onSend(element) {
+ * println("Sent to channel $it")
+ * }
+ * }
+ * }
+ * ```
+ * Here, we start a [select] expression that waits for exactly one of the four [onSend] invocations
+ * to successfully send the element to the receiver,
+ * and the other three will instead invoke the `onUndeliveredElement` callback.
+ * See the "Undelivered elements" section in the [Channel] documentation
+ * for details on handling undelivered elements.
+ *
+ * Like [send], [onSend] obeys the rules of prompt cancellation:
+ * [select] may finish with a [CancellationException] even if the element was successfully sent.
*/
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.
- * This is synchronous variant of [send], which backs off in situations when `send` suspends or throws.
+ * Attempts to add the specified [element] to this channel without waiting.
+ *
+ * [trySend] never suspends and never throws exceptions.
+ * Instead, it returns a [ChannelResult] that encapsulates the result of the operation.
+ * This makes it different from [send], which can suspend and throw exceptions.
*
- * When `trySend` call returns a non-successful result, it guarantees that the element was not delivered to the consumer, and
- * it does not call `onUndeliveredElement` that was installed for this channel.
- * See "Undelivered elements" section in [Channel] documentation for details on handling undelivered elements.
+ * If this channel is currently full and cannot receive new elements at the time or is [closed][close],
+ * this function returns a result that indicates [a failure][ChannelResult.isFailure].
+ * In this case, it is guaranteed that the element was not delivered to the consumer and the
+ * `onUndeliveredElement` callback, if one is provided during the [Channel]'s construction, does *not* get called.
+ *
+ * [trySend] can be used as a non-`suspend` alternative to [send] in cases where it's known beforehand
+ * that the channel's buffer can not overflow.
+ * ```
+ * class Coordinates(val x: Int, val y: Int)
+ * // A channel for a single subscriber that stores the latest mouse position update.
+ * // If more than one subscriber is expected, consider using a `StateFlow` instead.
+ * val mousePositionUpdates = Channel(Channel.CONFLATED)
+ * // Notifies the subscriber about the new mouse position.
+ * // If the subscriber is slow, the intermediate updates are dropped.
+ * fun moveMouse(coordinates: Coordinates) {
+ * val result = mousePositionUpdates.trySend(coordinates)
+ * if (result.isClosed) {
+ * error("Mouse position is no longer being processed")
+ * }
+ * }
+ * ```
*/
public fun trySend(element: E): ChannelResult
/**
- * Closes this channel.
- * This is an idempotent operation — subsequent invocations of this function have no effect and return `false`.
- * Conceptually, it sends a special "close token" over this channel.
- *
- * Immediately after invocation of this function,
- * [isClosedForSend] starts returning `true`. However, [isClosedForReceive][ReceiveChannel.isClosedForReceive]
- * on the side of [ReceiveChannel] starts returning `true` only after all previously sent elements
- * are received.
- *
- * A channel that was closed without a [cause] throws a [ClosedSendChannelException] on attempts to [send]
- * and [ClosedReceiveChannelException] on attempts to [receive][ReceiveChannel.receive].
- * A channel that was closed with non-null [cause] is called a _failed_ channel. Attempts to send or
- * receive on a failed channel throw the specified [cause] exception.
+ * Closes this channel so that subsequent attempts to [send] to it fail.
+ *
+ * Returns `true` if the channel was not closed previously and the call to this function closed it.
+ * If the channel was already closed, this function does nothing and returns `false`.
+ *
+ * The existing elements in the channel remain there, and likewise,
+ * the calls to [send] an [onSend] that have suspended before [close] was called will not be affected.
+ * Only the subsequent calls to [send], [trySend], or [onSend] will fail.
+ * [isClosedForSend] will start returning `true` immediately after this function is called.
+ *
+ * Once all the existing elements are received, the channel will be considered closed for `receive` as well.
+ * This means that [receive][ReceiveChannel.receive] will also start throwing exceptions.
+ * At that point, [isClosedForReceive][ReceiveChannel.isClosedForReceive] will start returning `true`.
+ *
+ * If the [cause] is non-null, it will be thrown from all the subsequent attempts to [send] to this channel,
+ * as well as from all the attempts to [receive][ReceiveChannel.receive] from the channel after no elements remain.
+ *
+ * If the [cause] is null, the channel is considered to have completed normally.
+ * All subsequent calls to [send] will throw a [ClosedSendChannelException],
+ * whereas calling [receive][ReceiveChannel.receive] will throw a [ClosedReceiveChannelException]
+ * after there are no more elements.
+ *
+ * ```
+ * val channel = Channel()
+ * channel.send(1)
+ * channel.close()
+ * try {
+ * channel.send(2)
+ * error("The channel is closed, so this line is never reached")
+ * } catch (e: ClosedSendChannelException) {
+ * // expected
+ * }
+ * ```
*/
public fun close(cause: Throwable? = null): Boolean
/**
- * Registers a [handler] which is synchronously invoked once the channel is [closed][close]
+ * Registers a [handler] that is synchronously invoked once the channel is [closed][close]
* or the receiving side of this channel is [cancelled][ReceiveChannel.cancel].
* Only one handler can be attached to a channel during its lifetime.
* The `handler` is invoked when [isClosedForSend] starts to return `true`.
* If the channel is closed already, the handler is invoked immediately.
*
* The meaning of `cause` that is passed to the handler:
- * - `null` if the channel was closed normally without the corresponding argument.
- * - Instance of [CancellationException] if the channel was cancelled normally without the corresponding argument.
+ * - `null` if the channel was [closed][close] normally with `cause = null`.
+ * - Instance of [CancellationException] if the channel was [cancelled][ReceiveChannel.cancel] normally
+ * without the corresponding argument.
* - The cause of `close` or `cancel` otherwise.
*
* ### Execution context and exception safety
*
- * The [handler] is executed as part of the closing or cancelling operation, and only after the channel reaches its final state.
- * This means that if the handler throws an exception or hangs, the channel will still be successfully closed or cancelled.
+ * The [handler] is executed as part of the closing or cancelling operation,
+ * and only after the channel reaches its final state.
+ * This means that if the handler throws an exception or hangs,
+ * the channel will still be successfully closed or cancelled.
* Unhandled exceptions from [handler] are propagated to the closing or cancelling operation's caller.
*
* Example of usage:
* ```
- * val events = Channel(UNLIMITED)
+ * val events = Channel(Channel.UNLIMITED)
* callbackBasedApi.registerCallback { event ->
* events.trySend(event)
* .onClosed { /* channel is already closed, but the callback hasn't stopped yet */ }
@@ -132,7 +305,7 @@ public interface SendChannel {
* This restriction could be lifted in the future.
*
* @throws UnsupportedOperationException if the underlying channel does not support [invokeOnClose].
- * Implementation note: currently, [invokeOnClose] is unsupported only by Rx-like integrations
+ * Implementation note: currently, [invokeOnClose] is unsupported only by Rx-like integrations.
*
* @throws IllegalStateException if another handler was already registered
*/
@@ -178,19 +351,39 @@ public interface SendChannel {
}
/**
- * Receiver's interface to [Channel].
+ * Receiver's interface to a [Channel].
+ *
+ * Combined, [SendChannel] and [ReceiveChannel] define the complete [Channel] interface.
*/
public interface ReceiveChannel {
/**
- * Returns `true` if this channel was closed by invocation of [close][SendChannel.close] on the [SendChannel]
- * side and all previously sent items were already received, or if the receiving side was [cancelled][ReceiveChannel.cancel].
+ * Returns `true` if the sending side of this channel was [closed][SendChannel.close]
+ * and all previously sent items were already received (which also happens for [cancelled][cancel] channels).
*
- * This means that calling [receive] will result in a [ClosedReceiveChannelException] or a corresponding cancellation cause.
- * If the channel was closed because of an exception, it is considered closed, too, but is called a _failed_ channel.
- * All suspending attempts to receive an element from a failed channel throw the original [close][SendChannel.close] cause exception.
+ * Note that if this property returns `false`,
+ * it does not guarantee that a subsequent call to [receive] will succeed,
+ * as the channel can be concurrently cancelled or closed right after the check.
+ * For such scenarios, [receiveCatching] is the more robust solution:
+ * if the channel is closed, instead of throwing an exception, [receiveCatching] returns a result that allows
+ * querying it.
*
- * Note that if this property returns `false`, it does not guarantee that consecutive call to [receive] will succeed, as the
- * channel can be concurrently closed right after the check. For such scenarios, it is recommended to use [receiveCatching] instead.
+ * ```
+ * // DANGER! THIS CHECK IS NOT RELIABLE!
+ * if (!channel.isClosedForReceive) {
+ * channel.receive() // can still fail!
+ * } else {
+ * println("Can not receive: the channel is closed")
+ * null
+ * }
+ * // DO THIS INSTEAD:
+ * channel.receiveCatching().onClosed {
+ * println("Can not receive: the channel is closed")
+ * }.getOrNull()
+ * ```
+ *
+ * The primary intended usage of this property is for assertions and diagnostics to verify the expected state of
+ * the channel.
+ * Using it in production code is discouraged.
*
* @see ReceiveChannel.receiveCatching
* @see ReceiveChannel.cancel
@@ -200,107 +393,306 @@ public interface ReceiveChannel {
public val isClosedForReceive: Boolean
/**
- * Returns `true` if the channel is empty (contains no elements), which means that an attempt to [receive] will suspend.
- * This function returns `false` if the channel [is closed for `receive`][isClosedForReceive].
+ * Returns `true` if the channel contains no elements and isn't [closed for `receive`][isClosedForReceive].
+ *
+ * If [isEmpty] returns `true`, it means that calling [receive] at exactly the same moment would suspend.
+ * However, calling [receive] immediately after checking [isEmpty] may or may not suspend, as new elements
+ * could have been added or removed or the channel could have been closed for `receive` between the two invocations.
+ * Consider using [tryReceive] in cases when suspensions are undesirable:
+ *
+ * ```
+ * // DANGER! THIS CHECK IS NOT RELIABLE!
+ * while (!channel.isEmpty) {
+ * // can still suspend if other `receive` happens in parallel!
+ * val element = channel.receive()
+ * println(element)
+ * }
+ * // DO THIS INSTEAD:
+ * while (true) {
+ * val element = channel.tryReceive().getOrNull() ?: break
+ * println(element)
+ * }
+ * ```
*/
@ExperimentalCoroutinesApi
public val isEmpty: Boolean
/**
- * Retrieves and removes an element from this channel if it's not empty, or suspends the caller while the channel is empty,
- * or throws a [ClosedReceiveChannelException] if the channel [is closed for `receive`][isClosedForReceive].
- * If the channel was closed because of an exception, it is called a _failed_ channel and this function
- * will throw the original [close][SendChannel.close] cause exception.
- *
- * This suspending function is cancellable. If the [Job] of the current coroutine is cancelled while this
- * function is suspended, this function immediately resumes with a [CancellationException].
- * There is a **prompt cancellation guarantee**. If the job was cancelled while this function was
- * suspended, it will not resume successfully. The `receive` call can retrieve the element from the channel,
- * but then throw [CancellationException], thus failing to deliver the element.
- * See "Undelivered elements" section in [Channel] documentation for details on handling undelivered elements.
+ * Retrieves an element, removing it from the channel.
+ *
+ * This function suspends if the channel is empty, waiting until an element is available.
+ * If the channel is [closed for `receive`][isClosedForReceive], an exception is thrown (see below).
+ * ```
+ * val channel = Channel()
+ * launch {
+ * val element = channel.receive() // suspends until 5 is available
+ * check(element == 5)
+ * }
+ * channel.send(5)
+ * ```
+ *
+ * ## Suspending and cancellation
*
* This suspending function is cancellable: if the [Job] of the current coroutine is cancelled while this
* suspending function is waiting, this function immediately resumes with [CancellationException].
* There is a **prompt cancellation guarantee**: even if [receive] managed to retrieve the element from the channel,
- * but was cancelled while suspended, [CancellationException] will be thrown.
- * See [suspendCancellableCoroutine] for low-level details.
+ * but was cancelled while suspended, [CancellationException] will be thrown, and, if
+ * the channel has an `onUndeliveredElement` callback installed, the retrieved element will be passed to it.
+ * See the "Undelivered elements" section in the [Channel] documentation
+ * for details on handling undelivered elements.
+ * See [suspendCancellableCoroutine] for the low-level details of prompt cancellation.
*
- * Because of the prompt cancellation guarantee, some values retrieved from the channel can become lost.
- * See "Undelivered elements" section in [Channel] documentation for details on handling undelivered elements.
+ * Note that this function does not check for cancellation when it manages to immediately receive an element without
+ * suspending.
+ * Use [ensureActive] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed:
*
- * 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.
+ * ```
+ * val channel = Channel()
+ * launch { // a very fast producer
+ * while (true) {
+ * channel.send(42)
+ * }
+ * }
+ * val consumer = launch { // a slow consumer
+ * while (isActive) {
+ * val element = channel.receive()
+ * // some slow computation involving `element`
+ * }
+ * }
+ * delay(100.milliseconds)
+ * consumer.cancelAndJoin()
+ * ```
+ *
+ * ## Receiving from a closed channel
+ *
+ * - Attempting to [receive] from a [closed][SendChannel.close] channel while there are still some elements
+ * will successfully retrieve an element from the channel.
+ * - When a channel is [closed][SendChannel.close] and there are no elements remaining,
+ * the channel becomes [closed for `receive`][isClosedForReceive].
+ * After that,
+ * [receive] will rethrow the same (in the `===` sense) exception that was passed to [SendChannel.close],
+ * or [ClosedReceiveChannelException] if none was given.
+ *
+ * ## Related
*
* This function can be used in [select] invocations with the [onReceive] clause.
- * Use [tryReceive] to try receiving from this channel without waiting.
+ * Use [tryReceive] to try receiving from this channel without waiting and throwing.
+ * Use [receiveCatching] to receive from this channel without throwing.
*/
public suspend fun receive(): E
/**
* Clause for the [select] expression of the [receive] suspending function that selects with the element
* received from the channel.
- * The [select] invocation fails with an exception if the channel
- * [is closed for `receive`][isClosedForReceive] (see [close][SendChannel.close] for details).
+ *
+ * The [select] invocation fails with an exception if the channel [is closed for `receive`][isClosedForReceive]
+ * at any point, even if other [select] clauses could still work.
+ *
+ * Example:
+ * ```
+ * class ScreenSize(val width: Int, val height: Int)
+ * class MouseClick(val x: Int, val y: Int)
+ * val screenResizes = Channel(Channel.CONFLATED)
+ * val mouseClicks = Channel(Channel.CONFLATED)
+ *
+ * launch(Dispatchers.Main) {
+ * while (true) {
+ * select {
+ * screenResizes.onReceive { newSize ->
+ * // update the UI to the new screen size
+ * }
+ * mouseClicks.onReceive { click ->
+ * // react to a mouse click
+ * }
+ * }
+ * }
+ * }
+ * ```
+ *
+ * Like [receive], [onReceive] obeys the rules of prompt cancellation:
+ * [select] may finish with a [CancellationException] even if an element was successfully retrieved,
+ * in which case the `onUndeliveredElement` callback will be called.
*/
public val onReceive: SelectClause1
/**
- * Retrieves and removes an element from this channel if it's not empty, or suspends the caller while this channel is empty.
- * This method returns [ChannelResult] with the value of an element successfully retrieved from the channel
- * or the close cause if the channel was closed. Closed cause may be `null` if the channel was closed normally.
- * The result cannot be [failed][ChannelResult.isFailure] without being [closed][ChannelResult.isClosed].
+ * Retrieves an element, removing it from the channel.
+ *
+ * A difference from [receive] is that this function encapsulates a failure in its return value instead of throwing
+ * an exception.
+ * However, it will still throw [CancellationException] if the coroutine calling [receiveCatching] is cancelled.
+ *
+ * It is guaranteed that the only way this function can return a [failed][ChannelResult.isFailure] result is when
+ * the channel is [closed for `receive`][isClosedForReceive], so [ChannelResult.isClosed] is also true.
+ *
+ * This function suspends if the channel is empty, waiting until an element is available or the channel becomes
+ * closed.
+ * ```
+ * val channel = Channel()
+ * launch {
+ * while (true) {
+ * val result = channel.receiveCatching() // suspends
+ * when (val element = result.getOrNull()) {
+ * null -> break // the channel is closed
+ * else -> check(element == 5)
+ * }
+ * }
+ * }
+ * channel.send(5)
+ * ```
+ *
+ * ## Suspending and cancellation
*
* This suspending function is cancellable: if the [Job] of the current coroutine is cancelled while this
* suspending function is waiting, this function immediately resumes with [CancellationException].
* There is a **prompt cancellation guarantee**: even if [receiveCatching] managed to retrieve the element from the
- * channel, but was cancelled while suspended, [CancellationException] will be thrown.
- * See [suspendCancellableCoroutine] for low-level details.
+ * channel, but was cancelled while suspended, [CancellationException] will be thrown, and, if
+ * the channel has an `onUndeliveredElement` callback installed, the retrieved element will be passed to it.
+ * See the "Undelivered elements" section in the [Channel] documentation
+ * for details on handling undelivered elements.
+ * See [suspendCancellableCoroutine] for the low-level details of prompt cancellation.
*
- * Because of the prompt cancellation guarantee, some values retrieved from the channel can become lost.
- * See "Undelivered elements" section in [Channel] documentation for details on handling undelivered elements.
+ * Note that this function does not check for cancellation when it manages to immediately receive an element without
+ * suspending.
+ * Use [ensureActive] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed:
*
- * 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.
+ * ```
+ * val channel = Channel()
+ * launch { // a very fast producer
+ * while (true) {
+ * channel.send(42)
+ * }
+ * }
+ * val consumer = launch { // a slow consumer
+ * while (isActive) {
+ * val element = channel.receiveCatching().getOrNull() ?: break
+ * // some slow computation involving `element`
+ * }
+ * }
+ * delay(100.milliseconds)
+ * consumer.cancelAndJoin()
+ * ```
+ *
+ * ## Receiving from a closed channel
+ *
+ * - Attempting to [receiveCatching] from a [closed][SendChannel.close] channel while there are still some elements
+ * will successfully retrieve an element from the channel.
+ * - When a channel is [closed][SendChannel.close] and there are no elements remaining,
+ * the channel becomes [closed for `receive`][isClosedForReceive].
+ * After that, [receiveCatching] will return a result with [ChannelResult.isClosed] set.
+ * [ChannelResult.exceptionOrNull] will be the exact (in the `===` sense) exception
+ * that was passed to [SendChannel.close],
+ * or `null` if none was given.
+ *
+ * ## Related
*
* This function can be used in [select] invocations with the [onReceiveCatching] clause.
- * Use [tryReceive] to try receiving from this channel without waiting.
+ * Use [tryReceive] to try receiving from this channel without waiting and throwing.
+ * Use [receive] to receive from this channel and throw exceptions on error.
*/
public suspend fun receiveCatching(): ChannelResult
/**
- * Clause for the [select] expression of the [onReceiveCatching] suspending function that selects with the [ChannelResult] with a value
- * that is received from the channel or with a close cause if the channel
- * [is closed for `receive`][isClosedForReceive].
+ * Clause for the [select] expression of the [receiveCatching] suspending function that selects
+ * with a [ChannelResult] when an element is retrieved or the channel gets closed.
+ *
+ * Like [receiveCatching], [onReceiveCatching] obeys the rules of prompt cancellation:
+ * [select] may finish with a [CancellationException] even if an element was successfully retrieved,
+ * in which case the `onUndeliveredElement` callback will be called.
*/
+ // TODO: think of an example of when this could be useful
public val onReceiveCatching: SelectClause1>
/**
- * Retrieves and removes an element from this channel if it's not empty, returning a [successful][ChannelResult.success]
- * result, returns [failed][ChannelResult.failed] result if the channel is empty, and [closed][ChannelResult.closed]
- * result if the channel is closed.
+ * Attempts to retrieve an element without waiting, removing it from the channel.
+ *
+ * - When the channel is non-empty, a [successful][ChannelResult.isSuccess] result is returned,
+ * and [ChannelResult.getOrNull] returns the retrieved element.
+ * - When the channel is empty, a [failed][ChannelResult.isFailure] result is returned.
+ * - When the channel is already [closed for `receive`][isClosedForReceive],
+ * returns the ["channel is closed"][ChannelResult.isClosed] result.
+ * If the channel was [closed][SendChannel.close] with a cause (for example, [cancelled][cancel]),
+ * [ChannelResult.exceptionOrNull] contains the cause.
+ *
+ * This function is useful when implementing on-demand allocation of resources to be stored in the channel:
+ *
+ * ```
+ * val resourcePool = Channel(maxResources)
+ *
+ * suspend fun withResource(block: (Resource) -> Unit) {
+ * val result = resourcePool.tryReceive()
+ * val resource = result.getOrNull()
+ * ?: tryCreateNewResource() // try to create a new resource
+ * ?: resourcePool.receive() // could not create: actually wait for the resource
+ * try {
+ * block(resource)
+ * } finally {
+ * resourcePool.trySend(resource)
+ * }
+ * }
+ * ```
*/
public fun tryReceive(): ChannelResult
/**
* Returns a new iterator to receive elements from this channel using a `for` loop.
* Iteration completes normally when the channel [is closed for `receive`][isClosedForReceive] without a cause and
- * throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
+ * throws the exception passed to [close][SendChannel.close] if there was one.
+ *
+ * Instances of [ChannelIterator] are not thread-safe and shall not be used from concurrent coroutines.
+ *
+ * Example:
+ *
+ * ```
+ * val channel = produce {
+ * repeat(1000) {
+ * send(it)
+ * }
+ * }
+ * for (v in channel) {
+ * println(v)
+ * }
+ * ```
+ *
+ * Note that if an early return happens from the `for` loop, the channel does not get cancelled.
+ * To forbid sending new elements after the iteration is completed, use [consumeEach] or
+ * call [cancel] manually.
*/
public operator fun iterator(): ChannelIterator
/**
- * Cancels reception of remaining elements from this channel with an optional [cause].
- * This function closes the channel and removes all buffered sent elements from it.
+ * [Closes][SendChannel.close] the channel for new elements and removes all existing ones.
*
- * A cause can be used to specify an error message or to provide other details on
+ * A [cause] can be used to specify an error message or to provide other details on
* the cancellation reason for debugging purposes.
* If the cause is not specified, then an instance of [CancellationException] with a
* default message is created to [close][SendChannel.close] the channel.
*
- * Immediately after invocation of this function [isClosedForReceive] and
- * [isClosedForSend][SendChannel.isClosedForSend]
- * on the side of [SendChannel] start returning `true`. Any attempt to send to or receive from this channel
- * will lead to a [CancellationException].
+ * If the channel was already [closed][SendChannel.close],
+ * [cancel] only has the effect of removing all elements from the channel.
+ *
+ * Immediately after the invocation of this function,
+ * [isClosedForReceive] and, on the [SendChannel] side, [isClosedForSend][SendChannel.isClosedForSend]
+ * start returning `true`.
+ * Any attempt to send to or receive from this channel will lead to a [CancellationException].
+ * This also applies to the existing senders and receivers that are suspended at the time of the call:
+ * they will be resumed with a [CancellationException] immediately after [cancel] is called.
+ *
+ * If the channel has an `onUndeliveredElement` callback installed, this function will invoke it for each of the
+ * elements still in the channel, since these elements will be inaccessible otherwise.
+ * If the callback is not installed, these elements will simply be removed from the channel for garbage collection.
+ *
+ * ```
+ * val channel = Channel()
+ * channel.send(1)
+ * channel.send(2)
+ * channel.cancel()
+ * channel.trySend(3) // returns ChannelResult.isClosed
+ * for (element in channel) { println(element) } // prints nothing
+ * ```
+ *
+ * [consume] and [consumeEach] are convenient shorthands for cancelling the channel after the single consumer
+ * has finished processing.
*/
public fun cancel(cause: CancellationException? = null)
@@ -396,72 +788,156 @@ public interface ReceiveChannel {
}
/**
- * A discriminated union of channel operation result.
- * It encapsulates the successful or failed result of a channel operation or a failed operation to a closed channel with
- * an optional cause.
+ * A discriminated union representing a channel operation result.
+ * It encapsulates the knowledge of whether the operation succeeded, failed with an option to retry,
+ * or failed because the channel was closed.
+ *
+ * If the operation was [successful][isSuccess], [T] is the result of the operation:
+ * for example, for [ReceiveChannel.receiveCatching] and [ReceiveChannel.tryReceive],
+ * it is the element received from the channel, and for [Channel.trySend], it is [Unit],
+ * as the channel does not receive anything in return for sending a channel.
+ * This value can be retrieved with [getOrNull] or [getOrThrow].
*
- * The successful result represents a successful operation with a value of type [T], for example,
- * the result of [Channel.receiveCatching] operation or a successfully sent element as a result of [Channel.trySend].
+ * If the operation [failed][isFailure], it does not necessarily mean that the channel itself is closed.
+ * For example, [ReceiveChannel.receiveCatching] and [ReceiveChannel.tryReceive] can fail because the channel is empty,
+ * and [Channel.trySend] can fail because the channel is full.
*
- * The failed result represents a failed operation attempt to a channel, but it doesn't necessarily indicate that the channel is failed.
- * E.g. when the channel is full, [Channel.trySend] returns failed result, but the channel itself is not in the failed state.
+ * If the operation [failed][isFailure] because the channel was closed for that operation, [isClosed] returns `true`.
+ * The opposite is also true: if [isClosed] returns `true`, then the channel is closed for that operation
+ * ([ReceiveChannel.isClosedForReceive] or [SendChannel.isClosedForSend]).
+ * In this case, retrying the operation is meaningless: once closed, the channel will remain closed.
+ * The [exceptionOrNull] function returns the reason the channel was closed, if any was given.
*
- * The closed result represents an operation attempt to a closed channel and also implies that the operation has failed.
- * It is guaranteed that if the result is _closed_, then the target channel is either [closed for send][Channel.isClosedForSend]
- * or is [closed for receive][Channel.isClosedForReceive] depending on whether the failed operation was sending or receiving.
+ * Manually obtaining a [ChannelResult] instance is not supported.
+ * See the documentation for [ChannelResult]-returning functions for usage examples.
*/
@JvmInline
public value class ChannelResult
@PublishedApi internal constructor(@PublishedApi internal val holder: Any?) {
/**
- * Returns `true` if this instance represents a successful
- * operation outcome.
+ * Whether the operation succeeded.
+ *
+ * If this returns `true`, the operation was successful.
+ * In this case, [getOrNull] and [getOrThrow] can be used to retrieve the value.
+ *
+ * If this returns `false`, the operation failed.
+ * [isClosed] can be used to determine whether the operation failed because the channel was closed
+ * (and therefore retrying the operation is meaningless).
+ *
+ * ```
+ * val result = channel.tryReceive()
+ * if (result.isSuccess) {
+ * println("Successfully received the value ${result.getOrThrow()}")
+ * } else {
+ * println("Failed to receive the value.")
+ * if (result.isClosed) {
+ * println("The channel is closed.")
+ * if (result.exceptionOrNull() != null) {
+ * println("The reason: ${result.exceptionOrNull()}")
+ * }
+ * }
+ * }
+ * ```
*
- * In this case [isFailure] and [isClosed] return `false`.
+ * [isFailure] is a shorthand for `!isSuccess`.
+ * [getOrNull] can simplify [isSuccess] followed by [getOrThrow] into just one check if [T] is known
+ * to be non-nullable.
*/
public val isSuccess: Boolean get() = holder !is Failed
/**
- * Returns `true` if this instance represents unsuccessful operation.
+ * Whether the operation failed.
*
- * In this case [isSuccess] returns false, but it does not imply
- * that the channel is failed or closed.
- *
- * Example of a failed operation without an exception and channel being closed
- * is [Channel.trySend] attempt to a channel that is full.
+ * A shorthand for `!isSuccess`. See [isSuccess] for more details.
*/
public val isFailure: Boolean get() = holder is Failed
/**
- * Returns `true` if this instance represents unsuccessful operation
- * to a closed or cancelled channel.
+ * Whether the operation failed because the channel was closed.
+ *
+ * If this returns `true`, the channel was closed for the operation that returned this result.
+ * In this case, retrying the operation is meaningless: once closed, the channel will remain closed.
+ * [isSuccess] will return `false`.
+ * [exceptionOrNull] can be used to determine the reason the channel was [closed][SendChannel.close]
+ * if one was given.
*
- * In this case [isSuccess] returns `false`, [isFailure] returns `true`, but it does not imply
- * that [exceptionOrNull] returns non-null value.
+ * If this returns `false`, subsequent attempts to perform the same operation may succeed.
*
- * It can happen if the channel was [closed][Channel.close] normally without an exception.
+ * ```
+ * val result = channel.trySend(42)
+ * if (result.isClosed) {
+ * println("The channel is closed.")
+ * if (result.exceptionOrNull() != null) {
+ * println("The reason: ${result.exceptionOrNull()}")
+ * }
+ * }
*/
public val isClosed: Boolean get() = holder is Closed
/**
- * Returns the encapsulated value if this instance represents success or `null` if it represents failed result.
+ * Returns the encapsulated [T] if the operation succeeded, or `null` if it failed.
+ *
+ * For non-nullable [T], the following code can be used to handle the result:
+ * ```
+ * val result = channel.tryReceive()
+ * val value = result.getOrNull()
+ * if (value == null) {
+ * if (result.isClosed) {
+ * println("The channel is closed.")
+ * if (result.exceptionOrNull() != null) {
+ * println("The reason: ${result.exceptionOrNull()}")
+ * }
+ * }
+ * return
+ * }
+ * println("Successfully received the value $value")
+ * ```
+ *
+ * If [T] is nullable, [getOrThrow] together with [isSuccess] is a more reliable way to handle the result.
*/
@Suppress("UNCHECKED_CAST")
public fun getOrNull(): T? = if (holder !is Failed) holder as T else null
/**
- * Returns the encapsulated value if this instance represents success or throws an exception if it is closed or failed.
+ * Returns the encapsulated [T] if the operation succeeded, or throws the encapsulated exception if it failed.
+ *
+ * Example:
+ * ```
+ * val result = channel.tryReceive()
+ * if (result.isSuccess) {
+ * println("Successfully received the value ${result.getOrThrow()}")
+ * }
+ * ```
+ *
+ * @throws IllegalStateException if the operation failed, but the channel was not closed with a cause.
*/
public fun getOrThrow(): T {
@Suppress("UNCHECKED_CAST")
if (holder !is Failed) return holder as T
- if (holder is Closed && holder.cause != null) throw holder.cause
- error("Trying to call 'getOrThrow' on a failed channel result: $holder")
+ if (holder is Closed) {
+ check(holder.cause != null) { "Trying to call 'getOrThrow' on a channel closed without a cause" }
+ throw holder.cause
+ }
+ error("Trying to call 'getOrThrow' on a failed result of a non-closed channel")
}
/**
- * Returns the encapsulated exception if this instance represents failure or `null` if it is success
- * or unsuccessful operation to closed channel.
+ * Returns the exception with which the channel was closed, or `null` if the channel was not closed or was closed
+ * without a cause.
+ *
+ * [exceptionOrNull] can only return a non-`null` value if [isClosed] is `true`,
+ * but even if [isClosed] is `true`,
+ * [exceptionOrNull] can still return `null` if the channel was closed without a cause.
+ *
+ * ```
+ * val result = channel.tryReceive()
+ * if (result.isClosed) {
+ * // Now we know not to retry the operation later.
+ * // Check if the channel was closed with a cause and rethrow the exception:
+ * result.exceptionOrNull()?.let { throw it }
+ * // Otherwise, the channel was closed without a cause.
+ * }
+ * ```
*/
public fun exceptionOrNull(): Throwable? = (holder as? Closed)?.cause
@@ -503,9 +979,13 @@ public value class ChannelResult
}
/**
- * Returns the encapsulated value if this instance represents [success][ChannelResult.isSuccess] or the
- * result of [onFailure] function for the encapsulated [Throwable] exception if it is failed or closed
- * result.
+ * Returns the encapsulated value if the operation [succeeded][ChannelResult.isSuccess], or the
+ * result of [onFailure] function for [ChannelResult.exceptionOrNull] otherwise.
+ *
+ * A shorthand for `if (isSuccess) getOrNull() else onFailure(exceptionOrNull())`.
+ *
+ * @see ChannelResult.getOrNull
+ * @see ChannelResult.exceptionOrNull
*/
@OptIn(ExperimentalContracts::class)
public inline fun ChannelResult.getOrElse(onFailure: (exception: Throwable?) -> T): T {
@@ -517,8 +997,10 @@ public inline fun ChannelResult.getOrElse(onFailure: (exception: Throwabl
}
/**
- * Performs the given [action] on the encapsulated value if this instance represents [success][ChannelResult.isSuccess].
+ * Performs the given [action] on the encapsulated value if the operation [succeeded][ChannelResult.isSuccess].
* Returns the original `ChannelResult` unchanged.
+ *
+ * A shorthand for `this.also { if (isSuccess) action(getOrThrow()) }`.
*/
@OptIn(ExperimentalContracts::class)
public inline fun ChannelResult.onSuccess(action: (value: T) -> Unit): ChannelResult {
@@ -531,10 +1013,12 @@ public inline fun ChannelResult.onSuccess(action: (value: T) -> Unit): Ch
}
/**
- * Performs the given [action] on the encapsulated [Throwable] exception if this instance represents [failure][ChannelResult.isFailure].
+ * Performs the given [action] if the operation [failed][ChannelResult.isFailure].
* The result of [ChannelResult.exceptionOrNull] is passed to the [action] parameter.
*
* Returns the original `ChannelResult` unchanged.
+ *
+ * A shorthand for `this.also { if (isFailure) action(exceptionOrNull()) }`.
*/
@OptIn(ExperimentalContracts::class)
public inline fun ChannelResult.onFailure(action: (exception: Throwable?) -> Unit): ChannelResult {
@@ -546,13 +1030,16 @@ public inline fun ChannelResult.onFailure(action: (exception: Throwable?)
}
/**
- * Performs the given [action] on the encapsulated [Throwable] exception if this instance represents [failure][ChannelResult.isFailure]
- * due to channel being [closed][Channel.close].
+ * Performs the given [action] if the operation failed because the channel was [closed][ChannelResult.isClosed] for
+ * that operation.
* The result of [ChannelResult.exceptionOrNull] is passed to the [action] parameter.
+ *
* It is guaranteed that if action is invoked, then the channel is either [closed for send][Channel.isClosedForSend]
* or is [closed for receive][Channel.isClosedForReceive] depending on the failed operation.
*
* Returns the original `ChannelResult` unchanged.
+ *
+ * A shorthand for `this.also { if (isClosed) action(exceptionOrNull()) }`.
*/
@OptIn(ExperimentalContracts::class)
public inline fun ChannelResult.onClosed(action: (exception: Throwable?) -> Unit): ChannelResult {
@@ -564,17 +1051,20 @@ public inline fun ChannelResult.onClosed(action: (exception: Throwable?)
}
/**
- * Iterator for [ReceiveChannel]. Instances of this interface are *not thread-safe* and shall not be used
- * from concurrent coroutines.
+ * Iterator for a [ReceiveChannel].
+ * Instances of this interface are *not thread-safe* and shall not be used from concurrent coroutines.
*/
public interface ChannelIterator {
/**
- * Returns `true` if the channel has more elements, suspending the caller while this channel is empty,
- * or returns `false` if the channel [is closed for `receive`][ReceiveChannel.isClosedForReceive] without a cause.
- * It throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
+ * Prepare an element for retrieval by the invocation of [next].
*
- * This function retrieves and removes an element from this channel for the subsequent invocation
- * of [next].
+ * - If the element that was retrieved by an earlier [hasNext] call was not yet consumed by [next], returns `true`.
+ * - If the channel has an element available, returns `true` and removes it from the channel.
+ * This element will be returned by the subsequent invocation of [next].
+ * - If the channel is [closed for receiving][ReceiveChannel.isClosedForReceive] without a cause, returns `false`.
+ * - If the channel is closed with a cause, throws the original [close][SendChannel.close] cause exception.
+ * - If the channel is not closed but does not contain an element,
+ * suspends until either an element is sent to the channel or the channel gets closed.
*
* This suspending function is cancellable: if the [Job] of the current coroutine is cancelled while this
* suspending function is waiting, this function immediately resumes with [CancellationException].
@@ -583,10 +1073,12 @@ public interface ChannelIterator {
* See [suspendCancellableCoroutine] for low-level details.
*
* Because of the prompt cancellation guarantee, some values retrieved from the channel can become lost.
- * See "Undelivered elements" section in [Channel] documentation for details on handling undelivered elements.
+ * See the "Undelivered elements" section in the [Channel] documentation
+ * for details on handling undelivered elements.
*
- * 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.
+ * Note that this function does not check for cancellation when it is not suspended, that is,
+ * if the next element is immediately available.
+ * Use [ensureActive] or [CoroutineScope.isActive] to periodically check for cancellation in tight loops if needed.
*/
public suspend operator fun hasNext(): Boolean
@@ -604,66 +1096,72 @@ public interface ChannelIterator {
}
/**
- * Retrieves the element removed from the channel by a preceding call to [hasNext], or
+ * Retrieves the element removed from the channel by the preceding call to [hasNext], or
* throws an [IllegalStateException] if [hasNext] was not invoked.
- * This method should only be used in pair with [hasNext]:
+ *
+ * This method can only be used together with [hasNext]:
* ```
* while (iterator.hasNext()) {
* val element = iterator.next()
- * // ... handle element ...
+ * // ... handle the element ...
+ * }
+ * ```
+ *
+ * A more idiomatic way to iterate over a channel is to use a `for` loop:
+ * ```
+ * for (element in channel) {
+ * // ... handle the element ...
* }
* ```
*
- * This method throws a [ClosedReceiveChannelException] if the channel [is closed for `receive`][ReceiveChannel.isClosedForReceive] without a cause.
- * It throws the original [close][SendChannel.close] cause exception if the channel has _failed_.
+ * This method never throws if [hasNext] returned `true`.
+ * If [hasNext] threw the cause with which the channel was closed, this method will rethrow the same exception.
+ * If [hasNext] returned `false` because the channel was closed without a cause, this method throws
+ * a [ClosedReceiveChannelException].
*/
public operator fun next(): E
}
/**
* Channel is a non-blocking primitive for communication between a sender (via [SendChannel]) and a receiver (via [ReceiveChannel]).
- * Conceptually, a channel is similar to Java's [BlockingQueue][java.util.concurrent.BlockingQueue],
+ * Conceptually, a channel is similar to `java.util.concurrent.BlockingQueue`,
* but it has suspending operations instead of blocking ones and can be [closed][SendChannel.close].
*
- * ### Creating channels
+ * ### Channel capacity
*
- * The `Channel(capacity)` factory function is used to create channels of different kinds depending on
- * the value of the `capacity` integer:
+ * Most ways to create a [Channel] (in particular, the `Channel()` factory function) allow specifying a capacity,
+ * which determines how elements are buffered in the channel.
+ * There are several predefined constants for the capacity that have special behavior:
*
- * - When `capacity` is 0 — it creates a _rendezvous_ channel.
- * This channel does not have any buffer at all. An element is transferred from the sender
- * to the receiver only when [send] and [receive] invocations meet in time (rendezvous), so [send] suspends
- * until another coroutine invokes [receive], and [receive] suspends until another coroutine invokes [send].
+ * - [Channel.RENDEZVOUS] (or 0) creates a _rendezvous_ channel, which does not have a buffer at all.
+ * Instead, the sender and the receiver must rendezvous (meet):
+ * [SendChannel.send] suspends until another coroutine invokes [ReceiveChannel.receive], and vice versa.
+ * - [Channel.CONFLATED] creates a buffer for a single element and automatically changes the
+ * [buffer overflow strategy][BufferOverflow] to [BufferOverflow.DROP_OLDEST].
+ * - [Channel.UNLIMITED] creates a channel with an unlimited buffer, which never suspends the sender.
+ * - [Channel.BUFFERED] creates a channel with a buffer whose size depends on
+ * the [buffer overflow strategy][BufferOverflow].
*
- * - When `capacity` is [Channel.UNLIMITED] — it creates a channel with effectively unlimited buffer.
- * This channel has a linked-list buffer of unlimited capacity (limited only by available memory).
- * [Sending][send] to this channel never suspends, and [trySend] always succeeds.
+ * See each constant's documentation for more details.
*
- * - When `capacity` is [Channel.CONFLATED] — it creates a _conflated_ channel
- * This channel buffers at most one element and conflates all subsequent `send` and `trySend` invocations,
- * so that the receiver always gets the last element sent.
- * Back-to-back sent elements are conflated — only the last sent element is received,
- * while previously sent elements **are lost**.
- * [Sending][send] to this channel never suspends, and [trySend] always succeeds.
+ * If the capacity is positive but less than [Channel.UNLIMITED], the channel has a buffer with the specified capacity.
+ * It is safe to construct a channel with a large buffer, as memory is only allocated gradually as elements are added.
*
- * - When `capacity` is positive but less than [UNLIMITED] — it creates an array-based channel with the specified capacity.
- * This channel has an array buffer of a fixed `capacity`.
- * [Sending][send] suspends only when the buffer is full, and [receiving][receive] suspends only when the buffer is empty.
+ * Constructing a channel with a negative capacity not equal to a predefined constant is not allowed
+ * and throws an [IllegalArgumentException].
*
- * Buffered channels can be configured with an additional [`onBufferOverflow`][BufferOverflow] parameter. It controls the behaviour
- * of the channel's [send][Channel.send] function on buffer overflow:
+ * ### Buffer overflow
*
- * - [SUSPEND][BufferOverflow.SUSPEND] — the default, suspend `send` on buffer overflow until there is
- * free space in the buffer.
- * - [DROP_OLDEST][BufferOverflow.DROP_OLDEST] — do not suspend the `send`, add the latest value to the buffer,
- * drop the oldest one from the buffer.
- * A channel with `capacity = 1` and `onBufferOverflow = DROP_OLDEST` is a _conflated_ channel.
- * - [DROP_LATEST][BufferOverflow.DROP_LATEST] — do not suspend the `send`, drop the value that is being sent,
- * keep the buffer contents intact.
+ * Some ways to create a [Channel] also expose a [BufferOverflow] parameter (by convention, `onBufferOverflow`),
+ * which does not affect the receiver but determines the behavior of the sender when the buffer is full.
+ * The options include [suspending][BufferOverflow.SUSPEND] until there is space in the buffer,
+ * [dropping the oldest element][BufferOverflow.DROP_OLDEST] to make room for the new one, or
+ * [dropping the element to be sent][BufferOverflow.DROP_LATEST]. See the [BufferOverflow] documentation.
*
- * A non-default `onBufferOverflow` implicitly creates a channel with at least one buffered element and
- * is ignored for a channel with unlimited buffer. It cannot be specified for `capacity = CONFLATED`, which
- * is a shortcut by itself.
+ * By convention, the default value for [BufferOverflow] whenever it can not be configured is [BufferOverflow.SUSPEND].
+ *
+ * See the [Channel.RENDEZVOUS], [Channel.CONFLATED], and [Channel.UNLIMITED] documentation for a description of how
+ * they interact with the [BufferOverflow] parameter.
*
* ### Prompt cancellation guarantee
*
@@ -673,41 +1171,48 @@ public interface ChannelIterator {
* With a single-threaded [dispatcher][CoroutineDispatcher] like [Dispatchers.Main], this gives a
* guarantee that the coroutine promptly reacts to the cancellation of its [Job] and does not resume its execution.
*
- * > **Prompt cancellation guarantee** for channel operations was added since `kotlinx.coroutines` version `1.4.0`
- * > and had replaced a channel-specific atomic-cancellation that was not consistent with other suspending functions.
- * > The low-level mechanics of prompt cancellation are explained in [suspendCancellableCoroutine] function.
+ * > **Prompt cancellation guarantee** for channel operations was added in `kotlinx.coroutines` version `1.4.0`
+ * > and has replaced the channel-specific atomic cancellation that was not consistent with other suspending functions.
+ * > The low-level mechanics of prompt cancellation are explained in the [suspendCancellableCoroutine] documentation.
*
* ### Undelivered elements
*
* As a result of the prompt cancellation guarantee, when a closeable resource
- * (like open file or a handle to another native resource) is transferred via a channel from one coroutine to another,
- * it can fail to be delivered and will be lost if the receiving operation is cancelled in transit.
+ * (like an open file or a handle to another native resource) is transferred via a channel,
+ * it can be successfully extracted from the channel,
+ * but still be lost if the receiving operation is cancelled in parallel.
*
- * A `Channel()` constructor function has an `onUndeliveredElement` optional parameter.
- * When `onUndeliveredElement` parameter is set, the corresponding function is called once for each element
+ * The `Channel()` factory function has the optional parameter `onUndeliveredElement`.
+ * When that parameter is set, the corresponding function is called once for each element
* that was sent to the channel with the call to the [send][SendChannel.send] function but failed to be delivered,
* which can happen in the following cases:
*
- * - When [send][SendChannel.send] operation throws an exception because it was cancelled before it had a chance to actually
- * send the element or because the channel was [closed][SendChannel.close] or [cancelled][ReceiveChannel.cancel].
- * - When [receive][ReceiveChannel.receive], [receiveOrNull][ReceiveChannel.receiveOrNull], or [hasNext][ChannelIterator.hasNext]
- * operation throws an exception when it had retrieved the element from the
- * channel but was cancelled before the code following the receive call resumed.
- * - The channel was [cancelled][ReceiveChannel.cancel], in which case `onUndeliveredElement` is called on every
+ * - When an element is dropped due to the limited buffer capacity.
+ * This can happen when the overflow strategy is [BufferOverflow.DROP_LATEST] or [BufferOverflow.DROP_OLDEST].
+ * - When the sending operations like [send][SendChannel.send] or [onSend][SendChannel.onSend]
+ * throw an exception because it was cancelled
+ * before it had a chance to actually send the element
+ * or because the channel was [closed][SendChannel.close] or [cancelled][ReceiveChannel.cancel].
+ * - When the receiving operations like [receive][ReceiveChannel.receive],
+ * [onReceive][ReceiveChannel.onReceive], or [hasNext][ChannelIterator.hasNext]
+ * throw an exception after retrieving the element from the channel
+ * because of being cancelled before the code following them had a chance to resume.
+ * - When the channel was [cancelled][ReceiveChannel.cancel], in which case `onUndeliveredElement` is called on every
* remaining element in the channel's buffer.
*
- * Note, that `onUndeliveredElement` function is called synchronously in an arbitrary context. It should be fast, non-blocking,
- * and should not throw exceptions. Any exception thrown by `onUndeliveredElement` is wrapped into an internal runtime
- * exception which is either rethrown from the caller method or handed off to the exception handler in the current context
+ * Note that `onUndeliveredElement` is called synchronously in an arbitrary context.
+ * It should be fast, non-blocking, and should not throw exceptions.
+ * Any exception thrown by `onUndeliveredElement` is wrapped into an internal runtime exception
+ * which is either rethrown from the caller method or handed off to the exception handler in the current context
* (see [CoroutineExceptionHandler]) when one is available.
*
* A typical usage for `onUndeliveredElement` is to close a resource that is being transferred via the channel. The
- * following code pattern guarantees that opened resources are closed even if producer, consumer, and/or channel
- * are cancelled. Resources are never lost.
+ * following code pattern guarantees that opened resources are closed even if the producer, the consumer,
+ * and/or the channel are cancelled. Resources are never lost.
*
* ```
- * // Create the channel with onUndeliveredElement block that closes a resource
- * val channel = Channel(capacity) { resource -> resource.close() }
+ * // Create a channel with an onUndeliveredElement block that closes a resource
+ * val channel = Channel(onUndeliveredElement = { resource -> resource.close() })
*
* // Producer code
* val resourceToSend = openResource()
@@ -722,8 +1227,8 @@ public interface ChannelIterator {
* }
* ```
*
- * > Note, that if you do any kind of work in between `openResource()` and `channel.send(...)`, then you should
- * > ensure that resource gets closed in case this additional code fails.
+ * > Note that if any work happens between `openResource()` and `channel.send(...)`,
+ * > it is your responsibility to ensure that resource gets closed in case this additional code fails.
*/
public interface Channel : SendChannel, ReceiveChannel {
/**
@@ -731,26 +1236,115 @@ public interface Channel : SendChannel, ReceiveChannel {
*/
public companion object Factory {
/**
- * Requests a channel with an unlimited capacity buffer in the `Channel(...)` factory function.
+ * An unlimited buffer capacity.
+ *
+ * `Channel(UNLIMITED)` creates a channel with an unlimited buffer, which never suspends the sender.
+ * The total amount of elements that can be sent to the channel is limited only by the available memory.
+ *
+ * If [BufferOverflow] is specified for the channel, it is completely ignored,
+ * as the channel never suspends the sender.
+ *
+ * ```
+ * val channel = Channel(Channel.UNLIMITED)
+ * repeat(1000) {
+ * channel.trySend(it)
+ * }
+ * repeat(1000) {
+ * check(channel.tryReceive().getOrNull() == it)
+ * }
+ * ```
*/
public const val UNLIMITED: Int = Int.MAX_VALUE
/**
- * Requests a rendezvous channel in the `Channel(...)` factory function — a channel that does not have a buffer.
+ * The zero buffer capacity.
+ *
+ * For the default [BufferOverflow] value of [BufferOverflow.SUSPEND],
+ * `Channel(RENDEZVOUS)` creates a channel without a buffer.
+ * An element is transferred from the sender to the receiver only when [send] and [receive] invocations meet
+ * in time (that is, they _rendezvous_),
+ * so [send] suspends until another coroutine invokes [receive],
+ * and [receive] suspends until another coroutine invokes [send].
+ *
+ * ```
+ * val channel = Channel(Channel.RENDEZVOUS)
+ * check(channel.trySend(5).isFailure) // sending fails: no receiver is waiting
+ * launch(start = CoroutineStart.UNDISPATCHED) {
+ * val element = channel.receive() // suspends
+ * check(element == 3)
+ * }
+ * check(channel.trySend(3).isSuccess) // sending succeeds: receiver is waiting
+ * ```
+ *
+ * If a different [BufferOverflow] is specified,
+ * `Channel(RENDEZVOUS)` creates a channel with a buffer of size 1:
+ *
+ * ```
+ * val channel = Channel(0, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ * // None of the calls suspend, since the buffer overflow strategy is not SUSPEND
+ * channel.send(1)
+ * channel.send(2)
+ * channel.send(3)
+ * check(channel.receive() == 3)
+ * ```
*/
public const val RENDEZVOUS: Int = 0
/**
- * Requests a conflated channel in the `Channel(...)` factory function. This is a shortcut to creating
- * a channel with [`onBufferOverflow = DROP_OLDEST`][BufferOverflow.DROP_OLDEST].
+ * A single-element buffer with conflating behavior.
+ *
+ * Specifying [CONFLATED] as the capacity in the `Channel(...)` factory function is equivalent to
+ * creating a channel with a buffer of size 1 and a [BufferOverflow] strategy of [BufferOverflow.DROP_OLDEST]:
+ * `Channel(1, onBufferOverflow = BufferOverflow.DROP_OLDEST)`.
+ * Such a channel buffers at most one element and conflates all subsequent `send` and `trySend` invocations
+ * so that the receiver always gets the last element sent, **losing** the previously sent elements:
+ * see the "Undelivered elements" section in the [Channel] documentation.
+ * [Sending][send] to this channel never suspends, and [trySend] always succeeds.
+ *
+ * ```
+ * val channel = Channel(Channel.CONFLATED)
+ * channel.send(1)
+ * channel.send(2)
+ * channel.send(3)
+ * check(channel.receive() == 3)
+ * ```
+ *
+ * Specifying a [BufferOverflow] other than [BufferOverflow.SUSPEND] is not allowed with [CONFLATED], and
+ * an [IllegalArgumentException] is thrown if such a combination is used.
+ * For creating a conflated channel that instead keeps the existing element in the channel and throws out
+ * the new one, use `Channel(1, onBufferOverflow = BufferOverflow.DROP_LATEST)`.
*/
public const val CONFLATED: Int = -1
/**
- * Requests a buffered channel with the default buffer capacity in the `Channel(...)` factory function.
- * The default capacity for a channel that [suspends][BufferOverflow.SUSPEND] on overflow
- * is 64 and can be overridden by setting [DEFAULT_BUFFER_PROPERTY_NAME] on JVM.
- * For non-suspending channels, a buffer of capacity 1 is used.
+ * A channel capacity marker that is substituted by the default buffer capacity.
+ *
+ * When passed as a parameter to the `Channel(...)` factory function, the default buffer capacity is used.
+ * For [BufferOverflow.SUSPEND] (the default buffer overflow strategy), the default capacity is 64,
+ * but on the JVM it can be overridden by setting the [DEFAULT_BUFFER_PROPERTY_NAME] system property.
+ * The overridden value is used for all channels created with a default buffer capacity,
+ * including those created in third-party libraries.
+ *
+ * ```
+ * val channel = Channel(Channel.BUFFERED)
+ * repeat(100) {
+ * channel.trySend(it)
+ * }
+ * channel.close()
+ * // The check can fail if the default buffer capacity is changed
+ * check(channel.toList() == (0..<64).toList())
+ * ```
+ *
+ * If a different [BufferOverflow] is specified, `Channel(BUFFERED)` creates a channel with a buffer of size 1:
+ *
+ * ```
+ * val channel = Channel(Channel.BUFFERED, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ * channel.send(1)
+ * channel.send(2)
+ * channel.send(3)
+ * channel.close()
+ * check(channel.toList() == listOf(3))
+ * ```
*/
public const val BUFFERED: Int = -2
@@ -758,9 +1352,18 @@ public interface Channel : SendChannel, ReceiveChannel {
internal const val OPTIONAL_CHANNEL = -3
/**
- * Name of the property that defines the default channel capacity when
- * [BUFFERED] is used as parameter in `Channel(...)` factory function.
+ * Name of the JVM system property for the default channel capacity (64 by default).
+ *
+ * See [BUFFERED] for details on how this property is used.
+ *
+ * Setting this property affects the default channel capacity for channel constructors,
+ * channel-backed coroutines and flow operators that imply channel usage,
+ * including ones defined in 3rd-party libraries.
+ *
+ * Usage of this property is highly discouraged and is intended to be used as a last-ditch effort
+ * as an immediate measure for hot fixes and duct-taping.
*/
+ @DelicateCoroutinesApi
public const val DEFAULT_BUFFER_PROPERTY_NAME: String = "kotlinx.coroutines.channels.defaultBuffer"
internal val CHANNEL_DEFAULT_CAPACITY = systemProp(DEFAULT_BUFFER_PROPERTY_NAME,
@@ -770,16 +1373,55 @@ public interface Channel : SendChannel, ReceiveChannel {
}
/**
- * Creates a channel with the specified buffer capacity (or without a buffer by default).
- * See [Channel] interface documentation for details.
+ * Creates a channel. See the [Channel] interface documentation for details.
+ *
+ * This function is the most flexible way to create a channel.
+ * It allows specifying the channel's capacity, buffer overflow strategy, and an optional function to call
+ * to handle undelivered elements.
+ *
+ * ```
+ * val allocatedResources = HashSet()
+ * // An autocloseable resource that must be closed when it is no longer needed
+ * class Resource(val id: Int): AutoCloseable {
+ * init {
+ * allocatedResources.add(id)
+ * }
+ * override fun close() {
+ * allocatedResources.remove(id)
+ * }
+ * }
+ * // A channel with a 15-element buffer that drops the oldest element on buffer overflow
+ * // and closes the elements that were not delivered to the consumer
+ * val channel = Channel(
+ * capacity = 15,
+ * onBufferOverflow = BufferOverflow.DROP_OLDEST,
+ * onUndeliveredElement = { element -> element.close() }
+ * )
+ * // A sender's view of the channel
+ * val sendChannel: SendChannel = channel
+ * repeat(100) {
+ * sendChannel.send(Resource(it))
+ * }
+ * sendChannel.close()
+ * // A receiver's view of the channel
+ * val receiveChannel: ReceiveChannel = channel
+ * val receivedResources = receiveChannel.toList()
+ * // Check that the last 15 sent resources were received
+ * check(receivedResources.map { it.id } == (85 until 100).toList())
+ * // Close the resources that were successfully received
+ * receivedResources.forEach { it.close() }
+ * // The dropped resources were closed by the channel itself
+ * check(allocatedResources.isEmpty())
+ * ```
+ *
+ * For a full explanation of every parameter and their interaction, see the [Channel] interface documentation.
*
* @param capacity either a positive channel capacity or one of the constants defined in [Channel.Factory].
- * @param onBufferOverflow configures an action on buffer overflow (optional, defaults to
- * a [suspending][BufferOverflow.SUSPEND] attempt to [send][Channel.send] a value,
- * supported only when `capacity >= 0` or `capacity == Channel.BUFFERED`,
- * implicitly creates a channel with at least one buffered element).
- * @param onUndeliveredElement an optional function that is called when element was sent but was not delivered to the consumer.
- * See "Undelivered elements" section in [Channel] documentation.
+ * See the "Channel capacity" section in the [Channel] documentation.
+ * @param onBufferOverflow configures an action on buffer overflow.
+ * See the "Buffer overflow" section in the [Channel] documentation.
+ * @param onUndeliveredElement a function that is called when element was sent but was not delivered to the consumer.
+ * See the "Undelivered elements" section in the [Channel] documentation.
* @throws IllegalArgumentException when [capacity] < -2
*/
public fun Channel(
@@ -815,20 +1457,29 @@ public fun Channel(
public fun Channel(capacity: Int = RENDEZVOUS): Channel = Channel(capacity)
/**
- * Indicates an attempt to [send][SendChannel.send] to a [isClosedForSend][SendChannel.isClosedForSend] channel
- * that was closed without a cause. A _failed_ channel rethrows the original [close][SendChannel.close] cause
- * exception on send attempts.
+ * Indicates an attempt to [send][SendChannel.send] to a [closed-for-sending][SendChannel.isClosedForSend] channel
+ * that was [closed][SendChannel.close] without a cause.
*
- * This exception is a subclass of [IllegalStateException], because, conceptually, it is the sender's responsibility
- * to close the channel and not try to send anything thereafter. Attempts to
- * send to a closed channel indicate a logical error in the sender's code.
+ * If a cause was provided, that cause is thrown from [send][SendChannel.send] instead of this exception.
+ * In particular, if the channel was closed because it was [cancelled][ReceiveChannel.cancel],
+ * this exception will never be thrown: either the `cause` of the cancellation is thrown,
+ * or a new [CancellationException] gets constructed to be thrown from [SendChannel.send].
+ *
+ * This exception is a subclass of [IllegalStateException], because the sender should not attempt to send to a closed
+ * channel after it itself has [closed][SendChannel.close] it, and indicates an error on the part of the programmer.
+ * Usually, this exception can be avoided altogether by restructuring the code.
*/
public class ClosedSendChannelException(message: String?) : IllegalStateException(message)
/**
- * Indicates an attempt to [receive][ReceiveChannel.receive] from a [isClosedForReceive][ReceiveChannel.isClosedForReceive]
- * channel that was closed without a cause. A _failed_ channel rethrows the original [close][SendChannel.close] cause
- * exception on receive attempts.
+ * Indicates an attempt to [receive][ReceiveChannel.receive] from a
+ * [closed-for-receiving][ReceiveChannel.isClosedForReceive] channel
+ * that was [closed][SendChannel.close] without a cause.
+ *
+ * If a clause was provided, that clause is thrown from [receive][ReceiveChannel.receive] instead of this exception.
+ * In particular, if the channel was closed because it was [cancelled][ReceiveChannel.cancel],
+ * this exception will never be thrown: either the `cause` of the cancellation is thrown,
+ * or a new [CancellationException] gets constructed to be thrown from [ReceiveChannel.receive].
*
* This exception is a subclass of [NoSuchElementException] to be consistent with plain collections.
*/
diff --git a/kotlinx-coroutines-core/common/src/channels/Produce.kt b/kotlinx-coroutines-core/common/src/channels/Produce.kt
index 7a955597f1..e746c37d13 100644
--- a/kotlinx-coroutines-core/common/src/channels/Produce.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Produce.kt
@@ -81,6 +81,7 @@ public suspend fun ProducerScope<*>.awaitClose(block: () -> Unit = {}) {
* The kind of the resulting channel depends on the specified [capacity] parameter.
* See the [Channel] interface documentation for details.
* By default, an unbuffered channel is created.
+ * If an invalid [capacity] value is specified, an [IllegalArgumentException] is thrown.
*
* ### Behavior on termination
*
@@ -114,9 +115,9 @@ public suspend fun ProducerScope<*>.awaitClose(block: () -> Unit = {}) {
* channel.cancel()
* ```
*
- * If this coroutine finishes with an exception, it will close the channel with that exception as the cause and
- * the resulting channel will become _failed_, so after receiving all the existing elements, all further attempts
- * to receive from it will throw the exception with which the coroutine finished.
+ * If this coroutine finishes with an exception, it will close the channel with that exception as the cause,
+ * so after receiving all the existing elements,
+ * all further attempts to receive from it will throw the exception with which the coroutine finished.
*
* ```
* val produceJob = Job()
@@ -150,8 +151,7 @@ public suspend fun ProducerScope<*>.awaitClose(block: () -> Unit = {}) {
* } // throws a `CancellationException` exception after reaching -1
* ```
*
- * Note that cancelling `produce` via structured concurrency closes the channel with a cause,
- * making it a _failed_ channel.
+ * Note that cancelling `produce` via structured concurrency closes the channel with a cause.
*
* The behavior around coroutine cancellation and error handling is experimental and may change in a future release.
*
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt b/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt
index dc3b709a5f..2c37e24162 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Limit.kt
@@ -5,6 +5,7 @@ package kotlinx.coroutines.flow
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.internal.*
+import kotlin.coroutines.*
import kotlin.jvm.*
import kotlinx.coroutines.flow.flow as safeFlow
import kotlinx.coroutines.flow.internal.unsafeFlow as flow
@@ -133,5 +134,7 @@ internal suspend inline fun Flow.collectWhile(crossinline predicate: susp
collect(collector)
} catch (e: AbortFlowException) {
e.checkOwnership(collector)
+ // The task might have been cancelled before AbortFlowException was thrown.
+ coroutineContext.ensureActive()
}
}
diff --git a/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt b/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt
index 42b40c5909..4247a72346 100644
--- a/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt
+++ b/kotlinx-coroutines-core/common/src/flow/operators/Lint.kt
@@ -58,7 +58,7 @@ public fun StateFlow.distinctUntilChanged(): Flow = noImpl()
* @suppress
*/
@Deprecated(
- message = "isActive is resolved into the extension of outer CoroutineScope which is likely to be an error." +
+ message = "isActive is resolved into the extension of outer CoroutineScope which is likely to be an error. " +
"Use currentCoroutineContext().isActive or cancellable() operator instead " +
"or specify the receiver of isActive explicitly. " +
"Additionally, flow {} builder emissions are cancellable by default.",
@@ -72,7 +72,7 @@ public val FlowCollector<*>.isActive: Boolean
* @suppress
*/
@Deprecated(
- message = "cancel() is resolved into the extension of outer CoroutineScope which is likely to be an error." +
+ message = "cancel() is resolved into the extension of outer CoroutineScope which is likely to be an error. " +
"Use currentCoroutineContext().cancel() instead or specify the receiver of cancel() explicitly",
level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("currentCoroutineContext().cancel(cause)")
@@ -83,7 +83,7 @@ public fun FlowCollector<*>.cancel(cause: CancellationException? = null): Unit =
* @suppress
*/
@Deprecated(
- message = "coroutineContext is resolved into the property of outer CoroutineScope which is likely to be an error." +
+ message = "coroutineContext is resolved into the property of outer CoroutineScope which is likely to be an error. " +
"Use currentCoroutineContext() instead or specify the receiver of coroutineContext explicitly",
level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("currentCoroutineContext()")
diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt
new file mode 100644
index 0000000000..6d1cd6fee9
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/flow/terminal/Logic.kt
@@ -0,0 +1,107 @@
+@file:JvmMultifileClass
+@file:JvmName("FlowKt")
+
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.*
+import kotlin.jvm.*
+
+
+/**
+ * A terminal operator that returns `true` and immediately cancels the flow
+ * if at least one element matches the given [predicate].
+ *
+ * If the flow does not emit any elements or no element matches the predicate, the function returns `false`.
+ *
+ * Equivalent to `!all { !predicate(it) }` (see [Flow.all]) and `!none { predicate(it) }` (see [Flow.none]).
+ *
+ * Example:
+ *
+ * ```
+ * val myFlow = flow {
+ * repeat(10) {
+ * emit(it)
+ * }
+ * throw RuntimeException("You still didn't find the required number? I gave you ten!")
+ * }
+ * println(myFlow.any { it > 5 }) // true
+ * println(flowOf(1, 2, 3).any { it > 5 }) // false
+ * ```
+ *
+ * @see Iterable.any
+ * @see Sequence.any
+ */
+public suspend fun Flow.any(predicate: suspend (T) -> Boolean): Boolean {
+ var found = false
+ collectWhile {
+ val satisfies = predicate(it)
+ if (satisfies) found = true
+ !satisfies
+ }
+ return found
+}
+
+/**
+ * A terminal operator that returns `true` if all elements match the given [predicate],
+ * or returns `false` and cancels the flow as soon as the first element not matching the predicate is encountered.
+ *
+ * If the flow terminates without emitting any elements, the function returns `true` because there
+ * are no elements in it that *do not* match the predicate.
+ * See a more detailed explanation of this logic concept in the
+ * ["Vacuous truth"](https://en.wikipedia.org/wiki/Vacuous_truth) article.
+ *
+ * Equivalent to `!any { !predicate(it) }` (see [Flow.any]) and `none { !predicate(it) }` (see [Flow.none]).
+ *
+ * Example:
+ *
+ * ```
+ * val myFlow = flow {
+ * repeat(10) {
+ * emit(it)
+ * }
+ * throw RuntimeException("You still didn't find the required number? I gave you ten!")
+ * }
+ * println(myFlow.all { it <= 5 }) // false
+ * println(flowOf(1, 2, 3).all { it <= 5 }) // true
+ * ```
+ *
+ * @see Iterable.all
+ * @see Sequence.all
+ */
+public suspend fun Flow.all(predicate: suspend (T) -> Boolean): Boolean {
+ var foundCounterExample = false
+ collectWhile {
+ val satisfies = predicate(it)
+ if (!satisfies) foundCounterExample = true
+ satisfies
+ }
+ return !foundCounterExample
+}
+
+/**
+ * A terminal operator that returns `true` if no elements match the given [predicate],
+ * or returns `false` and cancels the flow as soon as the first element matching the predicate is encountered.
+ *
+ * If the flow terminates without emitting any elements, the function returns `true` because there
+ * are no elements in it that match the predicate.
+ * See a more detailed explanation of this logic concept in the
+ * ["Vacuous truth"](https://en.wikipedia.org/wiki/Vacuous_truth) article.
+ *
+ * Equivalent to `!any(predicate)` (see [Flow.any]) and `all { !predicate(it) }` (see [Flow.all]).
+ *
+ * Example:
+ * ```
+ * val myFlow = flow {
+ * repeat(10) {
+ * emit(it)
+ * }
+ * throw RuntimeException("You still didn't find the required number? I gave you ten!")
+ * }
+ * println(myFlow.none { it > 5 }) // false
+ * println(flowOf(1, 2, 3).none { it > 5 }) // true
+ * ```
+ *
+ * @see Iterable.none
+ * @see Sequence.none
+ */
+public suspend fun Flow.none(predicate: suspend (T) -> Boolean): Boolean = !any(predicate)
diff --git a/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt b/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt
index f241196b72..fae4525c64 100644
--- a/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt
+++ b/kotlinx-coroutines-core/common/src/flow/terminal/Reduce.kt
@@ -107,7 +107,7 @@ public suspend fun Flow.first(predicate: suspend (T) -> Boolean): T {
true
}
}
- if (result === NULL) throw NoSuchElementException("Expected at least one element matching the predicate $predicate")
+ if (result === NULL) throw NoSuchElementException("Expected at least one element matching the predicate")
return result as T
}
diff --git a/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt b/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt
index 26e7c5abd4..4c8f54e877 100644
--- a/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt
+++ b/kotlinx-coroutines-core/common/src/internal/DispatchedContinuation.kt
@@ -187,10 +187,10 @@ internal class DispatchedContinuation(
override fun resumeWith(result: Result) {
val state = result.toState()
- if (dispatcher.isDispatchNeeded(context)) {
+ if (dispatcher.safeIsDispatchNeeded(context)) {
_state = state
resumeMode = MODE_ATOMIC
- dispatcher.dispatch(context, this)
+ dispatcher.safeDispatch(context, this)
} else {
executeUnconfined(state, MODE_ATOMIC) {
withCoroutineContext(context, countOrElement) {
@@ -205,10 +205,10 @@ internal class DispatchedContinuation(
@Suppress("NOTHING_TO_INLINE")
internal inline fun resumeCancellableWith(result: Result) {
val state = result.toState()
- if (dispatcher.isDispatchNeeded(context)) {
+ if (dispatcher.safeIsDispatchNeeded(context)) {
_state = state
resumeMode = MODE_CANCELLABLE
- dispatcher.dispatch(context, this)
+ dispatcher.safeDispatch(context, this)
} else {
executeUnconfined(state, MODE_CANCELLABLE) {
if (!resumeCancelled(state)) {
@@ -249,6 +249,22 @@ internal class DispatchedContinuation(
"DispatchedContinuation[$dispatcher, ${continuation.toDebugString()}]"
}
+internal fun CoroutineDispatcher.safeDispatch(context: CoroutineContext, runnable: Runnable) {
+ try {
+ dispatch(context, runnable)
+ } catch (e: Throwable) {
+ throw DispatchException(e, this, context)
+ }
+}
+
+internal fun CoroutineDispatcher.safeIsDispatchNeeded(context: CoroutineContext): Boolean {
+ try {
+ return isDispatchNeeded(context)
+ } catch (e: Throwable) {
+ throw DispatchException(e, this, context)
+ }
+}
+
/**
* It is not inline to save bytecode (it is pretty big and used in many places)
* and we leave it public so that its name is not mangled in use stack traces if it shows there.
diff --git a/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt b/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt
index 309685bb7c..ad5fed1205 100644
--- a/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt
+++ b/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt
@@ -76,7 +76,6 @@ internal abstract class DispatchedTask internal constructor(
final override fun run() {
assert { resumeMode != MODE_UNINITIALIZED } // should have been set before dispatching
- var fatalException: Throwable? = null
try {
val delegate = delegate as DispatchedContinuation
val continuation = delegate.continuation
@@ -102,11 +101,10 @@ internal abstract class DispatchedTask internal constructor(
}
}
}
+ } catch (e: DispatchException) {
+ handleCoroutineException(delegate.context, e.cause)
} catch (e: Throwable) {
- // This instead of runCatching to have nicer stacktrace and debug experience
- fatalException = e
- } finally {
- fatalException?.let { handleFatalException(it) }
+ handleFatalException(e)
}
}
@@ -143,8 +141,8 @@ internal fun DispatchedTask.dispatch(mode: Int) {
// dispatch directly using this instance's Runnable implementation
val dispatcher = delegate.dispatcher
val context = delegate.context
- if (dispatcher.isDispatchNeeded(context)) {
- dispatcher.dispatch(context, this)
+ if (dispatcher.safeIsDispatchNeeded(context)) {
+ dispatcher.safeDispatch(context, this)
} else {
resumeUnconfined()
}
@@ -205,3 +203,17 @@ internal inline fun DispatchedTask<*>.runUnconfinedEventLoop(
internal inline fun Continuation<*>.resumeWithStackTrace(exception: Throwable) {
resumeWith(Result.failure(recoverStackTrace(exception, this)))
}
+
+/**
+ * This exception holds an exception raised in [CoroutineDispatcher.dispatch] method.
+ * When dispatcher methods fail unexpectedly, it is likely a user-induced programmatic bug,
+ * such as calling `executor.close()` prematurely. To avoid reporting such exceptions as fatal errors,
+ * we handle them with a separate code path. See also #4091.
+ *
+ * @see safeDispatch
+ */
+internal class DispatchException(
+ override val cause: Throwable,
+ dispatcher: CoroutineDispatcher,
+ context: CoroutineContext,
+) : Exception("Coroutine dispatcher $dispatcher threw an exception, context = $context", cause)
diff --git a/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt b/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt
index eb5196144f..488331fc37 100644
--- a/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt
+++ b/kotlinx-coroutines-core/common/src/internal/LimitedDispatcher.kt
@@ -42,7 +42,7 @@ internal class LimitedDispatcher(
override fun dispatch(context: CoroutineContext, block: Runnable) {
dispatchInternal(block) { worker ->
- dispatcher.dispatch(this, worker)
+ dispatcher.safeDispatch(this, worker)
}
}
@@ -116,10 +116,10 @@ internal class LimitedDispatcher(
}
currentTask = obtainTaskOrDeallocateWorker() ?: return
// 16 is our out-of-thin-air constant to emulate fairness. Used in JS dispatchers as well
- if (++fairnessCounter >= 16 && dispatcher.isDispatchNeeded(this@LimitedDispatcher)) {
+ if (++fairnessCounter >= 16 && dispatcher.safeIsDispatchNeeded(this@LimitedDispatcher)) {
// Do "yield" to let other views execute their runnable as well
// Note that we do not decrement 'runningWorkers' as we are still committed to our part of work
- dispatcher.dispatch(this@LimitedDispatcher, this)
+ dispatcher.safeDispatch(this@LimitedDispatcher, this)
return
}
}
diff --git a/kotlinx-coroutines-core/common/src/internal/Scopes.kt b/kotlinx-coroutines-core/common/src/internal/Scopes.kt
index 7e561c83dc..9b830bd5c9 100644
--- a/kotlinx-coroutines-core/common/src/internal/Scopes.kt
+++ b/kotlinx-coroutines-core/common/src/internal/Scopes.kt
@@ -23,6 +23,13 @@ internal open class ScopeCoroutine(
uCont.intercepted().resumeCancellableWith(recoverResult(state, uCont))
}
+ /**
+ * Invoked when a scoped coorutine was completed in an undispatched manner directly
+ * at the place of its start because it never suspended.
+ */
+ open fun afterCompletionUndispatched() {
+ }
+
override fun afterResume(state: Any?) {
// Resume direct because scope is already in the correct context
uCont.resumeWith(recoverResult(state, uCont))
diff --git a/kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt b/kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt
index 2c50f3c4aa..b92c51502a 100644
--- a/kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt
+++ b/kotlinx-coroutines-core/common/src/internal/Synchronized.common.kt
@@ -24,5 +24,6 @@ public inline fun synchronized(lock: SynchronizedObject, block: () -> T): T
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
+ @Suppress("LEAKED_IN_PLACE_LAMBDA") // Contract is preserved, invoked immediately or throws
return synchronizedImpl(lock, block)
}
diff --git a/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt b/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt
index 2f9a434a1f..1e87d767af 100644
--- a/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt
+++ b/kotlinx-coroutines-core/common/src/intrinsics/Cancellable.kt
@@ -58,6 +58,7 @@ private fun dispatcherFailure(completion: Continuation<*>, e: Throwable) {
* 2) Rethrow the exception immediately, so it will crash the caller (e.g. when the coroutine had
* no parent or it was async/produce over MainScope).
*/
- completion.resumeWith(Result.failure(e))
- throw e
+ val reportException = if (e is DispatchException) e.cause else e
+ completion.resumeWith(Result.failure(reportException))
+ throw reportException
}
diff --git a/kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt b/kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt
index 0d2e0404fc..511199701a 100644
--- a/kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt
+++ b/kotlinx-coroutines-core/common/src/intrinsics/Undispatched.kt
@@ -20,7 +20,8 @@ internal fun (suspend (R) -> T).startCoroutineUndispatched(receiver: R, c
startCoroutineUninterceptedOrReturn(receiver, actualCompletion)
}
} catch (e: Throwable) {
- actualCompletion.resumeWithException(e)
+ val reportException = if (e is DispatchException) e.cause else e
+ actualCompletion.resumeWithException(reportException)
return
}
if (value !== COROUTINE_SUSPENDED) {
@@ -78,6 +79,7 @@ private inline fun ScopeCoroutine.undispatchedResult(
if (result === COROUTINE_SUSPENDED) return COROUTINE_SUSPENDED // (1)
val state = makeCompletingOnce(result)
if (state === COMPLETING_WAITING_CHILDREN) return COROUTINE_SUSPENDED // (2)
+ afterCompletionUndispatched()
return if (state is CompletedExceptionally) { // (3)
when {
shouldThrow(state.cause) -> throw recoverStackTrace(state.cause, uCont)
diff --git a/kotlinx-coroutines-core/common/test/SupervisorTest.kt b/kotlinx-coroutines-core/common/test/SupervisorTest.kt
index 1fb0ff9a06..f528059577 100644
--- a/kotlinx-coroutines-core/common/test/SupervisorTest.kt
+++ b/kotlinx-coroutines-core/common/test/SupervisorTest.kt
@@ -1,5 +1,3 @@
-@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-21913
-
package kotlinx.coroutines
import kotlinx.coroutines.testing.*
diff --git a/kotlinx-coroutines-core/common/test/channels/BufferedChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/BufferedChannelTest.kt
index 39b9f9d755..d314f8b1f4 100644
--- a/kotlinx-coroutines-core/common/test/channels/BufferedChannelTest.kt
+++ b/kotlinx-coroutines-core/common/test/channels/BufferedChannelTest.kt
@@ -5,6 +5,20 @@ import kotlinx.coroutines.*
import kotlin.test.*
class BufferedChannelTest : TestBase() {
+
+ /** Tests that a buffered channel does not consume enough memory to fail with an OOM. */
+ @Test
+ fun testMemoryConsumption() = runTest {
+ val largeChannel = Channel(Int.MAX_VALUE / 2)
+ repeat(10_000) {
+ largeChannel.send(it)
+ }
+ repeat(10_000) {
+ val element = largeChannel.receive()
+ assertEquals(it, element)
+ }
+ }
+
@Test
fun testIteratorHasNextIsIdempotent() = runTest {
val q = Channel()
diff --git a/kotlinx-coroutines-core/common/test/channels/ProduceTest.kt b/kotlinx-coroutines-core/common/test/channels/ProduceTest.kt
index 654982ea8f..c8e9667fb2 100644
--- a/kotlinx-coroutines-core/common/test/channels/ProduceTest.kt
+++ b/kotlinx-coroutines-core/common/test/channels/ProduceTest.kt
@@ -266,6 +266,13 @@ class ProduceTest : TestBase() {
}
}
+ @Test
+ fun testProduceWithInvalidCapacity() = runTest {
+ assertFailsWith {
+ produce(capacity = -3) { }
+ }
+ }
+
private suspend fun cancelOnCompletion(coroutineContext: CoroutineContext) = CoroutineScope(coroutineContext).apply {
val source = Channel()
expect(1)
diff --git a/kotlinx-coroutines-core/common/test/channels/RendezvousChannelTest.kt b/kotlinx-coroutines-core/common/test/channels/RendezvousChannelTest.kt
index 8fd41b8376..00aa1925d2 100644
--- a/kotlinx-coroutines-core/common/test/channels/RendezvousChannelTest.kt
+++ b/kotlinx-coroutines-core/common/test/channels/RendezvousChannelTest.kt
@@ -273,4 +273,24 @@ class RendezvousChannelTest : TestBase() {
channel.cancel(TestCancellationException())
channel.receiveCatching().getOrThrow()
}
+
+ /** Tests that [BufferOverflow.DROP_OLDEST] takes precedence over [Channel.RENDEZVOUS]. */
+ @Test
+ fun testDropOldest() = runTest {
+ val channel = Channel(Channel.RENDEZVOUS, onBufferOverflow = BufferOverflow.DROP_OLDEST)
+ channel.send(1)
+ channel.send(2)
+ channel.send(3)
+ assertEquals(3, channel.receive())
+ }
+
+ /** Tests that [BufferOverflow.DROP_LATEST] takes precedence over [Channel.RENDEZVOUS]. */
+ @Test
+ fun testDropLatest() = runTest {
+ val channel = Channel(Channel.RENDEZVOUS, onBufferOverflow = BufferOverflow.DROP_LATEST)
+ channel.send(1)
+ channel.send(2)
+ channel.send(3)
+ assertEquals(1, channel.receive())
+ }
}
diff --git a/kotlinx-coroutines-core/common/test/flow/operators/BooleanTerminationTest.kt b/kotlinx-coroutines-core/common/test/flow/operators/BooleanTerminationTest.kt
new file mode 100644
index 0000000000..3087c78f67
--- /dev/null
+++ b/kotlinx-coroutines-core/common/test/flow/operators/BooleanTerminationTest.kt
@@ -0,0 +1,106 @@
+package kotlinx.coroutines.flow
+
+import kotlinx.coroutines.testing.*
+import kotlin.test.*
+
+class BooleanTerminationTest : TestBase() {
+ @Test
+ fun testAnyNominal() = runTest {
+ val flow = flow {
+ emit(1)
+ emit(2)
+ }
+
+ assertTrue(flow.any { it > 0 })
+ assertTrue(flow.any { it % 2 == 0 })
+ assertFalse(flow.any { it > 5 })
+ }
+
+ @Test
+ fun testAnyEmpty() = runTest {
+ assertFalse(emptyFlow().any { it > 0 })
+ }
+
+ @Test
+ fun testAnyInfinite() = runTest {
+ assertTrue(flow { while (true) { emit(5) } }.any { it == 5 })
+ }
+
+ @Test
+ fun testAnyShortCircuit() = runTest {
+ assertTrue(flow {
+ emit(1)
+ emit(2)
+ expectUnreached()
+ }.any {
+ it == 2
+ })
+ }
+
+ @Test
+ fun testAllNominal() = runTest {
+ val flow = flow {
+ emit(1)
+ emit(2)
+ }
+
+ assertTrue(flow.all { it > 0 })
+ assertFalse(flow.all { it % 2 == 0 })
+ assertFalse(flow.all { it > 5 })
+ }
+
+ @Test
+ fun testAllEmpty() = runTest {
+ assertTrue(emptyFlow().all { it > 0 })
+ }
+
+ @Test
+ fun testAllInfinite() = runTest {
+ assertFalse(flow { while (true) { emit(5) } }.all { it == 0 })
+ }
+
+ @Test
+ fun testAllShortCircuit() = runTest {
+ assertFalse(flow {
+ emit(1)
+ emit(2)
+ expectUnreached()
+ }.all {
+ it <= 1
+ })
+ }
+
+ @Test
+ fun testNoneNominal() = runTest {
+ val flow = flow {
+ emit(1)
+ emit(2)
+ }
+
+ assertFalse(flow.none { it > 0 })
+ assertFalse(flow.none { it % 2 == 0 })
+ assertTrue(flow.none { it > 5 })
+ }
+
+ @Test
+ fun testNoneEmpty() = runTest {
+ assertTrue(emptyFlow().none { it > 0 })
+ }
+
+ @Test
+ fun testNoneInfinite() = runTest {
+ assertFalse(flow { while (true) { emit(5) } }.none { it == 5 })
+ }
+
+ @Test
+ fun testNoneShortCircuit() = runTest {
+ assertFalse(flow {
+ emit(1)
+ emit(2)
+ expectUnreached()
+ }.none {
+ it == 2
+ })
+ }
+
+}
diff --git a/kotlinx-coroutines-core/common/test/flow/terminal/FirstTest.kt b/kotlinx-coroutines-core/common/test/flow/terminal/FirstTest.kt
index 74336262b8..cdb36bd8f8 100644
--- a/kotlinx-coroutines-core/common/test/flow/terminal/FirstTest.kt
+++ b/kotlinx-coroutines-core/common/test/flow/terminal/FirstTest.kt
@@ -2,9 +2,11 @@ package kotlinx.coroutines.flow
import kotlinx.coroutines.testing.*
import kotlinx.coroutines.*
+import kotlinx.coroutines.CoroutineStart.*
import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.internal.*
import kotlin.test.*
+import kotlin.time.*
class FirstTest : TestBase() {
@Test
@@ -173,4 +175,21 @@ class FirstTest : TestBase() {
assertFailsWith { flow.first() }
}
-}
+
+ @Test
+ fun testFirstThrowOnCancellation() = runTest {
+ val job = launch(start = UNDISPATCHED) {
+ flow {
+ try {
+ emit(Unit)
+ } finally {
+ runCatching { yield() }
+ finish(2)
+ }
+ }.first()
+ expectUnreached()
+ }
+ expect(1)
+ job.cancel()
+ }
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt b/kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt
index 36faaf4e52..ad9ec556a8 100644
--- a/kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt
+++ b/kotlinx-coroutines-core/common/test/selects/SelectRendezvousChannelTest.kt
@@ -419,10 +419,10 @@ class SelectRendezvousChannelTest : TestBase() {
fun testSelectSendWhenClosed() = runTest {
expect(1)
val c = Channel(Channel.RENDEZVOUS)
- val sender = launch(start = CoroutineStart.UNDISPATCHED) {
+ launch(start = CoroutineStart.UNDISPATCHED) {
expect(2)
c.send(1) // enqueue sender
- expectUnreached()
+ finish(4)
}
c.close() // then close
assertFailsWith {
@@ -434,8 +434,7 @@ class SelectRendezvousChannelTest : TestBase() {
}
}
}
- sender.cancel()
- finish(4)
+ assertEquals(1, c.receive())
}
// only for debugging
diff --git a/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt b/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt
index a172e66c8b..2e6ce5ae4a 100644
--- a/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt
+++ b/kotlinx-coroutines-core/concurrent/src/internal/LockFreeLinkedList.kt
@@ -1,5 +1,3 @@
-@file:Suppress("NO_EXPLICIT_VISIBILITY_IN_API_MODE")
-
package kotlinx.coroutines.internal
import kotlinx.atomicfu.*
diff --git a/kotlinx-coroutines-core/concurrent/test/RunBlockingTest.kt b/kotlinx-coroutines-core/concurrent/test/RunBlockingTest.kt
index f04b491c6d..43f7976ffa 100644
--- a/kotlinx-coroutines-core/concurrent/test/RunBlockingTest.kt
+++ b/kotlinx-coroutines-core/concurrent/test/RunBlockingTest.kt
@@ -4,6 +4,8 @@ import kotlinx.coroutines.testing.*
import kotlinx.coroutines.exceptions.*
import kotlin.coroutines.*
import kotlin.test.*
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
class RunBlockingTest : TestBase() {
@@ -176,4 +178,20 @@ class RunBlockingTest : TestBase() {
}
}
}
+
+ /** Tests that the delayed tasks scheduled on a closed `runBlocking` event loop get processed in reasonable time. */
+ @Test
+ fun testReschedulingDelayedTasks() {
+ val job = runBlocking {
+ val dispatcher = coroutineContext[ContinuationInterceptor]!!
+ GlobalScope.launch(dispatcher) {
+ delay(1.milliseconds)
+ }
+ }
+ runBlocking {
+ withTimeout(10.seconds) {
+ job.join()
+ }
+ }
+ }
}
diff --git a/kotlinx-coroutines-core/js/src/Promise.kt b/kotlinx-coroutines-core/js/src/Promise.kt
index f84338a888..5eb93d348e 100644
--- a/kotlinx-coroutines-core/js/src/Promise.kt
+++ b/kotlinx-coroutines-core/js/src/Promise.kt
@@ -63,5 +63,5 @@ public fun Promise.asDeferred(): Deferred {
public suspend fun Promise.await(): T = suspendCancellableCoroutine { cont: CancellableContinuation ->
this@await.then(
onFulfilled = { cont.resume(it) },
- onRejected = { cont.resumeWithException(it) })
+ onRejected = { cont.resumeWithException(it as? Throwable ?: Exception("Non-Kotlin exception $it")) })
}
diff --git a/kotlinx-coroutines-core/js/test/PromiseTest.kt b/kotlinx-coroutines-core/js/test/PromiseTest.kt
index 319778dddb..f9cb0ed56d 100644
--- a/kotlinx-coroutines-core/js/test/PromiseTest.kt
+++ b/kotlinx-coroutines-core/js/test/PromiseTest.kt
@@ -83,4 +83,27 @@ class PromiseTest : TestBase() {
if (seq != 1) error("Unexpected result: $seq")
}
}
+
+ @Test
+ fun testAwaitPromiseRejectedWithNonKotlinException() = GlobalScope.promise {
+ lateinit var r: (dynamic) -> Unit
+ val toAwait = Promise { _, reject -> r = reject }
+ val throwable = async(start = CoroutineStart.UNDISPATCHED) {
+ assertFails { toAwait.await() }
+ }
+ r("Rejected")
+ assertContains(throwable.await().message ?: "", "Rejected")
+ }
+
+ @Test
+ fun testAwaitPromiseRejectedWithKotlinException() = GlobalScope.promise {
+ lateinit var r: (dynamic) -> Unit
+ val toAwait = Promise { _, reject -> r = reject }
+ val throwable = async(start = CoroutineStart.UNDISPATCHED) {
+ assertFails { toAwait.await() }
+ }
+ r(RuntimeException("Rejected"))
+ assertIs(throwable.await())
+ assertEquals("Rejected", throwable.await().message)
+ }
}
diff --git a/kotlinx-coroutines-core/jsAndWasmShared/src/Runnable.kt b/kotlinx-coroutines-core/jsAndWasmShared/src/Runnable.kt
index 1a9e0ae7a0..d93e3f2073 100644
--- a/kotlinx-coroutines-core/jsAndWasmShared/src/Runnable.kt
+++ b/kotlinx-coroutines-core/jsAndWasmShared/src/Runnable.kt
@@ -2,19 +2,21 @@ package kotlinx.coroutines
/**
* A runnable task for [CoroutineDispatcher.dispatch].
+ *
+ * Equivalent to the type `() -> Unit`.
*/
-public actual interface Runnable {
+public actual fun interface Runnable {
/**
* @suppress
*/
public actual fun run()
}
-/**
- * Creates [Runnable] task instance.
- */
-@Suppress("FunctionName")
-public actual inline fun Runnable(crossinline block: () -> Unit): Runnable =
+@Deprecated(
+ "Preserved for binary compatibility, see https://github.com/Kotlin/kotlinx.coroutines/issues/4309",
+ level = DeprecationLevel.HIDDEN
+)
+public inline fun Runnable(crossinline block: () -> Unit): Runnable =
object : Runnable {
override fun run() {
block()
diff --git a/kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin b/kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin
index f8b6bcd87d..cac12595e5 100644
Binary files a/kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin and b/kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin differ
diff --git a/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/proguard/coroutines.pro b/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/proguard/coroutines.pro
index c3911b83b5..1380396073 100644
--- a/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/proguard/coroutines.pro
+++ b/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/proguard/coroutines.pro
@@ -16,7 +16,7 @@
volatile ;
}
-# These classes are only required by kotlinx.coroutines.debug.AgentPremain, which is only loaded when
+# These classes are only required by kotlinx.coroutines.debug.internal.AgentPremain, which is only loaded when
# kotlinx-coroutines-core is used as a Java agent, so these are not needed in contexts where ProGuard is used.
-dontwarn java.lang.instrument.ClassFileTransformer
-dontwarn sun.misc.SignalHandler
diff --git a/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/r8/coroutines.pro b/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/r8/coroutines.pro
index 1ac5ce5710..69a28956ac 100644
--- a/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/r8/coroutines.pro
+++ b/kotlinx-coroutines-core/jvm/resources/META-INF/com.android.tools/r8/coroutines.pro
@@ -12,7 +12,7 @@
volatile ;
}
-# These classes are only required by kotlinx.coroutines.debug.AgentPremain, which is only loaded when
+# These classes are only required by kotlinx.coroutines.debug.internal.AgentPremain, which is only loaded when
# kotlinx-coroutines-core is used as a Java agent, so these are not needed in contexts where ProGuard is used.
-dontwarn java.lang.instrument.ClassFileTransformer
-dontwarn sun.misc.SignalHandler
diff --git a/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro b/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro
index 6d29ed25ce..874b097457 100644
--- a/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro
+++ b/kotlinx-coroutines-core/jvm/resources/META-INF/proguard/coroutines.pro
@@ -16,7 +16,7 @@
volatile ;
}
-# These classes are only required by kotlinx.coroutines.debug.AgentPremain, which is only loaded when
+# These classes are only required by kotlinx.coroutines.debug.internal.AgentPremain, which is only loaded when
# kotlinx-coroutines-core is used as a Java agent, so these are not needed in contexts where ProGuard is used.
-dontwarn java.lang.instrument.ClassFileTransformer
-dontwarn sun.misc.SignalHandler
diff --git a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt
index ae8275f86f..7628d6ac85 100644
--- a/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt
+++ b/kotlinx-coroutines-core/jvm/src/CoroutineContext.kt
@@ -185,7 +185,8 @@ internal actual class UndispatchedCoroutineactual constructor (
* `withContext` for the sake of logging, MDC, tracing etc., meaning that there exists thousands of
* undispatched coroutines.
* Each access to Java's [ThreadLocal] leaves a footprint in the corresponding Thread's `ThreadLocalMap`
- * that is cleared automatically as soon as the associated thread-local (-> UndispatchedCoroutine) is garbage collected.
+ * that is cleared automatically as soon as the associated thread-local (-> UndispatchedCoroutine) is garbage collected
+ * when either the corresponding thread is GC'ed or it cleans up its stale entries on other TL accesses.
* When such coroutines are promoted to old generation, `ThreadLocalMap`s become bloated and an arbitrary accesses to thread locals
* start to consume significant amount of CPU because these maps are open-addressed and cleaned up incrementally on each access.
* (You can read more about this effect as "GC nepotism").
@@ -253,18 +254,26 @@ internal actual class UndispatchedCoroutineactual constructor (
}
}
+ override fun afterCompletionUndispatched() {
+ clearThreadLocal()
+ }
+
override fun afterResume(state: Any?) {
+ clearThreadLocal()
+ // resume undispatched -- update context but stay on the same dispatcher
+ val result = recoverResult(state, uCont)
+ withContinuationContext(uCont, null) {
+ uCont.resumeWith(result)
+ }
+ }
+
+ private fun clearThreadLocal() {
if (threadLocalIsSet) {
threadStateToRecover.get()?.let { (ctx, value) ->
restoreThreadContext(ctx, value)
}
threadStateToRecover.remove()
}
- // resume undispatched -- update context but stay on the same dispatcher
- val result = recoverResult(state, uCont)
- withContinuationContext(uCont, null) {
- uCont.resumeWith(result)
- }
}
}
diff --git a/kotlinx-coroutines-core/jvm/src/Exceptions.kt b/kotlinx-coroutines-core/jvm/src/Exceptions.kt
index 6175595713..dafaaacbe3 100644
--- a/kotlinx-coroutines-core/jvm/src/Exceptions.kt
+++ b/kotlinx-coroutines-core/jvm/src/Exceptions.kt
@@ -24,9 +24,15 @@ public actual fun CancellationException(message: String?, cause: Throwable?) : C
internal actual class JobCancellationException public actual constructor(
message: String,
cause: Throwable?,
- @JvmField @Transient internal actual val job: Job
+ job: Job
) : CancellationException(message), CopyableThrowable {
+ @Transient
+ private val _job: Job? = job
+
+ // The safest option for transient -- return something that meanigfully reject any attemp to interact with the job
+ internal actual val job get() = _job ?: NonCancellable
+
init {
if (cause != null) initCause(cause)
}
@@ -61,6 +67,10 @@ internal actual class JobCancellationException public actual constructor(
override fun equals(other: Any?): Boolean =
other === this ||
other is JobCancellationException && other.message == message && other.job == job && other.cause == cause
- override fun hashCode(): Int =
- (message!!.hashCode() * 31 + job.hashCode()) * 31 + (cause?.hashCode() ?: 0)
+
+ override fun hashCode(): Int {
+ // since job is transient it is indeed nullable after deserialization
+ @Suppress("UNNECESSARY_SAFE_CALL")
+ return (message!!.hashCode() * 31 + (job?.hashCode() ?: 0)) * 31 + (cause?.hashCode() ?: 0)
+ }
}
diff --git a/kotlinx-coroutines-core/jvm/src/Executors.kt b/kotlinx-coroutines-core/jvm/src/Executors.kt
index 8ba3f18a24..bdfbe6dbbc 100644
--- a/kotlinx-coroutines-core/jvm/src/Executors.kt
+++ b/kotlinx-coroutines-core/jvm/src/Executors.kt
@@ -105,8 +105,8 @@ public fun CoroutineDispatcher.asExecutor(): Executor =
private class DispatcherExecutor(@JvmField val dispatcher: CoroutineDispatcher) : Executor {
override fun execute(block: Runnable) {
- if (dispatcher.isDispatchNeeded(EmptyCoroutineContext)) {
- dispatcher.dispatch(EmptyCoroutineContext, block)
+ if (dispatcher.safeIsDispatchNeeded(EmptyCoroutineContext)) {
+ dispatcher.safeDispatch(EmptyCoroutineContext, block)
} else {
block.run()
}
diff --git a/kotlinx-coroutines-core/jvm/src/Runnable.kt b/kotlinx-coroutines-core/jvm/src/Runnable.kt
index 73440eeb8f..805bef7554 100644
--- a/kotlinx-coroutines-core/jvm/src/Runnable.kt
+++ b/kotlinx-coroutines-core/jvm/src/Runnable.kt
@@ -2,12 +2,9 @@ package kotlinx.coroutines
/**
* A runnable task for [CoroutineDispatcher.dispatch].
+ *
+ * It is a typealias for [java.lang.Runnable], which is widely used in Java APIs.
+ * This makes it possible to directly pass the argument of [CoroutineDispatcher.dispatch]
+ * to the underlying Java implementation without any additional wrapping.
*/
public actual typealias Runnable = java.lang.Runnable
-
-/**
- * Creates [Runnable] task instance.
- */
-@Suppress("FunctionName")
-public actual inline fun Runnable(crossinline block: () -> Unit): Runnable =
- java.lang.Runnable { block() }
diff --git a/kotlinx-coroutines-core/jvm/src/channels/Actor.kt b/kotlinx-coroutines-core/jvm/src/channels/Actor.kt
index 62eb19e747..ef74a08f35 100644
--- a/kotlinx-coroutines-core/jvm/src/channels/Actor.kt
+++ b/kotlinx-coroutines-core/jvm/src/channels/Actor.kt
@@ -45,8 +45,8 @@ public interface ActorScope : CoroutineScope, ReceiveChannel {
* it will be started implicitly on the first message
* [sent][SendChannel.send] to this actors's mailbox channel.
*
- * Uncaught exceptions in this coroutine close the channel with this exception as a cause and
- * the resulting channel becomes _failed_, so that any attempt to send to such a channel throws exception.
+ * Uncaught exceptions in this coroutine close the channel with this exception as a cause,
+ * so that any attempt to send to such a channel throws exception.
*
* The kind of the resulting channel depends on the specified [capacity] parameter.
* See [Channel] interface documentation for details.
diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/AgentInstallationType.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/AgentInstallationType.kt
index 15eead7fc3..be37ff36e7 100644
--- a/kotlinx-coroutines-core/jvm/src/debug/internal/AgentInstallationType.kt
+++ b/kotlinx-coroutines-core/jvm/src/debug/internal/AgentInstallationType.kt
@@ -3,10 +3,16 @@ package kotlinx.coroutines.debug.internal
/**
* Object used to differentiate between agent installed statically or dynamically.
* This is done in a separate object so [DebugProbesImpl] can check for static installation
- * without having to depend on [kotlinx.coroutines.debug.AgentPremain], which is not compatible with Android.
+ * without having to depend on [AgentPremain], which is not compatible with Android.
* Otherwise, access to `AgentPremain.isInstalledStatically` triggers the load of its internal `ClassFileTransformer`
* that is not available on Android.
+ *
+ * The entity (despite being internal) has usages in the following products
+ * - Fleet (Reflection): FleetDebugProbes
+ * - Android (Hard Coded, ignored for Leak Detection)
+ * - IntelliJ (Suppress KotlinInternalInJava): CoroutineDumpState
*/
+@PublishedApi
internal object AgentInstallationType {
internal var isInstalledStatically = false
}
diff --git a/kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/AgentPremain.kt
similarity index 97%
rename from kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt
rename to kotlinx-coroutines-core/jvm/src/debug/internal/AgentPremain.kt
index 4f8abb8700..8d0c557ed2 100644
--- a/kotlinx-coroutines-core/jvm/src/debug/AgentPremain.kt
+++ b/kotlinx-coroutines-core/jvm/src/debug/internal/AgentPremain.kt
@@ -1,7 +1,6 @@
-package kotlinx.coroutines.debug
+package kotlinx.coroutines.debug.internal
import android.annotation.*
-import kotlinx.coroutines.debug.internal.*
import org.codehaus.mojo.animal_sniffer.*
import sun.misc.*
import java.lang.instrument.*
diff --git a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt
index 2ab03a770c..25594ad01a 100644
--- a/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt
+++ b/kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt
@@ -51,7 +51,7 @@ internal object DebugProbesImpl {
@Suppress("UNCHECKED_CAST")
private fun getDynamicAttach(): Function1? = runCatching {
- val clz = Class.forName("kotlinx.coroutines.debug.internal.ByteBuddyDynamicAttach")
+ val clz = Class.forName("kotlinx.coroutines.debug.ByteBuddyDynamicAttach")
val ctor = clz.constructors[0]
ctor.newInstance() as Function1
}.getOrNull()
diff --git a/kotlinx-coroutines-core/jvm/src/flow/internal/FlowExceptions.kt b/kotlinx-coroutines-core/jvm/src/flow/internal/FlowExceptions.kt
index d8c46d2a76..02817396e8 100644
--- a/kotlinx-coroutines-core/jvm/src/flow/internal/FlowExceptions.kt
+++ b/kotlinx-coroutines-core/jvm/src/flow/internal/FlowExceptions.kt
@@ -2,6 +2,10 @@ package kotlinx.coroutines.flow.internal
import kotlinx.coroutines.*
+/**
+ * Implementation note: `owner` is an internal marked that is used ONLY for identity checks by coroutines machinery,
+ * and it's never exposed, thus it's safe to have it both `@Transient` and non-nullable.
+ */
internal actual class AbortFlowException actual constructor(
@JvmField @Transient actual val owner: Any
) : CancellationException("Flow was aborted, no more elements needed") {
diff --git a/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt b/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt
index 0a931f5f5d..ee35a3a011 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/Concurrent.kt
@@ -3,12 +3,10 @@ package kotlinx.coroutines.internal
import java.util.*
import kotlin.concurrent.withLock as withLockJvm
-@Suppress("ACTUAL_WITHOUT_EXPECT")
internal actual typealias ReentrantLock = java.util.concurrent.locks.ReentrantLock
internal actual inline fun ReentrantLock.withLock(action: () -> T) = this.withLockJvm(action)
-@Suppress("ACTUAL_WITHOUT_EXPECT") // Visibility
internal actual typealias WorkaroundAtomicReference = java.util.concurrent.atomic.AtomicReference
// BenignDataRace is OptionalExpectation and doesn't have to be here
diff --git a/kotlinx-coroutines-core/jvm/src/internal/CoroutineExceptionHandlerImpl.kt b/kotlinx-coroutines-core/jvm/src/internal/CoroutineExceptionHandlerImpl.kt
index 0014e1742f..2d048ac71a 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/CoroutineExceptionHandlerImpl.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/CoroutineExceptionHandlerImpl.kt
@@ -32,7 +32,11 @@ internal actual fun propagateExceptionFinalResort(exception: Throwable) {
}
// This implementation doesn't store a stacktrace, which is good because a stacktrace doesn't make sense for this.
-internal actual class DiagnosticCoroutineContextException actual constructor(@Transient private val context: CoroutineContext) : RuntimeException() {
+internal actual class DiagnosticCoroutineContextException actual constructor(context: CoroutineContext) : RuntimeException() {
+
+ @Transient
+ private val context: CoroutineContext? = context
+
override fun getLocalizedMessage(): String {
return context.toString()
}
diff --git a/kotlinx-coroutines-core/jvm/src/internal/InternalAnnotations.kt b/kotlinx-coroutines-core/jvm/src/internal/InternalAnnotations.kt
index 16b4a48776..01279f23ba 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/InternalAnnotations.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/InternalAnnotations.kt
@@ -1,4 +1,3 @@
package kotlinx.coroutines.internal
-@Suppress("ACTUAL_WITHOUT_EXPECT") // Not the same name to WA the bug in the compiler
internal actual typealias IgnoreJreRequirement = org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
diff --git a/kotlinx-coroutines-core/jvm/src/internal/LocalAtomics.kt b/kotlinx-coroutines-core/jvm/src/internal/LocalAtomics.kt
index febc5b90d9..19398ed543 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/LocalAtomics.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/LocalAtomics.kt
@@ -1,4 +1,3 @@
package kotlinx.coroutines.internal
-@Suppress("ACTUAL_WITHOUT_EXPECT")
internal actual typealias LocalAtomicInt = java.util.concurrent.atomic.AtomicInteger
diff --git a/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt b/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt
index 619c61a1fa..acf07a553b 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt
@@ -198,10 +198,8 @@ private fun StackTraceElement.elementWiseEquals(e: StackTraceElement): Boolean {
&& fileName == e.fileName && className == e.className
}
-@Suppress("ACTUAL_WITHOUT_EXPECT")
internal actual typealias CoroutineStackFrame = kotlin.coroutines.jvm.internal.CoroutineStackFrame
-@Suppress("ACTUAL_WITHOUT_EXPECT")
internal actual typealias StackTraceElement = java.lang.StackTraceElement
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
diff --git a/kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt b/kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt
index d5f715621f..209c3d5f32 100644
--- a/kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt
+++ b/kotlinx-coroutines-core/jvm/src/internal/ThreadLocal.kt
@@ -2,7 +2,6 @@ package kotlinx.coroutines.internal
import java.lang.ThreadLocal
-@Suppress("ACTUAL_WITHOUT_EXPECT") // internal visibility
internal actual typealias CommonThreadLocal = ThreadLocal
internal actual fun commonThreadLocal(name: Symbol): CommonThreadLocal = ThreadLocal()
diff --git a/kotlinx-coroutines-core/jvm/src/module-info.java b/kotlinx-coroutines-core/jvm/src/module-info.java
index 2759a34296..ad4c2ee066 100644
--- a/kotlinx-coroutines-core/jvm/src/module-info.java
+++ b/kotlinx-coroutines-core/jvm/src/module-info.java
@@ -5,13 +5,12 @@
requires transitive kotlin.stdlib;
requires kotlinx.atomicfu;
- // these are used by kotlinx.coroutines.debug.AgentPremain
+ // these are used by kotlinx.coroutines.debug.internal.AgentPremain
requires static java.instrument; // contains java.lang.instrument.*
requires static jdk.unsupported; // contains sun.misc.Signal
exports kotlinx.coroutines;
exports kotlinx.coroutines.channels;
- exports kotlinx.coroutines.debug;
exports kotlinx.coroutines.debug.internal;
exports kotlinx.coroutines.flow;
exports kotlinx.coroutines.flow.internal;
diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt
index 3c22116b66..3430ebadec 100644
--- a/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt
+++ b/kotlinx-coroutines-core/jvm/src/scheduling/CoroutineScheduler.kt
@@ -384,14 +384,14 @@ internal class CoroutineScheduler(
* this [block] may execute blocking operations (IO, system calls, locking primitives etc.)
*
* [taskContext] -- concurrency context of given [block].
- * [tailDispatch] -- whether this [dispatch] call is the last action the (presumably) worker thread does in its current task.
- * If `true`, then the task will be dispatched in a FIFO manner and no additional workers will be requested,
- * but only if the current thread is a corresponding worker thread.
+ * [fair] -- whether this [dispatch] call is fair.
+ * If `true` then the task will be dispatched in a FIFO manner.
* Note that caller cannot be ensured that it is being executed on worker thread for the following reasons:
* - [CoroutineStart.UNDISPATCHED]
- * - Concurrent [close] that effectively shutdowns the worker thread
+ * - Concurrent [close] that effectively shutdowns the worker thread.
+ * Used for [yield].
*/
- fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, tailDispatch: Boolean = false) {
+ fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, fair: Boolean = false) {
trackTask() // this is needed for virtual time support
val task = createTask(block, taskContext)
val isBlockingTask = task.isBlocking
@@ -400,20 +400,18 @@ internal class CoroutineScheduler(
val stateSnapshot = if (isBlockingTask) incrementBlockingTasks() else 0
// try to submit the task to the local queue and act depending on the result
val currentWorker = currentWorker()
- val notAdded = currentWorker.submitToLocalQueue(task, tailDispatch)
+ val notAdded = currentWorker.submitToLocalQueue(task, fair)
if (notAdded != null) {
if (!addToGlobalQueue(notAdded)) {
// Global queue is closed in the last step of close/shutdown -- no more tasks should be accepted
throw RejectedExecutionException("$schedulerName was terminated")
}
}
- val skipUnpark = tailDispatch && currentWorker != null
// Checking 'task' instead of 'notAdded' is completely okay
if (isBlockingTask) {
// Use state snapshot to better estimate the number of running threads
- signalBlockingWork(stateSnapshot, skipUnpark = skipUnpark)
+ signalBlockingWork(stateSnapshot)
} else {
- if (skipUnpark) return
signalCpuWork()
}
}
@@ -429,8 +427,7 @@ internal class CoroutineScheduler(
}
// NB: should only be called from 'dispatch' method due to blocking tasks increment
- private fun signalBlockingWork(stateSnapshot: Long, skipUnpark: Boolean) {
- if (skipUnpark) return
+ private fun signalBlockingWork(stateSnapshot: Long) {
if (tryUnpark()) return
// Use state snapshot to avoid accidental thread overprovision
if (tryCreateWorker(stateSnapshot)) return
@@ -506,7 +503,7 @@ internal class CoroutineScheduler(
* Returns `null` if task was successfully added or an instance of the
* task that was not added or replaced (thus should be added to global queue).
*/
- private fun Worker?.submitToLocalQueue(task: Task, tailDispatch: Boolean): Task? {
+ private fun Worker?.submitToLocalQueue(task: Task, fair: Boolean): Task? {
if (this == null) return task
/*
* This worker could have been already terminated from this thread by close/shutdown and it should not
@@ -518,7 +515,7 @@ internal class CoroutineScheduler(
return task
}
mayHaveLocalTasks = true
- return localQueue.add(task, fair = tailDispatch)
+ return localQueue.add(task, fair = fair)
}
private fun currentWorker(): Worker? = (Thread.currentThread() as? Worker)?.takeIf { it.scheduler == this }
diff --git a/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt b/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt
index 350e3e9aee..28d5537108 100644
--- a/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt
+++ b/kotlinx-coroutines-core/jvm/src/scheduling/Dispatcher.kt
@@ -113,11 +113,21 @@ internal open class SchedulerCoroutineDispatcher(
override fun dispatch(context: CoroutineContext, block: Runnable): Unit = coroutineScheduler.dispatch(block)
- override fun dispatchYield(context: CoroutineContext, block: Runnable): Unit =
- coroutineScheduler.dispatch(block, tailDispatch = true)
+ override fun dispatchYield(context: CoroutineContext, block: Runnable): Unit {
+ /*
+ * 'dispatchYield' implementation is needed to address the scheduler's scheduling policy.
+ * By default, the scheduler dispatches tasks in a semi-LIFO order, meaning that for the
+ * task sequence [#1, #2, #3], the scheduling of task #4 will produce
+ * [#4, #1, #2, #3], allocates new worker and makes #4 stealable after some time.
+ * On a fast enough system, it means that `while (true) { yield() }` might obstruct the progress
+ * of the system and potentially starve it.
+ * To mitigate that, `dispatchYield` is a dedicated entry point that produces [#1, #2, #3, #4]
+ */
+ coroutineScheduler.dispatch(block, fair = true)
+ }
- internal fun dispatchWithContext(block: Runnable, context: TaskContext, tailDispatch: Boolean) {
- coroutineScheduler.dispatch(block, context, tailDispatch)
+ internal fun dispatchWithContext(block: Runnable, context: TaskContext, fair: Boolean) {
+ coroutineScheduler.dispatch(block, context, fair)
}
override fun close() {
diff --git a/kotlinx-coroutines-core/jvm/test/ExecutorsTest.kt b/kotlinx-coroutines-core/jvm/test/ExecutorsTest.kt
index 1ad2f8a2a4..965b8fc0be 100644
--- a/kotlinx-coroutines-core/jvm/test/ExecutorsTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/ExecutorsTest.kt
@@ -119,4 +119,106 @@ class ExecutorsTest : TestBase() {
dispatcher.close()
check(executorService.isShutdown)
}
+
+ @Test
+ fun testEarlyExecutorShutdown() {
+ runTestExceptionInDispatch(6, { it is RejectedExecutionException }) {
+ expect(1)
+ val dispatcher = newSingleThreadContext("Ctx")
+ launch(dispatcher) {
+ withContext(Dispatchers.Default) {
+ expect(2)
+ delay(100)
+ expect(4)
+ }
+ }
+
+ delay(50)
+ expect(3)
+
+ dispatcher.close()
+ }
+ }
+
+ @Test
+ fun testExceptionInDispatch() {
+ runTestExceptionInDispatch(5, { it is TestException }) {
+ val dispatcher = object : CoroutineDispatcher() {
+ private var closed = false
+ override fun dispatch(context: CoroutineContext, block: Runnable) {
+ if (closed) throw TestException()
+ Dispatchers.Default.dispatch(context, block)
+ }
+
+ fun close() {
+ closed = true
+ }
+ }
+ launch(dispatcher) {
+ withContext(Dispatchers.Default) {
+ expect(1)
+ delay(100)
+ expect(3)
+ }
+ }
+
+ delay(50)
+ expect(2)
+ dispatcher.close()
+ }
+ }
+
+ @Test
+ fun testExceptionInIsDispatchNeeded() {
+ val dispatcher = object : CoroutineDispatcher() {
+ override fun isDispatchNeeded(context: CoroutineContext): Boolean {
+ expect(2)
+ throw TestException()
+ }
+ override fun dispatch(context: CoroutineContext, block: Runnable) = expectUnreached()
+ }
+ try {
+ runBlocking {
+ expect(1)
+ try {
+ launch(dispatcher) {
+ expectUnreached()
+ }
+ expectUnreached()
+ } catch (_: TestException) {
+ expect(3)
+ }
+
+ }
+ } catch (_: TestException) {
+ finish(4)
+ }
+ }
+
+ private fun runTestExceptionInDispatch(
+ totalSteps: Int,
+ isExpectedException: (Throwable) -> Boolean,
+ block: suspend CoroutineScope.() -> Unit,
+ ) {
+ var mainThread: Thread? = null
+ val exceptionHandler = CoroutineExceptionHandler { _, e ->
+ if (isExpectedException(e)) {
+ expect(totalSteps - 1)
+ mainThread!!.run {
+ interrupt()
+ unpark(this)
+ }
+ } else {
+ expectUnreached()
+ }
+ }
+ try {
+ runBlocking(exceptionHandler) {
+ block()
+ mainThread = Thread.currentThread()
+ }
+ } catch (_: InterruptedException) {
+ finish(totalSteps)
+ }
+ }
}
diff --git a/kotlinx-coroutines-core/jvm/test/JobCancellationExceptionSerializerTest.kt b/kotlinx-coroutines-core/jvm/test/JobCancellationExceptionSerializerTest.kt
index c063e9457e..18c3d29db5 100644
--- a/kotlinx-coroutines-core/jvm/test/JobCancellationExceptionSerializerTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/JobCancellationExceptionSerializerTest.kt
@@ -36,4 +36,30 @@ class JobCancellationExceptionSerializerTest : TestBase() {
finish(4)
}
}
+
+ @Test
+ fun testHashCodeAfterDeserialization() = runTest {
+ try {
+ coroutineScope {
+ expect(1)
+ throw JobCancellationException(
+ message = "Job Cancelled",
+ job = Job(),
+ cause = null,
+ )
+ }
+ } catch (e: Throwable) {
+ finish(2)
+ val outputStream = ByteArrayOutputStream()
+ ObjectOutputStream(outputStream).use {
+ it.writeObject(e)
+ }
+ val deserializedException =
+ ObjectInputStream(outputStream.toByteArray().inputStream()).use {
+ it.readObject() as JobCancellationException
+ }
+ // verify hashCode does not fail even though Job is transient
+ assert(deserializedException.hashCode() != 0)
+ }
+ }
}
diff --git a/kotlinx-coroutines-core/jvm/test/JobChildStressTest.kt b/kotlinx-coroutines-core/jvm/test/JobChildStressTest.kt
index a30e6393b6..16fc64e83e 100644
--- a/kotlinx-coroutines-core/jvm/test/JobChildStressTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/JobChildStressTest.kt
@@ -77,6 +77,7 @@ class JobChildStressTest : TestBase() {
fun testChildAttachmentRacingWithLastChildCompletion() {
// All exceptions should get aggregated here
repeat(N_ITERATIONS) {
+ val canCloseThePool = CountDownLatch(1)
runBlocking {
val rogueJob = AtomicReference()
/** not using [createCompletableDeferredForTesting] because we don't need extra children. */
@@ -93,6 +94,7 @@ class JobChildStressTest : TestBase() {
rogueJob.set(launch(pool + deferred) {
throw TestException("isCancelled: ${coroutineContext.job.isCancelled}")
})
+ canCloseThePool.countDown()
}
}
@@ -100,6 +102,12 @@ class JobChildStressTest : TestBase() {
val rogue = rogueJob.get()
if (rogue?.isActive == true) {
throw TestException("Rogue job $rogue with parent " + rogue.parent + " and children list: " + rogue.parent?.children?.toList())
+ } else {
+ canCloseThePool.await()
+ rogueJob.get().let {
+ assertNotNull(it)
+ assertTrue(it.isCancelled)
+ }
}
}
}
diff --git a/kotlinx-coroutines-core/jvm/test/ThreadLocalsLeaksTest.kt b/kotlinx-coroutines-core/jvm/test/ThreadLocalsLeaksTest.kt
new file mode 100644
index 0000000000..f7a015a13e
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/ThreadLocalsLeaksTest.kt
@@ -0,0 +1,106 @@
+package kotlinx.coroutines
+
+import kotlinx.coroutines.testing.TestBase
+import kotlinx.coroutines.testing.isJavaAndWindows
+import java.lang.ref.WeakReference
+import kotlin.coroutines.AbstractCoroutineContextElement
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.ContinuationInterceptor
+import kotlin.coroutines.CoroutineContext
+import kotlin.test.*
+
+/*
+ * This is an adapted verion of test from #4296.
+ *
+ * qwwdfsad: the test relies on System.gc() actually collecting the garbage.
+ * If these tests flake on CI, first check that JDK/GC setup in not an issue.
+ */
+@Ignore
+class ThreadLocalCustomContinuationInterceptorTest : TestBase() {
+
+ private class CustomContinuationInterceptor(private val delegate: ContinuationInterceptor) :
+ AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
+
+ override fun interceptContinuation(continuation: Continuation): Continuation {
+ return delegate.interceptContinuation(continuation)
+ }
+ }
+
+ private class CustomNeverEqualContinuationInterceptor(private val delegate: ContinuationInterceptor) :
+ AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
+
+ override fun interceptContinuation(continuation: Continuation): Continuation {
+ return delegate.interceptContinuation(continuation)
+ }
+
+ override fun equals(other: Any?) = false
+ }
+
+ @Test(timeout = 20_000L)
+ fun testDefaultDispatcherNoSuspension() = ensureCoroutineContextGCed(Dispatchers.Default, suspend = false)
+
+ @Test(timeout = 20_000L)
+ fun testDefaultDispatcher() = ensureCoroutineContextGCed(Dispatchers.Default, suspend = true)
+
+ @Test(timeout = 20_000L)
+ fun testNonCoroutineDispatcher() = ensureCoroutineContextGCed(
+ CustomContinuationInterceptor(Dispatchers.Default),
+ suspend = true
+ )
+
+ @Test(timeout = 20_000L)
+ fun testNonCoroutineDispatcherSuspension() = ensureCoroutineContextGCed(
+ CustomContinuationInterceptor(Dispatchers.Default),
+ suspend = false
+ )
+
+ // Note asymmetric equals codepath never goes through the undispatched withContext, thus the separate test case
+
+ @Test(timeout = 20_000L)
+ fun testNonCoroutineDispatcherAsymmetricEquals() =
+ ensureCoroutineContextGCed(
+ CustomNeverEqualContinuationInterceptor(Dispatchers.Default),
+ suspend = true
+ )
+
+ @Test(timeout = 20_000L)
+ fun testNonCoroutineDispatcherAsymmetricEqualsSuspension() =
+ ensureCoroutineContextGCed(
+ CustomNeverEqualContinuationInterceptor(Dispatchers.Default),
+ suspend = false
+ )
+
+
+ @Volatile
+ private var letThatSinkIn: Any = "What is my purpose? To frag the garbage collctor"
+
+ private fun ensureCoroutineContextGCed(coroutineContext: CoroutineContext, suspend: Boolean) {
+ // Tests are pretty timing-sensitive and flake ehavily on our virtualized Windows environment
+ if (isJavaAndWindows) {
+ return
+ }
+
+ fun forceGcUntilRefIsCleaned(ref: WeakReference) {
+ while (ref.get() != null) {
+ System.gc()
+ letThatSinkIn = LongArray(1024 * 1024)
+ }
+ }
+
+ runTest {
+ lateinit var ref: WeakReference
+ val job = GlobalScope.launch(coroutineContext) {
+ val coroutineName = CoroutineName("Yo")
+ ref = WeakReference(coroutineName)
+ withContext(coroutineName) {
+ if (suspend) {
+ delay(1)
+ }
+ }
+ }
+ job.join()
+
+ forceGcUntilRefIsCleaned(ref)
+ }
+ }
+}
diff --git a/kotlinx-coroutines-core/jvm/test/channels/CancelledChannelLeakTest.kt b/kotlinx-coroutines-core/jvm/test/channels/CancelledChannelLeakTest.kt
new file mode 100644
index 0000000000..06108a6fe6
--- /dev/null
+++ b/kotlinx-coroutines-core/jvm/test/channels/CancelledChannelLeakTest.kt
@@ -0,0 +1,27 @@
+package kotlinx.coroutines.channels
+
+import kotlinx.coroutines.*
+import kotlinx.coroutines.testing.*
+import kotlin.test.*
+
+class CancelledChannelLeakTest : TestBase() {
+ /**
+ * Tests that cancellation removes the elements from the channel's buffer.
+ */
+ @Test
+ fun testBufferedChannelLeak() = runTest {
+ for (capacity in listOf(Channel.CONFLATED, Channel.RENDEZVOUS, 1, 2, 5, 10)) {
+ val channel = Channel(capacity)
+ val value = X()
+ launch(start = CoroutineStart.UNDISPATCHED) {
+ channel.send(value)
+ }
+ FieldWalker.assertReachableCount(1, channel) { it === value }
+ channel.cancel()
+ // the element must be removed so that there is no memory leak
+ FieldWalker.assertReachableCount(0, channel) { it === value }
+ }
+ }
+
+ class X
+}
\ No newline at end of file
diff --git a/kotlinx-coroutines-core/jvm/test/jdk8/future/FutureTest.kt b/kotlinx-coroutines-core/jvm/test/jdk8/future/FutureTest.kt
index 34534c8eb1..81178e193c 100644
--- a/kotlinx-coroutines-core/jvm/test/jdk8/future/FutureTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/jdk8/future/FutureTest.kt
@@ -10,7 +10,7 @@ import java.util.concurrent.*
import java.util.concurrent.atomic.*
import java.util.concurrent.locks.*
import java.util.function.*
-import kotlin.concurrent.*
+import kotlin.concurrent.withLock
import kotlin.coroutines.*
import kotlin.reflect.*
import kotlin.test.*
diff --git a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerTest.kt b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerTest.kt
index 042ea2f7d8..fe09090362 100644
--- a/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerTest.kt
+++ b/kotlinx-coroutines-core/jvm/test/scheduling/CoroutineSchedulerTest.kt
@@ -1,7 +1,6 @@
package kotlinx.coroutines.scheduling
import kotlinx.coroutines.testing.*
-import kotlinx.coroutines.*
import org.junit.Test
import java.lang.Runnable
import java.util.concurrent.*
@@ -80,7 +79,7 @@ class CoroutineSchedulerTest : TestBase() {
it.dispatch(Runnable {
expect(2)
finishLatch.countDown()
- }, tailDispatch = true)
+ }, fair = true)
})
startLatch.countDown()
diff --git a/kotlinx-coroutines-core/native/src/Builders.kt b/kotlinx-coroutines-core/native/src/Builders.kt
index 9c77b2cd87..77b7cacebb 100644
--- a/kotlinx-coroutines-core/native/src/Builders.kt
+++ b/kotlinx-coroutines-core/native/src/Builders.kt
@@ -64,6 +64,7 @@ public actual fun runBlocking(context: CoroutineContext, block: suspend Coro
var completed = false
ThreadLocalKeepAlive.addCheck { !completed }
try {
+ @Suppress("LEAKED_IN_PLACE_LAMBDA") // Contract is preserved, invoked immediately or throws
coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
return coroutine.joinBlocking()
} finally {
diff --git a/kotlinx-coroutines-core/native/src/EventLoop.kt b/kotlinx-coroutines-core/native/src/EventLoop.kt
index e457842e34..58128d52fd 100644
--- a/kotlinx-coroutines-core/native/src/EventLoop.kt
+++ b/kotlinx-coroutines-core/native/src/EventLoop.kt
@@ -4,7 +4,7 @@ package kotlinx.coroutines
import kotlin.coroutines.*
import kotlin.native.concurrent.*
-import kotlin.system.*
+import kotlin.time.*
internal actual abstract class EventLoopImplPlatform : EventLoop() {
@@ -15,7 +15,8 @@ internal actual abstract class EventLoopImplPlatform : EventLoop() {
}
protected actual fun reschedule(now: Long, delayedTask: EventLoopImplBase.DelayedTask) {
- DefaultExecutor.invokeOnTimeout(now, delayedTask, EmptyCoroutineContext)
+ val delayTimeMillis = delayNanosToMillis(delayedTask.nanoTime - now)
+ DefaultExecutor.invokeOnTimeout(delayTimeMillis, delayedTask, EmptyCoroutineContext)
}
}
@@ -26,5 +27,6 @@ internal class EventLoopImpl: EventLoopImplBase() {
internal actual fun createEventLoop(): EventLoop = EventLoopImpl()
-@Suppress("DEPRECATION")
-internal actual fun nanoTime(): Long = getTimeNanos()
+private val startingPoint = TimeSource.Monotonic.markNow()
+
+internal actual fun nanoTime(): Long = (TimeSource.Monotonic.markNow() - startingPoint).inWholeNanoseconds
diff --git a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt
index 7968ffdcef..8b217285a5 100644
--- a/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt
+++ b/kotlinx-coroutines-core/native/src/MultithreadedDispatchers.kt
@@ -92,8 +92,11 @@ private class MultiWorkerDispatcher(
*/
private val tasksAndWorkersCounter = atomic(0L)
+ @Suppress("NOTHING_TO_INLINE")
private inline fun Long.isClosed() = this and 1L == 1L
+ @Suppress("NOTHING_TO_INLINE")
private inline fun Long.hasTasks() = this >= 2
+ @Suppress("NOTHING_TO_INLINE")
private inline fun Long.hasWorkers() = this < 0
private fun workerRunLoop() = runBlocking {
diff --git a/kotlinx-coroutines-core/native/src/Runnable.kt b/kotlinx-coroutines-core/native/src/Runnable.kt
index 1a9e0ae7a0..d93e3f2073 100644
--- a/kotlinx-coroutines-core/native/src/Runnable.kt
+++ b/kotlinx-coroutines-core/native/src/Runnable.kt
@@ -2,19 +2,21 @@ package kotlinx.coroutines
/**
* A runnable task for [CoroutineDispatcher.dispatch].
+ *
+ * Equivalent to the type `() -> Unit`.
*/
-public actual interface Runnable {
+public actual fun interface Runnable {
/**
* @suppress
*/
public actual fun run()
}
-/**
- * Creates [Runnable] task instance.
- */
-@Suppress("FunctionName")
-public actual inline fun Runnable(crossinline block: () -> Unit): Runnable =
+@Deprecated(
+ "Preserved for binary compatibility, see https://github.com/Kotlin/kotlinx.coroutines/issues/4309",
+ level = DeprecationLevel.HIDDEN
+)
+public inline fun Runnable(crossinline block: () -> Unit): Runnable =
object : Runnable {
override fun run() {
block()
diff --git a/kotlinx-coroutines-core/native/src/internal/Concurrent.kt b/kotlinx-coroutines-core/native/src/internal/Concurrent.kt
index fcfe624d7c..4999361bf5 100644
--- a/kotlinx-coroutines-core/native/src/internal/Concurrent.kt
+++ b/kotlinx-coroutines-core/native/src/internal/Concurrent.kt
@@ -4,14 +4,12 @@ import kotlinx.atomicfu.*
import kotlinx.cinterop.*
import kotlinx.atomicfu.locks.withLock as withLock2
-@Suppress("ACTUAL_WITHOUT_EXPECT")
internal actual typealias ReentrantLock = kotlinx.atomicfu.locks.SynchronizedObject
internal actual inline fun ReentrantLock.withLock(action: () -> T): T = this.withLock2(action)
internal actual fun identitySet(expectedSize: Int): MutableSet = HashSet()
-@Suppress("ACTUAL_WITHOUT_EXPECT") // This suppress can be removed in 2.0: KT-59355
internal actual typealias BenignDataRace = kotlin.concurrent.Volatile
internal actual class WorkaroundAtomicReference actual constructor(value: V) {
diff --git a/kotlinx-coroutines-core/native/src/internal/StackTraceRecovery.kt b/kotlinx-coroutines-core/native/src/internal/StackTraceRecovery.kt
index 9c6df6b604..20732e017f 100644
--- a/kotlinx-coroutines-core/native/src/internal/StackTraceRecovery.kt
+++ b/kotlinx-coroutines-core/native/src/internal/StackTraceRecovery.kt
@@ -15,7 +15,6 @@ internal actual interface CoroutineStackFrame {
public actual fun getStackTraceElement(): StackTraceElement?
}
-@Suppress("ACTUAL_WITHOUT_EXPECT")
internal actual typealias StackTraceElement = Any
internal actual fun Throwable.initCause(cause: Throwable) {
diff --git a/kotlinx-coroutines-core/wasmJs/test/PromiseTest.kt b/kotlinx-coroutines-core/wasmJs/test/PromiseTest.kt
index 63550439eb..e72e661517 100644
--- a/kotlinx-coroutines-core/wasmJs/test/PromiseTest.kt
+++ b/kotlinx-coroutines-core/wasmJs/test/PromiseTest.kt
@@ -84,4 +84,27 @@ class PromiseTest : TestBase() {
null
}
}
+
+ @Test
+ fun testAwaitPromiseRejectedWithNonKotlinException() = GlobalScope.promise {
+ lateinit var r: (JsAny) -> Unit
+ val toAwait = Promise { _, reject -> r = reject }
+ val throwable = async(start = CoroutineStart.UNDISPATCHED) {
+ assertFails { toAwait.await() }
+ }
+ r("Rejected".toJsString())
+ assertIs(throwable.await())
+ }
+
+ @Test
+ fun testAwaitPromiseRejectedWithKotlinException() = GlobalScope.promise {
+ lateinit var r: (JsAny) -> Unit
+ val toAwait = Promise { _, reject -> r = reject }
+ val throwable = async(start = CoroutineStart.UNDISPATCHED) {
+ assertFails { toAwait.await() }
+ }
+ r(RuntimeException("Rejected").toJsReference())
+ assertIs(throwable.await())
+ assertEquals("Rejected", throwable.await().message)
+ }
}
diff --git a/kotlinx-coroutines-core/wasmWasi/src/EventLoop.kt b/kotlinx-coroutines-core/wasmWasi/src/EventLoop.kt
index aac143d022..a0f392e5b0 100644
--- a/kotlinx-coroutines-core/wasmWasi/src/EventLoop.kt
+++ b/kotlinx-coroutines-core/wasmWasi/src/EventLoop.kt
@@ -39,19 +39,19 @@ private fun sleep(nanos: Long, ptrTo32Bytes: Pointer, ptrTo8Bytes: Pointer, ptrT
}
internal actual object DefaultExecutor : EventLoopImplBase() {
+
+ init {
+ if (kotlin.wasm.internal.onExportedFunctionExit == null) {
+ kotlin.wasm.internal.onExportedFunctionExit = ::runEventLoop
+ }
+ }
+
override fun shutdown() {
// don't do anything: on WASI, the event loop is the default executor, we can't shut it down
}
override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle =
scheduleInvokeOnTimeout(timeMillis, block)
-
- actual override fun enqueue(task: Runnable) {
- if (kotlin.wasm.internal.onExportedFunctionExit == null) {
- kotlin.wasm.internal.onExportedFunctionExit = ::runEventLoop
- }
- super.enqueue(task)
- }
}
internal actual abstract class EventLoopImplPlatform : EventLoop() {
diff --git a/kotlinx-coroutines-debug/README.md b/kotlinx-coroutines-debug/README.md
index d3cb5267bc..f569edd51a 100644
--- a/kotlinx-coroutines-debug/README.md
+++ b/kotlinx-coroutines-debug/README.md
@@ -61,7 +61,7 @@ stacktraces will be dumped to the console.
### Using as JVM agent
Debug module can also be used as a standalone JVM agent to enable debug probes on the application startup.
-You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.9.0.jar`.
+You can run your application with an additional argument: `-javaagent:kotlinx-coroutines-debug-1.10.1.jar`.
Additionally, on Linux and Mac OS X you can use `kill -5 $pid` command in order to force your application to print all alive coroutines.
When used as Java agent, `"kotlinx.coroutines.debug.enable.creation.stack.trace"` system property can be used to control
[DebugProbes.enableCreationStackTraces] along with agent startup.
@@ -167,12 +167,9 @@ dependency of `kotlinx-coroutines-test`) may fail with `DuplicateRelativeFileExc
The problem is that Android merges the resources of all its dependencies into a single directory and complains about
conflicts, but:
-* `kotlinx-coroutines-debug` transitively depends on JNA and JNA-platform, both of which include license files in their
- META-INF directories. Trying to merge these files leads to conflicts, which means that any Android project that
- depends on JNA and JNA-platform will experience build failures.
-* Additionally, `kotlinx-coroutines-debug` embeds `byte-buddy-agent` and `byte-buddy`, along with their resource files.
- Then, if the project separately depends on `byte-buddy`, merging the resources of `kotlinx-coroutines-debug` with ones
- from `byte-buddy` and `byte-buddy-agent` will lead to conflicts as the resource files are duplicated.
+`kotlinx-coroutines-debug` transitively depends on JNA and JNA-platform, byte-buddy and byte-buddy-agent, all of them include license files in their
+META-INF directories. Trying to merge these files leads to conflicts, which means that any Android project that
+depends on JNA and JNA-platform will experience build failures.
One possible workaround for these issues is to add the following to the `android` block in your gradle file for the
application subproject:
diff --git a/kotlinx-coroutines-debug/build.gradle.kts b/kotlinx-coroutines-debug/build.gradle.kts
index a9b2d6730d..70f592a98e 100644
--- a/kotlinx-coroutines-debug/build.gradle.kts
+++ b/kotlinx-coroutines-debug/build.gradle.kts
@@ -1,22 +1,12 @@
-import com.github.jengelman.gradle.plugins.shadow.tasks.*
-import java.net.*
-import java.nio.file.*
+import org.gradle.api.JavaVersion
+import org.gradle.api.file.DuplicatesStrategy
+import org.gradle.api.tasks.bundling.Jar
+import org.gradle.api.tasks.testing.Test
plugins {
- id("com.github.johnrengelman.shadow")
id("org.jetbrains.kotlinx.kover") // apply plugin to use autocomplete for Kover DSL
}
-configurations {
- val shadowDeps by creating
- compileOnly.configure {
- extendsFrom(shadowDeps)
- }
- runtimeOnly.configure {
- extendsFrom(shadowDeps)
- }
-}
-
val junit_version by properties
val junit5_version by properties
val byte_buddy_version by properties
@@ -28,8 +18,8 @@ dependencies {
compileOnly("org.junit.jupiter:junit-jupiter-api:$junit5_version")
testImplementation("org.junit.jupiter:junit-jupiter-engine:$junit5_version")
testImplementation("org.junit.platform:junit-platform-testkit:1.7.0")
- add("shadowDeps", "net.bytebuddy:byte-buddy:$byte_buddy_version")
- add("shadowDeps", "net.bytebuddy:byte-buddy-agent:$byte_buddy_version")
+ implementation("net.bytebuddy:byte-buddy:$byte_buddy_version")
+ implementation("net.bytebuddy:byte-buddy-agent:$byte_buddy_version")
compileOnly("io.projectreactor.tools:blockhound:$blockhound_version")
testImplementation("io.projectreactor.tools:blockhound:$blockhound_version")
testImplementation("com.google.code.gson:gson:2.8.6")
@@ -50,61 +40,34 @@ tasks.withType().configureEach {
}
}
-val jar by tasks.existing(Jar::class) {
- enabled = false
-}
-
-val shadowJar by tasks.existing(ShadowJar::class) {
- // Shadow only byte buddy, do not package kotlin stdlib
- configurations = listOf(project.configurations["shadowDeps"])
- relocate("net.bytebuddy", "kotlinx.coroutines.repackaged.net.bytebuddy")
- /* These classifiers are both set to `null` to trick Gradle into thinking that this jar file is both the
- artifact from the `jar` task and the one from `shadowJar`. Without this, Gradle complains that the artifact
- from the `jar` task is not present when the compilaton finishes, even if the file with this name exists. */
- archiveClassifier.convention(null as String?)
- archiveClassifier = null
- archiveBaseName = jar.flatMap { it.archiveBaseName }
- archiveVersion = jar.flatMap { it.archiveVersion }
+tasks.named("jar") {
manifest {
attributes(
mapOf(
- "Premain-Class" to "kotlinx.coroutines.debug.AgentPremain",
+ "Premain-Class" to "kotlinx.coroutines.debug.internal.AgentPremain",
"Can-Redefine-Classes" to "true",
"Multi-Release" to "true"
)
)
}
+
// add module-info.class to the META-INF/versions/9/ directory.
dependsOn(tasks.compileModuleInfoJava)
- doLast {
- // We can't do that directly with the shadowJar task because it doesn't support replacing existing files.
- val zipPath = this@existing.outputs.files.singleFile.toPath()
- val zipUri = URI.create("jar:${zipPath.toUri()}")
- val moduleInfoFilePath = tasks.compileModuleInfoJava.get().outputs.files.asFileTree.matching {
- include("module-info.class")
- }.singleFile.toPath()
- FileSystems.newFileSystem(zipUri, emptyMap()).use { fs ->
- val moduleInfoFile = fs.getPath("META-INF/versions/9/module-info.class")
- Files.copy(moduleInfoFilePath, moduleInfoFile, StandardCopyOption.REPLACE_EXISTING)
- }
+ from(tasks.compileModuleInfoJava.get().outputs.files.asFileTree.matching {
+ include("module-info.class")
+ }) {
+ this.duplicatesStrategy = DuplicatesStrategy.INCLUDE
+ into("META-INF/versions/9")
}
}
-configurations {
- // shadowJar is already part of the `shadowRuntimeElements` and `shadowApiElements`, but the other subprojects
- // that depend on `kotlinx-coroutines-debug` look at `runtimeElements` and `apiElements`.
- artifacts {
- add("apiElements", shadowJar)
- add("runtimeElements", shadowJar)
- }
-}
kover {
reports {
filters {
excludes {
// Never used, safety mechanism
- classes("kotlinx.coroutines.debug.internal.NoOpProbesKt")
+ classes("kotlinx.coroutines.debug.NoOpProbesKt")
}
}
}
diff --git a/kotlinx-coroutines-debug/src/internal/Attach.kt b/kotlinx-coroutines-debug/src/Attach.kt
similarity index 90%
rename from kotlinx-coroutines-debug/src/internal/Attach.kt
rename to kotlinx-coroutines-debug/src/Attach.kt
index 63bfe8eafe..291d913f3e 100644
--- a/kotlinx-coroutines-debug/src/internal/Attach.kt
+++ b/kotlinx-coroutines-debug/src/Attach.kt
@@ -1,5 +1,5 @@
@file:Suppress("unused")
-package kotlinx.coroutines.debug.internal
+package kotlinx.coroutines.debug
import net.bytebuddy.*
import net.bytebuddy.agent.*
@@ -28,7 +28,7 @@ internal class ByteBuddyDynamicAttach : Function1