Skip to content

Commit

Permalink
Merge pull request #2337 from Kotlin/version-1.4.0
Browse files Browse the repository at this point in the history
Version 1.4.0
  • Loading branch information
qwwdfsad committed Oct 26, 2020
2 parents 768e92b + 00da1ac commit 4ea4078
Show file tree
Hide file tree
Showing 80 changed files with 1,655 additions and 2,026 deletions.
24 changes: 24 additions & 0 deletions CHANGES.md
@@ -1,5 +1,29 @@
# Change log for kotlinx.coroutines

## Version 1.4.0

### Improvements

* `StateFlow`, `SharedFlow` and corresponding operators are promoted to stable API (#2316).
* `Flow.debounce` operator with timeout selector based on each individual element is added (#1216, thanks to @mkano9!).
* `CoroutineContext.job` extension property is introduced (#2159).
* `Flow.combine operator` is reworked:
* Complete fairness is maintained for single-threaded dispatchers.
* Its performance is improved, depending on the use-case, by at least 50% (#2296).
* Quadratic complexity depending on the number of upstream flows is eliminated (#2296).
* `crossinline` and `inline`-heavy internals are removed, fixing sporadic SIGSEGV on Mediatek Android devices (#1683, #1743).
* `Flow.zip` operator performance is improved by 40%.
* Various API has been promoted to stable or its deprecation level has been raised (#2316).

### Bug fixes

* Suspendable `stateIn` operator propagates exception to the caller when upstream fails to produce initial value (#2329).
* Fix `SharedFlow` with replay for subscribers working at different speed (#2325).
* Do not fail debug agent installation when security manager does not provide access to system properties (#2311).
* Cancelled lazy coroutines are properly cleaned up from debug agent output (#2294).
* `BlockHound` false-positives are correctly filtered out (#2302, #2190, #2303).
* Potential crash during a race between cancellation and upstream in `Observable.asFlow` is fixed (#2104, #2299, thanks to @LouisCAD and @drinkthestars).

## Version 1.4.0-M1

### Breaking changes
Expand Down
22 changes: 11 additions & 11 deletions README.md
Expand Up @@ -2,7 +2,7 @@

[![official JetBrains project](https://jb.gg/badges/official.svg)](https://confluence.jetbrains.com/display/ALL/JetBrains+on+GitHub)
[![GitHub license](https://img.shields.io/badge/license-Apache%20License%202.0-blue.svg?style=flat)](https://www.apache.org/licenses/LICENSE-2.0)
[![Download](https://api.bintray.com/packages/kotlin/kotlinx/kotlinx.coroutines/images/download.svg?version=1.4.0-M1) ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/1.4.0-M1)
[![Download](https://api.bintray.com/packages/kotlin/kotlinx/kotlinx.coroutines/images/download.svg?version=1.4.0) ](https://bintray.com/kotlin/kotlinx/kotlinx.coroutines/1.4.0)
[![Kotlin](https://img.shields.io/badge/kotlin-1.4.0-blue.svg?logo=kotlin)](http://kotlinlang.org)
[![Slack channel](https://img.shields.io/badge/chat-slack-green.svg?logo=slack)](https://kotlinlang.slack.com/messages/coroutines/)

Expand All @@ -29,7 +29,7 @@ suspend fun main() = coroutineScope {
* [delay] and [yield] top-level suspending functions;
* [Flow] — cold asynchronous stream with [flow][_flow] builder and comprehensive operator set ([filter], [map], etc);
* [Channel], [Mutex], and [Semaphore] communication and synchronization primitives;
* [coroutineScope], [supervisorScope], [withContext], and [withTimeout] scope builders;
* [coroutineScope][_coroutineScope], [supervisorScope][_supervisorScope], [withContext], and [withTimeout] scope builders;
* [MainScope()] for Android and UI applications;
* [SupervisorJob()] and [CoroutineExceptionHandler] for supervision of coroutines hierarchies;
* [select] expression support and more.
Expand Down Expand Up @@ -86,7 +86,7 @@ Add dependencies (you can also add other modules that you need):
<dependency>
<groupId>org.jetbrains.kotlinx</groupId>
<artifactId>kotlinx-coroutines-core</artifactId>
<version>1.4.0-M1</version>
<version>1.4.0</version>
</dependency>
```

Expand All @@ -104,7 +104,7 @@ Add dependencies (you can also add other modules that you need):

```groovy
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.0-M1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.0'
}
```

Expand All @@ -130,7 +130,7 @@ Add dependencies (you can also add other modules that you need):

```groovy
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.0-M1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.0")
}
```

Expand All @@ -152,7 +152,7 @@ In common code that should get compiled for different platforms, you can add dep
```groovy
commonMain {
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.0-M1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.0")
}
}
```
Expand All @@ -163,7 +163,7 @@ Add [`kotlinx-coroutines-android`](ui/kotlinx-coroutines-android)
module as dependency when using `kotlinx.coroutines` on Android:

```groovy
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.0-M1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.0'
```

This gives you access to Android [Dispatchers.Main]
Expand All @@ -190,15 +190,15 @@ packagingOptions {
### JS

[Kotlin/JS](https://kotlinlang.org/docs/reference/js-overview.html) version of `kotlinx.coroutines` is published as
[`kotlinx-coroutines-core-js`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.4.0-M1/jar)
[`kotlinx-coroutines-core-js`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-js/1.4.0/jar)
(follow the link to get the dependency declaration snippet).

You can also use [`kotlinx-coroutines-core`](https://www.npmjs.com/package/kotlinx-coroutines-core) package via NPM.

### Native

[Kotlin/Native](https://kotlinlang.org/docs/reference/native-overview.html) version of `kotlinx.coroutines` is published as
[`kotlinx-coroutines-core-native`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-native/1.4.0-M1/jar)
[`kotlinx-coroutines-core-native`](https://search.maven.org/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core-native/1.4.0/jar)
(follow the link to get the dependency declaration snippet).

Only single-threaded code (JS-style) on Kotlin/Native is currently supported.
Expand Down Expand Up @@ -227,8 +227,8 @@ See [Contributing Guidelines](CONTRIBUTING.md).
[Dispatchers.Default]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-dispatchers/-default.html
[delay]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/delay.html
[yield]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/yield.html
[coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
[supervisorScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/supervisor-scope.html
[_coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
[_supervisorScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/supervisor-scope.html
[withContext]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-context.html
[withTimeout]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/with-timeout.html
[MainScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-main-scope.html
Expand Down
30 changes: 2 additions & 28 deletions benchmarks/build.gradle.kts
Expand Up @@ -31,38 +31,12 @@ tasks.named<KotlinCompile>("compileJmhKotlin") {
}
}

/*
* Due to a bug in the inliner it sometimes does not remove inlined symbols (that are later renamed) from unused code paths,
* and it breaks JMH that tries to post-process these symbols and fails because they are renamed.
*/
val removeRedundantFiles by tasks.registering(Delete::class) {
delete("$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$buildHistoOnScore\$1\$\$special\$\$inlined\$filter\$1\$1.class")
delete("$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$nBlanks\$1\$\$special\$\$inlined\$map\$1\$1.class")
delete("$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$score2\$1\$\$special\$\$inlined\$map\$1\$1.class")
delete("$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$bonusForDoubleLetter\$1\$\$special\$\$inlined\$map\$1\$1.class")
delete("$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$nBlanks\$1\$\$special\$\$inlined\$map\$1\$2\$1.class")
delete("$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$bonusForDoubleLetter\$1\$\$special\$\$inlined\$map\$1\$2\$1.class")
delete("$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$score2\$1\$\$special\$\$inlined\$map\$1\$2\$1.class")
delete("$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOptKt\$\$special\$\$inlined\$collect\$1\$1.class")
delete("$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOptKt\$\$special\$\$inlined\$collect\$2\$1.class")
delete("$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleOpt\$play\$histoOfLetters\$1\$\$special\$\$inlined\$fold\$1\$1.class")
delete("$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleBase\$play\$buildHistoOnScore\$1\$\$special\$\$inlined\$filter\$1\$1.class")
delete("$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/FlowPlaysScrabbleBase\$play\$histoOfLetters\$1\$\$special\$\$inlined\$fold\$1\$1.class")
delete("$buildDir/classes/kotlin/jmh/benchmarks/flow/scrabble/SaneFlowPlaysScrabble\$play\$buildHistoOnScore\$1\$\$special\$\$inlined\$filter\$1\$1.class")

// Primes
delete("$buildDir/classes/kotlin/jmh/benchmarks/flow/misc/Numbers\$\$special\$\$inlined\$filter\$1\$2\$1.class")
delete("$buildDir/classes/kotlin/jmh/benchmarks/flow/misc/Numbers\$\$special\$\$inlined\$filter\$1\$1.class")
}

tasks.named("jmhRunBytecodeGenerator") {
dependsOn(removeRedundantFiles)
}

// 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<JMHPluginExtension>("jmh") {
jmhVersion = "1.21"
jmhVersion = "1.26"
duplicateClassesStrategy = DuplicatesStrategy.INCLUDE
failOnError = true
resultFormat = "CSV"
Expand All @@ -80,7 +54,7 @@ tasks.named<Jar>("jmhJar") {
}

dependencies {
compile("org.openjdk.jmh:jmh-core:1.21")
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")
Expand Down
18 changes: 18 additions & 0 deletions benchmarks/src/jmh/kotlin/benchmarks/ChannelSinkBenchmark.kt
Expand Up @@ -50,4 +50,22 @@ open class ChannelSinkBenchmark {
for (i in start until (start + count))
send(i)
}

// Migrated from deprecated operators, are good only for stressing channels

private fun <E> ReceiveChannel<E>.filter(context: CoroutineContext = Dispatchers.Unconfined, predicate: suspend (E) -> Boolean): ReceiveChannel<E> =
GlobalScope.produce(context, onCompletion = { cancel() }) {
for (e in this@filter) {
if (predicate(e)) send(e)
}
}

private suspend inline fun <E, R> ReceiveChannel<E>.fold(initial: R, operation: (acc: R, E) -> R): R {
var accumulator = initial
consumeEach {
accumulator = operation(accumulator, it)
}
return accumulator
}
}

34 changes: 34 additions & 0 deletions benchmarks/src/jmh/kotlin/benchmarks/flow/CombineFlowsBenchmark.kt
@@ -0,0 +1,34 @@
/*
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package benchmarks.flow

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import org.openjdk.jmh.annotations.*
import java.util.concurrent.*

@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(value = 1)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
open class CombineFlowsBenchmark {

@Param("10", "100", "1000")
private var size = 10

@Benchmark
fun combine() = runBlocking {
combine((1 until size).map { flowOf(it) }) { a -> a}.collect()
}

@Benchmark
fun combineTransform() = runBlocking {
val list = (1 until size).map { flowOf(it) }.toList()
combineTransform((1 until size).map { flowOf(it) }) { emit(it) }.collect()
}
}

@@ -0,0 +1,47 @@
/*
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

package benchmarks.flow

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.internal.*
import org.openjdk.jmh.annotations.*
import java.util.concurrent.*

@Warmup(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 1, timeUnit = TimeUnit.SECONDS)
@Fork(value = 1)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@State(Scope.Benchmark)
open class CombineTwoFlowsBenchmark {

@Param("100", "100000", "1000000")
private var size = 100000

@Benchmark
fun combinePlain() = runBlocking {
val flow = (1 until size.toLong()).asFlow()
flow.combine(flow) { a, b -> a + b }.collect()
}

@Benchmark
fun combineTransform() = runBlocking {
val flow = (1 until size.toLong()).asFlow()
flow.combineTransform(flow) { a, b -> emit(a + b) }.collect()
}

@Benchmark
fun combineVararg() = runBlocking {
val flow = (1 until size.toLong()).asFlow()
combine(listOf(flow, flow)) { arr -> arr[0] + arr[1] }.collect()
}

@Benchmark
fun combineTransformVararg() = runBlocking {
val flow = (1 until size.toLong()).asFlow()
combineTransform(listOf(flow, flow)) { arr -> emit(arr[0] + arr[1]) }.collect()
}
}
6 changes: 3 additions & 3 deletions benchmarks/src/jmh/kotlin/benchmarks/flow/NumbersBenchmark.kt
Expand Up @@ -77,14 +77,14 @@ open class NumbersBenchmark {

@Benchmark
fun zipRx() {
val numbers = rxNumbers().take(natural.toLong())
val numbers = rxNumbers().take(natural)
val first = numbers
.filter { it % 2L != 0L }
.map { it * it }
val second = numbers
.filter { it % 2L == 0L }
.map { it * it }
first.zipWith(second, BiFunction<Long, Long, Long> { v1, v2 -> v1 + v2 }).filter { it % 3 == 0L }.count()
first.zipWith(second, { v1, v2 -> v1 + v2 }).filter { it % 3 == 0L }.count()
.blockingGet()
}

Expand All @@ -98,7 +98,7 @@ open class NumbersBenchmark {

@Benchmark
fun transformationsRx(): Long {
return rxNumbers().take(natural.toLong())
return rxNumbers().take(natural)
.filter { it % 2L != 0L }
.map { it * it }
.filter { (it + 1) % 3 == 0L }.count()
Expand Down
15 changes: 13 additions & 2 deletions buildSrc/build.gradle.kts
Expand Up @@ -9,6 +9,7 @@ plugins {
}

val cacheRedirectorEnabled = System.getenv("CACHE_REDIRECTOR")?.toBoolean() == true
val buildSnapshotTrain = properties["build_snapshot_train"]?.toString()?.toBoolean() == true

repositories {
if (cacheRedirectorEnabled) {
Expand All @@ -20,6 +21,10 @@ repositories {
maven("https://dl.bintray.com/kotlin/kotlin-eap")
maven("https://dl.bintray.com/kotlin/kotlin-dev")
}

if (buildSnapshotTrain) {
mavenLocal()
}
}

kotlinDslPluginOptions {
Expand All @@ -30,8 +35,14 @@ val props = Properties().apply {
file("../gradle.properties").inputStream().use { load(it) }
}

fun version(target: String): String =
props.getProperty("${target}_version")
fun version(target: String): String {
// Intercept reading from properties file
if (target == "kotlin") {
val snapshotVersion = properties["kotlin_snapshot_version"]
if (snapshotVersion != null) return snapshotVersion.toString()
}
return props.getProperty("${target}_version")
}

dependencies {
implementation(kotlin("gradle-plugin", version("kotlin")))
Expand Down
7 changes: 4 additions & 3 deletions buildSrc/settings.gradle.kts
@@ -1,17 +1,18 @@
/*
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

pluginManagement {
val build_snapshot_train: String? by settings
repositories {
val cacheRedirectorEnabled = System.getenv("CACHE_REDIRECTOR")?.toBoolean() == true

if (cacheRedirectorEnabled) {
println("Redirecting repositories for buildSrc buildscript")

maven("https://cache-redirector.jetbrains.com/plugins.gradle.org/m2")
} else {
maven("https://plugins.gradle.org/m2")
}
if (build_snapshot_train?.toBoolean() == true) {
mavenLocal()
}
}
}
12 changes: 6 additions & 6 deletions docs/basics.md
Expand Up @@ -235,12 +235,12 @@ World!
### Scope builder

In addition to the coroutine scope provided by different builders, it is possible to declare your own scope using the
[coroutineScope] builder. It creates a coroutine scope and does not complete until all launched children complete.
[coroutineScope][_coroutineScope] builder. It creates a coroutine scope and does not complete until all launched children complete.

[runBlocking] and [coroutineScope] may look similar because they both wait for their body and all its children to complete.
[runBlocking] and [coroutineScope][_coroutineScope] may look similar because they both wait for their body and all its children to complete.
The main difference is that the [runBlocking] method _blocks_ the current thread for waiting,
while [coroutineScope] just suspends, releasing the underlying thread for other usages.
Because of that difference, [runBlocking] is a regular function and [coroutineScope] is a suspending function.
while [coroutineScope][_coroutineScope] just suspends, releasing the underlying thread for other usages.
Because of that difference, [runBlocking] is a regular function and [coroutineScope][_coroutineScope] is a suspending function.

It can be demonstrated by the following example:

Expand Down Expand Up @@ -281,7 +281,7 @@ Coroutine scope is over
-->

Note that right after the "Task from coroutine scope" message (while waiting for nested launch)
"Task from runBlocking" is executed and printed — even though the [coroutineScope] is not completed yet.
"Task from runBlocking" is executed and printed — even though the [coroutineScope][_coroutineScope] is not completed yet.

### Extract function refactoring

Expand Down Expand Up @@ -403,7 +403,7 @@ Active coroutines that were launched in [GlobalScope] do not keep the process al
[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
[Job.join]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/join.html
[coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
[_coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
[CoroutineScope()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope.html
<!--- END -->

Expand Down

0 comments on commit 4ea4078

Please sign in to comment.