Skip to content

Version 1.4.0 #2337

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Oct 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
863258b
Fixed RejectedExecutionTest on JDK1.6
elizarov Oct 13, 2020
eb1b5db
Fix doc references that differ only in case (function vs interface) (…
elizarov Oct 16, 2020
20ad25f
Disable SecurityManager when IDEA is active
qwwdfsad Oct 16, 2020
993c192
Cleanup lazy coroutines that have been cancelled but not yet garbage …
qwwdfsad Oct 19, 2020
e941da2
Remove workaround for KT-42671 that triggers test failures from IDEA …
qwwdfsad Oct 19, 2020
9eaa9c6
Introduce CoroutineContext.job extension (#2312)
qwwdfsad Oct 19, 2020
9587590
Combine and zip rework (#2308)
qwwdfsad Oct 20, 2020
6843648
Release intercepted SafeCollector when onCompletion block is done (#2…
qwwdfsad Oct 22, 2020
3275d22
Propagate exception from stateIn to the caller when the upstream fail…
qwwdfsad Oct 22, 2020
22c4301
Propagate kotlin_snapshot_version to buildSrc and kts files (#2332)
qwwdfsad Oct 23, 2020
8df6f5a
Fix race condition in testUpstreamFailedImmediatelyWithInitialValue
qwwdfsad Oct 23, 2020
ee78090
Fix potential crash in Rx2 and Rx3 asFlow extension (#2333)
qwwdfsad Oct 23, 2020
45ba58e
Fix BlockHound false positives (#2331)
dkhalanskyjb Oct 26, 2020
53f007f
Fix SharedFlow with replay for subscribers working at different speed…
elizarov Oct 26, 2020
92db4e1
Add debounce with selector and kotlin.time (#2336)
elizarov Oct 26, 2020
e16eb9d
Update experimental declarations (#2316)
qwwdfsad Oct 26, 2020
727dd58
Merge remote-tracking branch 'origin/master' into develop
qwwdfsad Oct 26, 2020
f1e35a0
Version 1.4.0
qwwdfsad Oct 26, 2020
00da1ac
Acknowledge external contributors
qwwdfsad Oct 26, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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()
}
}

Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
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
Loading