= unsafeFlow {
+ // This return is needed to work around a bug in JS BE: KT-39227
+ return@unsafeFlow collectWhile { value ->
+ if (predicate(value)) {
+ emit(value)
+ true
+ } else {
+ false
+ }
+ }
+ }
+}
diff --git a/build.gradle b/build.gradle
index bc4dd36afc..a758393b6c 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,6 +2,7 @@
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
import org.jetbrains.kotlin.konan.target.HostManager
+import org.gradle.util.VersionNumber
apply plugin: 'jdk-convention'
apply from: rootProject.file("gradle/experimental.gradle")
@@ -79,6 +80,11 @@ buildscript {
import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType
+// Hierarchical project structures are not fully supported in 1.3.7x MPP, enable conditionally for 1.4.x
+if (VersionNumber.parse(kotlin_version) > VersionNumber.parse("1.3.79")) {
+ ext.set("kotlin.mpp.enableGranularSourceSetsMetadata", "true")
+}
+
// todo:KLUDGE: This is needed to workaround dependency resolution between Java and MPP modules
def configureKotlinJvmPlatform(configuration) {
configuration.attributes.attribute(KotlinPlatformType.attribute, KotlinPlatformType.jvm)
@@ -126,6 +132,7 @@ apiValidation {
if (build_snapshot_train) {
ignoredProjects.remove("site")
ignoredProjects.remove("example-frontend-js")
+ ignoredProjects.add("kotlinx-coroutines-core")
}
ignoredPackages += "kotlinx.coroutines.internal"
}
@@ -192,6 +199,8 @@ if (build_snapshot_train) {
exclude '**/*definitely/not/kotlinx*'
// Disable because of KT-11567 in 1.4
exclude '**/*CasesPublicAPITest*'
+ // Kotlin
+ exclude '**/*PrecompiledDebugProbesTest*'
}
}
diff --git a/coroutines-guide.md b/coroutines-guide.md
index 0b7b842acb..4b3c09c40f 100644
--- a/coroutines-guide.md
+++ b/coroutines-guide.md
@@ -47,7 +47,7 @@ The main coroutines guide has moved to the [docs folder](docs/coroutines-guide.m
* [Suspending functions](docs/flow.md#suspending-functions)
* [Flows](docs/flow.md#flows)
* [Flows are cold](docs/flow.md#flows-are-cold)
- * [Flow cancellation](docs/flow.md#flow-cancellation)
+ * [Flow cancellation basics](docs/flow.md#flow-cancellation-basics)
* [Flow builders](docs/flow.md#flow-builders)
* [Intermediate flow operators](docs/flow.md#intermediate-flow-operators)
* [Transform operator](docs/flow.md#transform-operator)
@@ -79,6 +79,8 @@ The main coroutines guide has moved to the [docs folder](docs/coroutines-guide.m
* [Successful completion](docs/flow.md#successful-completion)
* [Imperative versus declarative](docs/flow.md#imperative-versus-declarative)
* [Launching flow](docs/flow.md#launching-flow)
+ * [Flow cancellation checks](docs/flow.md#flow-cancellation-checks)
+ * [Making busy flow cancellable](docs/flow.md#making-busy-flow-cancellable)
* [Flow and Reactive Streams](docs/flow.md#flow-and-reactive-streams)
* [Channels](docs/channels.md#channels)
diff --git a/docs/basics.md b/docs/basics.md
index f171c2c29e..cb64328676 100644
--- a/docs/basics.md
+++ b/docs/basics.md
@@ -338,7 +338,7 @@ import kotlinx.coroutines.*
fun main() = runBlocking {
repeat(100_000) { // launch a lot of coroutines
launch {
- delay(1000L)
+ delay(5000L)
print(".")
}
}
@@ -351,7 +351,7 @@ fun main() = runBlocking {
-It launches 100K coroutines and, after a second, each coroutine prints a dot.
+It launches 100K coroutines and, after 5 seconds, each coroutine prints a dot.
Now, try that with threads. What would happen? (Most likely your code will produce some sort of out-of-memory error)
diff --git a/docs/flow.md b/docs/flow.md
index 0405e80df4..143f9e9300 100644
--- a/docs/flow.md
+++ b/docs/flow.md
@@ -10,7 +10,7 @@
* [Suspending functions](#suspending-functions)
* [Flows](#flows)
* [Flows are cold](#flows-are-cold)
- * [Flow cancellation](#flow-cancellation)
+ * [Flow cancellation basics](#flow-cancellation-basics)
* [Flow builders](#flow-builders)
* [Intermediate flow operators](#intermediate-flow-operators)
* [Transform operator](#transform-operator)
@@ -42,6 +42,8 @@
* [Successful completion](#successful-completion)
* [Imperative versus declarative](#imperative-versus-declarative)
* [Launching flow](#launching-flow)
+ * [Flow cancellation checks](#flow-cancellation-checks)
+ * [Making busy flow cancellable](#making-busy-flow-cancellable)
* [Flow and Reactive Streams](#flow-and-reactive-streams)
@@ -267,12 +269,10 @@ This is a key reason the `simple` function (which returns a flow) is not marked
By itself, `simple()` call returns quickly and does not wait for anything. The flow starts every time it is collected,
that is why we see "Flow started" when we call `collect` again.
-### Flow cancellation
-
-Flow adheres to the general cooperative cancellation of coroutines. However, flow infrastructure does not introduce
-additional cancellation points. It is fully transparent for cancellation. As usual, flow collection can be
-cancelled when the flow is suspended in a cancellable suspending function (like [delay]), and cannot be cancelled otherwise.
+### Flow cancellation basics
+Flow adheres to the general cooperative cancellation of coroutines. As usual, flow collection can be
+cancelled when the flow is suspended in a cancellable suspending function (like [delay]).
The following example shows how the flow gets cancelled on a timeout when running in a [withTimeoutOrNull] block
and stops executing its code:
@@ -316,6 +316,8 @@ Done
+See [Flow cancellation checks](#flow-cancellation-checks) section for more details.
+
### Flow builders
The `flow { ... }` builder from the previous examples is the most basic one. There are other builders for
@@ -695,7 +697,7 @@ This code produces the following exception:
```text
Exception in thread "main" java.lang.IllegalStateException: Flow invariant is violated:
Flow was collected in [CoroutineId(1), "coroutine#1":BlockingCoroutine{Active}@5511c7f8, BlockingEventLoop@2eac3323],
- but emission happened in [CoroutineId(1), "coroutine#1":DispatchedCoroutine{Active}@2dae0000, DefaultDispatcher].
+ but emission happened in [CoroutineId(1), "coroutine#1":DispatchedCoroutine{Active}@2dae0000, Dispatchers.Default].
Please refer to 'flow' documentation or use 'flowOn' instead
at ...
```
@@ -1777,6 +1779,127 @@ as cancellation and structured concurrency serve this purpose.
Note that [launchIn] also returns a [Job], which can be used to [cancel][Job.cancel] the corresponding flow collection
coroutine only without cancelling the whole scope or to [join][Job.join] it.
+### Flow cancellation checks
+
+For convenience, the [flow] builder performs additional [ensureActive] checks for cancellation on each emitted value.
+It means that a busy loop emitting from a `flow { ... }` is cancellable:
+
+
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+fun foo(): Flow = flow {
+ for (i in 1..5) {
+ println("Emitting $i")
+ emit(i)
+ }
+}
+
+fun main() = runBlocking {
+ foo().collect { value ->
+ if (value == 3) cancel()
+ println(value)
+ }
+}
+//sampleEnd
+```
+
+
+
+> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-37.kt).
+
+We get only numbers up to 3 and a [CancellationException] after trying to emit number 4:
+
+```text
+Emitting 1
+1
+Emitting 2
+2
+Emitting 3
+3
+Emitting 4
+Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@6d7b4f4c
+```
+
+
+
+However, most other flow operators do not do additional cancellation checks on their own for performance reasons.
+For example, if you use [IntRange.asFlow] extension to write the same busy loop and don't suspend anywhere,
+then there are no checks for cancellation:
+
+
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+fun main() = runBlocking {
+ (1..5).asFlow().collect { value ->
+ if (value == 3) cancel()
+ println(value)
+ }
+}
+//sampleEnd
+```
+
+
+
+> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-38.kt).
+
+All numbers from 1 to 5 are collected and cancellation gets detected only before return from `runBlocking`:
+
+```text
+1
+2
+3
+4
+5
+Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@3327bd23
+```
+
+
+
+#### Making busy flow cancellable
+
+In the case where you have a busy loop with coroutines you must explicitly check for cancellation.
+You can add `.onEach { currentCoroutineContext().ensureActive() }`, but there is a ready-to-use
+[cancellable] operator provided to do that:
+
+
+
+```kotlin
+import kotlinx.coroutines.*
+import kotlinx.coroutines.flow.*
+
+//sampleStart
+fun main() = runBlocking {
+ (1..5).asFlow().cancellable().collect { value ->
+ if (value == 3) cancel()
+ println(value)
+ }
+}
+//sampleEnd
+```
+
+
+
+> You can get the full code from [here](../kotlinx-coroutines-core/jvm/test/guide/example-flow-39.kt).
+
+With the `cancellable` operator only the numbers from 1 to 3 are collected:
+
+```text
+1
+2
+3
+Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@5ec0a365
+```
+
+
+
### Flow and Reactive Streams
For those who are familiar with [Reactive Streams](https://www.reactive-streams.org/) or reactive frameworks such as RxJava and project Reactor,
@@ -1813,6 +1936,8 @@ Integration modules include conversions from and to `Flow`, integration with Rea
[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/cancel.html
[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
+[ensureActive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/ensure-active.html
+[CancellationException]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html
[Flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-flow/index.html
[flow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/flow.html
@@ -1845,4 +1970,6 @@ Integration modules include conversions from and to `Flow`, integration with Rea
[catch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/catch.html
[onCompletion]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/on-completion.html
[launchIn]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/launch-in.html
+[IntRange.asFlow]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/kotlin.ranges.-int-range/as-flow.html
+[cancellable]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/cancellable.html
diff --git a/gradle.properties b/gradle.properties
index 0a45ecd693..6a1ae653f1 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -3,7 +3,7 @@
#
# Kotlin
-version=1.3.7-SNAPSHOT
+version=1.3.8-SNAPSHOT
group=org.jetbrains.kotlinx
kotlin_version=1.3.71
@@ -51,3 +51,12 @@ jekyll_version=4.0
# JS IR baceknd sometimes crashes with out-of-memory
# TODO: Remove once KT-37187 is fixed
org.gradle.jvmargs=-Xmx2g
+
+# Workaround for Bintray treating .sha512 files as artifacts
+# https://github.com/gradle/gradle/issues/11412
+systemProp.org.gradle.internal.publish.checksums.insecure=true
+
+# This is commented out, and the property is set conditionally in build.gradle, because 1.3.71 doesn't work with it.
+# Once this property is set by default in new versions or 1.3.71 is dropped, either uncomment or remove this line.
+#kotlin.mpp.enableGranularSourceSetsMetadata=true
+kotlin.mpp.enableCompatibilityMetadataVariant=true
\ No newline at end of file
diff --git a/gradle/compile-js-multiplatform.gradle b/gradle/compile-js-multiplatform.gradle
index 2b00d6fc82..93d371a21f 100644
--- a/gradle/compile-js-multiplatform.gradle
+++ b/gradle/compile-js-multiplatform.gradle
@@ -5,15 +5,22 @@
apply from: rootProject.file('gradle/node-js.gradle')
kotlin {
- targets {
- fromPreset(presets.js, 'js') {
- // Enable built-in test runner only for IR target.
- // These runners don't support changing js module name change.
- if (js.hasProperty("irTarget") && irTarget != null) {
- irTarget.nodejs()
- irTarget.compilations['main']?.dependencies {
- api "org.jetbrains.kotlinx:atomicfu-js:$atomicfu_version"
- }
+ js {
+ // In 1.3.7x js() has not member `moduleName`
+ // In 1.4.x it has and allow to safety set compiler output file name and does not break test integration
+ if (it.hasProperty("moduleName")) {
+ moduleName = project.name
+ }
+
+ // In 1.3.7x js() has not member `irTarget`
+ // In 1.4.x it has in `both` and `legacy` mode and js() is of type `KotlinJsTarget`
+ // `irTarget` is non-null in `both` mode
+ // and contains appropriate `irTarget` with type `KotlinJsIrTarget`
+ // `irTarget` is null in `legacy` mode
+ if (it.hasProperty("irTarget") && it.irTarget != null) {
+ irTarget.nodejs()
+ irTarget.compilations['main']?.dependencies {
+ api "org.jetbrains.kotlinx:atomicfu-js:$atomicfu_version"
}
}
}
@@ -32,7 +39,15 @@ kotlin {
// When source sets are configured
apply from: rootProject.file('gradle/test-mocha-js.gradle')
-compileKotlinJs {
+def compileJsLegacy = tasks.hasProperty("compileKotlinJsLegacy")
+ ? compileKotlinJsLegacy
+ : compileKotlinJs
+
+def compileTestJsLegacy = tasks.hasProperty("compileTestKotlinJsLegacy")
+ ? compileTestKotlinJsLegacy
+ : compileTestKotlinJs
+
+compileJsLegacy.configure {
kotlinOptions.metaInfo = true
kotlinOptions.sourceMap = true
kotlinOptions.moduleKind = 'umd'
@@ -44,20 +59,20 @@ compileKotlinJs {
}
}
-compileTestKotlinJs {
+compileTestJsLegacy.configure {
kotlinOptions.metaInfo = true
kotlinOptions.sourceMap = true
kotlinOptions.moduleKind = 'umd'
}
-task populateNodeModules(type: Copy, dependsOn: compileTestKotlinJs) {
+task populateNodeModules(type: Copy, dependsOn: compileTestJsLegacy) {
// we must copy output that is transformed by atomicfu
- from(kotlin.targets.js.compilations.main.output.allOutputs)
+ from(kotlin.js().compilations.main.output.allOutputs)
into "$node.nodeModulesDir/node_modules"
- def configuration = configurations.hasProperty("legacyjsTestRuntimeClasspath")
- ? configurations.legacyjsTestRuntimeClasspath
+ def configuration = configurations.hasProperty("jsLegacyTestRuntimeClasspath")
+ ? configurations.jsLegacyTestRuntimeClasspath
: configurations.jsTestRuntimeClasspath
from(files {
diff --git a/gradle/publish-bintray.gradle b/gradle/publish-bintray.gradle
index ee9337f8c8..b36c79763d 100644
--- a/gradle/publish-bintray.gradle
+++ b/gradle/publish-bintray.gradle
@@ -1,3 +1,5 @@
+import org.gradle.util.VersionNumber
+
/*
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/
@@ -11,6 +13,7 @@ apply plugin: 'maven-publish'
def isMultiplatform = project.name == "kotlinx-coroutines-core"
def isBom = project.name == "kotlinx-coroutines-bom"
+def isKotlin137x = VersionNumber.parse(kotlin_version) <= VersionNumber.parse("1.3.79")
if (!isBom) {
apply plugin: "com.github.johnrengelman.shadow"
@@ -64,8 +67,8 @@ publishing {
publications.all {
MavenCentralKt.configureMavenCentralMetadata(pom, project)
- // add empty javadocs (no need for MPP root publication which publishes only pom file)
- if (it.name != 'kotlinMultiplatform' && !isBom) {
+ // add empty javadocs
+ if (!isBom && it.name != "kotlinMultiplatform") {
it.artifact(javadocJar)
}
@@ -73,27 +76,42 @@ publishing {
def type = it.name
switch (type) {
case 'kotlinMultiplatform':
- it.artifactId = "$project.name-native"
+ // With Kotlin 1.4 & HMPP, the root module should have no suffix in the ID, but for compatibility with
+ // the consumers who can't read Gradle module metadata, we publish the JVM artifacts in it, too
+ it.artifactId = isKotlin137x ? "$project.name-native" : project.name
+ if (!isKotlin137x) {
+ apply from: "$rootDir/gradle/publish-mpp-root-module-in-platform.gradle"
+ publishPlatformArtifactsInRootModule(publications["jvm"])
+ }
break
case 'metadata':
- it.artifactId = "$project.name-common"
+ // As the old -common dependencies will fail to resolve with Gradle module metadata, rename the module
+ // to '*-metadata' so that the resolution failure are more clear
+ it.artifactId = isKotlin137x ? "$project.name-common" : "$project.name-metadata"
break
case 'jvm':
- it.artifactId = "$project.name"
+ it.artifactId = isKotlin137x ? project.name : "$project.name-jvm"
break
case 'js':
case 'native':
it.artifactId = "$project.name-$type"
break
}
-
- // disable metadata everywhere, but in native and js modules
- if (type == 'maven' || type == 'metadata' || type == 'jvm') {
- moduleDescriptorGenerator = null
+ // Hierarchical project structures are not fully supported in 1.3.7x MPP
+ if (isKotlin137x) {
+ // disable metadata everywhere, but in native and js modules
+ if (type == 'maven' || type == 'metadata' || type == 'jvm') {
+ moduleDescriptorGenerator = null
+ }
}
+
}
}
+tasks.matching { it.name == "generatePomFileForKotlinMultiplatformPublication"}.configureEach {
+ dependsOn(tasks["generatePomFileForJvmPublication"])
+}
+
task publishDevelopSnapshot() {
def branch = System.getenv('currentBranch')
if (branch == "develop") {
diff --git a/gradle/publish-mpp-root-module-in-platform.gradle b/gradle/publish-mpp-root-module-in-platform.gradle
new file mode 100644
index 0000000000..8bc0b502db
--- /dev/null
+++ b/gradle/publish-mpp-root-module-in-platform.gradle
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2014-2020 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
+ */
+
+
+/*
+ * Publish the platform JAR and POM so that consumers who depend on this module and can't read Gradle module metadata
+ * can still get the platform artifact and transitive dependencies from the POM.
+ *
+ * See the full rationale here https://youtrack.jetbrains.com/issue/KMM-237#focus=streamItem-27-4115233.0-0
+ */
+project.ext.publishPlatformArtifactsInRootModule = { platformPublication ->
+ def platformPomBuilder = null
+
+ platformPublication.pom.withXml { platformPomBuilder = asString() }
+
+ publishing.publications.kotlinMultiplatform {
+ platformPublication.artifacts.forEach {
+ artifact(it)
+ }
+
+ pom.withXml {
+ def pomStringBuilder = asString()
+ pomStringBuilder.setLength(0)
+ // The platform POM needs its artifact ID replaced with the artifact ID of the root module:
+ def platformPomString = platformPomBuilder.toString()
+ platformPomString.eachLine { line ->
+ if (!line.contains("