diff --git a/.gitignore b/.gitignore
index 52843ca5b7..36de0e50da 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,4 @@ build
out
target
local.properties
+/kotlin-js-store
diff --git a/CHANGES.md b/CHANGES.md
index 611e9c9c74..fb60da3017 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,5 +1,161 @@
# Change log for kotlinx.coroutines
+## Version 1.6.3
+
+* Updated atomicfu version to 0.17.3 (#3321), fixing the projects using this library with JS IR failing to build (#3305).
+
+## Version 1.6.2
+
+* Fixed a bug with `ThreadLocalElement` not being correctly updated when the most outer `suspend` function was called directly without `kotlinx.coroutines` (#2930).
+* Fixed multiple data races: one that might have been affecting `runBlocking` event loop, and a benign data race in `Mutex` (#3250, #3251).
+* Obsolete `TestCoroutineContext` is removed, which fixes the `kotlinx-coroutines-test` JPMS package being split between `kotlinx-coroutines-core` and `kotlinx-coroutines-test` (#3218).
+* Updated the ProGuard rules to further shrink the size of the resulting DEX file with coroutines (#3111, #3263). Thanks, @agrieve!
+* Atomicfu is updated to `0.17.2`, which includes a more efficient and robust JS IR transformer (#3255).
+* Kotlin is updated to `1.6.21`, Gradle version is updated to `7.4.2` (#3281). Thanks, @wojtek-kalicinski!
+* Various documentation improvements.
+
+## Version 1.6.1
+
+* Rollback of time-related functions dispatching on `Dispatchers.Main`.
+ This behavior was introduced in 1.6.0 and then found inconvenient and erroneous (#3106, #3113).
+* Reworked the newly-introduced `CopyableThreadContextElement` to solve issues uncovered after the initial release (#3227).
+* Fixed a bug with `ThreadLocalElement` not being properly updated in racy scenarios (#2930).
+* Reverted eager loading of default `CoroutineExceptionHandler` that triggered ANR on some devices (#3180).
+* New API to convert a `CoroutineDispatcher` to a Rx scheduler (#968, #548). Thanks @recheej!
+* Fixed a memory leak with the very last element emitted from `flow` builder being retained in memory (#3197).
+* Fixed a bug with `limitedParallelism` on K/N with new memory model throwing `ClassCastException` (#3223).
+* `CoroutineContext` is added to the exception printed to the default `CoroutineExceptionHandler` to improve debuggability (#3153).
+* Static memory consumption of `Dispatchers.Default` was significantly reduced (#3137).
+* Updated slf4j version in `kotlinx-coroutines-slf4j` from 1.7.25 to 1.7.32.
+
+## Version 1.6.0
+
+Note that this is a full changelog relative to the 1.5.2 version. Changelog relative to 1.6.0-RC3 can be found at the end.
+
+### kotlinx-coroutines-test rework
+
+* `kotlinx-coroutines-test` became a multiplatform library usable from K/JVM, K/JS, and K/N.
+* Its API was completely reworked to address long-standing issues with consistency, structured concurrency and correctness (#1203, #1609, #2379, #1749, #1204, #1390, #1222, #1395, #1881, #1910, #1772, #1626, #1742, #2082, #2102, #2405, #2462
+ ).
+* The old API is deprecated for removal, but the new API is based on the similar concepts ([README](kotlinx-coroutines-test/README.md)), and the migration path is designed to be graceful: [migration guide](kotlinx-coroutines-test/MIGRATION.md).
+
+### Dispatchers
+
+* Introduced `CoroutineDispatcher.limitedParallelism` that allows obtaining a view of the original dispatcher with limited parallelism (#2919).
+* `Dispatchers.IO.limitedParallelism` usages ignore the bound on the parallelism level of `Dispatchers.IO` itself to avoid starvation (#2943).
+* Introduced new `Dispatchers.shutdown` method for containerized environments (#2558).
+* `newSingleThreadContext` and `newFixedThreadPoolContext` are promoted to delicate API (#2919).
+
+### Breaking changes
+
+* When racing with cancellation, the `future` builder no longer reports unhandled exceptions into the global `CoroutineExceptionHandler`. Thanks @vadimsemenov! (#2774, #2791).
+* `Mutex.onLock` is deprecated for removal (#2794).
+* `Dispatchers.Main` is now used as the default source of time for `delay` and `withTimeout` when present(#2972).
+ * To opt-out from this behaviour, `kotlinx.coroutines.main.delay` system property can be set to `false`.
+* Java target of coroutines build is now 8 instead of 6 (#1589).
+* **Source-breaking change**: extension `collect` no longer resolves when used with a non-in-place argument of a functional type. This is a candidate for a fix, uncovered after 1.6.0, see #3107 for the additional details.
+
+### Bug fixes and improvements
+
+* Kotlin is updated to 1.6.0.
+* Kotlin/Native [new memory model](https://blog.jetbrains.com/kotlin/2021/08/try-the-new-kotlin-native-memory-manager-development-preview/) is now supported in regular builds of coroutines conditionally depending on whether `kotlin.native.binary.memoryModel` is enabled (#2914).
+* Introduced `CopyableThreadContextElement` for mutable context elements shared among multiple coroutines. Thanks @yorickhenning! (#2893).
+* `transformWhile`, `awaitClose`, `ProducerScope`, `merge`, `runningFold`, `runingReduce`, and `scan` are promoted to stable API (#2971).
+* `SharedFlow.subscriptionCount` no longer conflates incoming updates and gives all subscribers a chance to observe a short-lived subscription (#2488, #2863, #2871).
+* `Flow` exception transparency mechanism is improved to be more exception-friendly (#3017, #2860).
+* Cancellation from `flat*` operators that leverage multiple coroutines is no longer propagated upstream (#2964).
+* `SharedFlow.collect` now returns `Nothing` (#2789, #2502).
+* `DisposableHandle` is now `fun interface`, and corresponding inline extension is removed (#2790).
+* `FlowCollector` is now `fun interface`, and corresponding inline extension is removed (#3047).
+* Deprecation level of all previously deprecated signatures is raised (#3024).
+* The version file is shipped with each JAR as a resource (#2941).
+* Unhandled exceptions on K/N are passed to the standard library function `processUnhandledException` (#2981).
+* A direct executor is used for `Task` callbacks in `kotlinx-coroutines-play-services` (#2990).
+* Metadata of coroutines artifacts leverages Gradle platform to have all versions of dependencies aligned (#2865).
+* Default `CoroutineExceptionHandler` is loaded eagerly and does not invoke `ServiceLoader` on its exception-handling path (#2552).
+* Fixed the R8 rules for `ServiceLoader` optimization (#2880).
+* Fixed BlockHound integration false-positives (#2894, #2866, #2937).
+* Fixed the exception handler being invoked several times on Android, thanks to @1zaman (#3056).
+* `SendChannel.trySendBlocking` is now available on Kotlin/Native (#3064).
+* The exception recovery mechanism now uses `ClassValue` when available (#2997).
+* JNA is updated to 5.9.0 to support Apple M1 (#3001).
+* Obsolete method on internal `Delay` interface is deprecated (#2979).
+* Support of deprecated `CommonPool` is removed.
+* `@ExperimentalTime` is no longer needed for methods that use `Duration` (#3041).
+* JDK 1.6 is no longer required for building the project (#3043).
+* New version of Dokka is used, fixing the memory leak when building the coroutines and providing brand new reference visuals (https://kotlin.github.io/kotlinx.coroutines/) (#3051, #3054).
+
+### Changelog relative to version 1.6.0-RC3
+
+* Restored MPP binary compatibility on K/JS and K/N (#3104).
+* Fixed Dispatchers.Main not being fully initialized on Android and Swing (#3101).
+
+## Version 1.6.0-RC3
+
+* Fixed the error in 1.6.0-RC2 because of which `Flow.collect` couldn't be called due to the `@InternalCoroutinesApi` annotation (#3082)
+* Fixed some R8 warnings introduced in 1.6.0-RC (#3090)
+* `TestCoroutineScheduler` now provides a `TimeSource` with its virtual time via the `timeSource` property. Thanks @hfhbd! (#3087)
+
+## Version 1.6.0-RC2
+
+* `@ExperimentalTime` is no longer needed for methods that use `Duration` (#3041).
+* `FlowCollector` is now `fun interface`, and corresponding inline extension is removed (#3047).
+* Fixed the exception handler being invoked several times on Android, thanks to @1zaman (#3056).
+* The deprecated `TestCoroutineScope` is no longer sealed, to simplify migration from it (#3072).
+* `runTest` gives more informative errors when it times out waiting for external completion (#3071).
+* `SendChannel.trySendBlocking` is now available on Kotlin/Native (#3064).
+* Fixed the bug due to which `Dispatchers.Main` was not used for `delay` and `withTimeout` (#3046).
+* JDK 1.6 is no longer required for building the project (#3043).
+* New version of Dokka is used, fixing the memory leak when building the coroutines and providing brand new reference visuals (https://kotlin.github.io/kotlinx.coroutines/) (#3051, #3054).
+
+## Version 1.6.0-RC
+
+### kotlinx-coroutines-test rework
+
+* `kotlinx-coroutines-test` became a multiplatform library usable from K/JVM, K/JS, and K/N.
+* Its API was completely reworked to address long-standing issues with consistency, structured concurrency and correctness (#1203, #1609, #2379, #1749, #1204, #1390, #1222, #1395, #1881, #1910, #1772, #1626, #1742, #2082, #2102, #2405, #2462
+ ).
+* The old API is deprecated for removal, but the new API is based on the similar concepts ([README](kotlinx-coroutines-test/README.md)), and the migration path is designed to be graceful: [migration guide](kotlinx-coroutines-test/MIGRATION.md)
+
+### Dispatchers
+
+* Introduced `CoroutineDispatcher.limitedParallelism` that allows obtaining a view of the original dispatcher with limited parallelism (#2919).
+* `Dispatchers.IO.limitedParallelism` usages ignore the bound on the parallelism level of `Dispatchers.IO` itself to avoid starvation (#2943).
+* Introduced new `Dispatchers.shutdown` method for containerized environments (#2558).
+* `newSingleThreadContext` and `newFixedThreadPoolContext` are promoted to delicate API (#2919).
+
+### Breaking changes
+
+* When racing with cancellation, the `future` builder no longer reports unhandled exceptions into the global `CoroutineExceptionHandler`. Thanks @vadimsemenov! (#2774, #2791).
+* `Mutex.onLock` is deprecated for removal (#2794).
+* `Dispatchers.Main` is now used as the default source of time for `delay` and `withTimeout` when present(#2972).
+ * To opt-out from this behaviour, `kotlinx.coroutines.main.delay` system property can be set to `false`.
+* Java target of coroutines build is now 8 instead of 6 (#1589).
+
+### Bug fixes and improvements
+
+* Kotlin is updated to 1.6.0.
+* Kotlin/Native [new memory model](https://blog.jetbrains.com/kotlin/2021/08/try-the-new-kotlin-native-memory-manager-development-preview/) is now supported in regular builds of coroutines conditionally depending on whether `kotlin.native.binary.memoryModel` is enabled (#2914).
+* Introduced `CopyableThreadContextElement` for mutable context elements shared among multiple coroutines. Thanks @yorickhenning! (#2893).
+* `transformWhile`, `awaitClose`, `ProducerScope`, `merge`, `runningFold`, `runingReduce`, and `scan` are promoted to stable API (#2971).
+* `SharedFlow.subscriptionCount` no longer conflates incoming updates and gives all subscribers a chance to observe a short-lived subscription (#2488, #2863, #2871).
+* `Flow` exception transparency mechanism is improved to be more exception-friendly (#3017, #2860).
+* Cancellation from `flat*` operators that leverage multiple coroutines is no longer propagated upstream (#2964).
+* `SharedFlow.collect` now returns `Nothing` (#2789, #2502).
+* `DisposableHandle` is now `fun interface`, and corresponding inline extension is removed (#2790).
+* Deprecation level of all previously deprecated signatures is raised (#3024).
+* The version file is shipped with each JAR as a resource (#2941).
+* Unhandled exceptions on K/N are passed to the standard library function `processUnhandledException` (#2981).
+* A direct executor is used for `Task` callbacks in `kotlinx-coroutines-play-services` (#2990).
+* Metadata of coroutines artifacts leverages Gradle platform to have all versions of dependencies aligned (#2865).
+* Default `CoroutineExceptionHandler` is loaded eagerly and does not invoke `ServiceLoader` on its exception-handling path (#2552).
+* Fixed the R8 rules for `ServiceLoader` optimization (#2880).
+* Fixed BlockHound integration false-positives (#2894, #2866, #2937).
+* The exception recovery mechanism now uses `ClassValue` when available (#2997).
+* JNA is updated to 5.9.0 to support Apple M1 (#3001).
+* Obsolete method on internal `Delay` interface is deprecated (#2979).
+* Support of deprecated `CommonPool` is removed.
+
## Version 1.5.2
* Kotlin is updated to 1.5.30.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7d6e32d878..77b727b4c6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -80,15 +80,12 @@ to Gradle (in Preferences -> Build, Execution, Deployment -> Build Tools -> Grad
### Environment requirements
* JDK >= 11 referred to by the `JAVA_HOME` environment variable.
-* JDK 1.6 referred to by the `JDK_16` environment variable.
- It is OK to have `JDK_16` pointing to a non 1.6 JDK (e.g. `JAVA_HOME`) for external contributions.
* JDK 1.8 referred to by the `JDK_18` environment variable. Only used by nightly stress-tests.
It is OK to have `JDK_18` to a non 1.8 JDK (e.g. `JAVA_HOME`) for external contributions.
-
+
For external contributions you can for example add this to your shell startup scripts (e.g. `~/.zshrc`):
```shell
# This assumes JAVA_HOME is set already to a JDK >= 11 version
-export JDK_16="$JAVA_HOME"
export JDK_18="$JAVA_HOME"
```
diff --git a/README.md b/README.md
index 6a13f07aa3..3b24731d25 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,14 @@
# kotlinx.coroutines
-[](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
+[](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://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.5.2/pom)
-[](http://kotlinlang.org)
+[](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core/1.6.3/pom)
+[](http://kotlinlang.org)
[](https://kotlinlang.slack.com/messages/coroutines/)
Library support for Kotlin coroutines with [multiplatform](#multiplatform) support.
-This is a companion version for the Kotlin `1.5.30` release.
+This is a companion version for the Kotlin `1.6.0` release.
```kotlin
suspend fun main() = coroutineScope {
@@ -61,9 +62,9 @@ suspend fun main() = coroutineScope {
## Documentation
* Presentations and videos:
- * [Introduction to Coroutines](https://www.youtube.com/watch?v=_hfBv0a09Jc) (Roman Elizarov at KotlinConf 2017, [slides](https://www.slideshare.net/elizarov/introduction-to-coroutines-kotlinconf-2017))
- * [Deep dive into Coroutines](https://www.youtube.com/watch?v=YrrUCSi72E8) (Roman Elizarov at KotlinConf 2017, [slides](https://www.slideshare.net/elizarov/deep-dive-into-coroutines-on-jvm-kotlinconf-2017))
* [Kotlin Coroutines in Practice](https://www.youtube.com/watch?v=a3agLJQ6vt8) (Roman Elizarov at KotlinConf 2018, [slides](https://www.slideshare.net/elizarov/kotlin-coroutines-in-practice-kotlinconf-2018))
+ * [Deep Dive into Coroutines](https://www.youtube.com/watch?v=YrrUCSi72E8) (Roman Elizarov at KotlinConf 2017, [slides](https://www.slideshare.net/elizarov/deep-dive-into-coroutines-on-jvm-kotlinconf-2017))
+ * [History of Structured Concurrency in Coroutines](https://www.youtube.com/watch?v=Mj5P47F6nJg) (Roman Elizarov at Hydra 2019, [slides](https://speakerdeck.com/elizarov/structured-concurrency))
* Guides and manuals:
* [Guide to kotlinx.coroutines by example](https://kotlinlang.org/docs/coroutines-guide.html) (**read it first**)
* [Guide to UI programming with coroutines](ui/coroutines-guide-ui.md)
@@ -83,7 +84,7 @@ Add dependencies (you can also add other modules that you need):
org.jetbrains.kotlinx
kotlinx-coroutines-core
- 1.5.2
+ 1.6.3
```
@@ -91,7 +92,7 @@ And make sure that you use the latest Kotlin version:
```xml
- 1.5.30
+ 1.6.21
```
@@ -99,55 +100,39 @@ And make sure that you use the latest Kotlin version:
Add dependencies (you can also add other modules that you need):
-```groovy
+```kotlin
dependencies {
- implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2'
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3")
}
```
And make sure that you use the latest Kotlin version:
-```groovy
-buildscript {
- ext.kotlin_version = '1.5.30'
+```kotlin
+plugins {
+ // For build.gradle.kts (Kotlin DSL)
+ kotlin("jvm") version "1.6.21"
+
+ // For build.gradle (Groovy DSL)
+ id "org.jetbrains.kotlin.jvm" version "1.6.21"
}
```
Make sure that you have `mavenCentral()` in the list of repositories:
-```
-repository {
+```kotlin
+repositories {
mavenCentral()
}
```
-### Gradle Kotlin DSL
-
-Add dependencies (you can also add other modules that you need):
-
-```groovy
-dependencies {
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
-}
-```
-
-And make sure that you use the latest Kotlin version:
-
-```groovy
-plugins {
- kotlin("jvm") version "1.5.20"
-}
-```
-
-Make sure that you have `mavenCentral()` in the list of repositories.
-
### Android
Add [`kotlinx-coroutines-android`](ui/kotlinx-coroutines-android)
module as a dependency when using `kotlinx.coroutines` on Android:
-```groovy
-implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2'
+```kotlin
+implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.3")
```
This gives you access to the Android [Dispatchers.Main]
@@ -165,7 +150,8 @@ For more details see ["Optimization" section for Android](ui/kotlinx-coroutines-
The `kotlinx-coroutines-core` artifact contains a resource file that is not required for the coroutines to operate
normally and is only used by the debugger. To exclude it at no loss of functionality, add the following snippet to the
`android` block in your Gradle file for the application subproject:
-```groovy
+
+```kotlin
packagingOptions {
resources.excludes += "DebugProbesKt.bin"
}
@@ -177,10 +163,11 @@ Core modules of `kotlinx.coroutines` are also available for
[Kotlin/JS](https://kotlinlang.org/docs/reference/js-overview.html) and [Kotlin/Native](https://kotlinlang.org/docs/reference/native-overview.html).
In common code that should get compiled for different platforms, you can add a dependency to `kotlinx-coroutines-core` right to the `commonMain` source set:
-```groovy
+
+```kotlin
commonMain {
dependencies {
- implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.3")
}
}
```
@@ -192,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://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.5.2/jar)
+[`kotlinx-coroutines-core-js`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.6.3/jar)
(follow the link to get the dependency declaration snippet) and as [`kotlinx-coroutines-core`](https://www.npmjs.com/package/kotlinx-coroutines-core) NPM package.
#### Native
diff --git a/RELEASE.md b/RELEASE.md
index edc7726a0a..e297abd3c4 100644
--- a/RELEASE.md
+++ b/RELEASE.md
@@ -1,15 +1,15 @@
# kotlinx.coroutines release checklist
-To release new `` of `kotlinx-coroutines`:
+To release a new `` of `kotlinx-coroutines`:
-1. Checkout `develop` branch:
+1. Checkout the `develop` branch:
`git checkout develop`
-2. Retrieve the most recent `develop`:
+2. Retrieve the most recent `develop`:
`git pull`
-
+
3. Make sure the `master` branch is fully merged into `develop`:
- `git merge origin/master`
+ `git merge origin/master`
4. Search & replace `` with `` across the project files. Should replace in:
* Docs
@@ -17,62 +17,64 @@ To release new `` of `kotlinx-coroutines`:
* [`kotlinx-coroutines-debug/README.md`](kotlinx-coroutines-debug/README.md)
* [`kotlinx-coroutines-test/README.md`](kotlinx-coroutines-test/README.md)
* [`coroutines-guide-ui.md`](ui/coroutines-guide-ui.md)
- * Properties
- * [`gradle.properties`](gradle.properties)
+ * Properties
+ * [`gradle.properties`](gradle.properties)
* Make sure to **exclude** `CHANGES.md` from replacements.
-
- As an alternative approach you can use `./bump-version.sh old_version new_version`
-
+
+ As an alternative approach, you can use `./bump-version.sh old_version new_version`
+
5. Write release notes in [`CHANGES.md`](CHANGES.md):
- * Use old releases as example of style.
+ * Use the old releases for style guidance.
* Write each change on a single line (don't wrap with CR).
- * Study commit message from previous release.
+ * Look through the commit messages since the previous release.
6. Create the branch for this release:
`git checkout -b version-`
-7. Commit updated files to a new version branch:
+7. Commit the updated files to the new version branch:
`git commit -a -m "Version "`
-
-8. Push the new version into the branch:
+
+8. Push the new version to GitHub:
`git push -u origin version-`
-
-9. Create Pull-Request on GitHub from `version-` branch into `master`:
+
+9. Create a Pull-Request on GitHub from the `version-` branch into `master`:
* Review it.
- * Make sure it build on CI.
+ * Make sure it builds on CI.
* Get approval for it.
-
-0. Merge new version branch into `master`:
- `git checkout master`
- `git merge version-`
- `git push`
-1. On [TeamCity integration server](https://teamcity.jetbrains.com/project.html?projectId=KotlinTools_KotlinxCoroutines):
+0. On [TeamCity integration server](https://teamcity.jetbrains.com/project.html?projectId=KotlinTools_KotlinxCoroutines):
* Wait until "Build" configuration for committed `master` branch passes tests.
- * Run "Deploy (Configure, RUN THIS ONE)" configuration with the corresponding new version.
+ * Run "Deploy (Configure, RUN THIS ONE)" configuration with the corresponding new version:
+ - Use the `version-` branch
+ - Set the `DeployVersion` build parameter to ``
+ * Wait until all four "Deploy" configurations finish.
-2. In [GitHub](https://github.com/kotlin/kotlinx.coroutines) interface:
- * Create a release named ``.
- * Cut & paste lines from [`CHANGES.md`](CHANGES.md) into description.
-
-3. Build and publish documentation for web-site
- (make sure you have [Docker](https://www.docker.com/) installed first):
- `site/deploy.sh push`
-
-4. In [Nexus](https://oss.sonatype.org/#stagingRepositories) admin interface:
+1. In [Nexus](https://oss.sonatype.org/#stagingRepositories) admin interface:
* Close the repository and wait for it to verify.
* Release the repository.
-
-5. Announce new release in [Slack](https://kotlinlang.slack.com)
-6. Switch into `develop` branch:
+2. Merge the new version branch into `master`:
+ `git checkout master`
+ `git merge version-`
+ `git push`
+
+3. In [GitHub](https://github.com/kotlin/kotlinx.coroutines) interface:
+ * Create a release named ``, creating the `` tag.
+ * Cut & paste lines from [`CHANGES.md`](CHANGES.md) into description.
+
+4. Build and publish the documentation for the web-site:
+ `site/deploy.sh push`
+
+5. Announce the new release in [Slack](https://kotlinlang.slack.com)
+
+6. Switch onto the `develop` branch:
`git checkout develop`
-
+
7. Fetch the latest `master`:
- `git fetch`
-
-8. Merge release from `master`:
+ `git fetch`
+
+8. Merge the release from `master`:
`git merge origin/master`
-
-9. Push updates to `develop`:
- `git push`
+
+9. Push the updates to GitHub:
+ `git push`
diff --git a/benchmarks/build.gradle.kts b/benchmarks/build.gradle.kts
index ce0bff1cdf..f64c4aaa21 100644
--- a/benchmarks/build.gradle.kts
+++ b/benchmarks/build.gradle.kts
@@ -8,7 +8,6 @@ import me.champeau.gradle.*
import org.jetbrains.kotlin.gradle.tasks.*
plugins {
- id("net.ltgt.apt")
id("com.github.johnrengelman.shadow")
id("me.champeau.gradle.jmh") apply false
}
@@ -31,8 +30,6 @@ tasks.named("compileJmhKotlin") {
}
}
-
-
// It is better to use the following to run benchmarks, otherwise you may get unexpected errors:
// ./gradlew --no-daemon cleanJmhJar jmh -Pjmh="MyBenchmark"
extensions.configure("jmh") {
@@ -46,21 +43,34 @@ extensions.configure("jmh") {
// includeTests = false
}
-tasks.named("jmhJar") {
+val jmhJarTask = tasks.named("jmhJar") {
archiveBaseName by "benchmarks"
archiveClassifier by null
archiveVersion by null
destinationDirectory.file("$rootDir")
}
+tasks {
+ // For some reason the DuplicatesStrategy from jmh is not enough
+ // and errors with duplicates appear unless I force it to WARN only:
+ withType {
+ duplicatesStrategy = DuplicatesStrategy.WARN
+ }
+
+ build {
+ dependsOn(jmhJarTask)
+ }
+}
+
dependencies {
- compile("org.openjdk.jmh:jmh-core:1.26")
- compile("io.projectreactor:reactor-core:${version("reactor")}")
- compile("io.reactivex.rxjava2:rxjava:2.1.9")
- compile("com.github.akarnokd:rxjava2-extensions:0.20.8")
+ implementation("org.openjdk.jmh:jmh-core:1.26")
+ implementation("io.projectreactor:reactor-core:${version("reactor")}")
+ implementation("io.reactivex.rxjava2:rxjava:2.1.9")
+ implementation("com.github.akarnokd:rxjava2-extensions:0.20.8")
- compile("com.typesafe.akka:akka-actor_2.12:2.5.0")
- compile(project(":kotlinx-coroutines-core"))
+ implementation("com.typesafe.akka:akka-actor_2.12:2.5.0")
+ implementation(project(":kotlinx-coroutines-core"))
+ implementation(project(":kotlinx-coroutines-reactive"))
// add jmh dependency on main
"jmhImplementation"(sourceSets.main.get().runtimeClasspath)
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt b/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt
index 9948a371bc..ce64c6a49b 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/ParametrizedDispatcherBase.kt
@@ -25,12 +25,11 @@ abstract class ParametrizedDispatcherBase : CoroutineScope {
private var closeable: Closeable? = null
@Setup
- @UseExperimental(InternalCoroutinesApi::class)
open fun setup() {
coroutineContext = when {
dispatcher == "fjp" -> ForkJoinPool.commonPool().asCoroutineDispatcher()
dispatcher == "scheduler" -> {
- ExperimentalCoroutineDispatcher(CORES_COUNT).also { closeable = it }
+ Dispatchers.Default
}
dispatcher.startsWith("ftp") -> {
newFixedThreadPoolContext(dispatcher.substring(4).toInt(), dispatcher).also { closeable = it }
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt b/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt
index 40ddc8ec36..9e1bfc43bb 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/SemaphoreBenchmark.kt
@@ -6,13 +6,10 @@ package benchmarks
import benchmarks.common.*
import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.scheduling.ExperimentalCoroutineDispatcher
-import kotlinx.coroutines.sync.Semaphore
-import kotlinx.coroutines.sync.withPermit
+import kotlinx.coroutines.channels.*
+import kotlinx.coroutines.sync.*
import org.openjdk.jmh.annotations.*
-import java.util.concurrent.ForkJoinPool
-import java.util.concurrent.TimeUnit
+import java.util.concurrent.*
@Warmup(iterations = 3, time = 500, timeUnit = TimeUnit.MICROSECONDS)
@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MICROSECONDS)
@@ -84,7 +81,7 @@ open class SemaphoreBenchmark {
enum class SemaphoreBenchDispatcherCreator(val create: (parallelism: Int) -> CoroutineDispatcher) {
FORK_JOIN({ parallelism -> ForkJoinPool(parallelism).asCoroutineDispatcher() }),
- EXPERIMENTAL({ parallelism -> ExperimentalCoroutineDispatcher(corePoolSize = parallelism, maxPoolSize = parallelism) })
+ EXPERIMENTAL({ parallelism -> Dispatchers.Default }) // TODO doesn't take parallelism into account
}
private const val WORK_INSIDE = 80
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt
index e7bd1f5fb9..acfb3f3c6d 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/flow/scrabble/SequencePlaysScrabble.kt
@@ -30,7 +30,7 @@ open class SequencePlaysScrabble : ShakespearePlaysScrabble() {
val bonusForDoubleLetter: (String) -> Int = { word: String ->
toBeMaxed(word)
.map { letterScores[it - 'a'.toInt()] }
- .max()!!
+ .maxOrNull()!!
}
val score3: (String) -> Int = { word: String ->
diff --git a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongWithBlockingContext.kt b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongWithBlockingContext.kt
index a6f0a473c1..d874f3bbe1 100644
--- a/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongWithBlockingContext.kt
+++ b/benchmarks/src/jmh/kotlin/benchmarks/scheduler/actors/PingPongWithBlockingContext.kt
@@ -27,10 +27,8 @@ import kotlin.coroutines.*
@State(Scope.Benchmark)
open class PingPongWithBlockingContext {
- @UseExperimental(InternalCoroutinesApi::class)
- private val experimental = ExperimentalCoroutineDispatcher(8)
- @UseExperimental(InternalCoroutinesApi::class)
- private val blocking = experimental.blocking(8)
+ private val experimental = Dispatchers.Default
+ private val blocking = Dispatchers.IO.limitedParallelism(8)
private val threadPool = newFixedThreadPoolContext(8, "PongCtx")
@TearDown
diff --git a/build.gradle b/build.gradle
index e4b12ff3ad..649434bba8 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,21 +2,16 @@
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+
+import org.jetbrains.kotlin.config.KotlinCompilerVersion
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+import org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile
import org.jetbrains.kotlin.konan.target.HostManager
-import org.gradle.util.VersionNumber
import org.jetbrains.dokka.gradle.DokkaTaskPartial
-import org.jetbrains.dokka.gradle.DokkaMultiModuleTask
-apply plugin: 'jdk-convention'
-apply from: rootProject.file("gradle/opt-in.gradle")
+import static Projects.*
-def coreModule = "kotlinx-coroutines-core"
-// Not applicable for Kotlin plugin
-def sourceless = ['kotlinx.coroutines', 'kotlinx-coroutines-bom', 'integration-testing']
-def internal = ['kotlinx.coroutines', 'benchmarks', 'integration-testing']
-// Not published
-def unpublished = internal + ['example-frontend-js', 'android-unit-tests']
+apply plugin: 'jdk-convention'
buildscript {
/*
@@ -47,12 +42,6 @@ buildscript {
}
}
- if (using_snapshot_version) {
- repositories {
- mavenLocal()
- }
- }
-
repositories {
mavenCentral()
maven { url "https://plugins.gradle.org/m2/" }
@@ -65,11 +54,13 @@ buildscript {
classpath "org.jetbrains.dokka:dokka-gradle-plugin:$dokka_version"
classpath "org.jetbrains.kotlinx:atomicfu-gradle-plugin:$atomicfu_version"
classpath "org.jetbrains.kotlinx:kotlinx-knit:$knit_version"
- classpath "com.moowork.gradle:gradle-node-plugin:$gradle_node_version"
+ classpath "com.github.node-gradle:gradle-node-plugin:$gradle_node_version"
classpath "org.jetbrains.kotlinx:binary-compatibility-validator:$binary_compatibility_validator_version"
+ classpath "ru.vyarus:gradle-animalsniffer-plugin:1.5.4" // Android API check
+ classpath "org.jetbrains.kotlinx:kover:$kover_version"
// JMH plugins
- classpath "com.github.jengelman.gradle.plugins:shadow:5.1.0"
+ classpath "gradle.plugin.com.github.johnrengelman:shadow:7.1.2"
}
CacheRedirector.configureBuildScript(buildscript, rootProject)
@@ -102,13 +93,6 @@ allprojects {
kotlin_version = rootProject.properties['kotlin_snapshot_version']
}
- if (using_snapshot_version) {
- repositories {
- mavenLocal()
- maven { url "https://oss.sonatype.org/content/repositories/snapshots" }
- }
- }
-
ext.unpublished = unpublished
// This project property is set during nightly stress test
@@ -121,11 +105,14 @@ allprojects {
}
apply plugin: "binary-compatibility-validator"
+apply plugin: "base"
+apply plugin: "kover-conventions"
+
apiValidation {
ignoredProjects += unpublished + ["kotlinx-coroutines-bom"]
if (build_snapshot_train) {
ignoredProjects.remove("example-frontend-js")
- ignoredProjects.add("kotlinx-coroutines-core")
+ ignoredProjects.add(coreModule)
}
ignoredPackages += "kotlinx.coroutines.internal"
}
@@ -139,30 +126,54 @@ allprojects {
*/
google()
mavenCentral()
+ maven { url "https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev" }
}
}
+// needs to be before evaluationDependsOn due to weird Gradle ordering
+apply plugin: "animalsniffer-conventions"
+
// Add dependency to core source sets. Core is configured in kx-core/build.gradle
configure(subprojects.findAll { !sourceless.contains(it.name) && it.name != coreModule }) {
evaluationDependsOn(":$coreModule")
- def platform = PlatformKt.platformOf(it)
- apply plugin: "kotlin-${platform}-conventions"
- dependencies {
- // See comment below for rationale, it will be replaced with "project" dependency
- api project(":$coreModule")
- // the only way IDEA can resolve test classes
- testImplementation project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs
+ if (isMultiplatform(it)) {
+ apply plugin: "kotlin-multiplatform"
+ apply from: rootProject.file("gradle/compile-jvm-multiplatform.gradle")
+ apply from: rootProject.file("gradle/compile-common.gradle")
+
+ if (rootProject.ext["native_targets_enabled"] as Boolean) {
+ apply from: rootProject.file("gradle/compile-native-multiplatform.gradle")
+ }
+
+ apply from: rootProject.file("gradle/compile-js-multiplatform.gradle")
+ apply from: rootProject.file("gradle/publish-npm-js.gradle")
+ kotlin.sourceSets.commonMain.dependencies {
+ api project(":$coreModule")
+ }
+ kotlin.sourceSets.jvmTest.dependencies {
+ implementation project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs
+ }
+ } else {
+ def platform = PlatformKt.platformOf(it)
+ apply plugin: "kotlin-${platform}-conventions"
+ dependencies {
+ api project(":$coreModule")
+ // the only way IDEA can resolve test classes
+ testImplementation project(":$coreModule").kotlin.targets.jvm.compilations.test.output.allOutputs
+ }
}
}
+apply plugin: "bom-conventions"
+
// Configure subprojects with Kotlin sources
configure(subprojects.findAll { !sourceless.contains(it.name) }) {
// Use atomicfu plugin, it also adds all the necessary dependencies
apply plugin: 'kotlinx-atomicfu'
// Configure options for all Kotlin compilation tasks
- tasks.withType(org.jetbrains.kotlin.gradle.tasks.AbstractKotlinCompile).all {
- kotlinOptions.freeCompilerArgs += optInAnnotations.collect { "-Xopt-in=" + it }
+ tasks.withType(AbstractKotlinCompile).all {
+ kotlinOptions.freeCompilerArgs += OptInPreset.optInAnnotations.collect { "-Xopt-in=" + it }
kotlinOptions.freeCompilerArgs += "-progressive"
// Disable KT-36770 for RxJava2 integration
kotlinOptions.freeCompilerArgs += "-XXLanguage:-ProhibitUsingNullableTypeParameterAgainstNotNullAnnotated"
@@ -189,7 +200,7 @@ if (build_snapshot_train) {
}
println "Manifest of kotlin-compiler-embeddable.jar for coroutines"
- configure(subprojects.findAll { it.name == "kotlinx-coroutines-core" }) {
+ configure(subprojects.findAll { it.name == coreModule }) {
configurations.matching { it.name == "kotlinCompilerClasspath" }.all {
resolvedConfiguration.getFiles().findAll { it.name.contains("kotlin-compiler-embeddable") }.each {
def manifest = zipTree(it).matching {
@@ -206,9 +217,8 @@ if (build_snapshot_train) {
// Redefine source sets because we are not using 'kotlin/main/fqn' folder convention
configure(subprojects.findAll {
- !sourceless.contains(it.name) &&
+ !sourceless.contains(it.name) && !isMultiplatform(it) &&
it.name != "benchmarks" &&
- it.name != coreModule &&
it.name != "example-frontend-js"
}) {
// Pure JS and pure MPP doesn't have this notion and are configured separately
@@ -225,7 +235,7 @@ def core_docs_url = "https://kotlin.github.io/kotlinx.coroutines/$coreModule/"
def core_docs_file = "$projectDir/kotlinx-coroutines-core/build/dokka/htmlPartial/package-list"
apply plugin: "org.jetbrains.dokka"
-configure(subprojects.findAll { !unpublished.contains(it.name) }) {
+configure(subprojects.findAll { !unpublished.contains(it.name) && it.name != coreModule }) {
if (it.name != 'kotlinx-coroutines-bom') {
apply from: rootProject.file('gradle/dokka.gradle.kts')
}
@@ -245,10 +255,44 @@ configure(subprojects.findAll { !unpublished.contains(it.name) }) {
}
}
}
+
+ def thisProject = it
+ if (thisProject.name in sourceless) {
+ return
+ }
+
+ def versionFileTask = thisProject.tasks.register("versionFileTask") {
+ def name = thisProject.name.replace("-", "_")
+ def versionFile = thisProject.layout.buildDirectory.file("${name}.version")
+ it.outputs.file(versionFile)
+
+ it.doLast {
+ versionFile.get().asFile.text = version.toString()
+ }
+ }
+
+ List jarTasks
+ if (isMultiplatform(it)) {
+ jarTasks = ["jvmJar", "metadataJar"]
+ } else if (it.name == "kotlinx-coroutines-debug") {
+ // We shadow debug module instead of just packaging it
+ jarTasks = ["shadowJar"]
+ } else {
+ jarTasks = ["jar"]
+ }
+
+ for (name in jarTasks) {
+ thisProject.tasks.named(name, Jar) {
+ it.dependsOn versionFileTask
+ it.from(versionFileTask) {
+ into("META-INF")
+ }
+ }
+ }
}
// Report Kotlin compiler version when building project
-println("Using Kotlin compiler version: $org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION")
+println("Using Kotlin compiler version: $KotlinCompilerVersion.VERSION")
// --------------- Cache redirector ---------------
@@ -262,8 +306,6 @@ def publishTasks = getTasksByName("publish", true) + getTasksByName("publishNpm"
task deploy(dependsOn: publishTasks)
-apply plugin: 'base'
-
clean.dependsOn gradle.includedBuilds.collect { it.task(':clean') }
// --------------- Knit configuration ---------------
@@ -302,12 +344,12 @@ allprojects { subProject ->
.matching {
// Excluding substituted project itself because of circular dependencies, but still do it
// for "*Test*" configurations
- subProject.name != "kotlinx-coroutines-core" || it.name.contains("Test")
+ subProject.name != coreModule || it.name.contains("Test")
}
.configureEach { conf ->
conf.resolutionStrategy.dependencySubstitution {
- substitute(module("org.jetbrains.kotlinx:kotlinx-coroutines-core"))
- .using(project(":kotlinx-coroutines-core"))
+ substitute(module("org.jetbrains.kotlinx:$coreModule"))
+ .using(project(":$coreModule"))
.because("Because Kotlin compiler embeddable leaks coroutines into the runtime classpath, " +
"triggering all sort of incompatible class changes errors")
}
diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts
index c54e226af1..eaa03f2f15 100644
--- a/buildSrc/build.gradle.kts
+++ b/buildSrc/build.gradle.kts
@@ -19,7 +19,6 @@ repositories {
maven("https://plugins.gradle.org/m2")
}
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev")
-
if (buildSnapshotTrain) {
mavenLocal()
}
@@ -44,6 +43,25 @@ fun version(target: String): String {
dependencies {
implementation(kotlin("gradle-plugin", version("kotlin")))
- implementation("org.jetbrains.dokka:dokka-gradle-plugin:${version("dokka")}")
- implementation("org.jetbrains.dokka:dokka-core:${version("dokka")}")
+ /*
+ * Dokka is compiled with language level = 1.4, but depends on Kotlin 1.6.0, while
+ * our version of Gradle bundles Kotlin 1.4.x and can read metadata only up to 1.5.x,
+ * thus we're excluding stdlib compiled with 1.6.0 from dependencies.
+ */
+ implementation("org.jetbrains.dokka:dokka-gradle-plugin:${version("dokka")}") {
+ exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk8")
+ exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk7")
+ exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
+ }
+ implementation("org.jetbrains.dokka:dokka-core:${version("dokka")}") {
+ exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk8")
+ exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk7")
+ exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
+ }
+ implementation("ru.vyarus:gradle-animalsniffer-plugin:1.5.3") // Android API check
+ implementation("org.jetbrains.kotlinx:kover:${version("kover")}") {
+ exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk8")
+ exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib-jdk7")
+ exclude(group = "org.jetbrains.kotlin", module = "kotlin-stdlib")
+ }
}
diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts
index e30c3ee597..c2e859f65d 100644
--- a/buildSrc/settings.gradle.kts
+++ b/buildSrc/settings.gradle.kts
@@ -4,7 +4,6 @@
pluginManagement {
val build_snapshot_train: String? by settings
repositories {
- maven(url = "https://maven.pkg.jetbrains.space/kotlin/p/dokka/dev/")
val cacheRedirectorEnabled = System.getenv("CACHE_REDIRECTOR")?.toBoolean() == true
if (cacheRedirectorEnabled) {
println("Redirecting repositories for buildSrc buildscript")
diff --git a/buildSrc/src/main/kotlin/OptInPreset.kt b/buildSrc/src/main/kotlin/OptInPreset.kt
new file mode 100644
index 0000000000..fdcdb8ecf8
--- /dev/null
+++ b/buildSrc/src/main/kotlin/OptInPreset.kt
@@ -0,0 +1,14 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+@file:JvmName("OptInPreset")
+
+val optInAnnotations = listOf(
+ "kotlin.RequiresOptIn",
+ "kotlin.experimental.ExperimentalTypeInference",
+ "kotlin.ExperimentalMultiplatform",
+ "kotlinx.coroutines.DelicateCoroutinesApi",
+ "kotlinx.coroutines.ExperimentalCoroutinesApi",
+ "kotlinx.coroutines.ObsoleteCoroutinesApi",
+ "kotlinx.coroutines.InternalCoroutinesApi",
+ "kotlinx.coroutines.FlowPreview")
diff --git a/buildSrc/src/main/kotlin/Projects.kt b/buildSrc/src/main/kotlin/Projects.kt
index dd284b6132..af7098935d 100644
--- a/buildSrc/src/main/kotlin/Projects.kt
+++ b/buildSrc/src/main/kotlin/Projects.kt
@@ -1,8 +1,31 @@
/*
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-
-import org.gradle.api.Project
+@file:JvmName("Projects")
+import org.gradle.api.*
fun Project.version(target: String): String =
property("${target}_version") as String
+
+val coreModule = "kotlinx-coroutines-core"
+val testModule = "kotlinx-coroutines-test"
+
+val multiplatform = setOf(coreModule, testModule)
+// Not applicable for Kotlin plugin
+val sourceless = setOf("kotlinx.coroutines", "kotlinx-coroutines-bom")
+val internal = setOf("kotlinx.coroutines", "benchmarks")
+// Not published
+val unpublished = internal + setOf("example-frontend-js", "android-unit-tests")
+
+val Project.isMultiplatform: Boolean get() = name in multiplatform
+
+// Projects that we do not check for Android API level 14 check due to various limitations
+val androidNonCompatibleProjects = setOf(
+ "kotlinx-coroutines-debug",
+ "kotlinx-coroutines-swing",
+ "kotlinx-coroutines-javafx",
+ "kotlinx-coroutines-jdk8",
+ "kotlinx-coroutines-jdk9",
+ "kotlinx-coroutines-reactor",
+ "kotlinx-coroutines-test"
+)
diff --git a/buildSrc/src/main/kotlin/Publishing.kt b/buildSrc/src/main/kotlin/Publishing.kt
index 8c6dd5de3d..cb612c5077 100644
--- a/buildSrc/src/main/kotlin/Publishing.kt
+++ b/buildSrc/src/main/kotlin/Publishing.kt
@@ -7,6 +7,7 @@
import org.gradle.api.Project
import org.gradle.api.artifacts.dsl.*
import org.gradle.api.publish.maven.*
+import org.gradle.kotlin.dsl.*
import org.gradle.plugins.signing.*
import java.net.*
@@ -56,6 +57,11 @@ fun configureMavenPublication(rh: RepositoryHandler, project: Project) {
password = project.getSensitiveProperty("libs.sonatype.password")
}
}
+
+ // Something that's easy to "clean" for development, not mavenLocal
+ rh.maven("${project.rootProject.buildDir}/repo") {
+ name = "buildRepo"
+ }
}
fun signPublicationIfKeyPresent(project: Project, publication: MavenPublication) {
diff --git a/buildSrc/src/main/kotlin/SourceSets.kt b/buildSrc/src/main/kotlin/SourceSets.kt
new file mode 100644
index 0000000000..3ad1dd4dcc
--- /dev/null
+++ b/buildSrc/src/main/kotlin/SourceSets.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+import org.jetbrains.kotlin.gradle.plugin.*
+
+fun KotlinSourceSet.configureMultiplatform() {
+ val srcDir = if (name.endsWith("Main")) "src" else "test"
+ val platform = name.dropLast(4)
+ kotlin.srcDir("$platform/$srcDir")
+ if (name == "jvmMain") {
+ resources.srcDir("$platform/resources")
+ } else if (name == "jvmTest") {
+ resources.srcDir("$platform/test-resources")
+ }
+ languageSettings {
+ optInAnnotations.forEach { optIn(it) }
+ progressiveMode = true
+ }
+}
diff --git a/buildSrc/src/main/kotlin/UnpackAar.kt b/buildSrc/src/main/kotlin/UnpackAar.kt
index b3152d7ab0..afe2627a3d 100644
--- a/buildSrc/src/main/kotlin/UnpackAar.kt
+++ b/buildSrc/src/main/kotlin/UnpackAar.kt
@@ -2,18 +2,49 @@
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
+import org.gradle.api.*
import org.gradle.api.artifacts.transform.InputArtifact
import org.gradle.api.artifacts.transform.TransformAction
import org.gradle.api.artifacts.transform.TransformOutputs
import org.gradle.api.artifacts.transform.TransformParameters
+import org.gradle.api.attributes.*
import org.gradle.api.file.FileSystemLocation
import org.gradle.api.provider.Provider
+import org.gradle.kotlin.dsl.*
import java.io.File
import java.nio.file.Files
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
-// TODO move back to kotlinx-coroutines-play-services when it's migrated to the kts
+// Attributes used by aar dependencies
+val artifactType = Attribute.of("artifactType", String::class.java)
+val unpackedAar = Attribute.of("unpackedAar", Boolean::class.javaObjectType)
+
+fun Project.configureAar() = configurations.configureEach {
+ afterEvaluate {
+ if (isCanBeResolved && !isCanBeConsumed) {
+ attributes.attribute(unpackedAar, true) // request all AARs to be unpacked
+ }
+ }
+}
+
+fun DependencyHandlerScope.configureAarUnpacking() {
+ attributesSchema {
+ attribute(unpackedAar)
+ }
+
+ artifactTypes {
+ create("aar") {
+ attributes.attribute(unpackedAar, false)
+ }
+ }
+
+ registerTransform(UnpackAar::class.java) {
+ from.attribute(unpackedAar, false).attribute(artifactType, "aar")
+ to.attribute(unpackedAar, true).attribute(artifactType, "jar")
+ }
+}
+
@Suppress("UnstableApiUsage")
abstract class UnpackAar : TransformAction {
@get:InputArtifact
diff --git a/buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts b/buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts
new file mode 100644
index 0000000000..f00a0b315f
--- /dev/null
+++ b/buildSrc/src/main/kotlin/animalsniffer-conventions.gradle.kts
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+import ru.vyarus.gradle.plugin.animalsniffer.*
+
+configure(subprojects) {
+ // Skip JDK 8 projects or unpublished ones
+ if (!shouldSniff()) return@configure
+ apply(plugin = "ru.vyarus.animalsniffer")
+ project.plugins.withType(JavaPlugin::class.java) {
+ configure {
+ sourceSets = listOf((project.extensions.getByName("sourceSets") as SourceSetContainer).getByName("main"))
+ }
+ val signature: Configuration by configurations
+ dependencies {
+ signature("net.sf.androidscents.signature:android-api-level-14:4.0_r4@signature")
+ signature("org.codehaus.mojo.signature:java17:1.0@signature")
+ }
+ }
+}
+
+fun Project.shouldSniff(): Boolean {
+ // Skip all non-JVM projects
+ if (platformOf(project) != "jvm") return false
+ val name = project.name
+ if (name in unpublished || name in sourceless || name in androidNonCompatibleProjects) return false
+ return true
+}
diff --git a/buildSrc/src/main/kotlin/bom-conventions.gradle.kts b/buildSrc/src/main/kotlin/bom-conventions.gradle.kts
new file mode 100644
index 0000000000..45f30edff1
--- /dev/null
+++ b/buildSrc/src/main/kotlin/bom-conventions.gradle.kts
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+import org.gradle.kotlin.dsl.*
+import org.jetbrains.kotlin.gradle.dsl.*
+
+
+configure(subprojects.filter { it.name !in unpublished }) {
+ if (name == "kotlinx-coroutines-bom" || name == "kotlinx.coroutines") return@configure
+ if (isMultiplatform) {
+ kotlinExtension.sourceSets.getByName("jvmMain").dependencies {
+ api(project.dependencies.platform(project(":kotlinx-coroutines-bom")))
+ }
+ } else {
+ dependencies {
+ "api"(platform(project(":kotlinx-coroutines-bom")))
+ }
+ }
+}
diff --git a/buildSrc/src/main/kotlin/kotlin-jvm-conventions.gradle.kts b/buildSrc/src/main/kotlin/kotlin-jvm-conventions.gradle.kts
index c7744f8702..90847f4567 100644
--- a/buildSrc/src/main/kotlin/kotlin-jvm-conventions.gradle.kts
+++ b/buildSrc/src/main/kotlin/kotlin-jvm-conventions.gradle.kts
@@ -11,8 +11,8 @@ plugins {
}
java {
- sourceCompatibility = JavaVersion.VERSION_1_6
- targetCompatibility = JavaVersion.VERSION_1_6
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
}
dependencies {
diff --git a/buildSrc/src/main/kotlin/kover-conventions.gradle.kts b/buildSrc/src/main/kotlin/kover-conventions.gradle.kts
new file mode 100644
index 0000000000..052e2bb684
--- /dev/null
+++ b/buildSrc/src/main/kotlin/kover-conventions.gradle.kts
@@ -0,0 +1,54 @@
+import kotlinx.kover.api.*
+import kotlinx.kover.tasks.*
+
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+apply(plugin = "kover")
+
+val notCovered = sourceless + internal + unpublished
+
+val expectedCoverage = mutableMapOf(
+ // These have lower coverage in general, it can be eventually fixed
+ "kotlinx-coroutines-swing" to 70, // awaitFrame is not tested
+ "kotlinx-coroutines-javafx" to 39, // JavaFx is not tested on TC because its graphic subsystem cannot be initialized in headless mode
+
+ // Reactor has lower coverage in general due to various fatal error handling features
+ "kotlinx-coroutines-reactor" to 75)
+
+extensions.configure {
+ disabledProjects = notCovered
+ /*
+ * Is explicitly enabled on TC in a separate build step.
+ * Examples:
+ * ./gradlew :p:check -- doesn't verify coverage
+ * ./gradlew :p:check -Pkover.enabled=true -- verifies coverage
+ * ./gradlew :p:koverReport -Pkover.enabled=true -- generates report
+ */
+ isDisabled = !(properties["kover.enabled"]?.toString()?.toBoolean() ?: false)
+ // TODO remove when updating Kover to version 0.5.x
+ intellijEngineVersion.set("1.0.657")
+}
+
+subprojects {
+ val projectName = name
+ if (projectName in notCovered) return@subprojects
+ tasks.withType {
+ rule {
+ bound {
+ /*
+ * 85 is our baseline that we aim to raise to 90+.
+ * Missing coverage is typically due to bugs in the agent
+ * (e.g. signatures deprecated with an error are counted),
+ * sometimes it's various diagnostic `toString` or `catch` for OOMs/VerificationErrors,
+ * but some places are definitely worth visiting.
+ */
+ minValue = expectedCoverage[projectName] ?: 85 // COVERED_LINES_PERCENTAGE
+ }
+ }
+ }
+
+ tasks.withType {
+ htmlReportDir.set(file(rootProject.buildDir.toString() + "/kover/" + project.name + "/html"))
+ }
+}
diff --git a/bump-version.sh b/bump-version.sh
index ae0fc0b02c..e49910f428 100755
--- a/bump-version.sh
+++ b/bump-version.sh
@@ -21,6 +21,7 @@ update_version "kotlinx-coroutines-debug/README.md"
update_version "kotlinx-coroutines-test/README.md"
update_version "ui/coroutines-guide-ui.md"
update_version "gradle.properties"
+update_version "integration-test/gradle.properties"
# Escape dots, e.g. 1.0.0 -> 1\.0\.0
escaped_old_version=$(echo $old_version | sed s/[.]/\\\\./g)
diff --git a/coroutines-guide.md b/coroutines-guide.md
index 3b4707cf83..3cc035ae6a 100644
--- a/coroutines-guide.md
+++ b/coroutines-guide.md
@@ -1,14 +1,3 @@
The main coroutines guide has moved to the [docs folder](docs/topics/coroutines-guide.md) and split up into smaller documents.
-## Table of contents
-
-
-
-
-
-
-
-
-
-
-
+It is recommended to read the guide on the [kotlinlang website](https://kotlinlang.org/docs/coroutines-guide.html), with proper HTML formatting and runnable samples.
diff --git a/docs/images/coroutine-breakpoint.png b/docs/images/coroutine-breakpoint.png
index b547e77da6..d0e34e89c0 100644
Binary files a/docs/images/coroutine-breakpoint.png and b/docs/images/coroutine-breakpoint.png differ
diff --git a/docs/images/coroutine-idea-debugging-1.png b/docs/images/coroutine-idea-debugging-1.png
index 0afe992515..c824307290 100644
Binary files a/docs/images/coroutine-idea-debugging-1.png and b/docs/images/coroutine-idea-debugging-1.png differ
diff --git a/docs/images/flow-breakpoint.png b/docs/images/flow-breakpoint.png
index aa98e18e7d..a7a38cceaa 100644
Binary files a/docs/images/flow-breakpoint.png and b/docs/images/flow-breakpoint.png differ
diff --git a/docs/images/flow-build-project.png b/docs/images/flow-build-project.png
index 22186213cf..12221c77a0 100644
Binary files a/docs/images/flow-build-project.png and b/docs/images/flow-build-project.png differ
diff --git a/docs/images/flow-debug-project.png b/docs/images/flow-debug-project.png
index 98d392e2cc..f5b20bd9f2 100644
Binary files a/docs/images/flow-debug-project.png and b/docs/images/flow-debug-project.png differ
diff --git a/docs/topics/cancellation-and-timeouts.md b/docs/topics/cancellation-and-timeouts.md
index 5221db922a..47f465ad60 100644
--- a/docs/topics/cancellation-and-timeouts.md
+++ b/docs/topics/cancellation-and-timeouts.md
@@ -103,6 +103,42 @@ job: I'm sleeping 4 ...
main: Now I can quit.
-->
+The same problem can be observed by catching a [CancellationException] and not rethrowing it:
+
+```kotlin
+import kotlinx.coroutines.*
+
+fun main() = runBlocking {
+//sampleStart
+ val job = launch(Dispatchers.Default) {
+ repeat(5) { i ->
+ try {
+ // print a message twice a second
+ println("job: I'm sleeping $i ...")
+ delay(500)
+ } catch (e: Exception) {
+ // log the exception
+ println(e)
+ }
+ }
+ }
+ delay(1300L) // delay a bit
+ println("main: I'm tired of waiting!")
+ job.cancelAndJoin() // cancels the job and waits for its completion
+ println("main: Now I can quit.")
+//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-03.kt).
+>
+{type="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,
+which does not rethrow [CancellationException].
+
## Making computation code cancellable
There are two approaches to making computation code cancellable. The first one is to periodically
@@ -137,7 +173,7 @@ 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](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-04.kt).
>
{type="note"}
@@ -154,8 +190,8 @@ main: Now I can quit.
## Closing resources with `finally`
-Cancellable suspending functions throw [CancellationException] on cancellation which can be handled in
-the usual way. For example, `try {...} finally {...}` expression and Kotlin `use` function execute their
+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
finalization actions normally when a coroutine is cancelled:
```kotlin
@@ -182,7 +218,7 @@ 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](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-05.kt).
>
{type="note"}
@@ -237,7 +273,7 @@ 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](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-06.kt).
>
{type="note"}
@@ -275,7 +311,7 @@ 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](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-07.kt).
>
{type="note"}
@@ -318,7 +354,7 @@ 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-07.kt).
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-08.kt).
>
{type="note"}
@@ -378,7 +414,7 @@ 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-08.kt).
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-09.kt).
>
{type="note"}
@@ -387,13 +423,13 @@ fun main() {
If you run the above code you'll see that it does not always print zero, though it may depend on the timings
of your machine you may need to tweak timeouts in this example to actually see non-zero values.
-> Note, that incrementing and decrementing `acquired` counter here from 100K coroutines is completely safe,
-> since it always happens from the same main thread. More on that will be explained in the next chapter
+> Note that incrementing and decrementing `acquired` counter here from 100K coroutines is completely safe,
+> since it always happens from the same main thread. More on that will be explained in the chapter
> on coroutine context.
>
{type="note"}
-To workaround this problem you can store a reference to the resource in the variable as opposed to returning it
+To work around this problem you can store a reference to the resource in the variable as opposed to returning it
from the `withTimeout` block.
```kotlin
@@ -431,7 +467,7 @@ 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-09.kt).
+> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-cancel-10.kt).
>
{type="note"}
diff --git a/docs/topics/channels.md b/docs/topics/channels.md
index 7f41eaec2b..7cf222c8a0 100644
--- a/docs/topics/channels.md
+++ b/docs/topics/channels.md
@@ -573,6 +573,7 @@ Now let's see how it works in practice:
import kotlinx.coroutines.*
import kotlinx.coroutines.channels.*
+//sampleStart
fun main() = runBlocking {
val tickerChannel = ticker(delayMillis = 100, initialDelayMillis = 0) // create ticker channel
var nextElement = withTimeoutOrNull(1) { tickerChannel.receive() }
@@ -596,7 +597,9 @@ fun main() = runBlocking {
tickerChannel.cancel() // indicate that no more elements are needed
}
+//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).
>
diff --git a/docs/topics/coroutine-context-and-dispatchers.md b/docs/topics/coroutine-context-and-dispatchers.md
index 9648214848..6c06b14d07 100644
--- a/docs/topics/coroutine-context-and-dispatchers.md
+++ b/docs/topics/coroutine-context-and-dispatchers.md
@@ -148,7 +148,7 @@ The Coroutine Debugger of the Kotlin plugin simplifies debugging coroutines in I
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.
-
+{width=700}
With the coroutine debugger, you can:
* Check the state of each coroutine.
@@ -306,7 +306,7 @@ However, this parent-child relation can be explicitly overriden in one of two wa
1. When a different scope is explicitly specified when launching a coroutine (for example, `GlobalScope.launch`),
then it does not inherit a `Job` from the parent scope.
-2. When a different `Job` object is passed as the context for the new coroutine (as show in the example below),
+2. When a different `Job` object is passed as the context for the new coroutine (as shown in the example below),
then it overrides the `Job` of the parent scope.
In both cases, the launched coroutine is not tied to the scope it was launched from and operates independently.
@@ -334,8 +334,8 @@ fun main() = runBlocking {
}
delay(500)
request.cancel() // cancel processing of the request
- delay(1000) // delay a second to see what happens
println("main: Who has survived request cancellation?")
+ delay(1000) // delay the main thread for a second to see what happens
//sampleEnd
}
```
@@ -350,8 +350,8 @@ The output of this code is:
```text
job1: I run in my own Job and execute independently!
job2: I am a child of the request coroutine
-job1: I am not affected by cancellation of the request
main: Who has survived request cancellation?
+job1: I am not affected by cancellation of the request
```
diff --git a/docs/topics/coroutines-basics.md b/docs/topics/coroutines-basics.md
index 5d9d0e6db1..68ae97886f 100644
--- a/docs/topics/coroutines-basics.md
+++ b/docs/topics/coroutines-basics.md
@@ -75,7 +75,7 @@ Coroutines follow a principle of
which delimits the lifetime of the coroutine. The above example shows that [runBlocking] establishes the corresponding
scope and that is why the previous example waits until `World!` is printed after a second's delay and only then exits.
-In the real application, you will be launching a lot of coroutines. Structured concurrency ensures that they are not
+In a real application, you will be launching a lot of coroutines. Structured concurrency ensures that they are not
lost and do not leak. An outer scope cannot complete until all its children coroutines complete.
Structured concurrency also ensures that any errors in the code are properly reported and are never lost.
@@ -245,14 +245,17 @@ Done
-## Coroutines ARE light-weight
+## Coroutines are light-weight
-Run the following code:
+Coroutines are less resource-intensive than JVM threads. Code that exhausts the
+JVM's available memory when using threads can be expressed using coroutines
+without hitting resource limits. For example, the following code launches
+100000 distinct coroutines that each wait 5 seconds and then print a period
+('.') while consuming very little memory:
```kotlin
import kotlinx.coroutines.*
-//sampleStart
fun main() = runBlocking {
repeat(100_000) { // launch a lot of coroutines
launch {
@@ -261,8 +264,9 @@ fun main() = runBlocking {
}
}
}
-//sampleEnd
```
+
> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-basic-06.kt).
>
@@ -270,10 +274,9 @@ fun main() = runBlocking {
-It launches 100K coroutines and, after 5 seconds, each coroutine prints a dot.
-
-Now, try that with threads (remove `runBlocking`, replace `launch` with `thread`, and replace `delay` with `Thread.sleep`).
-What would happen? (Most likely your code will produce some sort of out-of-memory error)
+If you write the same program using threads (remove `runBlocking`, replace
+`launch` with `thread`, and replace `delay` with `Thread.sleep`), it will
+likely consume too much memory and throw an out-of-memory error.
diff --git a/docs/topics/debug-coroutines-with-idea.md b/docs/topics/debug-coroutines-with-idea.md
index e59075e071..8bbbb98585 100644
--- a/docs/topics/debug-coroutines-with-idea.md
+++ b/docs/topics/debug-coroutines-with-idea.md
@@ -16,7 +16,7 @@ The tutorial assumes you have prior knowledge of the [coroutines](coroutines-gui
The `src` directory contains Kotlin source files and resources. The `main.kt` file contains sample code that will print `Hello World!`.
-2. Change code in the `main()` function:
+3. Change code in the `main()` function:
* Use the [`runBlocking()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) block to wrap a coroutine.
* Use the [`async()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html) function to create coroutines that compute deferred values `a` and `b`.
@@ -61,7 +61,7 @@ The tutorial assumes you have prior knowledge of the [coroutines](coroutines-gui

-3. Resume the debugger session by clicking **Resume program** in the **Debug** tool window:
+3. Resume the debugger session by clicking **Resume Program** in the **Debug** tool window:

@@ -70,7 +70,7 @@ The tutorial assumes you have prior knowledge of the [coroutines](coroutines-gui
* The second coroutine is calculating the `a` value – it has the **RUNNING** status.
* The third coroutine has the **CREATED** status and isn’t calculating the value of `b`.
-4. Resume the debugger session by clicking **Resume program** in the **Debug** tool window:
+4. Resume the debugger session by clicking **Resume Program** in the **Debug** tool window:

diff --git a/docs/topics/debug-flow-with-idea.md b/docs/topics/debug-flow-with-idea.md
index 745dcb1762..edc841d3b5 100644
--- a/docs/topics/debug-flow-with-idea.md
+++ b/docs/topics/debug-flow-with-idea.md
@@ -18,7 +18,7 @@ Create a Kotlin [flow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-corou
The `src` directory contains Kotlin source files and resources. The `main.kt` file contains sample code that will print `Hello World!`.
-2. Create the `simple()` function that returns a flow of three numbers:
+3. Create the `simple()` function that returns a flow of three numbers:
* Use the [`delay()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html) function to imitate CPU-consuming blocking code. It suspends the coroutine for 100 ms without blocking the thread.
* Produce the values in the `for` loop using the [`emit()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow-collector/emit.html) function.
@@ -36,7 +36,7 @@ Create a Kotlin [flow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-corou
}
```
-3. Change the code in the `main()` function:
+4. Change the code in the `main()` function:
* Use the [`runBlocking()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html) block to wrap a coroutine.
* Collect the emitted values using the [`collect()`](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/collect.html) function.
@@ -53,13 +53,13 @@ Create a Kotlin [flow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-corou
}
```
-4. Build the code by clicking **Build Project**.
+5. Build the code by clicking **Build Project**.

## Debug the coroutine
-1. Set a breakpoint at the at the line where the `emit()` function is called:
+1. Set a breakpoint at the line where the `emit()` function is called:

@@ -74,7 +74,7 @@ Create a Kotlin [flow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-corou

-3. Resume the debugger session by clicking **Resume program** in the **Debug** tool window. The program stops at the same breakpoint.
+3. Resume the debugger session by clicking **Resume Program** in the **Debug** tool window. The program stops at the same breakpoint.

@@ -101,7 +101,7 @@ Create a Kotlin [flow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-corou
}
```
-4. Build the code by clicking **Build Project**.
+3. Build the code by clicking **Build Project**.
## Debug a Kotlin flow with two coroutines
@@ -117,7 +117,7 @@ Create a Kotlin [flow](https://kotlin.github.io/kotlinx.coroutines/kotlinx-corou
The `buffer()` function buffers emitted values from the flow.
The emitter coroutine has the **RUNNING** status, and the collector coroutine has the **SUSPENDED** status.
-2. Resume the debugger session by clicking **Resume program** in the **Debug** tool window.
+3. Resume the debugger session by clicking **Resume Program** in the **Debug** tool window.

diff --git a/docs/topics/debugging.md b/docs/topics/debugging.md
index 5ff4d549e0..6faad018bd 100644
--- a/docs/topics/debugging.md
+++ b/docs/topics/debugging.md
@@ -7,7 +7,6 @@
* [Stacktrace recovery](#stacktrace-recovery)
* [Stacktrace recovery machinery](#stacktrace-recovery-machinery)
* [Debug agent](#debug-agent)
- * [Debug agent and Android](#debug-agent-and-android)
* [Android optimization](#android-optimization)
@@ -77,12 +76,6 @@ additionally enhancing stacktraces with information where coroutine was created.
The full tutorial of how to use debug agent can be found in the corresponding [readme](../../kotlinx-coroutines-debug/README.md).
-### Debug agent and Android
-
-Unfortunately, Android runtime does not support Instrument API necessary for `kotlinx-coroutines-debug` to function, triggering `java.lang.NoClassDefFoundError: Failed resolution of: Ljava/lang/management/ManagementFactory;`.
-
-Nevertheless, it will be possible to support debug agent on Android as soon as [GradleAspectJ-Android](https://github.com/Archinamon/android-gradle-aspectj) will support android-gradle 3.3
-
-There are couple of observations to make out of it.
+There are a couple of observations to make out of it.
First of all, `select` is _biased_ to the first clause. When several clauses are selectable at the same time,
the first one among them gets selected. Here, both channels are constantly producing strings, so `a` channel,
@@ -228,7 +228,7 @@ channel is already closed.
Select expression has [onSend][SendChannel.onSend] clause that can be used for a great good in combination
with a biased nature of selection.
-Let us write an example of producer of integers that sends its values to a `side` channel when
+Let us write an example of a producer of integers that sends its values to a `side` channel when
the consumers on its primary channel cannot keep up with it:
```kotlin
diff --git a/docs/topics/shared-mutable-state-and-concurrency.md b/docs/topics/shared-mutable-state-and-concurrency.md
index 40b0134a4b..1b84f136ce 100644
--- a/docs/topics/shared-mutable-state-and-concurrency.md
+++ b/docs/topics/shared-mutable-state-and-concurrency.md
@@ -9,7 +9,7 @@ but others are unique.
## The problem
-Let us launch a hundred coroutines all doing the same action thousand times.
+Let us launch a hundred coroutines all doing the same action a thousand times.
We'll also measure their completion time for further comparisons:
```kotlin
@@ -384,7 +384,7 @@ single reference to the actor can be carried around as its handle.
The first step of using an actor is to define a class of messages that an actor is going to process.
Kotlin's [sealed classes](https://kotlinlang.org/docs/reference/sealed-classes.html) are well suited for that purpose.
We define `CounterMsg` sealed class with `IncCounter` message to increment a counter and `GetCounter` message
-to get its value. The later needs to send a response. A [CompletableDeferred] communication
+to get its value. The latter needs to send a response. A [CompletableDeferred] communication
primitive, that represents a single value that will be known (communicated) in the future,
is used here for that purpose.
diff --git a/gradle.properties b/gradle.properties
index 26e5147c51..63fdf28c62 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,18 +3,18 @@
#
# Kotlin
-version=1.5.2-SNAPSHOT
+version=1.6.3-SNAPSHOT
group=org.jetbrains.kotlinx
-kotlin_version=1.5.30
+kotlin_version=1.6.21
# Dependencies
junit_version=4.12
junit5_version=5.7.0
-atomicfu_version=0.16.3
-knit_version=0.3.0
+atomicfu_version=0.17.3
+knit_version=0.4.0
html_version=0.7.2
lincheck_version=2.14
-dokka_version=1.5.0
+dokka_version=1.6.21
byte_buddy_version=1.10.9
reactor_version=3.4.1
reactive_streams_version=1.0.3
@@ -22,20 +22,21 @@ rxjava2_version=2.2.8
rxjava3_version=3.0.2
javafx_version=11.0.2
javafx_plugin_version=0.0.8
-binary_compatibility_validator_version=0.7.0
+binary_compatibility_validator_version=0.10.0
+kover_version=0.5.0
blockhound_version=1.0.2.RELEASE
-jna_version=5.5.0
+jna_version=5.9.0
# Android versions
android_version=4.1.1.4
androidx_annotation_version=1.1.0
-robolectric_version=4.0.2
+robolectric_version=4.4
baksmali_version=2.2.7
# JS
kotlin.js.compiler=both
-gradle_node_version=1.2.0
-node_version=8.9.3
+gradle_node_version=3.1.1
+node_version=10.0.0
npm_version=5.7.1
mocha_version=6.2.2
mocha_headless_chrome_version=1.8.2
@@ -53,7 +54,8 @@ jekyll_version=4.0
# JS IR backend sometimes crashes with out-of-memory
# TODO: Remove once KT-37187 is fixed
-org.gradle.jvmargs=-Xmx4g
+org.gradle.jvmargs=-Xmx3g
kotlin.mpp.enableCompatibilityMetadataVariant=true
kotlin.mpp.stability.nowarn=true
+kotlinx.atomicfu.enableIrTransformation=true
diff --git a/gradle/compile-js-multiplatform.gradle b/gradle/compile-js-multiplatform.gradle
index d6df7e403a..c6fc757c7d 100644
--- a/gradle/compile-js-multiplatform.gradle
+++ b/gradle/compile-js-multiplatform.gradle
@@ -60,7 +60,7 @@ compileTestJsLegacy.configure {
task populateNodeModules(type: Copy, dependsOn: compileTestJsLegacy) {
// we must copy output that is transformed by atomicfu
from(kotlin.js().compilations.main.output.allOutputs)
- into "$node.nodeModulesDir/node_modules"
+ into node.nodeProjectDir.dir("node_modules")
def configuration = configurations.hasProperty("jsLegacyTestRuntimeClasspath")
? configurations.jsLegacyTestRuntimeClasspath
diff --git a/gradle/compile-jvm-multiplatform.gradle b/gradle/compile-jvm-multiplatform.gradle
index 5e65042746..88b717976d 100644
--- a/gradle/compile-jvm-multiplatform.gradle
+++ b/gradle/compile-jvm-multiplatform.gradle
@@ -2,12 +2,16 @@
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-sourceCompatibility = 1.6
-targetCompatibility = 1.6
+sourceCompatibility = 1.8
+targetCompatibility = 1.8
kotlin {
jvm {}
sourceSets {
+ jvmMain.dependencies {
+ compileOnly "org.codehaus.mojo:animal-sniffer-annotations:1.20"
+ }
+
jvmTest.dependencies {
api "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
// Workaround to make addSuppressed work in tests
diff --git a/gradle/dokka.gradle.kts b/gradle/dokka.gradle.kts
index 659890a30b..2470ded3ea 100644
--- a/gradle/dokka.gradle.kts
+++ b/gradle/dokka.gradle.kts
@@ -37,12 +37,8 @@ tasks.withType(DokkaTaskPartial::class).configureEach {
packageListUrl.set(rootProject.projectDir.toPath().resolve("site/stdlib.package.list").toUri().toURL())
}
- if (project.name != "kotlinx-coroutines-core") {
+ if (!project.isMultiplatform) {
dependsOn(project.configurations["compileClasspath"])
- doFirst {
- // resolve classpath only during execution
- classpath.from(project.configurations["compileClasspath"].files)// + project.sourceSets.main.output.files)
- }
}
}
}
@@ -66,10 +62,6 @@ if (project.name == "kotlinx-coroutines-core") {
val jvmMain by getting {
makeLinkMapping(project.file("jvm"))
}
-
- configureEach {
- classpath.from(project.configurations["jvmCompileClasspath"].files)
- }
}
}
}
diff --git a/gradle/node-js.gradle b/gradle/node-js.gradle
index 42f101c5f4..5eddc5fa37 100644
--- a/gradle/node-js.gradle
+++ b/gradle/node-js.gradle
@@ -2,13 +2,13 @@
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-apply plugin: 'com.moowork.node'
+apply plugin: 'com.github.node-gradle.node'
node {
version = "$node_version"
npmVersion = "$npm_version"
download = true
- nodeModulesDir = file(buildDir)
+ nodeProjectDir = file(buildDir)
}
// Configures testing for JS modules
@@ -25,7 +25,7 @@ task prepareNodePackage(type: Copy) {
from("npm") {
exclude 'package.json'
}
- into "$node.nodeModulesDir"
+ into node.nodeProjectDir
}
npmInstall.dependsOn prepareNodePackage
diff --git a/gradle/opt-in.gradle b/gradle/opt-in.gradle
deleted file mode 100644
index 22f022dbb5..0000000000
--- a/gradle/opt-in.gradle
+++ /dev/null
@@ -1,13 +0,0 @@
-/*
- * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-ext.optInAnnotations = [
- "kotlin.RequiresOptIn",
- "kotlin.experimental.ExperimentalTypeInference",
- "kotlin.ExperimentalMultiplatform",
- "kotlinx.coroutines.DelicateCoroutinesApi",
- "kotlinx.coroutines.ExperimentalCoroutinesApi",
- "kotlinx.coroutines.ObsoleteCoroutinesApi",
- "kotlinx.coroutines.InternalCoroutinesApi",
- "kotlinx.coroutines.FlowPreview"]
diff --git a/gradle/publish-npm-js.gradle b/gradle/publish-npm-js.gradle
index 382c6749ca..9d4152770c 100644
--- a/gradle/publish-npm-js.gradle
+++ b/gradle/publish-npm-js.gradle
@@ -40,17 +40,15 @@ task preparePublishNpm(type: Copy) {
task publishNpm(type: NpmTask, dependsOn: [preparePublishNpm]) {
workingDir = npmDeployDir
-
- doFirst {
- def npmDeployTag = distTag(version)
- def deployArgs = ['publish',
- "--//registry.npmjs.org/:_authToken=$authToken",
- "--tag=$npmDeployTag"]
- if (dryRun == "true") {
- println("$npmDeployDir \$ npm arguments: $deployArgs")
- args = ['pack']
- } else {
- args = deployArgs
- }
+
+ def npmDeployTag = distTag(version)
+ def deployArgs = ['publish',
+ "--//registry.npmjs.org/:_authToken=$authToken",
+ "--tag=$npmDeployTag"]
+ if (dryRun == "true") {
+ println("$npmDeployDir \$ npm arguments: $deployArgs")
+ args = ['pack']
+ } else {
+ args = deployArgs
}
}
diff --git a/gradle/publish.gradle b/gradle/publish.gradle
index 3a0a4224ab..00034bfa87 100644
--- a/gradle/publish.gradle
+++ b/gradle/publish.gradle
@@ -6,17 +6,18 @@ import org.gradle.util.VersionNumber
// Configures publishing of Maven artifacts to Maven Central
-apply plugin: 'maven'
apply plugin: 'maven-publish'
apply plugin: 'signing'
// ------------- tasks
-def isMultiplatform = project.name == "kotlinx-coroutines-core"
+def isMultiplatform = project.name == "kotlinx-coroutines-core" || project.name == "kotlinx-coroutines-test"
def isBom = project.name == "kotlinx-coroutines-bom"
if (!isBom) {
- apply plugin: "com.github.johnrengelman.shadow"
+ if (project.name == "kotlinx-coroutines-debug") {
+ apply plugin: "com.github.johnrengelman.shadow"
+ }
// empty xxx-javadoc.jar
task javadocJar(type: Jar) {
diff --git a/gradle/test-mocha-js.gradle b/gradle/test-mocha-js.gradle
index d011eeaa20..27d2e5b394 100644
--- a/gradle/test-mocha-js.gradle
+++ b/gradle/test-mocha-js.gradle
@@ -9,7 +9,7 @@ task installDependenciesMochaNode(type: NpmTask, dependsOn: [npmInstall]) {
"mocha@$mocha_version",
"source-map-support@$source_map_support_version",
'--no-save']
- if (project.hasProperty("teamcity")) args += ["mocha-teamcity-reporter@$mocha_teamcity_reporter_version"]
+ if (project.hasProperty("teamcity")) args.addAll(["mocha-teamcity-reporter@$mocha_teamcity_reporter_version"])
}
def compileJsLegacy = tasks.hasProperty("compileKotlinJsLegacy")
@@ -22,9 +22,9 @@ def compileTestJsLegacy = tasks.hasProperty("compileTestKotlinJsLegacy")
// todo: use atomicfu-transformed test files here (not critical)
task testMochaNode(type: NodeTask, dependsOn: [compileTestJsLegacy, installDependenciesMochaNode]) {
- script = file("$node.nodeModulesDir/node_modules/mocha/bin/mocha")
- args = [compileTestJsLegacy.outputFile, '--require', 'source-map-support/register']
- if (project.hasProperty("teamcity")) args += ['--reporter', 'mocha-teamcity-reporter']
+ script = file("${node.nodeProjectDir.getAsFile().get()}/node_modules/mocha/bin/mocha")
+ args = [compileTestJsLegacy.outputFile.path, '--require', 'source-map-support/register']
+ if (project.hasProperty("teamcity")) args.addAll(['--reporter', 'mocha-teamcity-reporter'])
}
def jsLegacyTestTask = project.tasks.findByName('jsLegacyTest') ? jsLegacyTest : jsTest
@@ -40,8 +40,8 @@ task installDependenciesMochaChrome(type: NpmTask, dependsOn: [npmInstall]) {
"kotlin@$kotlin_version",
"kotlin-test@$kotlin_version",
'--no-save']
- if (project.hasProperty("teamcity")) args += [
- "mocha-teamcity-reporter@$mocha_teamcity_reporter_version"]
+ if (project.hasProperty("teamcity")) args.addAll([
+ "mocha-teamcity-reporter@$mocha_teamcity_reporter_version"])
}
def mochaChromeTestPage = file("$buildDir/test-page.html")
@@ -51,19 +51,20 @@ task prepareMochaChrome(dependsOn: [compileTestJsLegacy, installDependenciesMoch
}
prepareMochaChrome.doLast {
+ def nodeProjDir = node.nodeProjectDir.getAsFile().get()
mochaChromeTestPage.text = """
Mocha Tests
-
+
-
+
-
-
+
+
@@ -73,9 +74,9 @@ prepareMochaChrome.doLast {
}
task testMochaChrome(type: NodeTask, dependsOn: prepareMochaChrome) {
- script = file("$node.nodeModulesDir/node_modules/mocha-headless-chrome/bin/start")
- args = [compileTestJsLegacy.outputFile, '--file', mochaChromeTestPage]
- if (project.hasProperty("teamcity")) args += ['--reporter', 'mocha-teamcity-reporter']
+ script = file("${node.nodeProjectDir.getAsFile().get()}/node_modules/mocha-headless-chrome/bin/start")
+ args = [compileTestJsLegacy.outputFile.path, '--file', mochaChromeTestPage]
+ if (project.hasProperty("teamcity")) args.addAll(['--reporter', 'mocha-teamcity-reporter'])
}
// todo: Commented out because mocha-headless-chrome does not work on TeamCity
@@ -90,13 +91,13 @@ task installDependenciesMochaJsdom(type: NpmTask, dependsOn: [npmInstall]) {
"jsdom-global@$jsdom_global_version",
"source-map-support@$source_map_support_version",
'--no-save']
- if (project.hasProperty("teamcity")) args += ["mocha-teamcity-reporter@$mocha_teamcity_reporter_version"]
+ if (project.hasProperty("teamcity")) args.addAll(["mocha-teamcity-reporter@$mocha_teamcity_reporter_version"])
}
task testMochaJsdom(type: NodeTask, dependsOn: [compileTestJsLegacy, installDependenciesMochaJsdom]) {
- script = file("$node.nodeModulesDir/node_modules/mocha/bin/mocha")
- args = [compileTestJsLegacy.outputFile, '--require', 'source-map-support/register', '--require', 'jsdom-global/register']
- if (project.hasProperty("teamcity")) args += ['--reporter', 'mocha-teamcity-reporter']
+ script = file("${node.nodeProjectDir.getAsFile().get()}/node_modules/mocha/bin/mocha")
+ args = [compileTestJsLegacy.outputFile.path, '--require', 'source-map-support/register', '--require', 'jsdom-global/register']
+ if (project.hasProperty("teamcity")) args.addAll(['--reporter', 'mocha-teamcity-reporter'])
}
jsLegacyTestTask.dependsOn testMochaJsdom
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index d7ae858e5a..f57489cf8a 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -4,6 +4,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
diff --git a/integration-testing/README.md b/integration-testing/README.md
index 4754081a45..0ede9b254e 100644
--- a/integration-testing/README.md
+++ b/integration-testing/README.md
@@ -1,14 +1,13 @@
# Integration tests
-This is a supplementary subproject of kotlinx.coroutines that provides
-integration tests.
+This is a supplementary project that provides integration tests.
The tests are the following:
-* `NpmPublicationValidator` tests that version of NPM artifact is correct and that it has neither source nor package dependencies on atomicfu
- In order for the test to work, one needs to run gradle with `-PdryRun=true`.
- `-PdryRun` affects `npmPublish` so that it only provides a packed publication
- and does not in fact attempt to send the build for publication.
-* `MavenPublicationValidator` depends on the published artifacts and tests artifacts binary content and absence of atomicfu in the classpath
+* `MavenPublicationValidator` depends on the published artifacts and tests artifacts binary content and absence of atomicfu in the classpath.
+* `CoreAgentTest` checks that `kotlinx-coroutines-core` can be run as a Java agent.
* `DebugAgentTest` checks that the coroutine debugger can be run as a Java agent.
+* `smokeTest` builds the test project that depends on coroutines.
-All the available tests can be run with `integration-testing:test`.
+The `integration-testing` project is expected to be in a subdirectory of the main `kotlinx.coroutines` project.
+
+To run all the available tests: `cd integration-testing` + `./gradlew check`.
diff --git a/integration-testing/build.gradle b/integration-testing/build.gradle
index 6efa3a14e6..60b6cdcd07 100644
--- a/integration-testing/build.gradle
+++ b/integration-testing/build.gradle
@@ -5,7 +5,7 @@
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
plugins {
- id("kotlin-jvm-conventions")
+ id "org.jetbrains.kotlin.jvm"
}
repositories {
@@ -13,27 +13,44 @@ repositories {
mavenCentral()
}
+java {
+ sourceCompatibility = JavaVersion.VERSION_1_8
+ targetCompatibility = JavaVersion.VERSION_1_8
+}
+
+dependencies {
+ testImplementation "org.jetbrains.kotlin:kotlin-test:$kotlin_version"
+}
+
sourceSets {
- npmTest {
- kotlin
- compileClasspath += sourceSets.test.runtimeClasspath
- runtimeClasspath += sourceSets.test.runtimeClasspath
- }
mavenTest {
kotlin
compileClasspath += sourceSets.test.runtimeClasspath
runtimeClasspath += sourceSets.test.runtimeClasspath
+
+ dependencies {
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
+ }
}
debugAgentTest {
kotlin
compileClasspath += sourceSets.test.runtimeClasspath
runtimeClasspath += sourceSets.test.runtimeClasspath
- }
+ dependencies {
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-debug:$coroutines_version"
+ }
+ }
coreAgentTest {
kotlin
compileClasspath += sourceSets.test.runtimeClasspath
runtimeClasspath += sourceSets.test.runtimeClasspath
+
+ dependencies {
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
+ }
}
}
@@ -43,68 +60,34 @@ compileDebugAgentTestKotlin {
}
}
-task npmTest(type: Test) {
- def sourceSet = sourceSets.npmTest
- environment "projectRoot", project.rootDir
- environment "deployVersion", version
- def dryRunNpm = project.properties['dryRun']
- def doRun = dryRunNpm == "true" // so that we don't accidentally publish anything, especially before the test
- onlyIf { doRun }
- if (doRun) { // `onlyIf` only affects execution of the task, not the dependency subtree
- dependsOn(project(':').getTasksByName("publishNpm", true))
- }
- testClassesDirs = sourceSet.output.classesDirs
- classpath = sourceSet.runtimeClasspath
-}
-
task mavenTest(type: Test) {
+ environment "version", coroutines_version
def sourceSet = sourceSets.mavenTest
- dependsOn(project(':').getTasksByName("publishToMavenLocal", true))
testClassesDirs = sourceSet.output.classesDirs
classpath = sourceSet.runtimeClasspath
- // we can't depend on the subprojects because we need to test the classfiles that are published in the end.
- // also, we can't put this in the `dependencies` block because the resolution would happen before publication.
- def mavenTestClasspathConfiguration = project.configurations.detachedConfiguration(
- project.dependencies.create("org.jetbrains.kotlinx:kotlinx-coroutines-core:$version"),
- project.dependencies.create("org.jetbrains.kotlinx:kotlinx-coroutines-android:$version"))
-
- mavenTestClasspathConfiguration.attributes {
- attribute(KotlinPlatformType.attribute, KotlinPlatformType.jvm)
- }
-
- classpath += mavenTestClasspathConfiguration
}
task debugAgentTest(type: Test) {
def sourceSet = sourceSets.debugAgentTest
- dependsOn(project(':kotlinx-coroutines-debug').shadowJar)
- jvmArgs ('-javaagent:' + project(':kotlinx-coroutines-debug').shadowJar.outputs.files.getFiles()[0])
+ def coroutinesDebugJar = sourceSet.runtimeClasspath.filter {it.name == "kotlinx-coroutines-debug-${coroutines_version}.jar" }.singleFile
+ jvmArgs ('-javaagent:' + coroutinesDebugJar)
testClassesDirs = sourceSet.output.classesDirs
classpath = sourceSet.runtimeClasspath
+ systemProperties project.properties.subMap(["overwrite.probes"])
}
task coreAgentTest(type: Test) {
def sourceSet = sourceSets.coreAgentTest
- dependsOn(project(':kotlinx-coroutines-core').jvmJar)
- jvmArgs ('-javaagent:' + project(':kotlinx-coroutines-core').jvmJar.outputs.files.getFiles()[0])
+ def coroutinesCoreJar = sourceSet.runtimeClasspath.filter {it.name == "kotlinx-coroutines-core-jvm-${coroutines_version}.jar" }.singleFile
+ jvmArgs ('-javaagent:' + coroutinesCoreJar)
testClassesDirs = sourceSet.output.classesDirs
classpath = sourceSet.runtimeClasspath
}
-dependencies {
- testImplementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
- testImplementation 'junit:junit:4.12'
- npmTestImplementation 'org.apache.commons:commons-compress:1.18'
- npmTestImplementation 'com.google.code.gson:gson:2.8.5'
- debugAgentTestCompile project(':kotlinx-coroutines-core')
- debugAgentTestCompile project(':kotlinx-coroutines-debug')
- coreAgentTestCompile project(':kotlinx-coroutines-core')
-}
-
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
check {
- dependsOn([npmTest, mavenTest, debugAgentTest, coreAgentTest])
+ dependsOn([mavenTest, debugAgentTest, coreAgentTest, 'smokeTest:build'])
}
diff --git a/integration-testing/gradle.properties b/integration-testing/gradle.properties
new file mode 100644
index 0000000000..1f334cafea
--- /dev/null
+++ b/integration-testing/gradle.properties
@@ -0,0 +1,4 @@
+kotlin_version=1.6.21
+coroutines_version=1.6.3-SNAPSHOT
+
+kotlin.code.style=official
diff --git a/integration-testing/gradle/wrapper/gradle-wrapper.jar b/integration-testing/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000000..7454180f2a
Binary files /dev/null and b/integration-testing/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/integration-testing/gradle/wrapper/gradle-wrapper.properties b/integration-testing/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000000..92f06b50fd
--- /dev/null
+++ b/integration-testing/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/integration-testing/gradlew b/integration-testing/gradlew
new file mode 100755
index 0000000000..1b6c787337
--- /dev/null
+++ b/integration-testing/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright © 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
+# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
+# * compound commands having a testable exit status, especially «case»;
+# * various built-in commands including «command», «set», and «ulimit».
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/integration-testing/gradlew.bat b/integration-testing/gradlew.bat
new file mode 100644
index 0000000000..107acd32c4
--- /dev/null
+++ b/integration-testing/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/integration-testing/settings.gradle b/integration-testing/settings.gradle
new file mode 100644
index 0000000000..67336c9880
--- /dev/null
+++ b/integration-testing/settings.gradle
@@ -0,0 +1,19 @@
+pluginManagement {
+ resolutionStrategy {
+ eachPlugin {
+ if (requested.id.id == "org.jetbrains.kotlin.multiplatform" || requested.id.id == "org.jetbrains.kotlin.jvm") {
+ useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version")
+ }
+ }
+ }
+
+ repositories {
+ mavenCentral()
+ maven { url "https://plugins.gradle.org/m2/" }
+ mavenLocal()
+ }
+}
+
+include 'smokeTest'
+
+rootProject.name = "kotlinx-coroutines-integration-testing"
diff --git a/integration-testing/smokeTest/build.gradle b/integration-testing/smokeTest/build.gradle
new file mode 100644
index 0000000000..b200bb2fe8
--- /dev/null
+++ b/integration-testing/smokeTest/build.gradle
@@ -0,0 +1,43 @@
+plugins {
+ id 'org.jetbrains.kotlin.multiplatform'
+}
+
+repositories {
+ // Coroutines from the outer project are published by previous CI buils step
+ mavenLocal()
+ mavenCentral()
+}
+
+kotlin {
+ jvm()
+ js(IR) {
+ nodejs()
+ }
+
+ sourceSets {
+ commonMain {
+ dependencies {
+ implementation kotlin('stdlib-common')
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
+ }
+ }
+ commonTest {
+ dependencies {
+ implementation kotlin('test-common')
+ implementation kotlin('test-annotations-common')
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
+ }
+ }
+ jsTest {
+ dependencies {
+ implementation kotlin('test-js')
+ }
+ }
+ jvmTest {
+ dependencies {
+ implementation kotlin('test')
+ implementation kotlin('test-junit')
+ }
+ }
+ }
+}
diff --git a/integration-testing/smokeTest/src/commonMain/kotlin/Sample.kt b/integration-testing/smokeTest/src/commonMain/kotlin/Sample.kt
new file mode 100644
index 0000000000..c5da677bb1
--- /dev/null
+++ b/integration-testing/smokeTest/src/commonMain/kotlin/Sample.kt
@@ -0,0 +1,9 @@
+import kotlinx.coroutines.*
+
+suspend fun doWorld() = coroutineScope {
+ launch {
+ delay(1000L)
+ println("World!")
+ }
+ println("Hello")
+}
diff --git a/integration-testing/smokeTest/src/commonTest/kotlin/SampleTest.kt b/integration-testing/smokeTest/src/commonTest/kotlin/SampleTest.kt
new file mode 100644
index 0000000000..a8c6598e88
--- /dev/null
+++ b/integration-testing/smokeTest/src/commonTest/kotlin/SampleTest.kt
@@ -0,0 +1,13 @@
+/*
+ * Copyright 2016-2022 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+import kotlinx.coroutines.test.*
+import kotlin.test.*
+
+class SampleTest {
+ @Test
+ fun test() = runTest {
+ doWorld()
+ }
+}
diff --git a/integration-testing/src/debugAgentTest/kotlin/PrecompiledDebugProbesTest.kt b/integration-testing/src/debugAgentTest/kotlin/PrecompiledDebugProbesTest.kt
index ce82e577ca..84886a18ab 100644
--- a/integration-testing/src/debugAgentTest/kotlin/PrecompiledDebugProbesTest.kt
+++ b/integration-testing/src/debugAgentTest/kotlin/PrecompiledDebugProbesTest.kt
@@ -20,20 +20,19 @@ class PrecompiledDebugProbesTest {
val classFileResourcePath = className.replace(".", "/") + ".class"
val stream = clz.classLoader.getResourceAsStream(classFileResourcePath)!!
val array = stream.readBytes()
- val binFile = clz.classLoader.getResourceAsStream("DebugProbesKt.bin")!!
- val binContent = binFile.readBytes()
+ // we expect the integration testing project to be in a subdirectory of the main kotlinx.coroutines project
+ val base = File("").absoluteFile.parentFile
+ val probes = File(base, "kotlinx-coroutines-core/jvm/resources/DebugProbesKt.bin")
+ val binContent = probes.readBytes()
if (overwrite) {
- val url = clz.classLoader.getResource("DebugProbesKt.bin")!!
- val base = url.toExternalForm().toString().removePrefix("jar:file:").substringBefore("/build")
- val probes = File(base, "jvm/resources/DebugProbesKt.bin")
FileOutputStream(probes).use { it.write(array) }
println("Content was successfully overwritten!")
} else {
assertTrue(
array.contentEquals(binContent),
"Compiled DebugProbesKt.class does not match the file shipped as a resource in kotlinx-coroutines-core. " +
- "Typically it happens because of the Kotlin version update (-> binary metadata). In that case, run the same test with -Poverwrite.probes=true and " +
- "ensure that classfile has major version equal to 50 (Java 6 compliance)")
+ "Typically it happens because of the Kotlin version update (-> binary metadata). In that case, run the same test with -Poverwrite.probes=true."
+ )
}
}
}
diff --git a/integration-testing/src/mavenTest/kotlin/MavenPublicationValidator.kt b/integration-testing/src/mavenTest/kotlin/MavenPublicationAtomicfuValidator.kt
similarity index 97%
rename from integration-testing/src/mavenTest/kotlin/MavenPublicationValidator.kt
rename to integration-testing/src/mavenTest/kotlin/MavenPublicationAtomicfuValidator.kt
index 39d6598b55..dbb1921d80 100644
--- a/integration-testing/src/mavenTest/kotlin/MavenPublicationValidator.kt
+++ b/integration-testing/src/mavenTest/kotlin/MavenPublicationAtomicfuValidator.kt
@@ -8,7 +8,7 @@ import org.junit.*
import org.junit.Assert.assertTrue
import java.util.jar.*
-class MavenPublicationValidator {
+class MavenPublicationAtomicfuValidator {
private val ATOMIC_FU_REF = "Lkotlinx/atomicfu/".toByteArray()
@Test
diff --git a/integration-testing/src/mavenTest/kotlin/MavenPublicationVersionValidator.kt b/integration-testing/src/mavenTest/kotlin/MavenPublicationVersionValidator.kt
new file mode 100644
index 0000000000..da87d4cc59
--- /dev/null
+++ b/integration-testing/src/mavenTest/kotlin/MavenPublicationVersionValidator.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines.validator
+
+import org.junit.*
+import org.junit.Test
+import java.util.jar.*
+import kotlin.test.*
+
+class MavenPublicationVersionValidator {
+
+ @Test
+ fun testMppJar() {
+ val clazz = Class.forName("kotlinx.coroutines.Job")
+ JarFile(clazz.protectionDomain.codeSource.location.file).checkForVersion("kotlinx_coroutines_core.version")
+ }
+
+ @Test
+ fun testAndroidJar() {
+ val clazz = Class.forName("kotlinx.coroutines.android.HandlerDispatcher")
+ JarFile(clazz.protectionDomain.codeSource.location.file).checkForVersion("kotlinx_coroutines_android.version")
+ }
+
+ private fun JarFile.checkForVersion(file: String) {
+ val actualFile = "META-INF/$file"
+ val version = System.getenv("version")
+ use {
+ for (e in entries()) {
+ if (e.name == actualFile) {
+ val string = getInputStream(e).readAllBytes().decodeToString()
+ assertEquals(version, string)
+ return
+ }
+ }
+ error("File $file not found")
+ }
+ }
+}
diff --git a/integration-testing/src/npmTest/kotlin/NpmPublicationValidator.kt b/integration-testing/src/npmTest/kotlin/NpmPublicationValidator.kt
deleted file mode 100644
index 8e1b9f99bf..0000000000
--- a/integration-testing/src/npmTest/kotlin/NpmPublicationValidator.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-/*
- * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
- */
-
-package kotlinx.coroutines.validator
-
-import com.google.gson.*
-import org.apache.commons.compress.archivers.tar.*
-import org.junit.*
-import java.io.*
-import java.util.zip.*
-import org.junit.Assert.*
-
-class NpmPublicationValidator {
- private val VERSION = System.getenv("deployVersion")!!
- private val BUILD_DIR = System.getenv("projectRoot")!!
- private val NPM_ARTIFACT = "$BUILD_DIR/kotlinx-coroutines-core/build/npm/kotlinx-coroutines-core-$VERSION.tgz"
-
- @Test
- fun testPackageJson() {
- println("Checking dependencies of $NPM_ARTIFACT")
- val visited = visit("package.json") {
- val json = JsonParser().parse(content()).asJsonObject
- assertEquals(VERSION, json["version"].asString)
- assertNull(json["dependencies"])
- val peerDependencies = json["peerDependencies"].asJsonObject
- assertEquals(1, peerDependencies.size())
- assertNotNull(peerDependencies["kotlin"])
- }
- assertEquals(1, visited)
- }
-
- @Test
- fun testAtomicfuDependencies() {
- println("Checking contents of $NPM_ARTIFACT")
- val visited = visit(".js") {
- val content = content()
- assertFalse(content, content.contains("atomicfu", true))
- assertFalse(content, content.contains("atomicint", true))
- assertFalse(content, content.contains("atomicboolean", true))
- }
- assertEquals(2, visited)
- }
-
- private fun InputStream.content(): String {
- val bais = ByteArrayOutputStream()
- val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
- var read = read(buffer, 0, DEFAULT_BUFFER_SIZE)
- while (read >= 0) {
- bais.write(buffer, 0, read)
- read = read(buffer, 0, DEFAULT_BUFFER_SIZE)
- }
- return bais.toString()
- }
-
- private inline fun visit(fileSuffix: String, block: InputStream.(entry: TarArchiveEntry) -> Unit): Int {
- var visited = 0
- TarArchiveInputStream(GZIPInputStream(FileInputStream(NPM_ARTIFACT))).use { tais ->
- var entry: TarArchiveEntry? = tais.nextTarEntry ?: return 0
- do {
- if (entry!!.name.endsWith(fileSuffix)) {
- ++visited
- tais.block(entry)
- }
- entry = tais.nextTarEntry
- } while (entry != null)
-
- return visited
- }
- }
-}
diff --git a/integration/kotlinx-coroutines-guava/README.md b/integration/kotlinx-coroutines-guava/README.md
index 34b8e5818f..b930a6194c 100644
--- a/integration/kotlinx-coroutines-guava/README.md
+++ b/integration/kotlinx-coroutines-guava/README.md
@@ -62,6 +62,6 @@ Integration with Guava [ListenableFuture](https://github.com/google/guava/wiki/L
-[com.google.common.util.concurrent.ListenableFuture]: https://kotlin.github.io/kotlinx.coroutines/https://google.github.io/guava/releases/28.0-jre/api/docs/com/google/common/util/concurrent/ListenableFuture.html
+[com.google.common.util.concurrent.ListenableFuture]: https://kotlin.github.io/kotlinx.coroutines/https://google.github.io/guava/releases/31.0.1-jre/api/docs/com/google/common/util/concurrent/ListenableFuture.html
diff --git a/integration/kotlinx-coroutines-guava/build.gradle.kts b/integration/kotlinx-coroutines-guava/build.gradle.kts
index 12a6ca70b7..2a84ca937e 100644
--- a/integration/kotlinx-coroutines-guava/build.gradle.kts
+++ b/integration/kotlinx-coroutines-guava/build.gradle.kts
@@ -2,10 +2,15 @@
* Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
-val guavaVersion = "28.0-jre"
+val guavaVersion = "31.0.1-jre"
dependencies {
- compile("com.google.guava:guava:$guavaVersion")
+ api("com.google.guava:guava:$guavaVersion")
+}
+
+java {
+ targetCompatibility = JavaVersion.VERSION_1_8
+ sourceCompatibility = JavaVersion.VERSION_1_8
}
externalDocumentationLink(
diff --git a/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt b/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt
index 8f11e0a916..0820f1f101 100644
--- a/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt
+++ b/integration/kotlinx-coroutines-guava/src/ListenableFuture.kt
@@ -14,7 +14,7 @@ import kotlin.coroutines.*
/**
* Starts [block] in a new coroutine and returns a [ListenableFuture] pointing to its result.
*
- * The coroutine is immediately started. Passing [CoroutineStart.LAZY] to [start] throws
+ * The coroutine is started immediately. Passing [CoroutineStart.LAZY] to [start] throws
* [IllegalArgumentException], because Futures don't have a way to start lazily.
*
* When the created coroutine [isCompleted][Job.isCompleted], it will try to
@@ -35,10 +35,12 @@ import kotlin.coroutines.*
* See [newCoroutineContext][CoroutineScope.newCoroutineContext] for a description of debugging
* facilities.
*
- * Note that the error and cancellation semantics of [future] are _subtly different_ than [asListenableFuture]'s.
- * In particular, any exception that happens in the coroutine after returned future is
- * successfully cancelled will be passed to the [CoroutineExceptionHandler] from the [context].
- * See [ListenableFutureCoroutine] for details.
+ * Note that the error and cancellation semantics of [future] are _different_ than [async]'s.
+ * In contrast to [Deferred], [Future] doesn't have an intermediate `Cancelling` state. If
+ * the returned `Future` is successfully cancelled, and `block` throws afterward, the thrown
+ * error is dropped, and getting the `Future`'s value will throw a `CancellationException` with
+ * no cause. This is to match the specification and behavior of
+ * `java.util.concurrent.FutureTask`.
*
* @param context added overlaying [CoroutineScope.coroutineContext] to form the new context.
* @param start coroutine start option. The default value is [CoroutineStart.DEFAULT].
@@ -133,10 +135,8 @@ public fun ListenableFuture.asDeferred(): Deferred {
// Finally, if this isn't done yet, attach a Listener that will complete the Deferred.
val deferred = CompletableDeferred()
Futures.addCallback(this, object : FutureCallback {
- override fun onSuccess(result: T?) {
- // Here we work with flexible types, so we unchecked cast to trick the type system
- @Suppress("UNCHECKED_CAST")
- runCatching { deferred.complete(result as T) }
+ override fun onSuccess(result: T) {
+ runCatching { deferred.complete(result) }
.onFailure { handleCoroutineException(EmptyCoroutineContext, it) }
}
@@ -241,8 +241,8 @@ public suspend fun ListenableFuture.await(): T {
return suspendCancellableCoroutine { cont: CancellableContinuation ->
addListener(
- ToContinuation(this, cont),
- MoreExecutors.directExecutor())
+ ToContinuation(this, cont),
+ MoreExecutors.directExecutor())
cont.invokeOnCancellation {
cancel(false)
}
@@ -284,16 +284,13 @@ private class ToContinuation(
* By documented contract, a [Future] has been cancelled if
* and only if its `isCancelled()` method returns true.
*
- * Any error that occurs after successfully cancelling a [ListenableFuture] will be passed
- * to the [CoroutineExceptionHandler] from the context. The contract of [Future] does not permit
- * it to return an error after it is successfully cancelled.
- *
- * By calling [asListenableFuture] on a [Deferred], any error that occurs after successfully
- * cancelling the [ListenableFuture] representation of the [Deferred] will _not_ be passed to
- * the [CoroutineExceptionHandler]. Cancelling a [Deferred] places that [Deferred] in the
- * cancelling/cancelled states defined by [Job], which _can_ show the error. It's assumed that
- * the [Deferred] pointing to the task will be used to observe any error outcome occurring after
- * cancellation.
+ * Any error that occurs after successfully cancelling a [ListenableFuture] is lost.
+ * The contract of [Future] does not permit it to return an error after it is successfully cancelled.
+ * On the other hand, we can't report an unhandled exception to [CoroutineExceptionHandler],
+ * otherwise [Future.cancel] can lead to an app crash which arguably is a contract violation.
+ * In contrast to [Future] which can't change its outcome after a successful cancellation,
+ * cancelling a [Deferred] places that [Deferred] in the cancelling/cancelled states defined by [Job],
+ * which _can_ show the error.
*
* This may be counterintuitive, but it maintains the error and cancellation contracts of both
* the [Deferred] and [ListenableFuture] types, while permitting both kinds of promise to point
@@ -312,10 +309,14 @@ private class ListenableFutureCoroutine(
}
override fun onCancelled(cause: Throwable, handled: Boolean) {
- if (!future.completeExceptionallyOrCancel(cause) && !handled) {
- // prevents loss of exception that was not handled by parent & could not be set to JobListenableFuture
- handleCoroutineException(context, cause)
- }
+ // Note: if future was cancelled in a race with a cancellation of this
+ // coroutine, and the future was successfully cancelled first, the cause of coroutine
+ // cancellation is dropped in this promise. A Future can only be completed once.
+ //
+ // This is consistent with FutureTask behaviour. A race between a Future.cancel() and
+ // a FutureTask.setException() for the same Future will similarly drop the
+ // cause of a failure-after-cancellation.
+ future.completeExceptionallyOrCancel(cause)
}
}
@@ -348,7 +349,7 @@ private class JobListenableFuture(private val jobToCancel: Job): ListenableFu
*
* To preserve Coroutine's [CancellationException], this future points to either `T` or [Cancelled].
*/
- private val auxFuture = SettableFuture.create()
+ private val auxFuture = SettableFuture.create()
/**
* `true` if [auxFuture.get][ListenableFuture.get] throws [ExecutionException].
@@ -433,7 +434,7 @@ private class JobListenableFuture(private val jobToCancel: Job): ListenableFu
}
/** See [get()]. */
- private fun getInternal(result: Any): T = if (result is Cancelled) {
+ private fun getInternal(result: Any?): T = if (result is Cancelled) {
throw CancellationException().initCause(result.exception)
} else {
// We know that `auxFuture` can contain either `T` or `Cancelled`.
diff --git a/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt b/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt
index 69ba193071..511b1b0322 100644
--- a/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt
+++ b/integration/kotlinx-coroutines-guava/test/ListenableFutureTest.kt
@@ -555,11 +555,7 @@ class ListenableFutureTest : TestBase() {
}
@Test
- fun testUnhandledExceptionOnExternalCancellation() = runTest(
- unhandled = listOf(
- { it -> it is TestException } // exception is unhandled because there is no parent
- )
- ) {
+ fun testUnhandledExceptionOnExternalCancellation() = runTest {
expect(1)
// No parent here (NonCancellable), so nowhere to propagate exception
val result = future(NonCancellable + Dispatchers.Unconfined) {
@@ -567,7 +563,7 @@ class ListenableFutureTest : TestBase() {
delay(Long.MAX_VALUE)
} finally {
expect(2)
- throw TestException() // this exception cannot be handled
+ throw TestException() // this exception cannot be handled and is set to be lost.
}
}
result.cancel(true)
@@ -708,23 +704,6 @@ class ListenableFutureTest : TestBase() {
assertEquals(testException, thrown.cause)
}
- @Test
- fun stressTestJobListenableFutureIsCancelledDoesNotThrow() = runTest {
- repeat(1000) {
- val deferred = CompletableDeferred()
- val asListenableFuture = deferred.asListenableFuture()
- // We heed two threads to test a race condition.
- withContext(Dispatchers.Default) {
- val cancellationJob = launch {
- asListenableFuture.cancel(false)
- }
- while (!cancellationJob.isCompleted) {
- asListenableFuture.isCancelled // Shouldn't throw.
- }
- }
- }
- }
-
private inline fun ListenableFuture<*>.checkFutureException() {
val e = assertFailsWith { get() }
val cause = e.cause!!
@@ -775,4 +754,61 @@ class ListenableFutureTest : TestBase() {
assertEquals(count, completed.get())
}
}
+
+ @Test
+ fun testFuturePropagatesExceptionToParentAfterCancellation() = runTest {
+ val throwLatch = CompletableDeferred()
+ val cancelLatch = CompletableDeferred()
+ val parent = Job()
+ val scope = CoroutineScope(parent)
+ val exception = TestException("propagated to parent")
+ val future = scope.future {
+ cancelLatch.complete(true)
+ withContext(NonCancellable) {
+ throwLatch.await()
+ throw exception
+ }
+ }
+ cancelLatch.await()
+ future.cancel(true)
+ throwLatch.complete(true)
+ parent.join()
+ assertTrue(parent.isCancelled)
+ assertEquals(exception, parent.getCancellationException().cause)
+ }
+
+ // Stress tests.
+
+ @Test
+ fun testFutureDoesNotReportToCoroutineExceptionHandler() = runTest {
+ repeat(1000) {
+ supervisorScope { // Don't propagate failures in children to parent and other children.
+ val innerFuture = SettableFuture.create()
+ val outerFuture = async { innerFuture.await() }
+
+ withContext(Dispatchers.Default) {
+ launch { innerFuture.setException(TestException("can be lost")) }
+ launch { outerFuture.cancel() }
+ // nothing should be reported to CoroutineExceptionHandler, otherwise `Future.cancel` contract violation.
+ }
+ }
+ }
+ }
+
+ @Test
+ fun testJobListenableFutureIsCancelledDoesNotThrow() = runTest {
+ repeat(1000) {
+ val deferred = CompletableDeferred()
+ val asListenableFuture = deferred.asListenableFuture()
+ // We heed two threads to test a race condition.
+ withContext(Dispatchers.Default) {
+ val cancellationJob = launch {
+ asListenableFuture.cancel(false)
+ }
+ while (!cancellationJob.isCompleted) {
+ asListenableFuture.isCancelled // Shouldn't throw.
+ }
+ }
+ }
+ }
}
diff --git a/integration/kotlinx-coroutines-jdk8/src/stream/Stream.kt b/integration/kotlinx-coroutines-jdk8/src/stream/Stream.kt
index 1d804e5950..b0d72de893 100644
--- a/integration/kotlinx-coroutines-jdk8/src/stream/Stream.kt
+++ b/integration/kotlinx-coroutines-jdk8/src/stream/Stream.kt
@@ -19,7 +19,6 @@ public fun Stream.consumeAsFlow(): Flow = StreamFlow(this)
private class StreamFlow(private val stream: Stream) : Flow {
private val consumed = atomic(false)
- @InternalCoroutinesApi
override suspend fun collect(collector: FlowCollector) {
if (!consumed.compareAndSet(false, true)) error("Stream.consumeAsFlow can be collected only once")
try {
diff --git a/integration/kotlinx-coroutines-play-services/README.md b/integration/kotlinx-coroutines-play-services/README.md
index e5e0e613b3..647dafd2c1 100644
--- a/integration/kotlinx-coroutines-play-services/README.md
+++ b/integration/kotlinx-coroutines-play-services/README.md
@@ -34,6 +34,12 @@ val currentLocationTask = fusedLocationProviderClient.getCurrentLocation(PRIORIT
val currentLocation = currentLocationTask.await(cancellationTokenSource) // cancelling `await` also cancels `currentLocationTask`, and vice versa
```
-[asDeferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/com.google.android.gms.tasks.-task/as-deferred.html
-[await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/com.google.android.gms.tasks.-task/await.html
-[asTask]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/kotlinx.coroutines.-deferred/as-task.html
+
+
+
+
+[asDeferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/as-deferred.html
+[await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/await.html
+[asTask]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-play-services/kotlinx.coroutines.tasks/as-task.html
+
+
diff --git a/integration/kotlinx-coroutines-play-services/build.gradle.kts b/integration/kotlinx-coroutines-play-services/build.gradle.kts
index 59f3b0bd5a..9f8a128703 100644
--- a/integration/kotlinx-coroutines-play-services/build.gradle.kts
+++ b/integration/kotlinx-coroutines-play-services/build.gradle.kts
@@ -4,36 +4,17 @@
val tasksVersion = "16.0.1"
-val artifactType = Attribute.of("artifactType", String::class.java)
-val unpackedAar = Attribute.of("unpackedAar", Boolean::class.javaObjectType)
-
-configurations.configureEach {
- afterEvaluate {
- if (isCanBeResolved) {
- attributes.attribute(unpackedAar, true) // request all AARs to be unpacked
- }
- }
-}
+project.configureAar()
dependencies {
- attributesSchema {
- attribute(unpackedAar)
- }
-
- artifactTypes {
- create("aar") {
- attributes.attribute(unpackedAar, false)
- }
- }
-
- registerTransform(UnpackAar::class.java) {
- from.attribute(unpackedAar, false).attribute(artifactType, "aar")
- to.attribute(unpackedAar, true).attribute(artifactType, "jar")
- }
-
+ configureAarUnpacking()
api("com.google.android.gms:play-services-tasks:$tasksVersion") {
exclude(group="com.android.support")
}
+
+ // Required by robolectric
+ testImplementation("androidx.test:core:1.2.0")
+ testImplementation("androidx.test:monitor:1.2.0")
}
externalDocumentationLink(
diff --git a/integration/kotlinx-coroutines-play-services/src/Tasks.kt b/integration/kotlinx-coroutines-play-services/src/Tasks.kt
index c37ac7a02d..0451d7beb8 100644
--- a/integration/kotlinx-coroutines-play-services/src/Tasks.kt
+++ b/integration/kotlinx-coroutines-play-services/src/Tasks.kt
@@ -8,6 +8,8 @@ package kotlinx.coroutines.tasks
import com.google.android.gms.tasks.*
import kotlinx.coroutines.*
+import java.lang.Runnable
+import java.util.concurrent.Executor
import kotlin.coroutines.*
/**
@@ -71,7 +73,8 @@ private fun Task.asDeferredImpl(cancellationTokenSource: CancellationToke
deferred.completeExceptionally(e)
}
} else {
- addOnCompleteListener {
+ // Run the callback directly to avoid unnecessarily scheduling on the main thread.
+ addOnCompleteListener(DirectExecutor) {
val e = it.exception
if (e == null) {
@Suppress("UNCHECKED_CAST")
@@ -114,7 +117,8 @@ public suspend fun Task.await(): T = awaitImpl(null)
* leads to an unspecified behaviour.
*/
@ExperimentalCoroutinesApi // Since 1.5.1, tentatively until 1.6.0
-public suspend fun Task.await(cancellationTokenSource: CancellationTokenSource): T = awaitImpl(cancellationTokenSource)
+public suspend fun Task.await(cancellationTokenSource: CancellationTokenSource): T =
+ awaitImpl(cancellationTokenSource)
private suspend fun Task.awaitImpl(cancellationTokenSource: CancellationTokenSource?): T {
// fast path
@@ -133,7 +137,8 @@ private suspend fun Task.awaitImpl(cancellationTokenSource: CancellationT
}
return suspendCancellableCoroutine { cont ->
- addOnCompleteListener {
+ // Run the callback directly to avoid unnecessarily scheduling on the main thread.
+ addOnCompleteListener(DirectExecutor) {
val e = it.exception
if (e == null) {
@Suppress("UNCHECKED_CAST")
@@ -150,3 +155,12 @@ private suspend fun Task.awaitImpl(cancellationTokenSource: CancellationT
}
}
}
+
+/**
+ * An [Executor] that just directly executes the [Runnable].
+ */
+private object DirectExecutor : Executor {
+ override fun execute(r: Runnable) {
+ r.run()
+ }
+}
diff --git a/integration/kotlinx-coroutines-play-services/test/FakeAndroid.kt b/integration/kotlinx-coroutines-play-services/test/FakeAndroid.kt
index 6026ffd75d..e286ee197b 100644
--- a/integration/kotlinx-coroutines-play-services/test/FakeAndroid.kt
+++ b/integration/kotlinx-coroutines-play-services/test/FakeAndroid.kt
@@ -2,10 +2,17 @@ package android.os
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
+import java.util.concurrent.*
class Handler(val looper: Looper) {
fun post(r: Runnable): Boolean {
- GlobalScope.launch { r.run() }
+ try {
+ GlobalScope.launch { r.run() }
+ } catch (e: RejectedExecutionException) {
+ // Execute leftover callbacks in place for tests
+ r.run()
+ }
+
return true
}
}
diff --git a/integration/kotlinx-coroutines-play-services/test/TaskTest.kt b/integration/kotlinx-coroutines-play-services/test/TaskTest.kt
index b125192e93..34fbe23b55 100644
--- a/integration/kotlinx-coroutines-play-services/test/TaskTest.kt
+++ b/integration/kotlinx-coroutines-play-services/test/TaskTest.kt
@@ -45,8 +45,8 @@ class TaskTest : TestBase() {
}
@Test
- fun testCancelledAsTask() {
- val deferred = GlobalScope.async {
+ fun testCancelledAsTask() = runTest {
+ val deferred = async(Dispatchers.Default) {
delay(100)
}.apply { cancel() }
@@ -60,8 +60,8 @@ class TaskTest : TestBase() {
}
@Test
- fun testThrowingAsTask() {
- val deferred = GlobalScope.async {
+ fun testThrowingAsTask() = runTest({ e -> e is TestException }) {
+ val deferred = async(Dispatchers.Default) {
throw TestException("Fail")
}
diff --git a/integration/kotlinx-coroutines-slf4j/build.gradle.kts b/integration/kotlinx-coroutines-slf4j/build.gradle.kts
index a341eefe13..3552333311 100644
--- a/integration/kotlinx-coroutines-slf4j/build.gradle.kts
+++ b/integration/kotlinx-coroutines-slf4j/build.gradle.kts
@@ -3,10 +3,10 @@
*/
dependencies {
- compile("org.slf4j:slf4j-api:1.7.25")
- testCompile("io.github.microutils:kotlin-logging:1.5.4")
- testRuntime("ch.qos.logback:logback-classic:1.2.3")
- testRuntime("ch.qos.logback:logback-core:1.2.3")
+ implementation("org.slf4j:slf4j-api:1.7.32")
+ testImplementation("io.github.microutils:kotlin-logging:2.1.0")
+ testRuntimeOnly("ch.qos.logback:logback-classic:1.2.7")
+ testRuntimeOnly("ch.qos.logback:logback-core:1.2.7")
}
externalDocumentationLink(
diff --git a/integration/kotlinx-coroutines-slf4j/src/MDCContext.kt b/integration/kotlinx-coroutines-slf4j/src/MDCContext.kt
index 9528f2b22d..0fbfece600 100644
--- a/integration/kotlinx-coroutines-slf4j/src/MDCContext.kt
+++ b/integration/kotlinx-coroutines-slf4j/src/MDCContext.kt
@@ -28,7 +28,7 @@ public typealias MDCContextMap = Map?
* }
* ```
*
- * Note that you cannot update MDC context from inside of the coroutine simply
+ * Note that you cannot update MDC context from inside the coroutine simply
* using [MDC.put]. These updates are going to be lost on the next suspension and
* reinstalled to the MDC context that was captured or explicitly specified in
* [contextMap] when this object was created on the next resumption.
@@ -43,6 +43,7 @@ public class MDCContext(
/**
* The value of [MDC] context map.
*/
+ @Suppress("MemberVisibilityCanBePrivate")
public val contextMap: MDCContextMap = MDC.getCopyOfContextMap()
) : ThreadContextElement, AbstractCoroutineContextElement(Key) {
/**
diff --git a/integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt b/integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt
index 7d18359c5d..532c47e9ed 100644
--- a/integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt
+++ b/integration/kotlinx-coroutines-slf4j/test/MDCContextTest.kt
@@ -102,9 +102,10 @@ class MDCContextTest : TestBase() {
val mainDispatcher = kotlin.coroutines.coroutineContext[ContinuationInterceptor]!!
withContext(Dispatchers.Default + MDCContext()) {
assertEquals("myValue", MDC.get("myKey"))
+ assertEquals("myValue", coroutineContext[MDCContext]?.contextMap?.get("myKey"))
withContext(mainDispatcher) {
assertEquals("myValue", MDC.get("myKey"))
}
}
}
-}
\ No newline at end of file
+}
diff --git a/js/example-frontend-js/src/ExampleMain.kt b/js/example-frontend-js/src/ExampleMain.kt
index d4e530b04a..67c6ef04e7 100644
--- a/js/example-frontend-js/src/ExampleMain.kt
+++ b/js/example-frontend-js/src/ExampleMain.kt
@@ -8,7 +8,7 @@ import kotlinx.html.div
import kotlinx.html.dom.*
import kotlinx.html.js.onClickFunction
import org.w3c.dom.*
-import kotlin.browser.*
+import kotlinx.browser.*
import kotlin.coroutines.*
import kotlin.math.*
import kotlin.random.Random
diff --git a/kotlinx-coroutines-core/README.md b/kotlinx-coroutines-core/README.md
index c21e5048f6..38a112e89d 100644
--- a/kotlinx-coroutines-core/README.md
+++ b/kotlinx-coroutines-core/README.md
@@ -57,7 +57,6 @@ helper function. [NonCancellable] job object is provided to suppress cancellatio
| [SendChannel][kotlinx.coroutines.channels.SendChannel] | [send][kotlinx.coroutines.channels.SendChannel.send] | [onSend][kotlinx.coroutines.channels.SendChannel.onSend] | [trySend][kotlinx.coroutines.channels.SendChannel.trySend]
| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receive][kotlinx.coroutines.channels.ReceiveChannel.receive] | [onReceive][kotlinx.coroutines.channels.ReceiveChannel.onReceive] | [tryReceive][kotlinx.coroutines.channels.ReceiveChannel.tryReceive]
| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receiveCatching][kotlinx.coroutines.channels.receiveCatching] | [onReceiveCatching][kotlinx.coroutines.channels.onReceiveCatching] | [tryReceive][kotlinx.coroutines.channels.ReceiveChannel.tryReceive]
-| [Mutex][kotlinx.coroutines.sync.Mutex] | [lock][kotlinx.coroutines.sync.Mutex.lock] | [onLock][kotlinx.coroutines.sync.Mutex.onLock] | [tryLock][kotlinx.coroutines.sync.Mutex.tryLock]
| none | [delay][kotlinx.coroutines.delay] | [onTimeout][kotlinx.coroutines.selects.SelectBuilder.onTimeout] | none
# Package kotlinx.coroutines
@@ -84,10 +83,6 @@ Select expression to perform multiple suspending operations simultaneously until
Low-level primitives for finer-grained control of coroutines.
-# Package kotlinx.coroutines.test
-
-Obsolete and deprecated module to test coroutines. Replaced with `kotlinx-coroutines-test` module.
-
@@ -121,8 +116,6 @@ Obsolete and deprecated module to test coroutines. Replaced with `kotlinx-corout
[kotlinx.coroutines.sync.Mutex]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/index.html
[kotlinx.coroutines.sync.Mutex.lock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/lock.html
-[kotlinx.coroutines.sync.Mutex.onLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/on-lock.html
-[kotlinx.coroutines.sync.Mutex.tryLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/try-lock.html
diff --git a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api
index 50bfb60d62..d227eb879b 100644
--- a/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api
+++ b/kotlinx-coroutines-core/api/kotlinx-coroutines-core.api
@@ -140,11 +140,24 @@ public final class kotlinx/coroutines/CompletionHandlerException : java/lang/Run
public fun (Ljava/lang/String;Ljava/lang/Throwable;)V
}
+public abstract interface class kotlinx/coroutines/CopyableThreadContextElement : kotlinx/coroutines/ThreadContextElement {
+ public abstract fun copyForChild ()Lkotlinx/coroutines/CopyableThreadContextElement;
+ public abstract fun mergeForChild (Lkotlin/coroutines/CoroutineContext$Element;)Lkotlin/coroutines/CoroutineContext;
+}
+
+public final class kotlinx/coroutines/CopyableThreadContextElement$DefaultImpls {
+ public static fun fold (Lkotlinx/coroutines/CopyableThreadContextElement;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
+ public static fun get (Lkotlinx/coroutines/CopyableThreadContextElement;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
+ public static fun minusKey (Lkotlinx/coroutines/CopyableThreadContextElement;Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
+ public static fun plus (Lkotlinx/coroutines/CopyableThreadContextElement;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
+}
+
public abstract interface class kotlinx/coroutines/CopyableThrowable {
public abstract fun createCopy ()Ljava/lang/Throwable;
}
public final class kotlinx/coroutines/CoroutineContextKt {
+ public static final fun newCoroutineContext (Lkotlin/coroutines/CoroutineContext;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
public static final fun newCoroutineContext (Lkotlinx/coroutines/CoroutineScope;Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
}
@@ -156,6 +169,7 @@ public abstract class kotlinx/coroutines/CoroutineDispatcher : kotlin/coroutines
public fun get (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
public final fun interceptContinuation (Lkotlin/coroutines/Continuation;)Lkotlin/coroutines/Continuation;
public fun isDispatchNeeded (Lkotlin/coroutines/CoroutineContext;)Z
+ public fun limitedParallelism (I)Lkotlinx/coroutines/CoroutineDispatcher;
public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
public final fun plus (Lkotlinx/coroutines/CoroutineDispatcher;)Lkotlinx/coroutines/CoroutineDispatcher;
public final fun releaseInterceptedContinuation (Lkotlin/coroutines/Continuation;)V
@@ -279,6 +293,7 @@ public final class kotlinx/coroutines/Dispatchers {
public static final fun getIO ()Lkotlinx/coroutines/CoroutineDispatcher;
public static final fun getMain ()Lkotlinx/coroutines/MainCoroutineDispatcher;
public static final fun getUnconfined ()Lkotlinx/coroutines/CoroutineDispatcher;
+ public final fun shutdown ()V
}
public final class kotlinx/coroutines/DispatchersKt {
@@ -367,7 +382,6 @@ public final class kotlinx/coroutines/Job$Key : kotlin/coroutines/CoroutineConte
}
public final class kotlinx/coroutines/JobKt {
- public static final fun DisposableHandle (Lkotlin/jvm/functions/Function0;)Lkotlinx/coroutines/DisposableHandle;
public static final fun Job (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/CompletableJob;
public static final synthetic fun Job (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job;
public static synthetic fun Job$default (Lkotlinx/coroutines/Job;ILjava/lang/Object;)Lkotlinx/coroutines/CompletableJob;
@@ -447,6 +461,7 @@ public class kotlinx/coroutines/JobSupport : kotlinx/coroutines/ChildJob, kotlin
public abstract class kotlinx/coroutines/MainCoroutineDispatcher : kotlinx/coroutines/CoroutineDispatcher {
public fun ()V
public abstract fun getImmediate ()Lkotlinx/coroutines/MainCoroutineDispatcher;
+ public fun limitedParallelism (I)Lkotlinx/coroutines/CoroutineDispatcher;
public fun toString ()Ljava/lang/String;
protected final fun toStringInternalImpl ()Ljava/lang/String;
}
@@ -543,6 +558,15 @@ public final class kotlinx/coroutines/TimeoutKt {
public static final fun withTimeoutOrNull-KLykuaI (JLkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
+public final class kotlinx/coroutines/YieldContext : kotlin/coroutines/AbstractCoroutineContextElement {
+ public static final field Key Lkotlinx/coroutines/YieldContext$Key;
+ public field dispatcherWasUnconfined Z
+ public fun ()V
+}
+
+public final class kotlinx/coroutines/YieldContext$Key : kotlin/coroutines/CoroutineContext$Key {
+}
+
public final class kotlinx/coroutines/YieldKt {
public static final fun yield (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
@@ -887,8 +911,6 @@ public final class kotlinx/coroutines/flow/FlowKt {
public static final fun asFlow ([Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
public static final fun asSharedFlow (Lkotlinx/coroutines/flow/MutableSharedFlow;)Lkotlinx/coroutines/flow/SharedFlow;
public static final fun asStateFlow (Lkotlinx/coroutines/flow/MutableStateFlow;)Lkotlinx/coroutines/flow/StateFlow;
- public static final fun broadcastIn (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineStart;)Lkotlinx/coroutines/channels/BroadcastChannel;
- public static synthetic fun broadcastIn$default (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/CoroutineScope;Lkotlinx/coroutines/CoroutineStart;ILjava/lang/Object;)Lkotlinx/coroutines/channels/BroadcastChannel;
public static final synthetic fun buffer (Lkotlinx/coroutines/flow/Flow;I)Lkotlinx/coroutines/flow/Flow;
public static final fun buffer (Lkotlinx/coroutines/flow/Flow;ILkotlinx/coroutines/channels/BufferOverflow;)Lkotlinx/coroutines/flow/Flow;
public static synthetic fun buffer$default (Lkotlinx/coroutines/flow/Flow;IILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
@@ -899,7 +921,7 @@ public final class kotlinx/coroutines/flow/FlowKt {
public static final fun catch (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
public static final fun channelFlow (Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
public static final fun collect (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
- public static final fun collect (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
+ public static final synthetic fun collect (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun collectIndexed (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun collectLatest (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun combine (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
@@ -958,10 +980,6 @@ public final class kotlinx/coroutines/flow/FlowKt {
public static final fun flowOf (Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
public static final fun flowOf ([Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
public static final fun flowOn (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;)Lkotlinx/coroutines/flow/Flow;
- public static final fun flowViaChannel (ILkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
- public static synthetic fun flowViaChannel$default (ILkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
- public static final fun flowWith (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
- public static synthetic fun flowWith$default (Lkotlinx/coroutines/flow/Flow;Lkotlin/coroutines/CoroutineContext;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
public static final fun fold (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun forEach (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)V
public static final fun getDEFAULT_CONCURRENCY ()I
@@ -978,8 +996,6 @@ public final class kotlinx/coroutines/flow/FlowKt {
public static final fun onCompletion (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
public static final fun onEach (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
public static final fun onEmpty (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
- public static final fun onErrorCollect (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
- public static synthetic fun onErrorCollect$default (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
public static final fun onErrorResume (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
public static final fun onErrorResumeNext (Lkotlinx/coroutines/flow/Flow;Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
public static final fun onErrorReturn (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
@@ -995,9 +1011,7 @@ public final class kotlinx/coroutines/flow/FlowKt {
public static final fun reduce (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function3;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public static final fun replay (Lkotlinx/coroutines/flow/Flow;)Lkotlinx/coroutines/flow/Flow;
public static final fun replay (Lkotlinx/coroutines/flow/Flow;I)Lkotlinx/coroutines/flow/Flow;
- public static final synthetic fun retry (Lkotlinx/coroutines/flow/Flow;ILkotlin/jvm/functions/Function1;)Lkotlinx/coroutines/flow/Flow;
public static final fun retry (Lkotlinx/coroutines/flow/Flow;JLkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/flow/Flow;
- public static synthetic fun retry$default (Lkotlinx/coroutines/flow/Flow;ILkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
public static synthetic fun retry$default (Lkotlinx/coroutines/flow/Flow;JLkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
public static final fun retryWhen (Lkotlinx/coroutines/flow/Flow;Lkotlin/jvm/functions/Function4;)Lkotlinx/coroutines/flow/Flow;
public static final fun runningFold (Lkotlinx/coroutines/flow/Flow;Ljava/lang/Object;Lkotlin/jvm/functions/Function3;)Lkotlinx/coroutines/flow/Flow;
@@ -1061,6 +1075,7 @@ public abstract interface class kotlinx/coroutines/flow/MutableStateFlow : kotli
}
public abstract interface class kotlinx/coroutines/flow/SharedFlow : kotlinx/coroutines/flow/Flow {
+ public abstract fun collect (Lkotlinx/coroutines/flow/FlowCollector;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun getReplayCache ()Ljava/util/List;
}
@@ -1284,36 +1299,3 @@ public final class kotlinx/coroutines/sync/SemaphoreKt {
public static final fun withPermit (Lkotlinx/coroutines/sync/Semaphore;Lkotlin/jvm/functions/Function0;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
-public final class kotlinx/coroutines/test/TestCoroutineContext : kotlin/coroutines/CoroutineContext {
- public fun ()V
- public fun (Ljava/lang/String;)V
- public synthetic fun (Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
- public final fun advanceTimeBy (JLjava/util/concurrent/TimeUnit;)J
- public static synthetic fun advanceTimeBy$default (Lkotlinx/coroutines/test/TestCoroutineContext;JLjava/util/concurrent/TimeUnit;ILjava/lang/Object;)J
- public final fun advanceTimeTo (JLjava/util/concurrent/TimeUnit;)V
- public static synthetic fun advanceTimeTo$default (Lkotlinx/coroutines/test/TestCoroutineContext;JLjava/util/concurrent/TimeUnit;ILjava/lang/Object;)V
- public final fun assertAllUnhandledExceptions (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
- public static synthetic fun assertAllUnhandledExceptions$default (Lkotlinx/coroutines/test/TestCoroutineContext;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
- public final fun assertAnyUnhandledException (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
- public static synthetic fun assertAnyUnhandledException$default (Lkotlinx/coroutines/test/TestCoroutineContext;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
- public final fun assertExceptions (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
- public static synthetic fun assertExceptions$default (Lkotlinx/coroutines/test/TestCoroutineContext;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
- public final fun assertUnhandledException (Ljava/lang/String;Lkotlin/jvm/functions/Function1;)V
- public static synthetic fun assertUnhandledException$default (Lkotlinx/coroutines/test/TestCoroutineContext;Ljava/lang/String;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
- public final fun cancelAllActions ()V
- public fun fold (Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)Ljava/lang/Object;
- public fun get (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext$Element;
- public final fun getExceptions ()Ljava/util/List;
- public fun minusKey (Lkotlin/coroutines/CoroutineContext$Key;)Lkotlin/coroutines/CoroutineContext;
- public final fun now (Ljava/util/concurrent/TimeUnit;)J
- public static synthetic fun now$default (Lkotlinx/coroutines/test/TestCoroutineContext;Ljava/util/concurrent/TimeUnit;ILjava/lang/Object;)J
- public fun plus (Lkotlin/coroutines/CoroutineContext;)Lkotlin/coroutines/CoroutineContext;
- public fun toString ()Ljava/lang/String;
- public final fun triggerActions ()V
-}
-
-public final class kotlinx/coroutines/test/TestCoroutineContextKt {
- public static final fun withTestContext (Lkotlinx/coroutines/test/TestCoroutineContext;Lkotlin/jvm/functions/Function1;)V
- public static synthetic fun withTestContext$default (Lkotlinx/coroutines/test/TestCoroutineContext;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)V
-}
-
diff --git a/kotlinx-coroutines-core/build.gradle b/kotlinx-coroutines-core/build.gradle
index c45ca08cef..9791b445bf 100644
--- a/kotlinx-coroutines-core/build.gradle
+++ b/kotlinx-coroutines-core/build.gradle
@@ -14,6 +14,8 @@ if (rootProject.ext.native_targets_enabled) {
apply from: rootProject.file("gradle/compile-js-multiplatform.gradle")
apply from: rootProject.file('gradle/publish-npm-js.gradle')
+apply from: rootProject.file('gradle/dokka.gradle.kts')
+apply from: rootProject.file('gradle/publish.gradle')
/* ==========================================================================
Configure source sets structure for kotlinx-coroutines-core:
@@ -70,39 +72,65 @@ if (rootProject.ext.native_targets_enabled) {
* because JMV-only projects depend on core, thus core should always be initialized before configuration.
*/
kotlin {
- configure(sourceSets) {
- def srcDir = name.endsWith('Main') ? 'src' : 'test'
- def platform = name[0..-5]
- kotlin.srcDirs = ["$platform/$srcDir"]
- if (name == "jvmMain") {
- resources.srcDirs = ["$platform/resources"]
- } else if (name == "jvmTest") {
- resources.srcDirs = ["$platform/test-resources"]
+ sourceSets.forEach {
+ SourceSetsKt.configureMultiplatform(it)
+ }
+
+ /*
+ * Configure four test runs:
+ * 1) Old memory model, Main thread
+ * 2) New memory model, Main thread
+ * 3) Old memory model, BG thread
+ * 4) New memory model, BG thread (required for Dispatchers.Main tests on Darwin)
+ *
+ * All new MM targets are build with optimize = true to have stress tests properly run.
+ */
+ targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithTests.class).configureEach {
+ binaries {
+ // Test for memory leaks using a special entry point that does not exit but returns from main
+ binaries.getTest("DEBUG").freeCompilerArgs += ["-e", "kotlinx.coroutines.mainNoExit"]
}
- languageSettings {
- progressiveMode = true
- optInAnnotations.each { useExperimentalAnnotation(it) }
+
+ binaries.test("newMM", [DEBUG]) {
+ def thisTest = it
+ freeCompilerArgs += ["-e", "kotlinx.coroutines.mainNoExit"]
+ optimized = true
+ binaryOptions["memoryModel"] = "experimental"
+ testRuns.create("newMM") {
+ setExecutionSourceFrom(thisTest)
+ // A hack to get different suffixes in the aggregated report.
+ executionTask.configure { targetName = "$targetName new MM" }
+ }
}
- }
- configure(targets) {
- // Configure additional binaries and test runs -- one for each OS
- if (["macos", "linux", "mingw"].any { name.startsWith(it) }) {
- binaries {
- // Test for memory leaks using a special entry point that does not exit but returns from main
- binaries.getTest("DEBUG").freeCompilerArgs += ["-e", "kotlinx.coroutines.mainNoExit"]
- // Configure a separate test where code runs in background
- test("background", [org.jetbrains.kotlin.gradle.plugin.mpp.NativeBuildType.DEBUG]) {
- freeCompilerArgs += ["-e", "kotlinx.coroutines.mainBackground"]
- }
+ binaries.test("worker", [DEBUG]) {
+ def thisTest = it
+ freeCompilerArgs += ["-e", "kotlinx.coroutines.mainBackground"]
+ testRuns.create("worker") {
+ setExecutionSourceFrom(thisTest)
+ executionTask.configure { targetName = "$targetName worker" }
}
- testRuns {
- background { setExecutionSourceFrom(binaries.backgroundDebugTest) }
+ }
+
+ binaries.test("workerWithNewMM", [DEBUG]) {
+ def thisTest = it
+ optimized = true
+ freeCompilerArgs += ["-e", "kotlinx.coroutines.mainBackground"]
+ binaryOptions["memoryModel"] = "experimental"
+ testRuns.create("workerWithNewMM") {
+ setExecutionSourceFrom(thisTest)
+ executionTask.configure { targetName = "$targetName worker with new MM" }
}
}
}
+
+ jvm {
+ // For animal sniffer
+ withJava()
+ }
}
+
configurations {
configureKotlinJvmPlatform(kotlinCompilerPluginClasspath)
}
@@ -159,28 +187,10 @@ kotlin.sourceSets {
jvmTest.dependencies {
api "org.jetbrains.kotlinx:lincheck:$lincheck_version"
api "org.jetbrains.kotlinx:kotlinx-knit-test:$knit_version"
- api "com.esotericsoftware:kryo:4.0.0"
implementation project(":android-unit-tests")
}
}
-task checkJdk16() {
- // only fail w/o JDK_16 when actually trying to compile, not during project setup phase
- doLast {
- if (!System.env.JDK_16) {
- throw new GradleException("JDK_16 environment variable is not defined. " +
- "Can't build against JDK 1.6 runtime and run JDK 1.6 compatibility tests. " +
- "Please ensure JDK 1.6 is installed and that JDK_16 points to it.")
- }
- }
-}
-
-tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile) {
- kotlinOptions.jdkHome = System.env.JDK_16
- // only fail when actually trying to compile, not during project setup phase
- dependsOn(checkJdk16)
-}
-
jvmTest {
minHeapSize = '1g'
maxHeapSize = '1g'
@@ -246,30 +256,37 @@ task jvmLincheckTest(type: Test, dependsOn: compileTestKotlinJvm) {
static void configureJvmForLincheck(task) {
task.minHeapSize = '1g'
- task.maxHeapSize = '6g' // we may need more space for building an interleaving tree in the model checking mode
+ task.maxHeapSize = '4g' // we may need more space for building an interleaving tree in the model checking mode
task.jvmArgs = ['--add-opens', 'java.base/jdk.internal.misc=ALL-UNNAMED', // required for transformation
- '--add-exports', 'java.base/jdk.internal.util=ALL-UNNAMED'] // in the model checking mode
+ '--add-exports', 'java.base/jdk.internal.util=ALL-UNNAMED'] // in the model checking mode
task.systemProperty 'kotlinx.coroutines.semaphore.segmentSize', '2'
task.systemProperty 'kotlinx.coroutines.semaphore.maxSpinCycles', '1' // better for the model checking mode
}
-task jdk16Test(type: Test, dependsOn: [compileTestKotlinJvm, checkJdk16]) {
- classpath = files { jvmTest.classpath }
- testClassesDirs = files { jvmTest.testClassesDirs }
- executable = "$System.env.JDK_16/bin/java"
- exclude '**/*LFStressTest.*' // lock-freedom tests use LockFreedomTestEnvironment which needs JDK8
- exclude '**/*LincheckTest.*' // Lincheck tests use LinChecker which needs JDK8
- exclude '**/exceptions/**' // exceptions tests check suppressed exception which needs JDK8
- exclude '**/ExceptionsGuideTest.*'
- exclude '**/RunInterruptibleStressTest.*' // fails on JDK 1.6 due to JDK bug
+// Always check additional test sets
+task moreTest(dependsOn: [jvmStressTest, jvmLincheckTest])
+check.dependsOn moreTest
+
+tasks.jvmLincheckTest {
+ kover {
+ enabled = false // Always disabled, lincheck doesn't really support coverage
+ }
}
-// Run jdk16Test test only during nightly stress test
-jdk16Test.onlyIf { project.properties['stressTest'] != null }
+def commonKoverExcludes =
+ ["kotlinx.coroutines.debug.*", // Tested by debug module
+ "kotlinx.coroutines.channels.ChannelsKt__DeprecatedKt.*", // Deprecated
+ "kotlinx.coroutines.scheduling.LimitingDispatcher", // Deprecated
+ "kotlinx.coroutines.scheduling.ExperimentalCoroutineDispatcher" // Deprecated
+ ]
-// Always check additional test sets
-task moreTest(dependsOn: [jvmStressTest, jvmLincheckTest, jdk16Test])
-check.dependsOn moreTest
+tasks.koverHtmlReport {
+ excludes = commonKoverExcludes
+}
+
+tasks.koverVerify {
+ excludes = commonKoverExcludes
+}
task testsJar(type: Jar, dependsOn: jvmTestClasses) {
classifier = 'tests'
diff --git a/kotlinx-coroutines-core/common/README.md b/kotlinx-coroutines-core/common/README.md
index fcfe334c62..b09c44c75e 100644
--- a/kotlinx-coroutines-core/common/README.md
+++ b/kotlinx-coroutines-core/common/README.md
@@ -60,17 +60,12 @@ helper function. [NonCancellable] job object is provided to suppress cancellatio
| [SendChannel][kotlinx.coroutines.channels.SendChannel] | [send][kotlinx.coroutines.channels.SendChannel.send] | [onSend][kotlinx.coroutines.channels.SendChannel.onSend] | [trySend][kotlinx.coroutines.channels.SendChannel.trySend]
| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receive][kotlinx.coroutines.channels.ReceiveChannel.receive] | [onReceive][kotlinx.coroutines.channels.ReceiveChannel.onReceive] | [tryReceive][kotlinx.coroutines.channels.ReceiveChannel.tryReceive]
| [ReceiveChannel][kotlinx.coroutines.channels.ReceiveChannel] | [receiveCatching][kotlinx.coroutines.channels.ReceiveChannel.receiveCatching] | [onReceiveCatching][kotlinx.coroutines.channels.ReceiveChannel.onReceiveCatching] | [tryReceive][kotlinx.coroutines.channels.ReceiveChannel.tryReceive]
-| [Mutex][kotlinx.coroutines.sync.Mutex] | [lock][kotlinx.coroutines.sync.Mutex.lock] | [onLock][kotlinx.coroutines.sync.Mutex.onLock] | [tryLock][kotlinx.coroutines.sync.Mutex.tryLock]
| none | [delay] | [onTimeout][kotlinx.coroutines.selects.SelectBuilder.onTimeout] | none
This module provides debugging facilities for coroutines (run JVM with `-ea` or `-Dkotlinx.coroutines.debug` options)
and [newCoroutineContext] function to write user-defined coroutine builders that work with these
debugging facilities. See [DEBUG_PROPERTY_NAME] for more details.
-This module provides a special CoroutineContext type [TestCoroutineCoroutineContext][kotlinx.coroutines.test.TestCoroutineContext] that
-allows the writer of code that contains Coroutines with delays and timeouts to write non-flaky unit-tests for that code allowing these tests to
-terminate in near zero time. See the documentation for this class for more information.
-
# Package kotlinx.coroutines
General-purpose coroutine builders, contexts, and helper functions.
@@ -131,8 +126,6 @@ Low-level primitives for finer-grained control of coroutines.
[kotlinx.coroutines.sync.Mutex]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/index.html
[kotlinx.coroutines.sync.Mutex.lock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/lock.html
-[kotlinx.coroutines.sync.Mutex.onLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/on-lock.html
-[kotlinx.coroutines.sync.Mutex.tryLock]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.sync/-mutex/try-lock.html
@@ -157,8 +150,4 @@ Low-level primitives for finer-grained control of coroutines.
[kotlinx.coroutines.selects.select]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/select.html
[kotlinx.coroutines.selects.SelectBuilder.onTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.selects/on-timeout.html
-
-
-[kotlinx.coroutines.test.TestCoroutineContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.test/-test-coroutine-context/index.html
-
diff --git a/kotlinx-coroutines-core/common/src/Annotations.kt b/kotlinx-coroutines-core/common/src/Annotations.kt
index 724cc8cb87..bacce39408 100644
--- a/kotlinx-coroutines-core/common/src/Annotations.kt
+++ b/kotlinx-coroutines-core/common/src/Annotations.kt
@@ -30,6 +30,19 @@ public annotation class DelicateCoroutinesApi
*/
@MustBeDocumented
@Retention(value = AnnotationRetention.BINARY)
+@Target(
+ AnnotationTarget.CLASS,
+ AnnotationTarget.ANNOTATION_CLASS,
+ AnnotationTarget.PROPERTY,
+ AnnotationTarget.FIELD,
+ AnnotationTarget.LOCAL_VARIABLE,
+ AnnotationTarget.VALUE_PARAMETER,
+ AnnotationTarget.CONSTRUCTOR,
+ AnnotationTarget.FUNCTION,
+ AnnotationTarget.PROPERTY_GETTER,
+ AnnotationTarget.PROPERTY_SETTER,
+ AnnotationTarget.TYPEALIAS
+)
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
public annotation class ExperimentalCoroutinesApi
diff --git a/kotlinx-coroutines-core/common/src/Await.kt b/kotlinx-coroutines-core/common/src/Await.kt
index e06ed33025..c1669e2554 100644
--- a/kotlinx-coroutines-core/common/src/Await.kt
+++ b/kotlinx-coroutines-core/common/src/Await.kt
@@ -29,8 +29,8 @@ public suspend fun awaitAll(vararg deferreds: Deferred): List =
* when all deferred computations are complete or resumes with the first thrown exception if any of computations
* complete exceptionally including cancellation.
*
- * This function is **not** equivalent to `this.map { it.await() }` which fails only when when it sequentially
- * gets to wait the failing deferred, while this `awaitAll` fails immediately as soon as any of the deferreds fail.
+ * This function is **not** equivalent to `this.map { it.await() }` which fails only when it sequentially
+ * gets to wait for the failing deferred, while this `awaitAll` fails immediately as soon as any of the deferreds fail.
*
* This suspending function is cancellable.
* If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting,
diff --git a/kotlinx-coroutines-core/common/src/Builders.common.kt b/kotlinx-coroutines-core/common/src/Builders.common.kt
index a11ffe9eb4..3dea68cfde 100644
--- a/kotlinx-coroutines-core/common/src/Builders.common.kt
+++ b/kotlinx-coroutines-core/common/src/Builders.common.kt
@@ -126,12 +126,15 @@ private class LazyDeferredCoroutine(
* This suspending function is cancellable. It immediately checks for cancellation of
* the resulting context and throws [CancellationException] if it is not [active][CoroutineContext.isActive].
*
- * This function uses dispatcher from the new context, shifting execution of the [block] into the
- * different thread if a new dispatcher is specified, and back to the original dispatcher
- * when it completes. Note that the result of `withContext` invocation is
- * dispatched into the original context in a cancellable way with a **prompt cancellation guarantee**,
- * which means that if the original [coroutineContext], in which `withContext` was invoked,
- * is cancelled by the time its dispatcher starts to execute the code,
+ * Calls to [withContext] whose [context] argument provides a [CoroutineDispatcher] that is
+ * different from the current one, by necessity, perform additional dispatches: the [block]
+ * can not be executed immediately and needs to be dispatched for execution on
+ * the passed [CoroutineDispatcher], and then when the [block] completes, the execution
+ * has to shift back to the original dispatcher.
+ *
+ * Note that the result of `withContext` invocation is dispatched into the original context in a cancellable way
+ * with a **prompt cancellation guarantee**, which means that if the original [coroutineContext]
+ * in which `withContext` was invoked is cancelled by the time its dispatcher starts to execute the code,
* it discards the result of `withContext` and throws [CancellationException].
*
* The cancellation behaviour described above is enabled if and only if the dispatcher is being changed.
@@ -148,7 +151,8 @@ public suspend fun withContext(
return suspendCoroutineUninterceptedOrReturn sc@ { uCont ->
// compute new context
val oldContext = uCont.context
- val newContext = oldContext + context
+ // Copy CopyableThreadContextElement if necessary
+ val newContext = oldContext.newCoroutineContext(context)
// always check for cancellation of new context
newContext.ensureActive()
// FAST PATH #1 -- new context is the same as the old one
diff --git a/kotlinx-coroutines-core/common/src/CloseableCoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/CloseableCoroutineDispatcher.kt
new file mode 100644
index 0000000000..9c6703291a
--- /dev/null
+++ b/kotlinx-coroutines-core/common/src/CloseableCoroutineDispatcher.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2016-2021 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+package kotlinx.coroutines
+
+/**
+ * [CoroutineDispatcher] that provides a method to close it,
+ * causing the rejection of any new tasks and cleanup of all underlying resources
+ * associated with the current dispatcher.
+ * Examples of closeable dispatchers are dispatchers backed by `java.lang.Executor` and
+ * by `kotlin.native.Worker`.
+ *
+ * **The `CloseableCoroutineDispatcher` class is not stable for inheritance in 3rd party libraries**, as new methods
+ * might be added to this interface in the future, but is stable for use.
+ */
+@ExperimentalCoroutinesApi
+public expect abstract class CloseableCoroutineDispatcher() : CoroutineDispatcher {
+
+ /**
+ * Initiate the closing sequence of the coroutine dispatcher.
+ * After a successful call to [close], no new tasks will
+ * be accepted to be [dispatched][dispatch], but the previously dispatched tasks will be run.
+ *
+ * Invocations of `close` are idempotent and thread-safe.
+ */
+ public abstract fun close()
+}
diff --git a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt
index 68b4b1a393..9153f39821 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineContext.common.kt
@@ -7,13 +7,20 @@ package kotlinx.coroutines
import kotlin.coroutines.*
/**
- * Creates a context for the new coroutine. It installs [Dispatchers.Default] when no other dispatcher or
- * [ContinuationInterceptor] is specified, and adds optional support for debugging facilities (when turned on).
+ * Creates a context for a new coroutine. It installs [Dispatchers.Default] when no other dispatcher or
+ * [ContinuationInterceptor] is specified and adds optional support for debugging facilities (when turned on)
+ * and copyable-thread-local facilities on JVM.
*/
public expect fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext
-internal expect fun createDefaultDispatcher(): CoroutineDispatcher
+/**
+ * Creates a context for coroutine builder functions that do not launch a new coroutine, e.g. [withContext].
+ * @suppress
+ */
+@InternalCoroutinesApi
+public expect fun CoroutineContext.newCoroutineContext(addedContext: CoroutineContext): CoroutineContext
+@PublishedApi
@Suppress("PropertyName")
internal expect val DefaultDelay: Delay
diff --git a/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
index d5613d4110..71b7ec726f 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineDispatcher.kt
@@ -61,6 +61,46 @@ public abstract class CoroutineDispatcher :
*/
public open fun isDispatchNeeded(context: CoroutineContext): Boolean = true
+ /**
+ * Creates a view of the current dispatcher that limits the parallelism to the given [value][parallelism].
+ * The resulting view uses the original dispatcher for execution, but with the guarantee that
+ * no more than [parallelism] coroutines are executed at the same time.
+ *
+ * This method does not impose restrictions on the number of views or the total sum of parallelism values,
+ * each view controls its own parallelism independently with the guarantee that the effective parallelism
+ * of all views cannot exceed the actual parallelism of the original dispatcher.
+ *
+ * ### Limitations
+ *
+ * The default implementation of `limitedParallelism` does not support direct dispatchers,
+ * such as executing the given runnable in place during [dispatch] calls.
+ * Any dispatcher that may return `false` from [isDispatchNeeded] is considered direct.
+ * For direct dispatchers, it is recommended to override this method
+ * and provide a domain-specific implementation or to throw an [UnsupportedOperationException].
+ *
+ * ### Example of usage
+ * ```
+ * private val backgroundDispatcher = newFixedThreadPoolContext(4, "App Background")
+ * // At most 2 threads will be processing images as it is really slow and CPU-intensive
+ * private val imageProcessingDispatcher = backgroundDispatcher.limitedParallelism(2)
+ * // At most 3 threads will be processing JSON to avoid image processing starvation
+ * private val jsonProcessingDispatcher = backgroundDispatcher.limitedParallelism(3)
+ * // At most 1 thread will be doing IO
+ * private val fileWriterDispatcher = backgroundDispatcher.limitedParallelism(1)
+ * ```
+ * Note how in this example the application has an executor with 4 threads, but the total sum of all limits
+ * is 6. Still, at most 4 coroutines can be executed simultaneously as each view limits only its own parallelism.
+ *
+ * Note that this example was structured in such a way that it illustrates the parallelism guarantees.
+ * In practice, it is usually better to use [Dispatchers.IO] or [Dispatchers.Default] instead of creating a
+ * `backgroundDispatcher`. It is both possible and advised to call `limitedParallelism` on them.
+ */
+ @ExperimentalCoroutinesApi
+ public open fun limitedParallelism(parallelism: Int): CoroutineDispatcher {
+ parallelism.checkParallelism()
+ return LimitedDispatcher(this, parallelism)
+ }
+
/**
* Dispatches execution of a runnable [block] onto another thread in the given [context].
* This method should guarantee that the given [block] will be eventually invoked,
diff --git a/kotlinx-coroutines-core/common/src/CoroutineScope.kt b/kotlinx-coroutines-core/common/src/CoroutineScope.kt
index 3ed233bfb9..b0928d5c58 100644
--- a/kotlinx-coroutines-core/common/src/CoroutineScope.kt
+++ b/kotlinx-coroutines-core/common/src/CoroutineScope.kt
@@ -12,7 +12,7 @@ import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*
/**
- * Defines a scope for new coroutines. Every **coroutine builder** (like [launch], [async], etc)
+ * Defines a scope for new coroutines. Every **coroutine builder** (like [launch], [async], etc.)
* is an extension on [CoroutineScope] and inherits its [coroutineContext][CoroutineScope.coroutineContext]
* to automatically propagate all its elements and cancellation.
*
@@ -28,8 +28,8 @@ import kotlin.coroutines.intrinsics.*
* By convention, the [context of a scope][CoroutineScope.coroutineContext] should contain an instance of a
* [job][Job] to enforce the discipline of **structured concurrency** with propagation of cancellation.
*
- * Every coroutine builder (like [launch], [async], etc)
- * and every scoping function (like [coroutineScope], [withContext], etc) provides _its own_ scope
+ * Every coroutine builder (like [launch], [async], and others)
+ * and every scoping function (like [coroutineScope] and [withContext]) provides _its own_ scope
* with its own [Job] instance into the inner block of code it runs.
* By convention, they all wait for all the coroutines inside their block to complete before completing themselves,
* thus enforcing the structured concurrency. See [Job] documentation for more details.
@@ -42,14 +42,14 @@ import kotlin.coroutines.intrinsics.*
* ### Custom usage
*
* `CoroutineScope` should be declared as a property on entities with a well-defined lifecycle that are
- * responsible for launching children coroutines. The corresponding instance of `CoroutineScope` shall be created
- * with either `CoroutineScope()` or `MainScope()` functions. The difference between them is only in the
- * [CoroutineDispatcher]:
+ * responsible for launching child coroutines. The corresponding instance of `CoroutineScope` shall be created
+ * with either `CoroutineScope()` or `MainScope()`:
*
- * * `CoroutineScope()` uses [Dispatchers.Default] for its coroutines.
- * * `MainScope()` uses [Dispatchers.Main] for its coroutines.
+ * * `CoroutineScope()` uses the [context][CoroutineContext] provided to it as a parameter for its coroutines
+ * and adds a [Job] if one is not provided as part of the context.
+ * * `MainScope()` uses [Dispatchers.Main] for its coroutines and has a [SupervisorJob].
*
- * **The key part of custom usage of `CustomScope` is cancelling it at the end of the lifecycle.**
+ * **The key part of custom usage of `CoroutineScope` is cancelling it at the end of the lifecycle.**
* The [CoroutineScope.cancel] extension function shall be used when the entity that was launching coroutines
* is no longer needed. It cancels all the coroutines that might still be running on behalf of it.
*
@@ -178,7 +178,7 @@ public val CoroutineScope.isActive: Boolean
* ```
* // concurrently load configuration and data
* suspend fun loadConfigurationAndData() {
- * coroutinesScope {
+ * coroutineScope {
* launch { loadConfiguration() }
* launch { loadData() }
* }
@@ -269,8 +269,8 @@ public suspend fun coroutineScope(block: suspend CoroutineScope.() -> R): R
* Creates a [CoroutineScope] that wraps the given coroutine [context].
*
* If the given [context] does not contain a [Job] element, then a default `Job()` is created.
- * This way, cancellation or failure of any child coroutine in this scope cancels all the other children,
- * just like inside [coroutineScope] block.
+ * This way, failure of any child coroutine in this scope or [cancellation][CoroutineScope.cancel] of the scope itself
+ * cancels all the scope's children, just like inside [coroutineScope] block.
*/
@Suppress("FunctionName")
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
diff --git a/kotlinx-coroutines-core/common/src/Delay.kt b/kotlinx-coroutines-core/common/src/Delay.kt
index 4543c5dda1..301ed2d322 100644
--- a/kotlinx-coroutines-core/common/src/Delay.kt
+++ b/kotlinx-coroutines-core/common/src/Delay.kt
@@ -19,15 +19,12 @@ import kotlin.time.*
*/
@InternalCoroutinesApi
public interface Delay {
- /**
- * Delays coroutine for a given time without blocking a thread and resumes it after a specified time.
- *
- * This suspending function is cancellable.
- * If the [Job] of the current coroutine is cancelled or completed while this suspending function is waiting, this function
- * immediately resumes with [CancellationException].
- * There is a **prompt cancellation guarantee**. If the job was cancelled while this function was
- * suspended, it will not resume successfully. See [suspendCancellableCoroutine] documentation for low-level details.
- */
+
+ /** @suppress **/
+ @Deprecated(
+ message = "Deprecated without replacement as an internal method never intended for public use",
+ level = DeprecationLevel.ERROR
+ ) // Error since 1.6.0
public suspend fun delay(time: Long) {
if (time <= 0) return // don't delay
return suspendCancellableCoroutine { scheduleResumeAfterDelay(time, it) }
@@ -54,8 +51,6 @@ public interface Delay {
* Schedules invocation of a specified [block] after a specified delay [timeMillis].
* The resulting [DisposableHandle] can be used to [dispose][DisposableHandle.dispose] of this invocation
* request if it is not needed anymore.
- *
- * This implementation uses a built-in single-threaded scheduled executor service.
*/
public fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle =
DefaultDelay.invokeOnTimeout(timeMillis, block, context)
@@ -138,7 +133,6 @@ public suspend fun delay(timeMillis: Long) {
*
* Implementation note: how exactly time is tracked is an implementation detail of [CoroutineDispatcher] in the context.
*/
-@ExperimentalTime
public suspend fun delay(duration: Duration): Unit = delay(duration.toDelayMillis())
/** Returns [Delay] implementation of the given context */
@@ -148,6 +142,5 @@ internal val CoroutineContext.delay: Delay get() = get(ContinuationInterceptor)
* Convert this duration to its millisecond value.
* Positive durations are coerced at least `1`.
*/
-@ExperimentalTime
internal fun Duration.toDelayMillis(): Long =
if (this > Duration.ZERO) inWholeMilliseconds.coerceAtLeast(1) else 0
diff --git a/kotlinx-coroutines-core/common/src/Dispatchers.common.kt b/kotlinx-coroutines-core/common/src/Dispatchers.common.kt
index 8681b182d8..28e67a423d 100644
--- a/kotlinx-coroutines-core/common/src/Dispatchers.common.kt
+++ b/kotlinx-coroutines-core/common/src/Dispatchers.common.kt
@@ -26,9 +26,9 @@ public expect object Dispatchers {
*
* Access to this property may throw an [IllegalStateException] if no main dispatchers are present in the classpath.
*
- * Depending on platform and classpath it can be mapped to different dispatchers:
+ * Depending on platform and classpath, it can be mapped to different dispatchers:
* - On JS and Native it is equivalent to the [Default] dispatcher.
- * - On JVM it either the Android main thread dispatcher, JavaFx or Swing EDT dispatcher. It is chosen by the
+ * - On JVM it is either the Android main thread dispatcher, JavaFx or Swing EDT dispatcher. It is chosen by the
* [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html).
*
* In order to work with the `Main` dispatcher, the following artifact should be added to the project runtime dependencies:
@@ -48,7 +48,7 @@ public expect object Dispatchers {
* stack overflows.
*
* ### Event loop
- * Event loop semantics is a purely internal concept and have no guarantees on the order of execution
+ * Event loop semantics is a purely internal concept and has no guarantees on the order of execution
* except that all queued coroutines will be executed on the current thread in the lexical scope of the outermost
* unconfined coroutine.
*
@@ -63,11 +63,11 @@ public expect object Dispatchers {
* }
* println("Done")
* ```
- * Can print both "1 2 3" and "1 3 2", this is an implementation detail that can be changed.
- * But it is guaranteed that "Done" will be printed only when both `withContext` calls are completed.
+ * Can print both "1 2 3" and "1 3 2". This is an implementation detail that can be changed.
+ * However, it is guaranteed that "Done" will be printed only when both `withContext` calls are completed.
*
* If you need your coroutine to be confined to a particular thread or a thread-pool after resumption,
- * but still want to execute it in the current call-frame until its first suspension, then you can use
+ * but still want to execute it in the current call-frame until its first suspension, you can use
* an optional [CoroutineStart] parameter in coroutine builders like
* [launch][CoroutineScope.launch] and [async][CoroutineScope.async] setting it to
* the value of [CoroutineStart.UNDISPATCHED].
diff --git a/kotlinx-coroutines-core/common/src/EventLoop.common.kt b/kotlinx-coroutines-core/common/src/EventLoop.common.kt
index e6a57c927a..12940c54e2 100644
--- a/kotlinx-coroutines-core/common/src/EventLoop.common.kt
+++ b/kotlinx-coroutines-core/common/src/EventLoop.common.kt
@@ -115,7 +115,12 @@ internal abstract class EventLoop : CoroutineDispatcher() {
}
}
- protected open fun shutdown() {}
+ final override fun limitedParallelism(parallelism: Int): CoroutineDispatcher {
+ parallelism.checkParallelism()
+ return this
+ }
+
+ open fun shutdown() {}
}
@ThreadLocal
@@ -231,8 +236,13 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay {
if (timeNanos < MAX_DELAY_NS) {
val now = nanoTime()
DelayedResumeTask(now + timeNanos, continuation).also { task ->
- continuation.disposeOnCancellation(task)
+ /*
+ * Order is important here: first we schedule the heap and only then
+ * publish it to continuation. Otherwise, `DelayedResumeTask` would
+ * have to know how to be disposed of even when it wasn't scheduled yet.
+ */
schedule(now, task)
+ continuation.disposeOnCancellation(task)
}
}
}
@@ -271,7 +281,7 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay {
// then process one event from queue
val task = dequeue()
if (task != null) {
- task.run()
+ platformAutoreleasePool { task.run() }
return 0
}
return nextTime
@@ -279,7 +289,7 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay {
public final override fun dispatch(context: CoroutineContext, block: Runnable) = enqueue(block)
- public fun enqueue(task: Runnable) {
+ open fun enqueue(task: Runnable) {
if (enqueueImpl(task)) {
// todo: we should unpark only when this delayed task became first in the queue
unpark()
@@ -405,6 +415,7 @@ internal abstract class EventLoopImplBase: EventLoopImplPlatform(), Delay {
*/
@JvmField var nanoTime: Long
) : Runnable, Comparable, DisposableHandle, ThreadSafeHeapNode {
+ @Volatile
private var _heap: Any? = null // null | ThreadSafeHeap | DISPOSED_TASK
override var heap: ThreadSafeHeap<*>?
@@ -526,3 +537,13 @@ internal expect object DefaultExecutor {
public fun enqueue(task: Runnable)
}
+/**
+ * Used by Darwin targets to wrap a [Runnable.run] call in an Objective-C Autorelease Pool. It is a no-op on JVM, JS and
+ * non-Darwin native targets.
+ *
+ * Coroutines on Darwin targets can call into the Objective-C world, where a callee may push a to-be-returned object to
+ * the Autorelease Pool, so as to avoid a premature ARC release before it reaches the caller. This means the pool must
+ * be eventually drained to avoid leaks. Since Kotlin Coroutines does not use [NSRunLoop], which provides automatic
+ * pool management, it must manage the pool creation and pool drainage manually.
+ */
+internal expect inline fun platformAutoreleasePool(crossinline block: () -> Unit)
diff --git a/kotlinx-coroutines-core/common/src/Job.kt b/kotlinx-coroutines-core/common/src/Job.kt
index 9552153aa9..31d90eeef0 100644
--- a/kotlinx-coroutines-core/common/src/Job.kt
+++ b/kotlinx-coroutines-core/common/src/Job.kt
@@ -387,7 +387,7 @@ public fun Job0(parent: Job? = null): Job = Job(parent)
/**
* A handle to an allocated object that can be disposed to make it eligible for garbage collection.
*/
-public interface DisposableHandle {
+public fun interface DisposableHandle {
/**
* Disposes the corresponding object, making it eligible for garbage collection.
* Repeated invocation of this function has no effect.
@@ -395,18 +395,6 @@ public interface DisposableHandle {
public fun dispose()
}
-/**
- * @suppress **This an internal API and should not be used from general code.**
- */
-@Suppress("FunctionName")
-@InternalCoroutinesApi
-public inline fun DisposableHandle(crossinline block: () -> Unit): DisposableHandle =
- object : DisposableHandle {
- override fun dispose() {
- block()
- }
- }
-
// -------------------- Parent-child communication --------------------
/**
diff --git a/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt b/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt
index 602da6e0b5..a7065ccd15 100644
--- a/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt
+++ b/kotlinx-coroutines-core/common/src/MainCoroutineDispatcher.kt
@@ -4,6 +4,8 @@
package kotlinx.coroutines
+import kotlinx.coroutines.internal.*
+
/**
* Base class for special [CoroutineDispatcher] which is confined to application "Main" or "UI" thread
* and used for any UI-based activities. Instance of `MainDispatcher` can be obtained by [Dispatchers.Main].
@@ -51,6 +53,12 @@ public abstract class MainCoroutineDispatcher : CoroutineDispatcher() {
*/
override fun toString(): String = toStringInternalImpl() ?: "$classSimpleName@$hexAddress"
+ override fun limitedParallelism(parallelism: Int): CoroutineDispatcher {
+ parallelism.checkParallelism()
+ // MainCoroutineDispatcher is single-threaded -- short-circuit any attempts to limit it
+ return this
+ }
+
/**
* Internal method for more specific [toString] implementations. It returns non-null
* string if this dispatcher is set in the platform as the main one.
diff --git a/kotlinx-coroutines-core/common/src/Timeout.kt b/kotlinx-coroutines-core/common/src/Timeout.kt
index 264a2b9d1b..46ab4ae8c8 100644
--- a/kotlinx-coroutines-core/common/src/Timeout.kt
+++ b/kotlinx-coroutines-core/common/src/Timeout.kt
@@ -64,7 +64,6 @@ public suspend fun withTimeout(timeMillis: Long, block: suspend CoroutineSco
*
* > Implementation note: how the time is tracked exactly is an implementation detail of the context's [CoroutineDispatcher].
*/
-@ExperimentalTime
public suspend fun withTimeout(timeout: Duration, block: suspend CoroutineScope.() -> T): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
@@ -131,7 +130,6 @@ public suspend fun withTimeoutOrNull(timeMillis: Long, block: suspend Corout
*
* > Implementation note: how the time is tracked exactly is an implementation detail of the context's [CoroutineDispatcher].
*/
-@ExperimentalTime
public suspend fun withTimeoutOrNull(timeout: Duration, block: suspend CoroutineScope.() -> T): T? =
withTimeoutOrNull(timeout.toDelayMillis(), block)
diff --git a/kotlinx-coroutines-core/common/src/Unconfined.kt b/kotlinx-coroutines-core/common/src/Unconfined.kt
index 4f48645895..5837ae83f3 100644
--- a/kotlinx-coroutines-core/common/src/Unconfined.kt
+++ b/kotlinx-coroutines-core/common/src/Unconfined.kt
@@ -11,10 +11,16 @@ import kotlin.jvm.*
* A coroutine dispatcher that is not confined to any specific thread.
*/
internal object Unconfined : CoroutineDispatcher() {
+
+ @ExperimentalCoroutinesApi
+ override fun limitedParallelism(parallelism: Int): CoroutineDispatcher {
+ throw UnsupportedOperationException("limitedParallelism is not supported for Dispatchers.Unconfined")
+ }
+
override fun isDispatchNeeded(context: CoroutineContext): Boolean = false
override fun dispatch(context: CoroutineContext, block: Runnable) {
- // It can only be called by the "yield" function. See also code of "yield" function.
+ /** It can only be called by the [yield] function. See also code of [yield] function. */
val yieldContext = context[YieldContext]
if (yieldContext != null) {
// report to "yield" that it is an unconfined dispatcher and don't call "block.run()"
@@ -32,6 +38,7 @@ internal object Unconfined : CoroutineDispatcher() {
/**
* Used to detect calls to [Unconfined.dispatch] from [yield] function.
*/
+@PublishedApi
internal class YieldContext : AbstractCoroutineContextElement(Key) {
companion object Key : CoroutineContext.Key
diff --git a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
index 4751296c87..b92ced6ab7 100644
--- a/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/AbstractChannel.kt
@@ -136,7 +136,7 @@ internal abstract class AbstractSendChannel(
return sendSuspend(element)
}
- @Suppress("DEPRECATION")
+ @Suppress("DEPRECATION", "DEPRECATION_ERROR")
override fun offer(element: E): Boolean {
// Temporary migration for offer users who rely on onUndeliveredElement
try {
diff --git a/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt b/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt
index 600eb6a951..0a96f75380 100644
--- a/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/ArrayBroadcastChannel.kt
@@ -33,6 +33,11 @@ internal class ArrayBroadcastChannel(
require(capacity >= 1) { "ArrayBroadcastChannel capacity must be at least 1, but $capacity was specified" }
}
+ /**
+ * NB: prior to changing any logic of ArrayBroadcastChannel internals, please ensure that
+ * you do not break internal invariants of the SubscriberList implementation on K/N and KJS
+ */
+
/*
* Writes to buffer are guarded by bufferLock, but reads from buffer are concurrent with writes
* - Write element to buffer then write "tail" (volatile)
@@ -60,6 +65,7 @@ internal class ArrayBroadcastChannel(
get() = _size.value
set(value) { _size.value = value }
+ @Suppress("DEPRECATION")
private val subscribers = subscriberList>()
override val isBufferAlwaysFull: Boolean get() = false
diff --git a/kotlinx-coroutines-core/common/src/channels/Channel.kt b/kotlinx-coroutines-core/common/src/channels/Channel.kt
index b15c4262ef..5ad79fdcff 100644
--- a/kotlinx-coroutines-core/common/src/channels/Channel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Channel.kt
@@ -64,7 +64,6 @@ public interface SendChannel {
*/
public val onSend: SelectClause2>
-
/**
* Immediately adds the specified [element] to this channel, if this doesn't violate its capacity restrictions,
* and returns the successful result. Otherwise, returns failed or closed result.
@@ -158,10 +157,10 @@ public interface SendChannel {
* @suppress **Deprecated**.
*/
@Deprecated(
- level = DeprecationLevel.WARNING,
+ level = DeprecationLevel.ERROR,
message = "Deprecated in the favour of 'trySend' method",
replaceWith = ReplaceWith("trySend(element).isSuccess")
- ) // Warning since 1.5.0
+ ) // Warning since 1.5.0, error since 1.6.0
public fun offer(element: E): Boolean {
val result = trySend(element)
if (result.isSuccess) return true
@@ -314,12 +313,12 @@ public interface ReceiveChannel {
* @suppress **Deprecated**.
*/
@Deprecated(
- level = DeprecationLevel.WARNING,
+ level = DeprecationLevel.ERROR,
message = "Deprecated in the favour of 'tryReceive'. " +
"Please note that the provided replacement does not rethrow channel's close cause as 'poll' did, " +
"for the precise replacement please refer to the 'poll' documentation",
replaceWith = ReplaceWith("tryReceive().getOrNull()")
- ) // Warning since 1.5.0
+ ) // Warning since 1.5.0, error since 1.6.0
public fun poll(): E? {
val result = tryReceive()
if (result.isSuccess) return result.getOrThrow()
@@ -365,7 +364,7 @@ public interface ReceiveChannel {
message = "Deprecated in favor of onReceiveCatching extension",
level = DeprecationLevel.ERROR,
replaceWith = ReplaceWith("onReceiveCatching")
- ) // Warning since 1.3.0, error in 1.5.0, will be hidden or removed in 1.6.0
+ ) // Warning since 1.3.0, error in 1.5.0, will be hidden or removed in 1.7.0
public val onReceiveOrNull: SelectClause1
get() {
return object : SelectClause1 {
@@ -685,7 +684,7 @@ public interface ChannelIterator {
* 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 `onDeliveredElement` is to close a resource that is being transferred via the channel. The
+ * 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.
*
diff --git a/kotlinx-coroutines-core/common/src/channels/Channels.common.kt b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt
index e0b4f9d2a5..a78e2f186d 100644
--- a/kotlinx-coroutines-core/common/src/channels/Channels.common.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Channels.common.kt
@@ -50,7 +50,7 @@ public inline fun BroadcastChannel.consume(block: ReceiveChannel.()
@Deprecated(
"Deprecated in the favour of 'receiveCatching'",
ReplaceWith("receiveCatching().getOrNull()"),
- DeprecationLevel.WARNING
+ DeprecationLevel.ERROR
) // Warning since 1.5.0, ERROR in 1.6.0, HIDDEN in 1.7.0
@Suppress("EXTENSION_SHADOWED_BY_MEMBER")
public suspend fun ReceiveChannel.receiveOrNull(): E? {
@@ -63,7 +63,7 @@ public suspend fun ReceiveChannel.receiveOrNull(): E? {
*/
@Deprecated(
"Deprecated in the favour of 'onReceiveCatching'",
- level = DeprecationLevel.WARNING
+ level = DeprecationLevel.ERROR
) // Warning since 1.5.0, ERROR in 1.6.0, HIDDEN in 1.7.0
public fun ReceiveChannel.onReceiveOrNull(): SelectClause1 {
@Suppress("DEPRECATION", "UNCHECKED_CAST")
diff --git a/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt b/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt
index f7f60cf97d..177e80cb49 100644
--- a/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt
+++ b/kotlinx-coroutines-core/common/src/channels/ConflatedChannel.kt
@@ -19,7 +19,7 @@ import kotlinx.coroutines.selects.*
*/
internal open class ConflatedChannel(onUndeliveredElement: OnUndeliveredElement?) : AbstractChannel(onUndeliveredElement) {
protected final override val isBufferAlwaysEmpty: Boolean get() = false
- protected final override val isBufferEmpty: Boolean get() = value === EMPTY
+ protected final override val isBufferEmpty: Boolean get() = lock.withLock { value === EMPTY }
protected final override val isBufferAlwaysFull: Boolean get() = false
protected final override val isBufferFull: Boolean get() = false
@@ -139,5 +139,5 @@ internal open class ConflatedChannel(onUndeliveredElement: OnUndeliveredEleme
// ------ debug ------
override val bufferDebugString: String
- get() = "(value=$value)"
+ get() = lock.withLock { "(value=$value)" }
}
diff --git a/kotlinx-coroutines-core/common/src/channels/Produce.kt b/kotlinx-coroutines-core/common/src/channels/Produce.kt
index 3342fb6ec9..da8f884be1 100644
--- a/kotlinx-coroutines-core/common/src/channels/Produce.kt
+++ b/kotlinx-coroutines-core/common/src/channels/Produce.kt
@@ -6,14 +6,11 @@ package kotlinx.coroutines.channels
import kotlinx.coroutines.*
import kotlin.coroutines.*
+import kotlinx.coroutines.flow.*
/**
- * Scope for the [produce][CoroutineScope.produce] coroutine builder.
- *
- * **Note: This is an experimental api.** Behavior of producers that work as children in a parent scope with respect
- * to cancellation and error handling may change in the future.
+ * Scope for the [produce][CoroutineScope.produce], [callbackFlow] and [channelFlow] builders.
*/
-@ExperimentalCoroutinesApi
public interface ProducerScope : CoroutineScope, SendChannel {
/**
* A reference to the channel this coroutine [sends][send] elements to.
@@ -45,7 +42,6 @@ public interface ProducerScope : CoroutineScope, SendChannel {
* }
* ```
*/
-@ExperimentalCoroutinesApi
public suspend fun ProducerScope<*>.awaitClose(block: () -> Unit = {}) {
check(kotlin.coroutines.coroutineContext[Job] === this) { "awaitClose() can only be invoked from the producer context" }
try {
@@ -137,7 +133,7 @@ internal fun CoroutineScope.produce(
return coroutine
}
-internal open class ProducerCoroutine(
+private class ProducerCoroutine(
parentContext: CoroutineContext, channel: Channel
) : ChannelCoroutine(parentContext, channel, true, active = true), ProducerScope {
override val isActive: Boolean
diff --git a/kotlinx-coroutines-core/common/src/flow/Builders.kt b/kotlinx-coroutines-core/common/src/flow/Builders.kt
index 66b55a90c0..c4b55e104b 100644
--- a/kotlinx-coroutines-core/common/src/flow/Builders.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Builders.kt
@@ -198,25 +198,6 @@ public fun LongRange.asFlow(): Flow = flow {
}
}
-/**
- * @suppress
- */
-@FlowPreview
-@Deprecated(
- message = "Use channelFlow with awaitClose { } instead of flowViaChannel and invokeOnClose { }.",
- level = DeprecationLevel.ERROR
-) // To be removed in 1.4.x
-@Suppress("DeprecatedCallableAddReplaceWith")
-public fun flowViaChannel(
- bufferSize: Int = BUFFERED,
- @BuilderInference block: CoroutineScope.(channel: SendChannel) -> Unit
-): Flow {
- return channelFlow {
- block(channel)
- awaitClose()
- }.buffer(bufferSize)
-}
-
/**
* Creates an instance of a _cold_ [Flow] with elements that are sent to a [SendChannel]
* provided to the builder's [block] of code via [ProducerScope]. It allows elements to be
diff --git a/kotlinx-coroutines-core/common/src/flow/Channels.kt b/kotlinx-coroutines-core/common/src/flow/Channels.kt
index 382953efcb..51ed4270c0 100644
--- a/kotlinx-coroutines-core/common/src/flow/Channels.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Channels.kt
@@ -178,46 +178,15 @@ public fun BroadcastChannel.asFlow(): Flow = flow {
emitAll(openSubscription())
}
-/**
- * ### Deprecated
- *
- * **This API is deprecated.** The [BroadcastChannel] provides a complex channel-like API for hot flows.
- * [SharedFlow] is an easier-to-use and more flow-centric API for the same purposes, so using
- * [shareIn] operator is preferred. It is not a direct replacement, so please
- * study [shareIn] documentation to see what kind of shared flow fits your use-case. As a rule of thumb:
- *
- * * Replace `broadcastIn(scope)` and `broadcastIn(scope, CoroutineStart.LAZY)` with `shareIn(scope, 0, SharingStarted.Lazily)`.
- * * Replace `broadcastIn(scope, CoroutineStart.DEFAULT)` with `shareIn(scope, 0, SharingStarted.Eagerly)`.
- */
-@Deprecated(
- message = "Use shareIn operator and the resulting SharedFlow as a replacement for BroadcastChannel",
- replaceWith = ReplaceWith("this.shareIn(scope, SharingStarted.Lazily, 0)"),
- level = DeprecationLevel.ERROR
-) // WARNING in 1.4.0, error in 1.5.0, removed in 1.6.0 (was @FlowPreview)
-public fun Flow.broadcastIn(
- scope: CoroutineScope,
- start: CoroutineStart = CoroutineStart.LAZY
-): BroadcastChannel {
- // Backwards compatibility with operator fusing
- val channelFlow = asChannelFlow()
- val capacity = when (channelFlow.onBufferOverflow) {
- BufferOverflow.SUSPEND -> channelFlow.produceCapacity
- BufferOverflow.DROP_OLDEST -> Channel.CONFLATED
- BufferOverflow.DROP_LATEST ->
- throw IllegalArgumentException("Broadcast channel does not support BufferOverflow.DROP_LATEST")
- }
- return scope.broadcast(channelFlow.context, capacity = capacity, start = start) {
- collect { value ->
- send(value)
- }
- }
-}
-
/**
* Creates a [produce] coroutine that collects the given flow.
*
* This transformation is **stateful**, it launches a [produce] coroutine
- * that collects the given flow and thus resulting channel should be properly closed or cancelled.
+ * that collects the given flow, and has the same behavior:
+ *
+ * * if collecting the flow throws, the channel will be closed with that exception
+ * * if the [ReceiveChannel] is cancelled, the collection of the flow will be cancelled
+ * * if collecting the flow completes normally, the [ReceiveChannel] will be closed normally
*
* A channel with [default][Channel.Factory.BUFFERED] buffer size is created.
* Use [buffer] operator on the flow before calling `produceIn` to specify a value other than
diff --git a/kotlinx-coroutines-core/common/src/flow/Flow.kt b/kotlinx-coroutines-core/common/src/flow/Flow.kt
index 0ccd343ead..3520c48b42 100644
--- a/kotlinx-coroutines-core/common/src/flow/Flow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Flow.kt
@@ -108,7 +108,7 @@ import kotlin.coroutines.*
* val myFlow = flow {
* // GlobalScope.launch { // is prohibited
* // launch(Dispatchers.IO) { // is prohibited
- * // withContext(CoroutineName("myFlow")) // is prohibited
+ * // withContext(CoroutineName("myFlow")) { // is prohibited
* emit(1) // OK
* coroutineScope {
* emit(2) // OK -- still the same coroutine
@@ -131,10 +131,12 @@ import kotlin.coroutines.*
*
* ### Exception transparency
*
- * Flow implementations never catch or handle exceptions that occur in downstream flows. From the implementation standpoint
- * it means that calls to [emit][FlowCollector.emit] and [emitAll] shall never be wrapped into
- * `try { ... } catch { ... }` blocks. Exception handling in flows shall be performed with
- * [catch][Flow.catch] operator and it is designed to only catch exceptions coming from upstream flows while passing
+ * When `emit` or `emitAll` throws, the Flow implementations must immediately stop emitting new values and finish with an exception.
+ * For diagnostics or application-specific purposes, the exception may be different from the one thrown by the emit operation,
+ * suppressing the original exception as discussed below.
+ * If there is a need to emit values after the downstream failed, please use the [catch][Flow.catch] operator.
+ *
+ * The [catch][Flow.catch] operator only catches upstream exceptions, but passes
* all downstream exceptions. Similarly, terminal operators like [collect][Flow.collect]
* throw any unhandled exceptions that occur in their code or in upstream flows, for example:
*
@@ -147,6 +149,13 @@ import kotlin.coroutines.*
* ```
* The same reasoning can be applied to the [onCompletion] operator that is a declarative replacement for the `finally` block.
*
+ * All exception-handling Flow operators follow the principle of exception suppression:
+ *
+ * If the upstream flow throws an exception during its completion when the downstream exception has been thrown,
+ * the downstream exception becomes superseded and suppressed by the upstream exception, being a semantic
+ * equivalent of throwing from `finally` block. However, this doesn't affect the operation of the exception-handling operators,
+ * which consider the downstream exception to be the root cause and behave as if the upstream didn't throw anything.
+ *
* Failure to adhere to the exception transparency requirement can lead to strange behaviors which make
* it hard to reason about the code because an exception in the `collect { ... }` could be somehow "caught"
* by an upstream flow, limiting the ability of local reasoning about the code.
@@ -163,19 +172,29 @@ import kotlin.coroutines.*
*
* **The `Flow` interface is not stable for inheritance in 3rd party libraries**, as new methods
* might be added to this interface in the future, but is stable for use.
- * Use the `flow { ... }` builder function to create an implementation.
+ *
+ * Use the `flow { ... }` builder function to create an implementation, or extend [AbstractFlow].
+ * These implementations ensure that the context preservation property is not violated, and prevent most
+ * of the developer mistakes related to concurrency, inconsistent flow dispatchers, and cancellation.
*/
public interface Flow {
+
/**
* Accepts the given [collector] and [emits][FlowCollector.emit] values into it.
- * This method should never be implemented or used directly.
*
- * The only way to implement the `Flow` interface directly is to extend [AbstractFlow].
- * To collect it into a specific collector, either `collector.emitAll(flow)` or `collect { ... }` extension
- * should be used. Such limitation ensures that the context preservation property is not violated and prevents most
- * of the developer mistakes related to concurrency, inconsistent flow dispatchers and cancellation.
+ * This method can be used along with SAM-conversion of [FlowCollector]:
+ * ```
+ * myFlow.collect { value -> println("Collected $value") }
+ * ```
+ *
+ * ### Method inheritance
+ *
+ * To ensure the context preservation property, it is not recommended implementing this method directly.
+ * Instead, [AbstractFlow] can be used as the base type to properly ensure flow's properties.
+ *
+ * All default flow implementations ensure context preservation and exception transparency properties on a best-effort basis
+ * and throw [IllegalStateException] if a violation was detected.
*/
- @InternalCoroutinesApi
public suspend fun collect(collector: FlowCollector)
}
@@ -205,7 +224,6 @@ public interface Flow {
@FlowPreview
public abstract class AbstractFlow : Flow, CancellableFlow {
- @InternalCoroutinesApi
public final override suspend fun collect(collector: FlowCollector) {
val safeCollector = SafeCollector(collector, coroutineContext)
try {
diff --git a/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt b/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt
index d1c1565cb0..2877fe55e7 100644
--- a/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt
+++ b/kotlinx-coroutines-core/common/src/flow/FlowCollector.kt
@@ -8,10 +8,25 @@ package kotlinx.coroutines.flow
* [FlowCollector] is used as an intermediate or a terminal collector of the flow and represents
* an entity that accepts values emitted by the [Flow].
*
- * This interface should usually not be implemented directly, but rather used as a receiver in a [flow] builder when implementing a custom operator.
+ * This interface should usually not be implemented directly, but rather used as a receiver in a [flow] builder when implementing a custom operator,
+ * or with SAM-conversion.
* Implementations of this interface are not thread-safe.
+ *
+ * Example of usage:
+ *
+ * ```
+ * val flow = getMyEvents()
+ * try {
+ * flow.collect { value ->
+ * println("Received $value")
+ * }
+ * println("My events are consumed successfully")
+ * } catch (e: Throwable) {
+ * println("Exception from the flow: $e")
+ * }
+ * ```
*/
-public interface FlowCollector {
+public fun interface FlowCollector {
/**
* Collects the value emitted by the upstream.
diff --git a/kotlinx-coroutines-core/common/src/flow/Migration.kt b/kotlinx-coroutines-core/common/src/flow/Migration.kt
index 6278081a5d..e398740bbb 100644
--- a/kotlinx-coroutines-core/common/src/flow/Migration.kt
+++ b/kotlinx-coroutines-core/common/src/flow/Migration.kt
@@ -260,7 +260,7 @@ public fun Flow.skip(count: Int): Flow = noImpl()
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Flow analogue of 'forEach' is 'collect'",
- replaceWith = ReplaceWith("collect(block)")
+ replaceWith = ReplaceWith("collect(action)")
)
public fun Flow.forEach(action: suspend (value: T) -> Unit): Unit = noImpl()
@@ -354,6 +354,7 @@ public fun Flow.concatWith(value: T): Flow = noImpl()
)
public fun Flow.concatWith(other: Flow): Flow = noImpl()
+/** @suppress */
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Flow analogue of 'combineLatest' is 'combine'",
@@ -362,6 +363,7 @@ public fun Flow.concatWith(other: Flow): Flow = noImpl()
public fun Flow.combineLatest(other: Flow, transform: suspend (T1, T2) -> R): Flow =
combine(this, other, transform)
+/** @suppress */
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Flow analogue of 'combineLatest' is 'combine'",
@@ -373,6 +375,7 @@ public fun Flow.combineLatest(
transform: suspend (T1, T2, T3) -> R
) = combine(this, other, other2, transform)
+/** @suppress */
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Flow analogue of 'combineLatest' is 'combine'",
@@ -385,6 +388,7 @@ public fun Flow.combineLatest(
transform: suspend (T1, T2, T3, T4) -> R
) = combine(this, other, other2, other3, transform)
+/** @suppress */
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Flow analogue of 'combineLatest' is 'combine'",
@@ -422,6 +426,7 @@ public fun Flow.delayFlow(timeMillis: Long): Flow = onStart { delay(ti
)
public fun Flow.delayEach(timeMillis: Long): Flow = onEach { delay(timeMillis) }
+/** @suppress */
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Flow analogues of 'switchMap' are 'transformLatest', 'flatMapLatest' and 'mapLatest'",
@@ -429,6 +434,7 @@ public fun Flow.delayEach(timeMillis: Long): Flow = onEach { delay(tim
)
public fun Flow.switchMap(transform: suspend (value: T) -> Flow): Flow = flatMapLatest(transform)
+/** @suppress */
@Deprecated(
level = DeprecationLevel.ERROR, // Warning since 1.3.8, was experimental when deprecated, ERROR since 1.5.0
message = "'scanReduce' was renamed to 'runningReduce' to be consistent with Kotlin standard library",
@@ -436,6 +442,7 @@ public fun Flow.switchMap(transform: suspend (value: T) -> Flow): F
)
public fun Flow.scanReduce(operation: suspend (accumulator: T, value: T) -> T): Flow = runningReduce(operation)
+/** @suppress */
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Flow analogue of 'publish()' is 'shareIn'. \n" +
@@ -446,6 +453,7 @@ public fun Flow.scanReduce(operation: suspend (accumulator: T, value: T)
)
public fun Flow.publish(): Flow = noImpl()
+/** @suppress */
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Flow analogue of 'publish(bufferSize)' is 'buffer' followed by 'shareIn'. \n" +
@@ -456,6 +464,7 @@ public fun Flow.publish(): Flow = noImpl()
)
public fun Flow.publish(bufferSize: Int): Flow = noImpl()
+/** @suppress */
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Flow analogue of 'replay()' is 'shareIn' with unlimited replay. \n" +
@@ -466,6 +475,7 @@ public fun Flow.publish(bufferSize: Int): Flow = noImpl()
)
public fun Flow.replay(): Flow = noImpl()
+/** @suppress */
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Flow analogue of 'replay(bufferSize)' is 'shareIn' with the specified replay parameter. \n" +
@@ -476,6 +486,7 @@ public fun Flow.replay(): Flow = noImpl()
)
public fun Flow.replay(bufferSize: Int): Flow = noImpl()
+/** @suppress */
@Deprecated(
level = DeprecationLevel.ERROR,
message = "Flow analogue of 'cache()' is 'shareIn' with unlimited replay and 'started = SharingStared.Lazily' argument'",
diff --git a/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt b/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt
index d79e203464..0a291f258f 100644
--- a/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/SharedFlow.kt
@@ -115,7 +115,7 @@ import kotlin.native.concurrent.*
* ### Implementation notes
*
* Shared flow implementation uses a lock to ensure thread-safety, but suspending collector and emitter coroutines are
- * resumed outside of this lock to avoid dead-locks when using unconfined coroutines. Adding new subscribers
+ * resumed outside of this lock to avoid deadlocks when using unconfined coroutines. Adding new subscribers
* has `O(1)` amortized cost, but emitting has `O(N)` cost, where `N` is the number of subscribers.
*
* ### Not stable for inheritance
@@ -129,6 +129,18 @@ public interface SharedFlow : Flow {
* A snapshot of the replay cache.
*/
public val replayCache: List
+
+ /**
+ * Accepts the given [collector] and [emits][FlowCollector.emit] values into it.
+ * To emit values from a shared flow into a specific collector, either `collector.emitAll(flow)` or `collect { ... }`
+ * SAM-conversion can be used.
+ *
+ * **A shared flow never completes**. A call to [Flow.collect] or any other terminal operator
+ * on a shared flow never completes normally.
+ *
+ * @see [Flow.collect] for implementation and inheritance details.
+ */
+ override suspend fun collect(collector: FlowCollector): Nothing
}
/**
@@ -155,8 +167,15 @@ public interface SharedFlow : Flow {
*/
public interface MutableSharedFlow : SharedFlow, FlowCollector {
/**
- * Emits a [value] to this shared flow, suspending on buffer overflow if the shared flow was created
- * with the default [BufferOverflow.SUSPEND] strategy.
+ * Emits a [value] to this shared flow, suspending on buffer overflow.
+ *
+ * This call can suspend only when the [BufferOverflow] strategy is
+ * [SUSPEND][BufferOverflow.SUSPEND] **and** there are subscribers collecting this shared flow.
+ *
+ * If there are no subscribers, the buffer is not used.
+ * Instead, the most recently emitted value is simply stored into
+ * the replay cache if one was configured, displacing the older elements there,
+ * or dropped if no replay cache was configured.
*
* See [tryEmit] for a non-suspending variant of this function.
*
@@ -167,12 +186,16 @@ public interface MutableSharedFlow : SharedFlow, FlowCollector {
/**
* Tries to emit a [value] to this shared flow without suspending. It returns `true` if the value was
- * emitted successfully. When this function returns `false`, it means that the call to a plain [emit]
- * function will suspend until there is a buffer space available.
+ * emitted successfully (see below). When this function returns `false`, it means that a call to a plain [emit]
+ * function would suspend until there is buffer space available.
*
- * A shared flow configured with a [BufferOverflow] strategy other than [SUSPEND][BufferOverflow.SUSPEND]
- * (either [DROP_OLDEST][BufferOverflow.DROP_OLDEST] or [DROP_LATEST][BufferOverflow.DROP_LATEST]) never
- * suspends on [emit], and thus `tryEmit` to such a shared flow always returns `true`.
+ * This call can return `false` only when the [BufferOverflow] strategy is
+ * [SUSPEND][BufferOverflow.SUSPEND] **and** there are subscribers collecting this shared flow.
+ *
+ * If there are no subscribers, the buffer is not used.
+ * Instead, the most recently emitted value is simply stored into
+ * the replay cache if one was configured, displacing the older elements there,
+ * or dropped if no replay cache was configured. In any case, `tryEmit` returns `true`.
*
* This method is **thread-safe** and can be safely invoked from concurrent coroutines without
* external synchronization.
@@ -198,6 +221,8 @@ public interface MutableSharedFlow : SharedFlow, FlowCollector {
* }
* .launchIn(scope) // launch it
* ```
+ *
+ * Implementation note: the resulting flow **does not** conflate subscription count.
*/
public val subscriptionCount: StateFlow
@@ -253,7 +278,7 @@ public fun MutableSharedFlow(
// ------------------------------------ Implementation ------------------------------------
-private class SharedFlowSlot : AbstractSharedFlowSlot>() {
+internal class SharedFlowSlot : AbstractSharedFlowSlot>() {
@JvmField
var index = -1L // current "to-be-emitted" index, -1 means the slot is free now
@@ -275,7 +300,7 @@ private class SharedFlowSlot : AbstractSharedFlowSlot>() {
}
}
-private class SharedFlowImpl(
+internal open class SharedFlowImpl(
private val replay: Int,
private val bufferCapacity: Int,
private val onBufferOverflow: BufferOverflow
@@ -334,8 +359,15 @@ private class SharedFlowImpl(
result
}
+ /*
+ * A tweak for SubscriptionCountStateFlow to get the latest value.
+ */
+ @Suppress("UNCHECKED_CAST")
+ protected val lastReplayedLocked: T
+ get() = buffer!!.getBufferAt(replayIndex + replaySize - 1) as T
+
@Suppress("UNCHECKED_CAST")
- override suspend fun collect(collector: FlowCollector) {
+ override suspend fun collect(collector: FlowCollector): Nothing {
val slot = allocateSlot()
try {
if (collector is SubscribedFlowCollector) collector.onSubscription()
diff --git a/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt b/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt
index f4c6f2ee8d..0554142408 100644
--- a/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt
+++ b/kotlinx-coroutines-core/common/src/flow/SharingStarted.kt
@@ -5,7 +5,7 @@
package kotlinx.coroutines.flow
import kotlinx.coroutines.*
-import kotlinx.coroutines.flow.internal.*
+import kotlinx.coroutines.internal.IgnoreJreRequirement
import kotlin.time.*
/**
@@ -135,7 +135,6 @@ public fun interface SharingStarted {
* are negative.
*/
@Suppress("FunctionName")
-@ExperimentalTime
public fun SharingStarted.Companion.WhileSubscribed(
stopTimeout: Duration = Duration.ZERO,
replayExpiration: Duration = Duration.INFINITE
@@ -204,5 +203,6 @@ private class StartedWhileSubscribed(
stopTimeout == other.stopTimeout &&
replayExpiration == other.replayExpiration
+ @IgnoreJreRequirement // desugared hashcode implementation
override fun hashCode(): Int = stopTimeout.hashCode() * 31 + replayExpiration.hashCode()
}
diff --git a/kotlinx-coroutines-core/common/src/flow/StateFlow.kt b/kotlinx-coroutines-core/common/src/flow/StateFlow.kt
index 9e82e78771..be6cbd6bbd 100644
--- a/kotlinx-coroutines-core/common/src/flow/StateFlow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/StateFlow.kt
@@ -380,7 +380,7 @@ private class StateFlowImpl(
throw UnsupportedOperationException("MutableStateFlow.resetReplayCache is not supported")
}
- override suspend fun collect(collector: FlowCollector) {
+ override suspend fun collect(collector: FlowCollector): Nothing {
val slot = allocateSlot()
try {
if (collector is SubscribedFlowCollector) collector.onSubscription()
@@ -415,10 +415,6 @@ private class StateFlowImpl(
fuseStateFlow(context, capacity, onBufferOverflow)
}
-internal fun MutableStateFlow.increment(delta: Int) {
- update { it + delta }
-}
-
internal fun StateFlow.fuseStateFlow(
context: CoroutineContext,
capacity: Int,
diff --git a/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt b/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt
index 7114cc08d3..39ca98391f 100644
--- a/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt
+++ b/kotlinx-coroutines-core/common/src/flow/internal/AbstractSharedFlow.kt
@@ -4,6 +4,7 @@
package kotlinx.coroutines.flow.internal
+import kotlinx.coroutines.channels.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.internal.*
import kotlin.coroutines.*
@@ -26,12 +27,12 @@ internal abstract class AbstractSharedFlow> : Sync
protected var nCollectors = 0 // number of allocated (!free) slots
private set
private var nextIndex = 0 // oracle for the next free slot index
- private var _subscriptionCount: MutableStateFlow? = null // init on first need
+ private var _subscriptionCount: SubscriptionCountStateFlow? = null // init on first need
val subscriptionCount: StateFlow